diff --git a/.github/workflows/ruby-ci.yml b/.github/workflows/ruby-ci.yml
index adc9677b38b..6a208f2f7f5 100644
--- a/.github/workflows/ruby-ci.yml
+++ b/.github/workflows/ruby-ci.yml
@@ -17,9 +17,7 @@ jobs:
strategy:
matrix:
version:
- - 2.5
- - 2.6
- - 2.7
+ - 3.1
gemfile:
- gemfiles/Gemfile.rails50
- gemfiles/Gemfile.rails51
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..5ad2e57628a
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,19 @@
+name: 'Close stale issues and PRs'
+on:
+ schedule:
+ - cron: '30 1 * * *'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v8
+ with:
+ stale-issue-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
+ stale-pr-message: 'To provide a cleaner slate for the maintenance of the library, this PR/Issue is being labeled stale after 60 days without activity. It will be closed in 14 days unless you comment with an update regarding its applicability to the current build. Thank you!'
+ days-before-close: 14
+ exempt-draft-pr: true
\ No newline at end of file
diff --git a/.rubocop.yml b/.rubocop.yml
index 50d63c3dd84..d8f742f981f 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -15,7 +15,7 @@ AllCops:
- "lib/active_merchant/billing/gateways/paypal_express.rb"
- "vendor/**/*"
ExtraDetails: false
- TargetRubyVersion: 2.5
+ TargetRubyVersion: 3.1
# Active Merchant gateways are not amenable to length restrictions
Metrics/ClassLength:
@@ -24,7 +24,7 @@ Metrics/ClassLength:
Metrics/ModuleLength:
Enabled: false
-Layout/AlignParameters:
+Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
Layout/DotPosition:
@@ -33,10 +33,96 @@ Layout/DotPosition:
Layout/CaseIndentation:
EnforcedStyle: end
-Layout/IndentHash:
+Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
Naming/PredicateName:
Exclude:
- "lib/active_merchant/billing/gateways/payeezy.rb"
- 'lib/active_merchant/billing/gateways/airwallex.rb'
+
+Gemspec/DateAssignment: # (new in 1.10)
+ Enabled: true
+Layout/SpaceBeforeBrackets: # (new in 1.7)
+ Enabled: true
+Lint/AmbiguousAssignment: # (new in 1.7)
+ Enabled: true
+Lint/DeprecatedConstants: # (new in 1.8)
+ Enabled: true # update later in next Update Rubocop PR
+Lint/DuplicateBranch: # (new in 1.3)
+ Enabled: false
+Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1)
+ Enabled: true
+Lint/EmptyBlock: # (new in 1.1)
+ Enabled: false # update later in next Update Rubocop PR
+ Exclude:
+ - 'lib/active_merchant/billing/gateways/authorize_net.rb'
+ - 'lib/active_merchant/billing/gateways/secure_net.rb'
+Lint/EmptyClass: # (new in 1.3)
+ Enabled: true
+Lint/FloatComparison:
+ Exclude:
+ - 'lib/active_merchant/billing/gateways/payu_latam.rb'
+Lint/LambdaWithoutLiteralBlock: # (new in 1.8)
+ Enabled: true
+Lint/NonDeterministicRequireOrder:
+ Exclude:
+ - 'script/generate'
+Lint/NoReturnInBeginEndBlocks: # (new in 1.2)
+ Enabled: true
+ Exclude:
+ - 'lib/active_merchant/billing/gateways/fat_zebra.rb'
+ - 'lib/active_merchant/billing/gateways/netbanx.rb'
+ - 'lib/active_merchant/billing/gateways/payway_dot_com.rb'
+Lint/NumberedParameterAssignment: # (new in 1.9)
+ Enabled: true
+Lint/OrAssignmentToConstant: # (new in 1.9)
+ Enabled: true
+Lint/RedundantDirGlobSort: # (new in 1.8)
+ Enabled: true
+Lint/SymbolConversion: # (new in 1.9)
+ Enabled: true
+Lint/ToEnumArguments: # (new in 1.1)
+ Enabled: true
+Lint/TripleQuotes: # (new in 1.9)
+ Enabled: true
+Lint/UnexpectedBlockArity: # (new in 1.5)
+ Enabled: true
+Lint/UnmodifiedReduceAccumulator: # (new in 1.1)
+ Enabled: true
+Style/ArgumentsForwarding: # (new in 1.1)
+ Enabled: true
+Style/CollectionCompact: # (new in 1.2)
+ Enabled: false # update later in next Update Rubocop PR
+Style/DocumentDynamicEvalDefinition: # (new in 1.1)
+ Enabled: true
+ Exclude:
+ - 'lib/active_merchant/billing/credit_card.rb'
+ - 'lib/active_merchant/billing/response.rb'
+Style/EndlessMethod: # (new in 1.8)
+ Enabled: true
+Style/HashConversion: # (new in 1.10)
+ Enabled: true
+ Exclude:
+ - 'lib/active_merchant/billing/gateways/payscout.rb'
+ - 'lib/active_merchant/billing/gateways/pac_net_raven.rb'
+Style/HashExcept: # (new in 1.7)
+ Enabled: true
+Style/IfWithBooleanLiteralBranches: # (new in 1.9)
+ Enabled: false # update later in next Update Rubocop PR
+Style/NegatedIfElseCondition: # (new in 1.2)
+ Enabled: true
+Style/NilLambda: # (new in 1.3)
+ Enabled: true
+Style/RedundantArgument: # (new in 1.4)
+ Enabled: false # update later in next Update Rubocop PR
+Style/StringChars: # (new in 1.12)
+ Enabled: false # update later in next Update Rubocop PR
+Style/SwapValues: # (new in 1.1)
+ Enabled: true
+Naming/VariableNumber:
+ Enabled: false
+Style/OptionalBooleanParameter:
+ Enabled: false
+Style/RedundantRegexpEscape:
+ Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 359bc075fb3..a9338fe8526 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -12,7 +12,7 @@
# SupportedHashRocketStyles: key, separator, table
# SupportedColonStyles: key, separator, table
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
-Layout/AlignHash:
+Layout/HashAlignment:
Enabled: false
# Offense count: 150
@@ -26,7 +26,7 @@ Lint/FormatParameterMismatch:
- 'test/unit/credit_card_formatting_test.rb'
# Offense count: 2
-Lint/HandleExceptions:
+Lint/SuppressedException:
Exclude:
- 'lib/active_merchant/billing/gateways/mastercard.rb'
- 'lib/active_merchant/billing/gateways/trust_commerce.rb'
@@ -65,6 +65,8 @@ Metrics/CyclomaticComplexity:
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/MethodLength:
Max: 163
+ IgnoredMethods:
+ - 'setup'
# Offense count: 2
# Configuration parameters: CountKeywordArgs.
@@ -99,7 +101,7 @@ Naming/MethodName:
# Offense count: 14
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id, to, by, on, in, at, ip, db
-Naming/UncommunicativeMethodParamName:
+Naming/MethodParameterName:
Exclude:
- 'lib/active_merchant/billing/gateways/blue_snap.rb'
- 'lib/active_merchant/billing/gateways/cyber_source.rb'
@@ -173,13 +175,6 @@ Style/BarePercentLiterals:
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:
@@ -231,10 +226,34 @@ Style/ColonMethodCall:
# 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'
+ Enabled: false # update later in next Update Rubocop PR
+
+Style/StringConcatenation:
+ Enabled: false # update later in next Update Rubocop PR
+Style/SingleArgumentDig:
+ Enabled: false # update later in next Update Rubocop PR
+Style/SlicingWithRange:
+ Enabled: false # update later in next Update Rubocop PR
+Style/HashEachMethods:
+ Enabled: false # update later in next Update Rubocop PR
+Style/CaseLikeIf:
+ Enabled: false # update later in next Update Rubocop PR
+Style/HashLikeCase:
+ Enabled: false # update later in next Update Rubocop PR
+Style/GlobalStdStream:
+ Enabled: false # update later in next Update Rubocop PR
+Style/HashTransformKeys:
+ Enabled: false # update later in next Update Rubocop PR
+Style/HashTransformValues:
+ Enabled: false # update later in next Update Rubocop PR
+Lint/RedundantSafeNavigation:
+ Enabled: false # update later in next Update Rubocop PR
+Lint/EmptyConditionalBody:
+ Enabled: false # update later in next Update Rubocop PR
+Style/SoleNestedConditional:
+ Exclude: # update later in next Update Rubocop PR
+ - 'lib/active_merchant/billing/gateways/card_connect.rb'
+ - 'lib/active_merchant/billing/gateways/blue_snap.rb'
# Offense count: 8
Style/CommentedKeyword:
@@ -381,17 +400,7 @@ Style/FormatString:
# 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'
+ Enabled: false
# Offense count: 679
# Cop supports --auto-correct.
@@ -410,6 +419,15 @@ Style/GlobalVars:
- 'test/unit/gateways/finansbank_test.rb'
- 'test/unit/gateways/garanti_test.rb'
+Lint/MissingSuper:
+ Exclude:
+ - 'lib/active_merchant/billing/gateways/payway.rb'
+ - 'lib/active_merchant/billing/response.rb'
+ - 'lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb'
+ - 'lib/active_merchant/billing/gateways/orbital.rb'
+ - 'lib/active_merchant/billing/gateways/linkpoint.rb'
+ - 'lib/active_merchant/errors.rb'
+
# Offense count: 196
# Configuration parameters: MinBodyLength.
Style/GuardClause:
@@ -448,6 +466,7 @@ Style/InverseMethods:
Exclude:
- 'lib/active_merchant/billing/gateways/ogone.rb'
- 'lib/active_merchant/billing/gateways/worldpay.rb'
+ Enabled: false # update later in next Update Rubocop PR
# Offense count: 32
# Cop supports --auto-correct.
@@ -733,6 +752,6 @@ Style/ZeroLengthPredicate:
# Offense count: 9321
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
-Metrics/LineLength:
+Layout/LineLength:
Max: 2602
diff --git a/CHANGELOG b/CHANGELOG
index fdbf33a2c50..38c8836c43d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,279 @@
= ActiveMerchant CHANGELOG
== HEAD
+* Bump Ruby version to 3.1 [dustinhaefele] #5104
+* FlexCharge: Update inquire method to use the new orders end-point
+* Worldpay: Prefer options for network_transaction_id [aenand] #5129
+* Braintree: Prefer options for network_transaction_id [aenand] #5129
+* Cybersource Rest: Update support for stored credentials [aenand] #5083
+* Plexo: Add support to NetworkToken payments [euribe09] #5130
+
+== Version 1.136.0 (June 3, 2024)
+* Shift4V2: Add new gateway based on SecurionPay adapter [heavyblade] #4860
+* TNS: Use the specified order_id in request if available [yunnydang] #4880
+* Cybersource: Support recurring apple pay [aenand] #4874
+* Verve BIN ranges and add card type to Rapyd gateway [jherreraa] #4875
+* Rapyd: Add network_reference_id, initiation_type, and update stored credential method [yunnydang] #4877
+* Adyen: Add the store field [yunnydang] #4878
+* Stripe Payment Intents: Expand balance txns for regular transactions [yunnydang] #4882
+* CyberSource (SOAP): Added support for 3DS exemption request fields [BritneyS] #4881
+* StripePI: Adding network tokenization fields to Stripe PaymentIntents [BritneyS] #4867
+* Shift4: Fixing currency bug [Heavyblade] #4887
+* Rapyd: fixing issue with json encoding and signatures [Heavyblade] #4892
+* SumUp: Setup, Scrub and Purchase build [sinourain] #4890
+* XpayGateway: Initial setup [javierpedrozaing] #4889
+* Rapyd: Add validation to not send cvv and network_reference_id [javierpedrozaing] #4895
+* Ebanx: Add Ecuador and Bolivia as supported countries [almalee24] #4893
+* Decidir: Add support for network tokens [almalee24] #4870
+* Element: Fix credit card name bug [almalee24] #4898
+* Adyen: Add payout endpoint [almalee24] #4885
+* Adding Oauth Response for access tokens [almalee24] #4851
+* CheckoutV2: Update stored credentials [almalee24] #4901
+* Revert "Adding Oauth Response for access tokens" [almalee24] #4906
+* Braintree: Create credit card nonce [gasb150] #4897
+* Adyen: Fix shopperEmail bug [almalee24] #4904
+* Add Cabal card bin ranges [yunnydang] #4908
+* Kushki: Fixing issue with 3DS info on visa cc [heavyblade] #4899
+* Adyen: Add MIT flagging for Network Tokens [aenand] #4905
+* Moneris: Update sca actions [almalee24] #4902
+* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
+* Rapyd: Add recurrence_type field [yunnydang] #4912
+* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
+* SumUp: Void and partial refund calls [sinourain] #4891
+* SecurionPay/Shift4_v2: authorization from [gasb150] #4913
+* Rapyd: Update recurrence_type field [yunnydang] #4922
+* Element: Add lodging fields [yunnydang] #4813
+* SafeCharge: Update sg_CreditType field on the credit method [yunnydang] #4918
+* Rapyd: add force_3ds_secure flag [Heavyblade] #4927
+* Beanstream: add alternate option for passing phone number [jcreiff] #4923
+* AuthorizeNet: Update network token method [almalee24] #4852
+* Adding Oauth Response for access tokens [almalee24] #4907
+* GlobalCollect: Added support for 3DS exemption request field [almalee24] #4917
+* NMI: Update supported countries list [jcreiff] #4931
+* Adyen: Add mcc field [jcreiff] #4926
+* Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935
+* Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920
+* Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940
+* Cecabank: Add scrub implementation [sinourain] #4945
+* GlobalCollect: Fix bug in success_from logic [DustinHaefele] #4939
+* Worldpay: Update 3ds logic to accept df_reference_id directly [DustinHaefele] #4929
+* Orbital: Enable Third Party Vaulting [javierpedrozaing] #4928
+* Payeezy: Add the customer_ref and reference_3 fields [yunnydang] #4942
+* Redsys Rest: Add support for new gateway type Redsys Rest [aenand] #4951
+* CyberSource: Surface the reconciliationID2 field [yunnydang] #4934
+* Worldpay: Update stored credentials logic [DustinHaefele] #4950
+* Vantiv Express: New Xml gateway [DustinHaefele] #4956
+* Shift4 V2: Add unstore function [javierpedrozaing] #4953
+* CommerceHub: Add 3DS global support [sinourain] #4957
+* SumUp Gateway: Fix refund method [sinourain] #4924
+* Braintree: Add v2 stored credential option [aenand] #4937
+* Cybersource REST: Remove request-target parens [curiousepic] #4960
+* Ogone: Fix signature calulcation for blank fields [Heavyblade] #4963
+* VisaNet Peru: Add purchaseNumber to response object [yunnydang] #4961
+* SafeCharge: Support tokens [almalee24] #4948
+* Redsys: Update to $0 verify [almalee24] #4944
+* Litle: Update stored credentials [almalee24] #4903
+* WorldPay: Accept GooglePay pan only [almalee24] #4943
+* Braintree: Correct issue in v2 stored credentials [aenand] #4967
+* Stripe Payment Intents: Add the card brand field [yunnydang] #4964
+* Rapyd: Enable new auth mode payment_redirect [javierpedrozaing] #4970
+* Cecabank: Fix exemption_type when it is blank and update the error code for some tests [sinourain] #4968
+* RedsysRest: Update to $0 verify [almalee24] #4973
+* CommerceHub: Add credit transaction [sinourain] #4965
+* PayTrace: Send CSC value on gateway request. [DustinHaefele] #4974
+* Orbital: Remove needless GSF for TPV [javierpedrozaing] #4959
+* Adyen: Provide ZZ as default country code [jcreiff] #4971
+* MIT: Add test_url [jcreiff] #4977
+* VantivExpress: Fix eci bug [almalee24] #4982
+* IPG: Allow for Merchant Aggregator credential usage [DustinHaefele] #4986
+* Adyen: Add support for `metadata` object [rachelkirk] #4987
+* Xpay: New adapter basic operations added [jherreraa] #4669
+* Rapyd: Enable idempotent request support [javierpedrozaing] #4980
+* Litle: Update account type [almalee24] #4976
+* Wompi: Add support for `tip_in_cents` [rachelkirk] #4983
+* HiPay: Add Gateway [gasb150] #4979
+* Xpay: New adapter basic operations added [jherreraa] #4669
+* Braintree: Add support for more payment details fields in response [yunnydang] #4992
+* CyberSource: Add the first_recurring_payment auth service field [yunnydang] #4989
+* CommerceHub: Add dynamic descriptors [jcreiff] #4994
+* Rapyd: Update email mapping [javierpedrozaing] #4996
+* SagePay: Add support for v4 [aenand] #4990
+* Braintree: Send merchant_account_id when generating client token [almalee24] #4991
+* CheckoutV2: Update reponse message for 3DS transactions [almalee24] #4975
+* HiPay: Scrub/Refund/Void [gasb150] #4995
+* Rapyd: Adding fixed_side and requested_currency options [Heavyblade] #4962
+* Add new card type Tuya. GlobalCollect & Decidir: Improve support for Tuya card type [sinourain] #4993
+* Cecabank: Encrypt credit card fields [sinourain] #4998
+* HiPay: Add unstore [gasb150] #4999
+* Rapyd: Fix transaction with two digits in month and year [javierpedrozaing] #5008
+* SagePay: Add support for stored credentials [almalee24] #5007
+* Payeezy: Pull cardholer name from billing address [almalee24] #5006
+* HiPay: Add 3ds params [gasb150] #5012
+* Cecabank: Fix gateway scrub method [sinourain] #5009
+* Pin: Add the platform_adjustment field [yunnydang] #5011
+* Priority: Allow gateway fields to be available on capture [yunnydang] #5010
+* Add payment_data to NetworkTokenizationCreditCard [almalee24] #4888
+* IPG: Update handling of ChargeTotal [jcreiff] #5017
+* Plexo: Add the invoice_number field [yunnydang] #5019
+* CheckoutV2: Handle empty address in payout destination data [jcreiff] #5024
+* CyberSource: Add the auth service aggregator_id field [yunnydang] #5026
+* Cecabank: exclude 3ds empty parameter [jherreraa] #5021
+* Moneris: Add the customer id field [yunnydang] #5028
+* Kushki: Add the product_details field [yunnydang] #5027
+* GlobalCollect: Add support for encryptedPaymentData [almalee24] #5015
+* Rapyd: Adding 500 errors handling [Heavyblade] #5029
+* SumUp: Add 3DS fields [sinourain] #5030
+* Cecabank: Enable network_transaction_id as GSF [javierpedrozaing] #5034
+* Braintree: Surface the paypal_details in response object [yunnydang] #5043
+* Worldline (formerly GlobalCollect): Update API endpoints [deemeyers] #5049
+* Quickbooks: Update scrub method [almalee24] #5049
+* Worldline (formerly GlobalCollect):Remove decrypted payment data [almalee24] #5032
+* StripePI: Update authorization_from [almalee24] #5048
+* FirstPay: Add REST JSON transaction methods [sinourain] #5035
+* Braintree: Add payment details to failed transaction hash [yunnydang] #5050
+* Cecabank: Amex CVV Update [sinourain] #5051
+* FirstPay: Add support for ApplePay and GooglePay [sinourain] #5036
+* XPay: Update 3DS to support 3 step process [sinourain] #5046
+* SagePay: Update API endpoints [almalee24] #5057
+* Bin Update: Add sodexo bins [yunnydang] #5061
+* Authorize Net: Add the surcharge field [yunnydang] #5062
+* Paymentez: Update field for reference_id [almalee24] #5065
+* CyberSource: Extend support for `gratuity_amount` and update Mastercard NT field order [rachelkirk] #5063
+* XPay: Refactor basic transactions after implement 3DS 3steps API [sinourain] #5058
+* AuthorizeNet: Remove turn_on_nt flow [almalee24] #5056
+* CheckoutV2: Add processing and recipient fields [yunnydang] #5068
+* RedsysRest: Omit CVV from requests when not present [jcreiff] #5077
+* Bin Update: Add Unionpay bin [yunnydang] #5079
+* MerchantWarrior: Adding support for 3DS Global fields [Heavyblade] #5072
+* FatZebra: Adding third-party 3DS params [Heavyblade] #5066
+* SumUp: Remove Void method [sinourain] #5060
+* StripePI: Add new ApplePay and GooglePay flow [almalee24] #5075
+* Braintree: Add merchant_account_id to Verify [almalee24] #5070
+* Paymentez: Update success_from [jherrera] #5082
+* Update Rubocop to 1.14.0 [almalee24] #5069
+* Adyen: Update error code mapping [dustinhaefele] #5085
+* Updates to StripePI scrub and Paymentez success_from [almalee24] #5090
+* Bin Update: Add Routex bin [yunnydang] #5089
+* SumUp: Improve success_from and message_from methods [sinourain] #5087
+* Adyen: Send new ignore_threed_dynamic for success_from [almalee24] #5078
+* Plexo: Add flow field to capture, purchase, and auth [yunnydang] #5092
+* PayTrace: Always send name in billing_address [almalee24] #5086
+* StripePI: Update eci format [almalee24] #5097
+* Paymentez: Remove reference_id flag [almalee24] #5081
+* Cybersource Rest: Add support for normalized three ds [aenand] #5105
+* Braintree: Add additional data to response [aenand] #5084
+* CheckoutV2: Retain and refresh OAuth access token [sinourain] #5098
+* Worldpay: Remove default ECI value [aenand] #5103
+* DataTrans: Add Gateway [gasb150] #5108
+* CyberSource: Update NT flow [almalee24] #5106
+* FlexCharge: Add Gateway [Heavyblade] #5108
+* Litle: Update enhanced data fields to pass integers [yunnydang] #5113
+* Litle: Update commodity code and line item total fields [yunnydang] #5115
+* Cybersource Rest: Add support for network tokens [aenand] #5107
+* Decidir: Add support for customer object [rachelkirk] #5071
+* Worldpay: Add support for stored credentials with network tokens [aenand] #5114
+* Paymentez: Update success_from method for refunds [almalee24] #5116
+* DataTrans: Add ThirdParty 3DS params [gasb150] #5118
+* FlexCharge: Add ThirdParty 3DS params [javierpedrozaing] #5121
+* FlexCharge: Add support for TPV store [edgarv09] #5120
+* CheckoutV2: Add sender payment fields to purchase and auth [yunnydang] #5124
+* HiPay: Fix parse authorization string [javierpedrozaing] #5119
+* Worldpay: Add support for deafult ECI value [aenand] #5126
+* DLocal: Update stored credentials [sinourain] #5112
+* NMI: Add NTID override [yunnydang] #5134
+* Cybersource Rest: Support L2/L3 data [aenand] #5117
+* Worldpay: Support L2/L3 data [aenand] #5117
+* Support UATP cardtype [javierpedrozaing] #5137
+* Litle: Add 141 and 142 as successful responses [almalee24] #5135
+
+== Version 1.135.0 (August 24, 2023)
+* PaymentExpress: Correct endpoints [steveh] #4827
+* Adyen: Add option to elect which error message [aenand] #4843
+* Reach: Update list of supported countries [jcreiff] #4842
+* Paysafe: Truncate address fields [jcreiff] #4841
+* Braintree: Support third party Network Tokens [aenand] #4775
+* Kushki: Fix add amount default method for subtotalIva and subtotalIva0 [yunnydang] #4845
+* Rapyd: Add customer object to requests [aenand] #4838
+* CyberSource: Add merchant_id [almalee24] #4844
+* Global Collect: Add agent numeric code and house number field [yunnydang] #4847
+* Deepstack: Add Deepstack Gateway [khoinguyendeepstack] #4830
+* Braintree: Additional tests for credit transactions [jcreiff] #4848
+* Rapyd: Change nesting of description, statement_descriptor, complete_payment_url, and error_payment_url [jcreiff] #4849
+* Rapyd: Add merchant_reference_id [jcreiff] #4858
+* Braintree: Return error for ACH on credit [jcreiff] #4859
+* Rapyd: Update handling of ewallet and billing address phone [jcreiff] #4863
+* IPG: Change credentials inputs to use a combined store and user ID string as the user ID input [kylene-spreedly] #4854
+* Braintree Blue: Update the credit card details transaction hash [yunnydang] #4865
+* VisaNet Peru: Update generate_purchase_number_stamp [almalee24] #4855
+* Braintree: Add sca_exemption [almalee24] #4864
+* Ebanx: Update Verify [almalee24] #4866
+* Quickbooks: Remove OAuth response from refresh_access_token [almalee24] #4949
+
+== Version 1.134.0 (July 25, 2023)
+* Update required Ruby version [almalee24] #4823
+* Kushki: Enable 3ds2 [jherreraa] #4832
+
+== Version 1.133.0 (July 20, 2023)
+* CyberSource: remove credentials from tests [bbraschi] #4836
+* Paysafe: Map order_id to merchantRefNum [jcreiff] #4839
+* Stripe PI: Gate sending NTID [almalee24] #4828
+
+== Version 1.132.0 (July 20, 2023)
+* Stripe Payment Intents: Add support for new card on file field [aenand] #4807
+* Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
+* Nuvei (formerly SafeCharge): Add customer details to credit action [yunnydang] #4820
+* IPG: Update live url to correct endpoint [curiousepic] #4121
+* VPos: Adding Panal Credit Card type [jherreraa] #4814
+* Stripe PI: Update parameters for creation of customer [almalee24] #4782
+* WorldPay: Update xml tag for Credit Cards [almalee24] #4797
+* PaywayDotCom: update `live_url` [jcreiff] #4824
+* Stripe & Stripe PI: Update login key validation [almalee24] #4816
+* CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
+* NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
+* Borgun: Update authorization_from & message_from [almalee24] #4826
+* Kushki: Add Brazil as supported country [almalee24] #4829
+* Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
+* MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
+* SafeCharge: Add unreferenced_refund field [yunnydang] #4831
+* CyberSource: include `paymentSolution` for ApplePay and GooglePay [bbraschi] #4835
+
+== Version 1.131.0 (June 21, 2023)
+* Redsys: Add supported countries [jcreiff] #4811
+* Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
+* CheckoutV2: Add support for several customer data fields [rachelkirk] #4800
+* Worldpay: check payment_method responds to payment_cryptogram and eci [bbraschi] #4812
+
+== Version 1.130.0 (June 13th, 2023)
+* Payu Latam - Update error code method to surface network code [yunnydang] #4773
+* CyberSource: Handling Canadian bank accounts [heavyblade] #4764
+* CyberSource Rest: Fixing currency detection [heavyblade] #4777
+* CyberSource: Allow business rules for requests with network tokens [aenand] #4764
+* Adyen: Update Mastercard error messaging [kylene-spreedly] #4770
+* Authorize.net: Update mapping for billing address phone number [jcreiff] #4778
+* Braintree: Update mapping for billing address phone number [jcreiff] #4779
+* CommerceHub: Enabling multi-use public key encryption [jherreraa] #4771
+* Ogone: Enable 3ds Global for Ogone Gateway [javierpedrozaing] #4776
+* Worldpay: Fix Google Pay [almalee24] #4774
+* Borgun change default TrCurrencyExponent and MerchantReturnUrl [naashton] #4788
+* Borgun: support for GBP currency [naashton] #4789
+* CyberSource: Enable auto void on r230 [aenand] #4794
+* Redsys: Set appropriate request fields for stored credentials with CITs and MITs [BritneyS] #4784
+* Stripe & Stripe PI: Validate API Key [almalee24] #4801
+* Add BIN for Maestro [jcreiff] #4799
+* D_Local: Add save field on card object [yunnydang] #4805
+* PayPal Express: Adds support for MsgSubID property on DoReferenceTransaction and DoExpressCheckoutPayment [wikiti] #4798
+* Checkout_v2: use credit_card?, not case equality with CreditCard [bbraschi] #4803
+* Shift4: Enable general credit feature [jherreraa] #4790
+
+== Version 1.129.0 (May 3rd, 2023)
+* Adyen: Update selectedBrand mapping for Google Pay [jcreiff] #4763
+* Shift4: Add vendorReference field [jcreiff] #4762
+* Shift4: Add OAuth error [aenand] #4760
+* Stripe PI: Add billing address details to Apple Pay and Google Pay tokenization request [BritneyS] #4761
+* Make gem compatible with Ruby 3+ [pi3r] #4768
+
+== Version 1.128.0 (April 24th, 2023)
+* CheckoutV2: Add support for Shipping Address [nicolas-maalouf-cko] #4755
* Element: Include Apple Pay - Google pay methods [jherrera] #4647
* dLocal: Add transaction query API(s) request [almalee24] #4584
* MercadoPago: Add transaction inquire request [molbrown] #4588
@@ -55,6 +328,47 @@
* Credorax: Support google pay and apple pay [edgarv09] #4661
* Plexo: Add support for 5 new credit card brands (passcard, edenred, anda, tarjeta-d, sodexo) [edgarv09] #4652
* Authorize.net: Google pay token support [sainterman] #4659
+* Credorax: Add support for Network Tokens [jherreraa] #4679
+* Stripe PI: use MultiResponse in create_setup_intent [jcreiff] #4683
+* Credorax: Correct NTID logic for MIT transactions [aenand] #4686
+* Adyen: Add support for `skip_mpi_data` flag [rachelkirk] #4654
+* Add Canadian Institution Numbers [jcreiff] #4687
+* Tns: update test URL [almalee24] #4698
+* TrustCommerce: Update `authorization_from` to handle `store` response [jherreraa] #4691
+* TrustCommerce: Verify feature added [jherreraa] #4692
+* Rapyd: Add customer object to transactions [javierpedrozaing] #4664
+* CybersourceRest: Add new gateway with authorize and purchase [heavyblade] #4690
+* Litle: Add prelive_url option [aenand] #4710
+* CommerceHub: Fixing verify status and prevent tokenization [heavyblade] #4716
+* Payeezy: Update Stored Credentials [almalee24] #4711
+* CheckoutV2: Add store/unstore [gasb150] #4712
+* CybersourceREST - Refund | Credit [sinourain] #4700
+* Braintree - Add Paypal custom fields [yunnydang] #4713
+* BlueSnap - Add descriptor phone number field [yunnydang] #4717
+* Braintree - Update transaction hash to include processor_authorization_code [yunnydang] #4718
+* CyberSourceRest: Add apple pay, google pay [gasb150] #4708
+* CybersourceREST - Void | Verify [sinourain] #4695
+* Credorax: Set default ECI values for token transactions [sainterman] #4693
+* CyberSourceRest: Add ACH Support [edgarv09] #4722
+* CybersourceREST: Add capture request [heavyblade] #4726
+* Paymentez: Add transaction inquire request [aenand] #4729
+* Ebanx: Add transaction inquire request [almalee24] #4725
+* Ebanx: Add support for Elo & Hipercard [almalee24] #4702
+* CheckoutV2: Add Idempotency key support [yunnydang] #4728
+* Adyen: Add support for shopper_statement field for capture [yunnydang] #4736
+* CheckoutV2: Update idempotency_key name [yunnydang] #4737
+* Payeezy: Enable external 3DS [jherreraa] #4715
+* Ebanx: Remove default email [aenand] #4747
+* CyberSourceRest: Add stored credentials support [jherreraa] #4707
+* Payeezy: Add `last_name` for `add_network_tokenization` [naashton] #4743
+* Stripe PI: Tokenize payment method at Stripe for `verify` [aenand] #4748
+* Kushki: Add support for the months and deferred fields [yunnydang] #4752
+* Borgun: Update TrCurrencyExponent for 3DS transactions with `ISK` [aenand] #4751
+* CyberSourceRest: Add gateway specific fields handling [jherreraa] #4746
+* IPG: Improve error handling [heavyblade] #4753
+* Shift4: Handle access token failed calls [heavyblade] #4745
+* Bogus: Add verify functionality [willemk] #4749
+* Litle: Update successful_from method [almalee24] #4765
== Version 1.127.0 (September 20th, 2022)
* BraintreeBlue: Add venmo profile_id [molbrown] #4512
@@ -706,6 +1020,7 @@
* PayU Latam: Improve error response [esmitperez] #3717
* Vantiv: Vantiv Express - CardPresentCode, PaymentType, SubmissionType, DuplicateCheckDisableFlag [esmitperez] #3730,#3731
* Cybersource: Ensure issueradditionaldata comes before partnerSolutionId [britth] #3733
+* RedsysRest: Add support for 3DS [almalee24] #5042
== Version 1.111.0
* Fat Zebra: standardized 3DS fields and card on file extra data for Visa scheme rules [montdidier] #3409
diff --git a/Gemfile b/Gemfile
index 174afc778d3..ffe8c804b8d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,12 +2,13 @@ source 'https://rubygems.org'
gemspec
gem 'jruby-openssl', platforms: :jruby
-gem 'rubocop', '~> 0.62.0', require: false
+gem 'rubocop', '~> 1.14.0', require: false
group :test, :remote_test do
# gateway-specific dependencies, keeping these gems out of the gemspec
- gem 'braintree', '>= 3.0.0', '<= 3.0.1'
- gem 'jose', '~> 1.1.3'
+ gem 'braintree', '>= 4.14.0'
+ gem 'jose', '~> 1.2.0'
gem 'jwe'
gem 'mechanize'
+ gem 'timecop'
end
diff --git a/activemerchant.gemspec b/activemerchant.gemspec
index e72702e8afc..78484f81232 100644
--- a/activemerchant.gemspec
+++ b/activemerchant.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.email = 'tobi@leetsoft.com'
s.homepage = 'http://activemerchant.org/'
- s.required_ruby_version = '>= 2.5'
+ s.required_ruby_version = '>= 3.1'
s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'CONTRIBUTORS', 'lib/**/*', 'vendor/**/*']
s.require_path = 'lib'
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
s.add_dependency('i18n', '>= 0.6.9')
s.add_dependency('nokogiri', '~> 1.4')
+ s.add_dependency('rexml', '~> 3.2.5')
s.add_development_dependency('mocha', '~> 1')
s.add_development_dependency('pry')
diff --git a/circle.yml b/circle.yml
index fcf9fe6fa42..d9438f7d281 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,6 +1,6 @@
machine:
ruby:
- version: '2.5.0'
+ version: '3.1.0'
dependencies:
cache_directories:
diff --git a/lib/active_merchant/billing/check.rb b/lib/active_merchant/billing/check.rb
index ca4d0171bd7..1d6feb931f2 100644
--- a/lib/active_merchant/billing/check.rb
+++ b/lib/active_merchant/billing/check.rb
@@ -7,8 +7,8 @@ module Billing #:nodoc:
# You may use Check in place of CreditCard with any gateway that supports it.
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
@@ -20,7 +20,7 @@ class Check < Model
309 310 315 320 338 340 509 540 608 614 623 809 815 819 828 829 837 839
865 879 889 899 241 242 248 250 265 275 277 290 294 301 303 307 311 314
321 323 327 328 330 332 334 335 342 343 346 352 355 361 362 366 370 372
- 376 378 807 853 890
+ 376 378 807 853 890 618 842
)
def name
diff --git a/lib/active_merchant/billing/compatibility.rb b/lib/active_merchant/billing/compatibility.rb
index 319ec8a4350..fcd14928b40 100644
--- a/lib/active_merchant/billing/compatibility.rb
+++ b/lib/active_merchant/billing/compatibility.rb
@@ -29,7 +29,7 @@ def self.humanize(lower_case_and_underscored_word)
result = lower_case_and_underscored_word.to_s.dup
result.gsub!(/_id$/, '')
result.tr!('_', ' ')
- result.gsub(/([a-z\d]*)/i, &:downcase).gsub(/^\w/) { $&.upcase }
+ result.gsub(/([a-z\d]*)/i, &:downcase).gsub(/^\w/) { Regexp.last_match(0).upcase }
end
end
end
@@ -90,8 +90,8 @@ def add_to_base(error)
add(:base, error)
end
- def each_full
- full_messages.each { |msg| yield msg }
+ def each_full(&block)
+ full_messages.each(&block)
end
def full_messages
@@ -113,6 +113,6 @@ def full_messages
end
end
- Compatibility::Model.send(:include, Rails::Model)
+ Compatibility::Model.include Rails::Model
end
end
diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb
index 32460e7785e..95e7ae5ce38 100644
--- a/lib/active_merchant/billing/credit_card.rb
+++ b/lib/active_merchant/billing/credit_card.rb
@@ -38,6 +38,10 @@ module Billing #:nodoc:
# * Edenred
# * Anda
# * Creditos directos (Tarjeta D)
+ # * Panal
+ # * Verve
+ # * Tuya
+ # * UATP
#
# 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
@@ -130,6 +134,10 @@ def number=(value)
# * +'edenred'+
# * +'anda'+
# * +'tarjeta-d'+
+ # * +'panal'+
+ # * +'verve'+
+ # * +'tuya'+
+ # * +'uatp'+
#
# Or, if you wish to test your implementation, +'bogus'+.
#
@@ -286,7 +294,7 @@ def name=(full_name)
end
%w(month year start_month start_year).each do |m|
- class_eval %(
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{m}=(v)
@#{m} = case v
when "", nil, 0
@@ -295,7 +303,7 @@ def #{m}=(v)
v.to_i
end
end
- )
+ RUBY
end
def verification_value?
@@ -393,9 +401,7 @@ def validate_essential_attributes #:nodoc:
def validate_card_brand_and_number #:nodoc:
errors = []
- if !empty?(brand)
- errors << [:brand, 'is invalid'] if !CreditCard.card_companies.include?(brand)
- end
+ errors << [:brand, 'is invalid'] if !empty?(brand) && !CreditCard.card_companies.include?(brand)
if empty?(number)
errors << [:number, 'is required']
@@ -403,9 +409,7 @@ def validate_card_brand_and_number #:nodoc:
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 << [:brand, 'does not match the card number'] if errors.empty? && !CreditCard.matching_brand?(number, brand)
errors
end
@@ -423,6 +427,7 @@ def validate_verification_value #:nodoc:
class ExpiryDate #:nodoc:
attr_reader :month, :year
+
def initialize(month, year)
@month = month.to_i
@year = year.to_i
diff --git a/lib/active_merchant/billing/credit_card_formatting.rb b/lib/active_merchant/billing/credit_card_formatting.rb
index ef8a6894ba6..d91d1dba38a 100644
--- a/lib/active_merchant/billing/credit_card_formatting.rb
+++ b/lib/active_merchant/billing/credit_card_formatting.rb
@@ -5,6 +5,10 @@ def expdate(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
+ def strftime_yyyymm(credit_card)
+ format(credit_card.year, :four_digits) + format(credit_card.month, :two_digits)
+ end
+
# This method is used to format numerical information pertaining to credit cards.
#
# format(2005, :two_digits) # => "05"
diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb
index 154f06b2556..82508247f4c 100644
--- a/lib/active_merchant/billing/credit_card_methods.rb
+++ b/lib/active_merchant/billing/credit_card_methods.rb
@@ -24,7 +24,11 @@ module CreditCardMethods
},
'maestro_no_luhn' => ->(num) { num =~ /^(501080|501081|501082)\d{6,13}$/ },
'forbrugsforeningen' => ->(num) { num =~ /^600722\d{10}$/ },
- 'sodexo' => ->(num) { num =~ /^(606071|603389|606070|606069|606068|600818|505864|505865)\d{10}$/ },
+ 'sodexo' => lambda { |num|
+ num&.size == 16 && (
+ SODEXO_BINS.any? { |bin| num.slice(0, bin.size) == bin }
+ )
+ },
'alia' => ->(num) { num =~ /^(504997|505878|601030|601073|505874)\d{10}$/ },
'vr' => ->(num) { num =~ /^(627416|637036)\d{10}$/ },
'unionpay' => ->(num) { (16..19).cover?(num&.size) && in_bin_range?(num.slice(0, 8), UNIONPAY_RANGES) },
@@ -39,13 +43,18 @@ module CreditCardMethods
'creditel' => ->(num) { num =~ /^601933\d{10}$/ },
'confiable' => ->(num) { num =~ /^560718\d{10}$/ },
'synchrony' => ->(num) { num =~ /^700600\d{10}$/ },
- 'routex' => ->(num) { num =~ /^(700676|700678)\d{13}$/ },
+ 'routex' => ->(num) { num =~ /^(700674|700676|700678)\d{13}$/ },
'mada' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MADA_RANGES) },
'bp_plus' => ->(num) { num =~ /^(7050\d\s\d{9}\s\d{3}$|705\d\s\d{8}\s\d{5}$)/ },
'passcard' => ->(num) { num =~ /^628026\d{10}$/ },
'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
'anda' => ->(num) { num =~ /^603199\d{10}$/ },
- 'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ }
+ 'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
+ 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
+ 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) },
+ 'verve' => ->(num) { (16..19).cover?(num&.size) && in_bin_range?(num.slice(0, 6), VERVE_RANGES) },
+ 'tuya' => ->(num) { num =~ /^588800\d{10}$/ },
+ 'uatp' => ->(num) { num =~ /^(1175|1290)\d{11}$/ }
}
SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -70,6 +79,13 @@ module CreditCardMethods
(491730..491759),
]
+ SODEXO_BINS = Set.new(
+ %w[
+ 606071 603389 606070 606069 606068 600818 505864 505865
+ 60607601 60607607 60894400 60894410 60894420 60607606
+ ]
+ )
+
CARNET_RANGES = [
(506199..506499),
]
@@ -108,7 +124,7 @@ module CreditCardMethods
MAESTRO_BINS = Set.new(
%w[ 500057
501018 501043 501045 501047 501049 501051 501072 501075 501083 501087 501089 501095
- 501500
+ 501500 501623
501879 502113 502120 502121 502301
503175 503337 503645 503670
504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
@@ -181,7 +197,8 @@ module CreditCardMethods
(601256..601276),
(601640..601652),
(601689..601700),
- (602011..602050),
+ (602011..602048),
+ [602050],
(630400..630499),
(639000..639099),
(670000..679999),
@@ -193,10 +210,10 @@ module CreditCardMethods
506745..506747, 506753..506753, 506774..506778, 509000..509007, 509009..509014,
509020..509030, 509035..509042, 509044..509089, 509091..509101, 509104..509807,
509831..509877, 509897..509900, 509918..509964, 509971..509986, 509995..509999,
- 627780..627780, 636368..636368, 650031..650033, 650035..650051, 650057..650081,
- 650406..650439, 650485..650504, 650506..650538, 650552..650598, 650720..650727,
- 650901..650922, 650928..650928, 650938..650939, 650946..650978, 651652..651704,
- 655000..655019, 655021..655057
+ 627780..627780, 636297..636298, 636368..636368, 650031..650033, 650035..650051,
+ 650057..650081, 650406..650439, 650485..650504, 650506..650538, 650552..650598,
+ 650720..650727, 650901..650922, 650928..650928, 650938..650939, 650946..650978,
+ 651652..651704, 655000..655019, 655021..655057
]
# Alelo provides BIN ranges by e-mailing them out periodically.
@@ -218,7 +235,8 @@ module CreditCardMethods
58965700..58965799,
60352200..60352299,
65027200..65027299,
- 65008700..65008700
+ 65008700..65008700,
+ 65090000..65090099
]
MADA_RANGES = [
@@ -233,7 +251,7 @@ module CreditCardMethods
# https://www.discoverglobalnetwork.com/content/dam/discover/en_us/dgn/pdfs/IPP-VAR-Enabler-Compliance.pdf
UNIONPAY_RANGES = [
- 62000000..62000000, 62212600..62379699, 62400000..62699999, 62820000..62889999,
+ 62000000..62000000, 62178570..62178570, 62212600..62379699, 62400000..62699999, 62820000..62889999,
81000000..81099999, 81100000..81319999, 81320000..81519999, 81520000..81639999, 81640000..81719999
]
@@ -241,6 +259,50 @@ module CreditCardMethods
3528..3589, 3088..3094, 3096..3102, 3112..3120, 3158..3159, 3337..3349
]
+ HIPERCARD_RANGES = [
+ 384100..384100, 384140..384140, 384160..384160, 606282..606282, 637095..637095,
+ 637568..637568, 637599..637599, 637609..637609, 637612..637612
+ ]
+
+ PANAL_RANGES = [[602049]]
+
+ VERVE_RANGES = [
+ [506099],
+ [506101],
+ [506103],
+ (506111..506114),
+ [506116],
+ [506118],
+ [506124],
+ [506127],
+ [506130],
+ (506132..506139),
+ [506141],
+ [506144],
+ (506146..506152),
+ (506154..506161),
+ (506163..506164),
+ [506167],
+ (506169..506198),
+ (507865..507866),
+ (507868..507872),
+ (507874..507899),
+ (507901..507909),
+ (507911..507919),
+ [507921],
+ (507923..507925),
+ (507927..507962),
+ [507964],
+ [627309],
+ [627903],
+ [628051],
+ [636625],
+ [637058],
+ [637634],
+ [639245],
+ [639383]
+ ]
+
def self.included(base)
base.extend(ClassMethods)
end
@@ -404,7 +466,7 @@ def valid_by_algorithm?(brand, numbers) #:nodoc:
valid_naranja_algo?(numbers)
when 'creditel'
valid_creditel_algo?(numbers)
- when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d'
+ when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d', 'hipercard'
true
when 'sodexo'
sodexo_no_luhn?(numbers) ? true : valid_luhn?(numbers)
diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb
index 063f65b9b9d..2cbeca869a1 100644
--- a/lib/active_merchant/billing/gateway.rb
+++ b/lib/active_merchant/billing/gateway.rb
@@ -315,6 +315,15 @@ def split_names(full_name)
[first_name, last_name]
end
+ def split_address(full_address)
+ address_parts = (full_address || '').split
+ return [nil, nil] if address_parts.size == 0
+
+ number = address_parts.shift
+ street = address_parts.join(' ')
+ [number, street]
+ end
+
def requires!(hash, *params)
params.each do |param|
if param.is_a?(Array)
diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb
index ccc10ba6c51..465be06170b 100644
--- a/lib/active_merchant/billing/gateways/adyen.rb
+++ b/lib/active_merchant/billing/gateways/adyen.rb
@@ -68,6 +68,9 @@ def authorize(money, payment, options = {})
add_application_info(post, options)
add_level_2_data(post, options)
add_level_3_data(post, options)
+ add_data_airline(post, options)
+ add_data_lodging(post, options)
+ add_metadata(post, options)
commit('authorise', post, options)
end
@@ -77,6 +80,7 @@ def capture(money, authorization, options = {})
add_reference(post, authorization, options)
add_splits(post, options)
add_network_transaction_reference(post, options)
+ add_shopper_statement(post, options)
commit('capture', post, options)
end
@@ -90,12 +94,28 @@ def refund(money, authorization, options = {})
end
def credit(money, payment, options = {})
- action = 'refundWithData'
+ action = options[:payout] ? 'payout' : 'refundWithData'
post = init_post(options)
add_invoice(post, money, options)
add_payment(post, payment, options, action)
add_shopper_reference(post, options)
add_network_transaction_reference(post, options)
+
+ if action == 'payout'
+ add_shopper_interaction(post, payment, options)
+ add_fraud_offset(post, options)
+ add_fund_source(post, options)
+ add_recurring_contract(post, options)
+ add_shopper_data(post, payment, options)
+
+ if (address = options[:billing_address] || options[:address]) && address[:country]
+ add_billing_address(post, options, address)
+ end
+
+ post[:dateOfBirth] = options[:date_of_birth] if options[:date_of_birth]
+ post[:nationality] = options[:nationality] if options[:nationality]
+ end
+
commit(action, post, options)
end
@@ -222,17 +242,35 @@ def scrub(transcript)
NETWORK_TOKENIZATION_CARD_SOURCE = {
'apple_pay' => 'applepay',
'android_pay' => 'androidpay',
- 'google_pay' => 'paywithgoogle'
+ 'google_pay' => 'googlepay'
}
def add_extra_data(post, payment, options)
post[:telephoneNumber] = (options[:billing_address][:phone_number] if options.dig(:billing_address, :phone_number)) || (options[:billing_address][:phone] if options.dig(:billing_address, :phone)) || ''
- 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[:captureDelayHours] = options[:capture_delay_hours] if options[:capture_delay_hours]
+ post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
+ post[:shopperIP] = options[:shopper_ip] || options[:ip] if options[:shopper_ip] || options[:ip]
+ post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
+ post[:store] = options[:store] if options[:store]
+ post[:mcc] = options[:mcc] if options[:mcc]
+
+ add_shopper_data(post, payment, options)
+ add_additional_data(post, payment, options)
+ add_risk_data(post, options)
+ add_shopper_reference(post, options)
+ add_merchant_data(post, options)
+ add_fraud_offset(post, options)
+ end
+
+ def add_fraud_offset(post, options)
+ post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
+ end
+
+ def add_additional_data(post, payment, options)
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]
@@ -241,11 +279,7 @@ def add_extra_data(post, payment, options)
post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data]
post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
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_shopper_data(post, options)
- add_risk_data(post, options)
- add_shopper_reference(post, options)
- add_merchant_data(post, options)
+ post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
end
def extract_and_transform(mapper, from)
@@ -290,13 +324,90 @@ def add_level_3_data(post, options)
post[:additionalData].compact!
end
- def add_shopper_data(post, options)
- post[:shopperEmail] = options[:email] if options[:email]
- post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
- post[:shopperIP] = options[:ip] if options[:ip]
- post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
- post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
- post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
+ def add_data_airline(post, options)
+ return unless options[:additional_data_airline]
+
+ mapper = %w[
+ agency_invoice_number
+ agency_plan_name
+ airline_code
+ airline_designator_code
+ boarding_fee
+ computerized_reservation_system
+ customer_reference_number
+ document_type
+ flight_date
+ ticket_issue_address
+ ticket_number
+ travel_agency_code
+ travel_agency_name
+ passenger_name
+ ].each_with_object({}) { |value, hash| hash["airline.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_airline]))
+
+ if options[:additional_data_airline][:leg].present?
+ leg_data = %w[
+ carrier_code
+ class_of_travel
+ date_of_travel
+ depart_airport
+ depart_tax
+ destination_code
+ fare_base_code
+ flight_number
+ stop_over_code
+ ].each_with_object({}) { |value, hash| hash["airline.leg.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(leg_data, options[:additional_data_airline][:leg]))
+ end
+
+ if options[:additional_data_airline][:passenger].present?
+ passenger_data = %w[
+ date_of_birth
+ first_name
+ last_name
+ telephone_number
+ traveller_type
+ ].each_with_object({}) { |value, hash| hash["airline.passenger.#{value}"] = value }
+
+ post[:additionalData].merge!(extract_and_transform(passenger_data, options[:additional_data_airline][:passenger]))
+ end
+ post[:additionalData].compact!
+ end
+
+ def add_data_lodging(post, options)
+ return unless options[:additional_data_lodging]
+
+ mapper = {
+ 'lodging.checkInDate': 'check_in_date',
+ 'lodging.checkOutDate': 'check_out_date',
+ 'lodging.customerServiceTollFreeNumber': 'customer_service_toll_free_number',
+ 'lodging.fireSafetyActIndicator': 'fire_safety_act_indicator',
+ 'lodging.folioCashAdvances': 'folio_cash_advances',
+ 'lodging.folioNumber': 'folio_number',
+ 'lodging.foodBeverageCharges': 'food_beverage_charges',
+ 'lodging.noShowIndicator': 'no_show_indicator',
+ 'lodging.prepaidExpenses': 'prepaid_expenses',
+ 'lodging.propertyPhoneNumber': 'property_phone_number',
+ 'lodging.room1.numberOfNights': 'number_of_nights',
+ 'lodging.room1.rate': 'rate',
+ 'lodging.totalRoomTax': 'total_room_tax',
+ 'lodging.totalTax': 'totalTax',
+ 'travelEntertainmentAuthData.duration': 'duration',
+ 'travelEntertainmentAuthData.market': 'market'
+ }
+
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_lodging]))
+ post[:additionalData].compact!
+ end
+
+ def add_shopper_statement(post, options)
+ return unless options[:shopper_statement]
+
+ post[:additionalData] = {
+ shopperStatement: options[:shopper_statement]
+ }
end
def add_merchant_data(post, options)
@@ -314,7 +425,7 @@ def add_merchant_data(post, options)
def add_risk_data(post, options)
if (risk_data = options[:risk_data])
- risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }]
+ risk_data = risk_data.map { |k, v| ["riskdata.#{k}", v] }.to_h
post[:additionalData].merge!(risk_data)
end
end
@@ -384,30 +495,39 @@ def add_recurring_processing_model(post, options)
def add_address(post, options)
if address = options[:shipping_address]
post[:deliveryAddress] = {}
- post[:deliveryAddress][:street] = address[:address1] || 'NA'
- post[:deliveryAddress][:houseNumberOrName] = address[:address2] || 'NA'
+ post[:deliveryAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
+ post[:deliveryAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
post[:deliveryAddress][:postalCode] = address[:zip] if address[:zip]
post[:deliveryAddress][:city] = address[:city] || 'NA'
post[:deliveryAddress][:stateOrProvince] = get_state(address)
- post[:deliveryAddress][:country] = address[:country] if address[:country]
+ post[:deliveryAddress][:country] = get_country(address)
end
return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash)
if (address = options[:billing_address] || options[:address]) && address[:country]
- post[:billingAddress] = {}
- post[:billingAddress][:street] = address[:address1] || 'NA'
- post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
- post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
- post[:billingAddress][:city] = address[:city] || 'NA'
- post[:billingAddress][:stateOrProvince] = get_state(address)
- post[:billingAddress][:country] = address[:country] if address[:country]
+ add_billing_address(post, options, address)
end
end
+ def add_billing_address(post, options, address)
+ post[:billingAddress] = {}
+ post[:billingAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
+ post[:billingAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
+ post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
+ post[:billingAddress][:city] = address[:city] || 'NA'
+ post[:billingAddress][:stateOrProvince] = get_state(address)
+ post[:billingAddress][:country] = get_country(address)
+ post[:telephoneNumber] = address[:phone_number] || address[:phone] || ''
+ end
+
def get_state(address)
address[:state] && !address[:state].blank? ? address[:state] : 'NA'
end
+ def get_country(address)
+ address[:country].present? ? address[:country] : 'ZZ'
+ end
+
def add_invoice(post, money, options)
currency = options[:currency] || currency(money)
amount = {
@@ -435,7 +555,7 @@ def add_payment(post, payment, options, action = nil)
elsif payment.is_a?(Check)
add_bank_account(post, payment, options, action)
else
- add_mpi_data_for_network_tokenization_card(post, payment) if payment.is_a?(NetworkTokenizationCreditCard)
+ add_mpi_data_for_network_tokenization_card(post, payment, options) if payment.is_a?(NetworkTokenizationCreditCard)
add_card(post, payment)
end
end
@@ -468,6 +588,17 @@ def add_card(post, credit_card)
post[:card] = card
end
+ def add_shopper_data(post, payment, options)
+ if payment && !payment.is_a?(String)
+ post[:shopperName] = {}
+ post[:shopperName][:firstName] = payment.first_name
+ post[:shopperName][:lastName] = payment.last_name
+ end
+
+ post[:shopperEmail] = options[:email] if options[:email]
+ post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
+ end
+
def capture_options(options)
return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]
@@ -486,7 +617,9 @@ def add_reference(post, authorization, options = {})
post[:originalReference] = original_reference
end
- def add_mpi_data_for_network_tokenization_card(post, payment)
+ def add_mpi_data_for_network_tokenization_card(post, payment, options)
+ return if options[:skip_mpi_data] == 'Y'
+
post[:mpiData] = {}
post[:mpiData][:authenticationResponse] = 'Y'
post[:mpiData][:cavv] = payment.payment_cryptogram
@@ -497,11 +630,12 @@ def add_mpi_data_for_network_tokenization_card(post, payment)
def add_recurring_contract(post, options = {})
return unless options[:recurring_contract_type]
- recurring = {
- contract: options[:recurring_contract_type]
- }
-
- post[:recurring] = recurring
+ post[:recurring] = {}
+ post[:recurring][:contract] = options[:recurring_contract_type]
+ post[:recurring][:recurringDetailName] = options[:recurring_detail_name] if options[:recurring_detail_name]
+ post[:recurring][:recurringExpiry] = options[:recurring_expiry] if options[:recurring_expiry]
+ post[:recurring][:recurringFrequency] = options[:recurring_frequency] if options[:recurring_frequency]
+ post[:recurring][:tokenService] = options[:token_service] if options[:token_service]
end
def add_application_info(post, options)
@@ -597,6 +731,30 @@ def add_3ds2_authenticated_data(post, options)
}
end
+ def add_fund_source(post, options)
+ return unless fund_source = options[:fund_source]
+
+ post[:fundSource] = {}
+ post[:fundSource][:additionalData] = fund_source[:additional_data] if fund_source[:additional_data]
+
+ if fund_source[:first_name] && fund_source[:last_name]
+ post[:fundSource][:shopperName] = {}
+ post[:fundSource][:shopperName][:firstName] = fund_source[:first_name]
+ post[:fundSource][:shopperName][:lastName] = fund_source[:last_name]
+ end
+
+ if (address = fund_source[:billing_address])
+ add_billing_address(post[:fundSource], options, address)
+ end
+ end
+
+ def add_metadata(post, options = {})
+ return unless options[:metadata]
+
+ post[:metadata] ||= {}
+ post[:metadata].merge!(options[:metadata]) if options[:metadata]
+ end
+
def parse(body)
return {} if body.blank?
@@ -615,7 +773,7 @@ def commit(action, parameters, options)
success = success_from(action, response, options)
Response.new(
success,
- message_from(action, response),
+ message_from(action, response, options),
response,
authorization: authorization_from(action, parameters, response),
test: test?,
@@ -635,8 +793,14 @@ def cvv_result_from(response)
end
def endpoint(action)
- recurring = %w(disable storeToken).include?(action)
- recurring ? "Recurring/#{RECURRING_API_VERSION}/#{action}" : "Payment/#{PAYMENT_API_VERSION}/#{action}"
+ case action
+ when 'disable', 'storeToken'
+ "Recurring/#{RECURRING_API_VERSION}/#{action}"
+ when 'payout'
+ "Payout/#{PAYMENT_API_VERSION}/#{action}"
+ else
+ "Payment/#{PAYMENT_API_VERSION}/#{action}"
+ end
end
def url(action)
@@ -663,7 +827,7 @@ def request_headers(options)
end
def success_from(action, response, options)
- if %w[RedirectShopper ChallengeShopper].include?(response.dig('resultCode')) && !options[:execute_threed] && !options[:threed_dynamic]
+ if %w[RedirectShopper ChallengeShopper].include?(response.dig('resultCode')) && !options[:execute_threed] && (!options[:threed_dynamic] || options[:ignore_threed_dynamic])
response['refusalReason'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.'
return false
end
@@ -680,18 +844,37 @@ def success_from(action, response, options)
response['response'] == '[detail-successfully-disabled]'
when 'refundWithData'
response['resultCode'] == 'Received'
+ when 'payout'
+ return false unless response['resultCode'] && response['authCode']
+
+ %[AuthenticationFinished Authorised Received].include?(response['resultCode'])
else
false
end
end
- def message_from(action, response)
- return authorize_message_from(response) if %w(authorise authorise3d authorise3ds2).include?(action.to_s)
+ def message_from(action, response, options = {})
+ case action.to_s
+ when 'authorise', 'authorise3d', 'authorise3ds2'
+ authorize_message_from(response, options)
+ when 'payout'
+ response['refusalReason'] || response['resultCode'] || response['message']
+ else
+ response['response'] || response['message'] || response['result'] || response['resultCode']
+ end
+ end
- response['response'] || response['message'] || response['result'] || response['resultCode']
+ def authorize_message_from(response, options = {})
+ return raw_authorize_error_message(response) if options[:raw_error_message]
+
+ if response['refusalReason'] && response['additionalData'] && (response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw'])
+ "#{response['refusalReason']} | #{response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw']}"
+ else
+ response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
+ end
end
- def authorize_message_from(response)
+ def raw_authorize_error_message(response)
if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
"#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
else
@@ -720,7 +903,7 @@ def post_data(action, parameters = {})
end
def error_code_from(response)
- STANDARD_ERROR_CODE_MAPPING[response['errorCode']] || response['errorCode']
+ response.dig('additionalData', 'refusalReasonRaw').try(:scan, /^\d+/).try(:first) || STANDARD_ERROR_CODE_MAPPING[response['errorCode']] || response['errorCode'] || response['refusalReason']
end
def network_transaction_id_from(response)
diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb
index e60f5d8de96..8d81e54999f 100644
--- a/lib/active_merchant/billing/gateways/airwallex.rb
+++ b/lib/active_merchant/billing/gateways/airwallex.rb
@@ -32,7 +32,7 @@ def initialize(options = {})
@client_id = options[:client_id]
@client_api_key = options[:client_api_key]
super
- @access_token = setup_access_token
+ @access_token = options[:access_token] || setup_access_token
end
def purchase(money, card, options = {})
@@ -133,13 +133,27 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
- response = ssl_post(build_request_url(:login), nil, token_headers)
- JSON.parse(response)['token']
+
+ begin
+ raw_response = ssl_post(build_request_url(:login), nil, token_headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = JSON.parse(raw_response)
+ if (token = response['token'])
+ token
+ else
+ oauth_response = Response.new(false, response['message'])
+ raise OAuthResponseError.new(oauth_response)
+ end
+ end
end
def build_request_url(action, id = nil)
base_url = (test? ? test_url : live_url)
- base_url + ENDPOINTS[action].to_s % { id: id }
+ endpoint = ENDPOINTS[action].to_s
+ endpoint = id.present? ? endpoint % { id: id } : endpoint
+ base_url + endpoint
end
def add_referrer_data(post)
@@ -278,11 +292,11 @@ def add_three_ds(post, options)
pm_options = post.dig('payment_method_options', 'card')
external_three_ds = {
- 'version': format_three_ds_version(three_d_secure),
- 'eci': three_d_secure[:eci]
+ version: format_three_ds_version(three_d_secure),
+ eci: three_d_secure[:eci]
}.merge(three_ds_version_specific_fields(three_d_secure))
- pm_options ? pm_options.merge!('external_three_ds': external_three_ds) : post['payment_method_options'] = { 'card': { 'external_three_ds': external_three_ds } }
+ pm_options ? pm_options.merge!(external_three_ds: external_three_ds) : post['payment_method_options'] = { card: { external_three_ds: external_three_ds } }
end
def format_three_ds_version(three_d_secure)
@@ -295,14 +309,14 @@ def format_three_ds_version(three_d_secure)
def three_ds_version_specific_fields(three_d_secure)
if three_d_secure[:version].to_f >= 2
{
- 'authentication_value': three_d_secure[:cavv],
- 'ds_transaction_id': three_d_secure[:ds_transaction_id],
- 'three_ds_server_transaction_id': three_d_secure[:three_ds_server_trans_id]
+ authentication_value: three_d_secure[:cavv],
+ ds_transaction_id: three_d_secure[:ds_transaction_id],
+ three_ds_server_transaction_id: three_d_secure[:three_ds_server_trans_id]
}
else
{
- 'cavv': three_d_secure[:cavv],
- 'xid': three_d_secure[:xid]
+ cavv: three_d_secure[:cavv],
+ xid: three_d_secure[:xid]
}
end
end
diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb
index 69086bc77fd..381b5859372 100644
--- a/lib/active_merchant/billing/gateways/alelo.rb
+++ b/lib/active_merchant/billing/gateways/alelo.rb
@@ -110,8 +110,18 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
- parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
- Response.new(true, parsed[:access_token], parsed)
+ begin
+ raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ if (access_token = response[:access_token])
+ Response.new(true, access_token, response)
+ else
+ raise OAuthResponseError.new(response)
+ end
+ end
end
def remote_encryption_key(access_token)
@@ -144,9 +154,11 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
- rescue ResponseError => error
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise e
+ rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
- raise error unless try_again && %w(401 404).include?(error.response.code) && @options[:access_token].present?
+ raise e unless retry?(try_again, e, :access_token)
@options.delete(:access_token)
@options.delete(:encryption_key)
@@ -206,9 +218,11 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }
multiresp
+ rescue ActiveMerchant::OAuthResponseError => e
+ raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
- if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
+ if retry?(try_again, e, :encryption_key)
@options.delete(:encryption_key)
commit(action, body, options, false)
else
@@ -217,6 +231,10 @@ def commit(action, body, options, try_again = true)
end
end
+ def retry?(try_again, error, key)
+ try_again && %w(401 404).include?(error.response.code) && @options[key].present?
+ end
+
def success_from(action, response)
case action
when 'capture/transaction/refund'
diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb
index 6ce77e44789..0fb03993cfd 100644
--- a/lib/active_merchant/billing/gateways/authorize_net.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net.rb
@@ -85,11 +85,10 @@ class AuthorizeNetGateway < Gateway
AVS_REASON_CODES = %w(27 45)
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/
+ 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
- APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT'
PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155'
INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54'
@@ -165,7 +164,7 @@ def credit(amount, payment, options = {})
xml.transactionType('refundTransaction')
xml.amount(amount(amount))
- add_payment_source(xml, payment, options, :credit)
+ add_payment_method(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)
@@ -262,7 +261,7 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options)
xml.transactionRequest do
xml.transactionType(transaction_type)
xml.amount(amount(amount))
- add_payment_source(xml, payment, options)
+ add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
add_tax_fields(xml, options)
add_duty_fields(xml, options)
@@ -273,6 +272,7 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options)
add_market_type_device_type(xml, payment, options)
add_settings(xml, payment, options)
add_user_fields(xml, amount, options)
+ add_surcharge_fields(xml, options)
add_ship_from_address(xml, options)
add_processing_options(xml, options)
add_subsequent_auth_information(xml, options)
@@ -287,8 +287,9 @@ def add_cim_auth_purchase(xml, transaction_type, amount, payment, options)
add_tax_fields(xml, options)
add_shipping_fields(xml, options)
add_duty_fields(xml, options)
- add_payment_source(xml, payment, options)
+ add_payment_method(xml, payment, options)
add_invoice(xml, transaction_type, options)
+ add_surcharge_fields(xml, options)
add_tax_exempt_status(xml, options)
end
end
@@ -362,7 +363,7 @@ def normal_refund(amount, authorization, options)
xml.accountType(options[:account_type])
xml.routingNumber(options[:routing_number])
xml.accountNumber(options[:account_number])
- xml.nameOnAccount("#{options[:first_name]} #{options[:last_name]}")
+ xml.nameOnAccount(truncate("#{options[:first_name]} #{options[:last_name]}", 22))
end
else
xml.creditCard do
@@ -407,20 +408,27 @@ def normal_void(authorization, options)
end
end
- def add_payment_source(xml, source, options, action = nil)
- return unless source
+ def add_payment_method(xml, payment_method, options, action = nil)
+ return unless payment_method
- 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)
+ case payment_method
+ when String
+ add_token_payment_method(xml, payment_method, options)
+ when Check
+ add_check(xml, payment_method)
else
- add_credit_card(xml, source, action)
+ if network_token?(payment_method, options, action)
+ add_network_token(xml, payment_method)
+ else
+ add_credit_card(xml, payment_method, action)
+ end
end
end
+ def network_token?(payment_method, options, action)
+ payment_method.instance_of?(NetworkTokenizationCreditCard) && action != :credit
+ end
+
def camel_case_lower(key)
String(key).split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
end
@@ -499,7 +507,6 @@ def add_credit_card(xml, credit_card, action)
xml.cardNumber(truncate(credit_card.number, 16))
xml.expirationDate(format(credit_card.month, :two_digits) + '/' + format(credit_card.year, :four_digits))
xml.cardCode(credit_card.verification_value) if credit_card.valid_card_verification_value?(credit_card.verification_value, credit_card.brand)
- xml.cryptogram(credit_card.payment_cryptogram) if credit_card.is_a?(NetworkTokenizationCreditCard) && action != :credit
end
end
end
@@ -526,17 +533,20 @@ def add_token_payment_method(xml, token, options)
xml.customerPaymentProfileId(customer_payment_profile_id)
end
- def add_apple_pay_payment_token(xml, apple_pay_payment_token)
+ def add_network_token(xml, payment_method)
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))
+ xml.creditCard do
+ xml.cardNumber(truncate(payment_method.number, 16))
+ xml.expirationDate(format(payment_method.month, :two_digits) + '/' + format(payment_method.year, :four_digits))
+ xml.isPaymentToken(true)
+ xml.cryptogram(payment_method.payment_cryptogram)
end
end
end
def add_market_type_device_type(xml, payment, options)
- return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay'
+ return unless payment.is_a?(CreditCard)
+ return if payment.is_a?(NetworkTokenizationCreditCard)
if valid_track_data
xml.retail do
@@ -604,6 +614,7 @@ def add_billing_address(xml, payment_source, options)
first_name, last_name = names_from(payment_source, address, options)
state = state_from(address, options)
full_address = "#{address[:address1]} #{address[:address2]}".strip
+ phone = address[:phone] || address[:phone_number] || ''
xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
@@ -613,7 +624,7 @@ def add_billing_address(xml, payment_source, options)
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.phoneNumber(truncate(phone, 25)) unless empty?(phone)
xml.faxNumber(truncate(address[:fax], 25)) unless empty?(address[:fax])
end
end
@@ -700,6 +711,16 @@ def add_duty_fields(xml, options)
end
end
+ def add_surcharge_fields(xml, options)
+ surcharge = options[:surcharge] if options[:surcharge]
+ if surcharge.is_a?(Hash)
+ xml.surcharge do
+ xml.amount(amount(surcharge[:amount].to_i)) if surcharge[:amount]
+ xml.description(surcharge[:description]) if surcharge[:description]
+ end
+ end
+ end
+
def add_shipping_fields(xml, options)
shipping = options[:shipping]
if shipping.is_a?(Hash)
@@ -753,13 +774,7 @@ def create_customer_payment_profile(credit_card, options)
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
+ add_credit_card(xml, credit_card, :cim_store_update)
end
end
end
@@ -775,13 +790,7 @@ def create_customer_profile(credit_card, options)
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
+ add_credit_card(xml, credit_card, :cim_store)
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
index 5bcf08b8107..d6dde0dea6f 100644
--- a/lib/active_merchant/billing/gateways/authorize_net_arb.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net_arb.rb
@@ -208,9 +208,9 @@ def add_subscription(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]
+ if trial = options[:trial] && (trial[:amount])
# The amount to be charged for each payment during a trial period (conditional)
- xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount]
+ xml.tag!('trialAmount', amount(trial[:amount]))
end
# Contains either the customer’s credit card
# or bank account payment information
@@ -260,9 +260,9 @@ def add_payment_schedule(xml, options)
# Contains information about the interval of time between payments
add_interval(xml, options)
add_duration(xml, options)
- if trial = options[:trial]
+ if trial = options[:trial] && (trial[:occurrences])
# Number of billing occurrences or payments in the trial period (optional)
- xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences]
+ xml.tag!('trialOccurrences', trial[:occurrences])
end
end
end
@@ -393,9 +393,13 @@ def recurring_commit(action, request)
test_mode = test? || message =~ /Test Mode/
success = response[:result_code] == 'Ok'
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test_mode,
- authorization: response[:subscription_id])
+ authorization: response[:subscription_id]
+ )
end
def recurring_parse(action, xml)
diff --git a/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/lib/active_merchant/billing/gateways/authorize_net_cim.rb
index 09eff729308..0fb0c866cda 100644
--- a/lib/active_merchant/billing/gateways/authorize_net_cim.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net_cim.rb
@@ -695,9 +695,7 @@ def add_transaction(xml, transaction)
add_order(xml, transaction[:order]) if transaction[:order].present?
end
- if %i[auth_capture auth_only capture_only].include?(transaction[:type])
- xml.tag!('recurringBilling', transaction[:recurring_billing]) if transaction.has_key?(:recurring_billing)
- end
+ xml.tag!('recurringBilling', transaction[:recurring_billing]) if %i[auth_capture auth_only capture_only].include?(transaction[:type]) && transaction.has_key?(:recurring_billing)
tag_unless_blank(xml, 'cardCode', transaction[:card_code]) unless %i[void refund prior_auth_capture].include?(transaction[:type])
end
end
diff --git a/lib/active_merchant/billing/gateways/axcessms.rb b/lib/active_merchant/billing/gateways/axcessms.rb
index b3e113338a6..eff4b112086 100644
--- a/lib/active_merchant/billing/gateways/axcessms.rb
+++ b/lib/active_merchant/billing/gateways/axcessms.rb
@@ -71,9 +71,13 @@ def commit(paymentcode, money, payment, options)
message = "#{response[:reason]} - #{response[:return]}"
authorization = response[:unique_id]
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
authorization: authorization,
- test: (response[:mode] != 'LIVE'))
+ test: (response[:mode] != 'LIVE')
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/banwire.rb b/lib/active_merchant/billing/gateways/banwire.rb
index 6e669c8eef1..d4e784361d2 100644
--- a/lib/active_merchant/billing/gateways/banwire.rb
+++ b/lib/active_merchant/billing/gateways/banwire.rb
@@ -89,11 +89,13 @@ def commit(money, parameters)
response = json_error(raw_response)
end
- Response.new(success?(response),
+ Response.new(
+ success?(response),
response['message'],
response,
test: test?,
- authorization: response['code_auth'])
+ authorization: response['code_auth']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
index b794899c579..87e5c89ba5e 100644
--- a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
+++ b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb
@@ -228,7 +228,7 @@ def add_address(post, options)
if billing_address = options[:billing_address] || options[:address]
post[:ordName] = billing_address[:name]
- post[:ordPhoneNumber] = billing_address[:phone]
+ post[:ordPhoneNumber] = billing_address[:phone] || billing_address[:phone_number]
post[:ordAddress1] = billing_address[:address1]
post[:ordAddress2] = billing_address[:address2]
post[:ordCity] = billing_address[:city]
@@ -413,11 +413,15 @@ def recurring_commit(params)
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,
+ build_response(
+ success?(response),
+ message_from(response),
+ response,
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
def recurring_post(data)
diff --git a/lib/active_merchant/billing/gateways/blue_pay.rb b/lib/active_merchant/billing/gateways/blue_pay.rb
index 60b6fc863a7..4f2f7eac987 100644
--- a/lib/active_merchant/billing/gateways/blue_pay.rb
+++ b/lib/active_merchant/billing/gateways/blue_pay.rb
@@ -344,14 +344,18 @@ def parse_recurring(response_fields, opts = {}) # expected status?
success = parsed[:status] != 'error'
message = parsed[:status]
- Response.new(success, message, parsed,
+ Response.new(
+ success,
+ message,
+ parsed,
test: test?,
- authorization: parsed[:rebill_id])
+ 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] }]
+ response_fields = CGI::parse(body).map { |k, v| [k.upcase, v.first] }.to_h
return parse_recurring(response_fields) if response_fields.include? 'REBILL_ID'
@@ -364,11 +368,15 @@ def parse(body)
# normalize message
message = message_from(parsed)
success = parsed[:response_code] == '1'
- Response.new(success, message, parsed,
+ 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])
+ cvv_result: parsed[:card_code]
+ )
end
def message_from(parsed)
diff --git a/lib/active_merchant/billing/gateways/blue_snap.rb b/lib/active_merchant/billing/gateways/blue_snap.rb
index de2ec414d5c..8490f0643d9 100644
--- a/lib/active_merchant/billing/gateways/blue_snap.rb
+++ b/lib/active_merchant/billing/gateways/blue_snap.rb
@@ -260,6 +260,7 @@ def add_metadata(doc, options)
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]
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
add_metadata(doc, options)
add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
add_level_3_data(doc, options)
@@ -366,6 +367,7 @@ def add_shipping_contact_info(doc, payment_method, options)
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]
+ doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
add_amount(doc, money, options)
vaulted_shopper_id = payment_method_details.vaulted_shopper_id
@@ -444,10 +446,10 @@ def parse_metadata_entry(node)
end
def parse_element(parsed, node)
- if !node.elements.empty?
- node.elements.each { |e| parse_element(parsed, e) }
- else
+ if node.elements.empty?
parsed[node.name.downcase] = node.text
+ else
+ node.elements.each { |e| parse_element(parsed, e) }
end
end
@@ -457,8 +459,8 @@ def api_request(action, request, verb, payment_method_details, options)
e.response
end
- def commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new())
- request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
+ def commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new(), &block)
+ request = build_xml_request(action, payment_method_details, &block)
response = api_request(action, request, verb, payment_method_details, options)
parsed = parse(response)
diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb
index 9aed8028586..30b8be9838a 100644
--- a/lib/active_merchant/billing/gateways/bogus.rb
+++ b/lib/active_merchant/billing/gateways/bogus.rb
@@ -90,6 +90,10 @@ def void(reference, options = {})
end
end
+ def verify(credit_card, options = {})
+ authorize(0, credit_card, options)
+ end
+
def store(paysource, options = {})
case normalize(paysource)
when /1$/
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
index 8d4883dd4f7..778c6bc64eb 100644
--- a/lib/active_merchant/billing/gateways/borgun.rb
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -96,6 +96,7 @@ def scrub(transcript)
CURRENCY_CODES['ISK'] = '352'
CURRENCY_CODES['EUR'] = '978'
CURRENCY_CODES['USD'] = '840'
+ CURRENCY_CODES['GBP'] = '826'
def add_3ds_fields(post, options)
post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id]
@@ -105,13 +106,18 @@ def add_3ds_fields(post, options)
def add_3ds_preauth_fields(post, options)
post[:SaleDescription] = options[:sale_description] || ''
- post[:MerchantReturnURL] = options[:merchant_return_url] if options[:merchant_return_url]
+ post[:MerchantReturnURL] = options[:redirect_url] if options[:redirect_url]
end
def add_invoice(post, money, options)
post[:TrAmount] = amount(money)
post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
- post[:TrCurrencyExponent] = options[:currency_exponent] || 0 if options[:apply_3d_secure] == '1'
+ # The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
+ if post[:TrCurrency] == '352' && options[:apply_3d_secure] != '1'
+ post[:TrCurrencyExponent] = 0
+ else
+ post[:TrCurrencyExponent] = 2
+ end
post[:TerminalID] = options[:terminal_id] || '1'
end
@@ -166,7 +172,7 @@ def commit(action, post, options = {})
success,
message_from(success, pairs),
pairs,
- authorization: authorization_from(pairs),
+ authorization: authorization_from(pairs, options),
test: test?
)
end
@@ -179,12 +185,12 @@ def message_from(succeeded, response)
if succeeded
'Succeeded'
else
- response[:message] || "Error with ActionCode=#{response[:actioncode]}"
+ response[:message] || response[:status_errormessage] || "Error with ActionCode=#{response[:actioncode]}"
end
end
- def authorization_from(response)
- [
+ def authorization_from(response, options)
+ authorization = [
response[:dateandtime],
response[:batch],
response[:transaction],
@@ -194,6 +200,8 @@ def authorization_from(response)
response[:tramount],
response[:trcurrency]
].join('|')
+
+ authorization == '|||||||' ? nil : authorization
end
def split_authorization(authorization)
diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
index 37417dd732a..dc9a3e0bc90 100644
--- a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
+++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb
@@ -29,7 +29,7 @@ def create_token_nonce_for_payment_method(payment_method)
json_response = JSON.parse(resp)
message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present?
- token = json_response.dig('data', 'tokenizeUsBankAccount', 'paymentMethod', 'id')
+ token = token_from(payment_method, json_response)
return token, message
end
@@ -41,7 +41,7 @@ def client_token
private
- def graphql_query
+ def graphql_bank_query
<<-GRAPHQL
mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
tokenizeUsBankAccount(input: $input) {
@@ -58,6 +58,23 @@ def graphql_query
GRAPHQL
end
+ def graphql_credit_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
def billing_address_from_options
return nil if options[:billing_address].blank?
@@ -72,7 +89,42 @@ def billing_address_from_options
}.compact
end
+ def build_nonce_credit_card_request(payment_method)
+ billing_address = billing_address_from_options
+ key_replacements = { city: :locality, state: :region, zipCode: :postalCode }
+ billing_address&.transform_keys! { |key| key_replacements[key] || key }
+ {
+ creditCard: {
+ number: payment_method.number,
+ expirationYear: payment_method.year.to_s,
+ expirationMonth: payment_method.month.to_s.rjust(2, '0'),
+ cvv: payment_method.verification_value,
+ cardholderName: payment_method.name,
+ billingAddress: billing_address
+ }
+ }
+ end
+
def build_nonce_request(payment_method)
+ input = payment_method.is_a?(Check) ? build_nonce_bank_request(payment_method) : build_nonce_credit_card_request(payment_method)
+ graphql_query = payment_method.is_a?(Check) ? graphql_bank_query : graphql_credit_query
+
+ {
+ clientSdkMetadata: {
+ platform: 'web',
+ source: 'client',
+ integration: 'custom',
+ sessionId: SecureRandom.uuid,
+ version: '3.83.0'
+ },
+ query: graphql_query,
+ variables: {
+ input: input
+ }
+ }.to_json
+ end
+
+ def build_nonce_bank_request(payment_method)
input = {
usBankAccount: {
achMandate: options[:ach_mandate],
@@ -94,19 +146,12 @@ def build_nonce_request(payment_method)
}
end
- {
- clientSdkMetadata: {
- platform: 'web',
- source: 'client',
- integration: 'custom',
- sessionId: SecureRandom.uuid,
- version: '3.83.0'
- },
- query: graphql_query,
- variables: {
- input: input
- }
- }.to_json
+ input
+ end
+
+ def token_from(payment_method, response)
+ tokenized_field = payment_method.is_a?(Check) ? 'tokenizeUsBankAccount' : 'tokenizeCreditCard'
+ response.dig('data', tokenized_field, 'paymentMethod', 'id')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index 146470d66ba..8a9782e3baf 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -75,9 +75,12 @@ def initialize(options = {})
@braintree_gateway = Braintree::Gateway.new(@configuration)
end
- def setup_purchase
+ def setup_purchase(options = {})
+ post = {}
+ add_merchant_account_id(post, options)
+
commit do
- Response.new(true, 'Client token created', { client_token: @braintree_gateway.client_token.generate })
+ Response.new(true, 'Client token created', { client_token: @braintree_gateway.client_token.generate(post) })
end
end
@@ -106,6 +109,8 @@ def purchase(money, credit_card_or_vault_id, options = {})
end
def credit(money, credit_card_or_vault_id, options = {})
+ return Response.new(false, DIRECT_BANK_ERROR) if credit_card_or_vault_id.is_a? Check
+
create_transaction(:credit, money, credit_card_or_vault_id, options)
end
@@ -149,6 +154,10 @@ def verify(creditcard, options = {})
}
}
}
+ if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id)
+ payload[:options] = { merchant_account_id: merchant_account_id }
+ end
+
commit do
result = @braintree_gateway.verification.create(payload)
response = Response.new(result.success?, message_from_transaction_result(result), response_options(result))
@@ -193,16 +202,20 @@ def update(vault_id, creditcard, options = {})
}
}, options)[:credit_card]
- result = @braintree_gateway.customer.update(vault_id,
+ result = @braintree_gateway.customer.update(
+ vault_id,
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]),
- credit_card: credit_card_params)
- Response.new(result.success?, message_from_result(result),
+ phone: phone_from(options),
+ credit_card: credit_card_params
+ )
+ Response.new(
+ result.success?,
+ message_from_result(result),
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?))
+ customer_vault_id: (result.customer.id if result.success?)
+ )
end
end
@@ -267,19 +280,21 @@ def add_customer_with_credit_card(creditcard, options)
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]),
+ phone: phone_from(options),
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),
+ 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?))
+ authorization: (result.customer.id if result.success?)
+ )
end
end
@@ -348,6 +363,10 @@ def merge_credit_card_options(parameters, options)
parameters
end
+ def phone_from(options)
+ options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ end
+
def map_address(address)
mapped = {
street_address: address[:address1],
@@ -369,8 +388,8 @@ def map_address(address)
def commit(&block)
yield
- rescue Braintree::BraintreeError => ex
- Response.new(false, ex.class.to_s)
+ rescue Braintree::BraintreeError => e
+ Response.new(false, e.class.to_s)
end
def message_from_result(result)
@@ -420,6 +439,8 @@ def response_options(result)
end
def avs_code_from(transaction)
+ return unless transaction
+
transaction.avs_error_response_code ||
avs_mapping["street: #{transaction.avs_street_address_response_code}, zip: #{transaction.avs_postal_code_response_code}"]
end
@@ -484,6 +505,55 @@ def additional_processor_response_from_result(result)
result.transaction&.additional_processor_response
end
+ def payment_instrument_type(result)
+ result&.payment_instrument_type
+ end
+
+ def credit_card_details(result)
+ if result
+ {
+ 'masked_number' => result.credit_card_details&.masked_number,
+ 'bin' => result.credit_card_details&.bin,
+ 'last_4' => result.credit_card_details&.last_4,
+ 'card_type' => result.credit_card_details&.card_type,
+ 'token' => result.credit_card_details&.token,
+ 'debit' => result.credit_card_details&.debit,
+ 'prepaid' => result.credit_card_details&.prepaid,
+ 'issuing_bank' => result.credit_card_details&.issuing_bank,
+ 'country_of_issuance' => result.credit_card_details&.country_of_issuance
+ }
+ end
+ end
+
+ def network_token_details(result)
+ if result
+ {
+ 'debit' => result.network_token_details&.debit,
+ 'prepaid' => result.network_token_details&.prepaid,
+ 'issuing_bank' => result.network_token_details&.issuing_bank
+ }
+ end
+ end
+
+ def google_pay_details(result)
+ if result
+ {
+ 'debit' => result.google_pay_details&.debit,
+ 'prepaid' => result.google_pay_details&.prepaid
+ }
+ end
+ end
+
+ def apple_pay_details(result)
+ if result
+ {
+ 'debit' => result.apple_pay_details&.debit,
+ 'prepaid' => result.apple_pay_details&.prepaid,
+ 'issuing_bank' => result.apple_pay_details&.issuing_bank
+ }
+ 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
@@ -543,7 +613,15 @@ def customer_hash(customer, include_credit_cards = false)
def transaction_hash(result)
unless result.success?
return { 'processor_response_code' => response_code_from_result(result),
- 'additional_processor_response' => additional_processor_response_from_result(result) }
+ 'additional_processor_response' => additional_processor_response_from_result(result),
+ 'payment_instrument_type' => payment_instrument_type(result.transaction),
+ 'credit_card_details' => credit_card_details(result.transaction),
+ 'network_token_details' => network_token_details(result.transaction),
+ 'google_pay_details' => google_pay_details(result.transaction),
+ 'apple_pay_details' => apple_pay_details(result.transaction),
+ 'avs_response_code' => avs_code_from(result.transaction),
+ 'cvv_response_code' => result.transaction&.cvv_response_code,
+ 'gateway_message' => result.message }
end
transaction = result.transaction
@@ -559,6 +637,14 @@ def transaction_hash(result)
vault_customer = nil
end
+ credit_card_details = credit_card_details(transaction)
+
+ network_token_details = network_token_details(transaction)
+
+ google_pay_details = google_pay_details(transaction)
+
+ apple_pay_details = apple_pay_details(transaction)
+
customer_details = {
'id' => transaction.customer_details.id,
'email' => transaction.customer_details.email,
@@ -584,12 +670,10 @@ def transaction_hash(result)
'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
+
+ paypal_details = {
+ 'payer_id' => transaction.paypal_details.payer_id,
+ 'payer_email' => transaction.paypal_details.payer_email
}
if transaction.risk_data
@@ -603,20 +687,35 @@ def transaction_hash(result)
risk_data = nil
end
+ if transaction.payment_receipt
+ payment_receipt = {
+ 'global_id' => transaction.payment_receipt.global_id
+ }
+ else
+ payment_receipt = nil
+ end
+
{
- '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),
- 'recurring' => transaction.recurring
+ 'order_id' => transaction.order_id,
+ 'amount' => transaction.amount.to_s,
+ 'status' => transaction.status,
+ 'credit_card_details' => credit_card_details,
+ 'network_token_details' => network_token_details,
+ 'apple_pay_details' => apple_pay_details,
+ 'google_pay_details' => google_pay_details,
+ 'paypal_details' => paypal_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),
+ 'processor_authorization_code' => transaction.processor_authorization_code,
+ 'recurring' => transaction.recurring,
+ 'payment_receipt' => payment_receipt,
+ 'payment_instrument_type' => payment_instrument_type(transaction)
}
end
@@ -627,8 +726,7 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
customer: {
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])
+ phone: phone_from(options)
},
options: {
store_in_vault: options[:store] ? true : false,
@@ -652,6 +750,7 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
add_descriptor(parameters, options)
add_risk_data(parameters, options)
+ add_paypal_options(parameters, options)
add_travel_data(parameters, options) if options[:travel_data]
add_lodging_data(parameters, options) if options[:lodging_data]
add_channel(parameters, options)
@@ -662,6 +761,8 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options)
add_3ds_info(parameters, options[:three_d_secure])
+ parameters[:sca_exemption] = options[:three_ds_exemption_type] if options[:three_ds_exemption_type]
+
if options[:payment_method_nonce].is_a?(String)
parameters.delete(:customer)
parameters[:payment_method_nonce] = options[:payment_method_nonce]
@@ -728,6 +829,15 @@ def add_risk_data(parameters, options)
}
end
+ def add_paypal_options(parameters, options)
+ return unless options[:paypal_custom_field] || options[:paypal_description]
+
+ parameters[:options][:paypal] = {
+ custom_field: options[:paypal_custom_field],
+ description: options[:paypal_description]
+ }
+ end
+
def add_level_2_data(parameters, options)
parameters[:tax_amount] = options[:tax_amount] if options[:tax_amount]
parameters[:tax_exempt] = options[:tax_exempt] if options[:tax_exempt]
@@ -791,15 +901,41 @@ def xid_or_ds_trans_id(three_d_secure_opts)
end
def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
+ # Braintree has informed us that the stored_credential mapping may be incorrect
+ # In order to prevent possible breaking changes we will only apply the new logic if
+ # specifically requested. This will be the default behavior in a future release.
return unless (stored_credential = options[:stored_credential])
- parameters[:external_vault] = {}
- if stored_credential[:initial_transaction]
- parameters[:external_vault][:status] = 'will_vault'
+ add_external_vault(parameters, options)
+
+ if options[:stored_credentials_v2]
+ stored_credentials_v2(parameters, stored_credential)
else
- parameters[:external_vault][:status] = 'vaulted'
- parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id]
+ stored_credentials_v1(parameters, stored_credential)
end
+ end
+
+ def stored_credentials_v2(parameters, stored_credential)
+ # Differences between v1 and v2 are
+ # initial_transaction + recurring/installment should be labeled {{reason_type}}_first
+ # unscheduled in AM should map to '' at BT because unscheduled here means not on a fixed timeline or fixed amount
+ case stored_credential[:reason_type]
+ when 'recurring', 'installment'
+ if stored_credential[:initial_transaction]
+ parameters[:transaction_source] = "#{stored_credential[:reason_type]}_first"
+ else
+ parameters[:transaction_source] = stored_credential[:reason_type]
+ end
+ when 'recurring_first', 'moto'
+ parameters[:transaction_source] = stored_credential[:reason_type]
+ when 'unscheduled'
+ parameters[:transaction_source] = stored_credential[:initiator] == 'merchant' ? stored_credential[:reason_type] : ''
+ else
+ parameters[:transaction_source] = ''
+ end
+ end
+
+ def stored_credentials_v1(parameters, stored_credential)
if stored_credential[:initiator] == 'merchant'
if stored_credential[:reason_type] == 'installment'
parameters[:transaction_source] = 'recurring'
@@ -813,56 +949,99 @@ def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
end
end
+ def add_external_vault(parameters, options = {})
+ 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] = options[:network_transaction_id] || stored_credential[:network_transaction_id]
+ 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)
- 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
+ add_third_party_token(parameters, credit_card_or_vault_id, options)
else
parameters[:customer].merge!(
first_name: credit_card_or_vault_id.first_name,
last_name: credit_card_or_vault_id.last_name
)
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
- Braintree::Version::Major < 3 ? pay_card = :android_pay_card : pay_card = :google_pay_card
- parameters[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
- }
+ case credit_card_or_vault_id.source
+ when :apple_pay
+ add_apple_pay(parameters, credit_card_or_vault_id)
+ when :google_pay
+ add_google_pay(parameters, credit_card_or_vault_id)
+ else
+ add_network_tokenization_card(parameters, credit_card_or_vault_id)
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
- }
+ add_credit_card(parameters, credit_card_or_vault_id)
end
end
end
+ def add_third_party_token(parameters, payment_method, options)
+ if options[:payment_method_token]
+ parameters[:payment_method_token] = payment_method
+ options.delete(:billing_address)
+ elsif options[:payment_method_nonce]
+ parameters[:payment_method_nonce] = payment_method
+ else
+ parameters[:customer_id] = payment_method
+ end
+ end
+
+ def add_credit_card(parameters, payment_method)
+ parameters[:credit_card] = {
+ number: payment_method.number,
+ cvv: payment_method.verification_value,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name
+ }
+ end
+
+ def add_apple_pay(parameters, payment_method)
+ parameters[:apple_pay_card] = {
+ number: payment_method.number,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name,
+ cryptogram: payment_method.payment_cryptogram,
+ eci_indicator: payment_method.eci
+ }
+ end
+
+ def add_google_pay(parameters, payment_method)
+ Braintree::Version::Major < 3 ? pay_card = :android_pay_card : pay_card = :google_pay_card
+ parameters[pay_card] = {
+ number: payment_method.number,
+ cryptogram: payment_method.payment_cryptogram,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ google_transaction_id: payment_method.transaction_id,
+ source_card_type: payment_method.brand,
+ source_card_last_four: payment_method.last_digits,
+ eci_indicator: payment_method.eci
+ }
+ end
+
+ def add_network_tokenization_card(parameters, payment_method)
+ parameters[:credit_card] = {
+ number: payment_method.number,
+ expiration_month: payment_method.month.to_s.rjust(2, '0'),
+ expiration_year: payment_method.year.to_s,
+ cardholder_name: payment_method.name,
+ network_tokenization_attributes: {
+ cryptogram: payment_method.payment_cryptogram,
+ ecommerce_indicator: payment_method.eci
+ }
+ }
+ end
+
def bank_account_errors(payment_method, options)
if payment_method.validate.present?
payment_method.validate
@@ -889,13 +1068,16 @@ def add_bank_account_to_customer(payment_method, options)
message = message_from_result(result)
message = not_verified_reason(result.payment_method) unless verified
- Response.new(verified, message,
+ Response.new(
+ verified,
+ message,
{
customer_vault_id: options[:customer],
bank_account_token: result.payment_method&.token,
verified: verified
},
- authorization: result.payment_method&.token)
+ authorization: result.payment_method&.token
+ )
end
def not_verified_reason(bank_account)
@@ -921,7 +1103,7 @@ def create_customer_from_bank_account(payment_method, options)
first_name: payment_method.first_name,
last_name: payment_method.last_name,
email: scrub_email(options[:email]),
- phone: options[:phone] || options.dig(:billing_address, :phone),
+ phone: phone_from(options),
device_data: options[:device_data]
}.compact
@@ -932,7 +1114,7 @@ def create_customer_from_bank_account(payment_method, options)
Response.new(
result.success?,
message_from_result(result),
- { customer_vault_id: customer_id, 'exists': true }
+ { customer_vault_id: customer_id, exists: true }
)
end
end
diff --git a/lib/active_merchant/billing/gateways/braintree_orange.rb b/lib/active_merchant/billing/gateways/braintree_orange.rb
index f56502eb7a0..a4f85d879a7 100644
--- a/lib/active_merchant/billing/gateways/braintree_orange.rb
+++ b/lib/active_merchant/billing/gateways/braintree_orange.rb
@@ -1,4 +1,4 @@
-require 'active_merchant/billing/gateways/smart_ps.rb'
+require 'active_merchant/billing/gateways/smart_ps'
require 'active_merchant/billing/gateways/braintree/braintree_common'
module ActiveMerchant #:nodoc:
diff --git a/lib/active_merchant/billing/gateways/card_connect.rb b/lib/active_merchant/billing/gateways/card_connect.rb
index 8895e72bad3..6a803aeb322 100644
--- a/lib/active_merchant/billing/gateways/card_connect.rb
+++ b/lib/active_merchant/billing/gateways/card_connect.rb
@@ -146,9 +146,12 @@ def store(payment, options = {})
def unstore(authorization, options = {})
account_id, profile_id = authorization.split('|')
- commit('profile', {},
+ commit(
+ 'profile',
+ {},
verb: :delete,
- path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}")
+ path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}"
+ )
end
def supports_scrubbing?
diff --git a/lib/active_merchant/billing/gateways/card_stream.rb b/lib/active_merchant/billing/gateways/card_stream.rb
index 76c8f318519..a20799c198d 100644
--- a/lib/active_merchant/billing/gateways/card_stream.rb
+++ b/lib/active_merchant/billing/gateways/card_stream.rb
@@ -248,12 +248,10 @@ def add_invoice(post, credit_card_or_reference, money, options)
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 %w[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, localized_amount(money, options[:currency] || currency(money)))
- end
+ if credit_card_or_reference.respond_to?(:number) && %w[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, localized_amount(money, options[:currency] || currency(money)))
end
add_pair(post, :type, options[:type] || '1')
diff --git a/lib/active_merchant/billing/gateways/cashnet.rb b/lib/active_merchant/billing/gateways/cashnet.rb
index 340210415c3..cd0c13c6e38 100644
--- a/lib/active_merchant/billing/gateways/cashnet.rb
+++ b/lib/active_merchant/billing/gateways/cashnet.rb
@@ -150,7 +150,7 @@ def parse(body)
match = body.match(/(.*)<\/cngateway>/)
return nil unless match
- Hash[CGI::parse(match[1]).map { |k, v| [k.to_sym, v.first] }]
+ CGI::parse(match[1]).map { |k, v| [k.to_sym, v.first] }.to_h
end
def handle_response(response)
diff --git a/lib/active_merchant/billing/gateways/cecabank.rb b/lib/active_merchant/billing/gateways/cecabank.rb
index d85b48f7ed9..18a0aed5d93 100644
--- a/lib/active_merchant/billing/gateways/cecabank.rb
+++ b/lib/active_merchant/billing/gateways/cecabank.rb
@@ -1,248 +1,15 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_xml'
+require 'active_merchant/billing/gateways/cecabank/cecabank_json'
+
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 = %i[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
+ self.abstract_class = true
- 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'
- response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
- 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 self.new(options = {})
+ return CecabankJsonGateway.new(options) if options[:is_rest_json]
- 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)
+ CecabankXmlGateway.new(options)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
new file mode 100644
index 00000000000..a397c2955c8
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb
@@ -0,0 +1,36 @@
+module CecabankCommon
+ #### CECA's MAGIC NUMBERS
+ CECA_ENCRIPTION = 'SHA2'
+ CECA_CURRENCIES_DICTIONARY = { 'EUR' => 978, 'USD' => 840, 'GBP' => 826 }
+
+ def self.included(base)
+ base.supported_countries = ['ES']
+ base.supported_cardtypes = %i[visa master american_express]
+ base.homepage_url = 'http://www.ceca.es/es/'
+ base.display_name = 'Cecabank'
+ base.default_currency = 'EUR'
+ base.money_format = :cents
+ end
+
+ # 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)
+ # * :cypher_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, :cypher_key)
+ super
+ end
+
+ def supports_scrubbing?
+ true
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
new file mode 100644
index 00000000000..e24df79c05b
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb
@@ -0,0 +1,316 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_common'
+
+module ActiveMerchant
+ module Billing
+ class CecabankJsonGateway < Gateway
+ include CecabankCommon
+
+ CECA_ACTIONS_DICTIONARY = {
+ purchase: :REST_AUTORIZACION,
+ authorize: :REST_PREAUTORIZACION,
+ capture: :REST_COBRO_PREAUTORIZACION,
+ refund: :REST_DEVOLUCION,
+ void: :REST_ANULACION
+ }.freeze
+
+ CECA_REASON_TYPES = {
+ installment: :I,
+ recurring: :R,
+ unscheduled: :C
+ }.freeze
+
+ CECA_INITIATOR = {
+ merchant: :N,
+ cardholder: :S
+ }.freeze
+
+ CECA_SCA_TYPES = {
+ low_value_exemption: :LOW,
+ transaction_risk_analysis_exemption: :TRA
+ }.freeze
+
+ self.test_url = 'https://tpv.ceca.es/tpvweb/rest/procesos/'
+ self.live_url = 'https://pgw.ceca.es/tpvweb/rest/procesos/'
+
+ def authorize(money, creditcard, options = {})
+ handle_purchase(:authorize, money, creditcard, options)
+ end
+
+ def capture(money, identification, options = {})
+ authorization, operation_number, _money = identification.split('#')
+
+ post = {}
+ options[:operation_number] = operation_number
+ add_auth_invoice_data(:capture, post, money, authorization, options)
+
+ commit('compra', post)
+ end
+
+ def purchase(money, creditcard, options = {})
+ handle_purchase(:purchase, money, creditcard, options)
+ end
+
+ def void(identification, options = {})
+ authorization, operation_number, money = identification.split('#')
+ options[:operation_number] = operation_number
+ handle_cancellation(:void, money.to_i, authorization, options)
+ end
+
+ def refund(money, identification, options = {})
+ authorization, operation_number, _money = identification.split('#')
+ options[:operation_number] = operation_number
+ handle_cancellation(:refund, money, authorization, options)
+ end
+
+ def scrub(transcript)
+ return '' if transcript.blank?
+
+ before_message = transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"')
+ request_data = JSON.parse(before_message)
+
+ if @options[:encryption_key]
+ params = parse(request_data['parametros'])
+ sensitive_fields = decrypt_sensitive_fields(params['encryptedData'])
+ filtered_params = filter_params(sensitive_fields)
+ params['encryptedData'] = encrypt_sensitive_fields(filtered_params)
+ else
+ params = filter_params(decode_params(request_data['parametros']))
+ end
+
+ request_data['parametros'] = encode_params(params)
+ before_message = before_message.gsub(%r(\")i, '\\\"')
+ after_message = request_data.to_json.gsub(%r(\")i, '\\\"')
+ transcript.sub(before_message, after_message)
+ end
+
+ private
+
+ def filter_params(params)
+ params.
+ gsub(%r(("pan\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("caducidad\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv2\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("csc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("authentication_value\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
+ end
+
+ def decrypt_sensitive_fields(data)
+ cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
+ cipher.key = [@options[:encryption_key]].pack('H*')
+ cipher.iv = @options[:initiator_vector]&.split('')&.map(&:to_i)&.pack('c*')
+ cipher.update([data].pack('H*')) + cipher.final
+ end
+
+ def encrypt_sensitive_fields(data)
+ cipher = OpenSSL::Cipher.new('AES-256-CBC').encrypt
+ cipher.key = [@options[:encryption_key]].pack('H*')
+ cipher.iv = @options[:initiator_vector]&.split('')&.map(&:to_i)&.pack('c*')
+ encrypted = cipher.update(data.to_json) + cipher.final
+ encrypted.unpack1('H*')
+ end
+
+ def handle_purchase(action, money, creditcard, options)
+ post = { parametros: { accion: CECA_ACTIONS_DICTIONARY[action] } }
+
+ add_invoice(post, money, options)
+ add_creditcard(post, creditcard)
+ add_stored_credentials(post, creditcard, options)
+ add_three_d_secure(post, options)
+
+ commit('compra', post)
+ end
+
+ def handle_cancellation(action, money, authorization, options = {})
+ post = {}
+ add_auth_invoice_data(action, post, money, authorization, options)
+
+ commit('anulacion', post)
+ end
+
+ def add_auth_invoice_data(action, post, money, authorization, options)
+ params = post[:parametros] ||= {}
+ params[:accion] = CECA_ACTIONS_DICTIONARY[action]
+ params[:referencia] = authorization
+
+ add_invoice(post, money, options)
+ end
+
+ def add_encryption(post)
+ post[:cifrado] = CECA_ENCRIPTION
+ post[:parametros][:encryptedData] = encrypt_sensitive_fields(post[:parametros][:encryptedData]) if @options[:encryption_key]
+ end
+
+ def add_signature(post, params_encoded, options)
+ post[:firma] = Digest::SHA2.hexdigest(@options[:cypher_key].to_s + params_encoded)
+ end
+
+ def add_merchant_data(post)
+ params = post[:parametros] ||= {}
+
+ params[:merchantID] = @options[:merchant_id]
+ params[:acquirerBIN] = @options[:acquirer_bin]
+ params[:terminalID] = @options[:terminal_id]
+ end
+
+ def add_invoice(post, money, options)
+ post[:parametros][:numOperacion] = options[:operation_number] || options[:order_id]
+ post[:parametros][:importe] = amount(money)
+ post[:parametros][:tipoMoneda] = CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)].to_s
+ post[:parametros][:exponente] = 2.to_s
+ end
+
+ def add_creditcard(post, creditcard)
+ params = post[:parametros] ||= {}
+
+ payment_method = {
+ pan: creditcard.number,
+ caducidad: strftime_yyyymm(creditcard)
+ }
+ if CreditCard.brand?(creditcard.number) == 'american_express'
+ payment_method[:csc] = creditcard.verification_value
+ else
+ payment_method[:cvv2] = creditcard.verification_value
+ end
+
+ @options[:encryption_key] ? params[:encryptedData] = payment_method : params.merge!(payment_method)
+ end
+
+ def add_stored_credentials(post, creditcard, options)
+ return unless stored_credential = options[:stored_credential]
+
+ return if options[:exemption_type].blank? && !(stored_credential[:reason_type] && stored_credential[:initiator])
+
+ params = post[:parametros] ||= {}
+ params[:exencionSCA] = 'MIT'
+
+ requires!(stored_credential, :reason_type, :initiator)
+ reason_type = CECA_REASON_TYPES[stored_credential[:reason_type].to_sym]
+ initiator = CECA_INITIATOR[stored_credential[:initiator].to_sym]
+ params[:tipoCOF] = reason_type
+ params[:inicioRec] = initiator
+ if initiator == :S
+ requires!(options, :recurring_frequency)
+ params[:finRec] = options[:recurring_end_date] || strftime_yyyymm(creditcard)
+ params[:frecRec] = options[:recurring_frequency]
+ end
+
+ network_transaction_id = options[:network_transaction_id].present? ? options[:network_transaction_id] : stored_credential[:network_transaction_id]
+ params[:mmppTxId] = network_transaction_id unless network_transaction_id.blank?
+ end
+
+ def add_three_d_secure(post, options)
+ params = post[:parametros] ||= {}
+ return unless three_d_secure = options[:three_d_secure]
+
+ params[:exencionSCA] ||= CECA_SCA_TYPES.fetch(options[:exemption_type]&.to_sym, :NONE)
+
+ three_d_response = {
+ exemption_type: options[:exemption_type],
+ three_ds_version: three_d_secure[:version],
+ directory_server_transaction_id: three_d_secure[:ds_transaction_id],
+ acs_transaction_id: three_d_secure[:acs_transaction_id],
+ authentication_response_status: three_d_secure[:authentication_response_status],
+ three_ds_server_trans_id: three_d_secure[:three_ds_server_trans_id],
+ ecommerce_indicator: three_d_secure[:eci],
+ enrolled: three_d_secure[:enrolled]
+ }
+
+ if @options[:encryption_key]
+ params[:encryptedData].merge!({ authentication_value: three_d_secure[:cavv] })
+ else
+ three_d_response[:authentication_value] = three_d_secure[:cavv]
+ end
+
+ three_d_response[:amount] = post[:parametros][:importe]
+ params[:ThreeDsResponse] = three_d_response.to_json
+ end
+
+ def commit(action, post)
+ auth_options = {
+ operation_number: post.dig(:parametros, :numOperacion),
+ amount: post.dig(:parametros, :importe)
+ }
+
+ add_encryption(post)
+ add_merchant_data(post)
+
+ params_encoded = encode_post_parameters(post)
+ add_signature(post, params_encoded, options)
+
+ response = parse(ssl_post(url(action), post.to_json, headers))
+ response[:parametros] = parse(response[:parametros]) if response[:parametros]
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, auth_options),
+ network_transaction_id: network_transaction_id_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def url(action)
+ (test? ? self.test_url : self.live_url) + action
+ end
+
+ def host
+ URI.parse(url('')).host
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Host' => host
+ }
+ end
+
+ def parse(string)
+ JSON.parse(string).with_indifferent_access
+ rescue JSON::ParserError
+ parse(decode_params(string))
+ end
+
+ def encode_post_parameters(post)
+ post[:parametros] = encode_params(post[:parametros])
+ end
+
+ def encode_params(params)
+ Base64.strict_encode64(params.is_a?(Hash) ? params.to_json : params)
+ end
+
+ def decode_params(params)
+ Base64.decode64(params)
+ end
+
+ def success_from(response)
+ response[:codResult].blank?
+ end
+
+ def message_from(response)
+ return response[:parametros].to_json if success_from(response)
+
+ response[:paramsEntradaError] || response[:idProceso]
+ end
+
+ def authorization_from(response, auth_options = {})
+ return unless response[:parametros]
+
+ [
+ response[:parametros][:referencia],
+ auth_options[:operation_number],
+ auth_options[:amount]
+ ].join('#')
+ end
+
+ def network_transaction_id_from(response)
+ response.dig(:parametros, :mmppTxId)
+ end
+
+ def error_code_from(response)
+ (response[:codResult] || :paramsEntradaError) unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
new file mode 100644
index 00000000000..d670e23ab49
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb
@@ -0,0 +1,220 @@
+require 'active_merchant/billing/gateways/cecabank/cecabank_common'
+
+module ActiveMerchant
+ module Billing
+ class CecabankXmlGateway < Gateway
+ include CecabankCommon
+
+ self.test_url = 'https://tpv.ceca.es'
+ self.live_url = 'https://pgw.ceca.es'
+
+ #### CECA's MAGIC NUMBERS
+ CECA_NOTIFICATIONS_URL = 'NONE'
+ 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'
+
+ # 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 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
+ elsif root.elements['OPERACION'].attributes['numeroOperacion'] == '000'
+ response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
+ else
+ response[:authorization] = root.attributes['numeroOperacion']
+ 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[:signature_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[:signature_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/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
index bfefa82ce19..28cd1014a0c 100644
--- a/lib/active_merchant/billing/gateways/checkout_v2.rb
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -17,14 +17,8 @@ class CheckoutV2Gateway < Gateway
TEST_ACCESS_TOKEN_URL = 'https://access.sandbox.checkout.com/connect/token'
def initialize(options = {})
- @options = options
- @access_token = nil
- begin
- requires!(options, :secret_key)
- rescue ArgumentError
- requires!(options, :client_id, :client_secret)
- @access_token = setup_access_token
- end
+ options.has_key?(:secret_key) ? requires!(options, :secret_key) : requires!(options, :client_id, :client_secret)
+
super
end
@@ -32,15 +26,14 @@ def purchase(amount, payment_method, options = {})
post = {}
build_auth_or_purchase(post, amount, payment_method, options)
- commit(:purchase, post)
+ commit(:purchase, post, options)
end
def authorize(amount, payment_method, options = {})
post = {}
post[:capture] = false
build_auth_or_purchase(post, amount, payment_method, options)
-
- options[:incremental_authorization] ? commit(:incremental_authorize, post, options[:incremental_authorization]) : commit(:authorize, post)
+ options[:incremental_authorization] ? commit(:incremental_authorize, post, options, options[:incremental_authorization]) : commit(:authorize, post, options)
end
def capture(amount, authorization, options = {})
@@ -48,28 +41,30 @@ def capture(amount, authorization, options = {})
post[:capture_type] = options[:capture_type] || 'Final'
add_invoice(post, amount, options)
add_customer_data(post, options)
+ add_shipping_address(post, options)
add_metadata(post, options)
- commit(:capture, post, authorization)
+ commit(:capture, post, options, authorization)
end
def credit(amount, payment, options = {})
post = {}
- post[:instruction] = {}
- post[:instruction][:funds_transfer_type] = options[:funds_transfer_type] || 'FD'
add_processing_channel(post, options)
add_invoice(post, amount, options)
add_payment_method(post, payment, options, :destination)
add_source(post, options)
+ add_instruction_data(post, options)
+ add_payout_sender_data(post, options)
+ add_payout_destination_data(post, options)
- commit(:credit, post)
+ commit(:credit, post, options)
end
def void(authorization, _options = {})
post = {}
add_metadata(post, options)
- commit(:void, post, authorization)
+ commit(:void, post, options, authorization)
end
def refund(amount, authorization, options = {})
@@ -78,15 +73,15 @@ def refund(amount, authorization, options = {})
add_customer_data(post, options)
add_metadata(post, options)
- commit(:refund, post, authorization)
+ commit(:refund, post, options, authorization)
end
def verify(credit_card, options = {})
authorize(0, credit_card, options)
end
- def verify_payment(authorization, option = {})
- commit(:verify_payment, authorization)
+ def verify_payment(authorization, options = {})
+ commit(:verify_payment, nil, options, authorization, :get)
end
def supports_scrubbing?
@@ -99,7 +94,36 @@ def scrub(transcript)
gsub(/("number\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]').
gsub(/("cryptogram\\":\\")\w+/, '\1[FILTERED]').
- gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]')
+ gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]').
+ gsub(/("token\\":\\")\w+/, '\1[FILTERED]').
+ gsub(/("access_token\\?"\s*:\s*\\?")[^"]*\w+/, '\1[FILTERED]')
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+ MultiResponse.run do |r|
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
+ r.process { verify(payment_method, options) }
+ break r unless r.success?
+
+ r.params['source']['customer'] = r.params['customer']
+ r.process { response(:store, true, r.params['source']) }
+ else
+ r.process { tokenize(payment_method, options) }
+ break r unless r.success?
+
+ token = r.params['token']
+ add_payment_method(post, token, options)
+ post.merge!(post.delete(:source))
+ add_customer_data(post, options)
+ add_shipping_address(post, options)
+ r.process { commit(:store, post, options) }
+ end
+ end
+ end
+
+ def unstore(id, options = {})
+ commit(:unstore, nil, options, id, :delete)
end
private
@@ -109,12 +133,17 @@ def build_auth_or_purchase(post, amount, payment_method, options)
add_authorization_type(post, options)
add_payment_method(post, payment_method, options)
add_customer_data(post, options)
+ add_extra_customer_data(post, payment_method, options)
+ add_shipping_address(post, options)
add_stored_credential_options(post, options)
add_transaction_data(post, options)
add_3ds(post, options)
add_metadata(post, options, payment_method)
add_processing_channel(post, options)
add_marketplace_data(post, options)
+ add_recipient_data(post, options)
+ add_processing_data(post, options)
+ add_payment_sender_data(post, options)
end
def add_invoice(post, money, options)
@@ -130,6 +159,72 @@ def add_invoice(post, money, options)
post[:metadata][:udf5] = application_id || 'ActiveMerchant'
end
+ def add_recipient_data(post, options)
+ return unless options[:recipient].is_a?(Hash)
+
+ recipient = options[:recipient]
+
+ post[:recipient] = {}
+ post[:recipient][:dob] = recipient[:dob] if recipient[:dob]
+ post[:recipient][:zip] = recipient[:zip] if recipient[:zip]
+ post[:recipient][:account_number] = recipient[:account_number] if recipient[:account_number]
+ post[:recipient][:first_name] = recipient[:first_name] if recipient[:first_name]
+ post[:recipient][:last_name] = recipient[:last_name] if recipient[:last_name]
+
+ if address = recipient[:address]
+ address1 = address[:address1] || address[:address_line1]
+ address2 = address[:address2] || address[:address_line2]
+
+ post[:recipient][:address] = {}
+ post[:recipient][:address][:address_line1] = address1 if address1
+ post[:recipient][:address][:address_line2] = address2 if address2
+ post[:recipient][:address][:city] = address[:city] if address[:city]
+ post[:recipient][:address][:state] = address[:state] if address[:state]
+ post[:recipient][:address][:zip] = address[:zip] if address[:zip]
+ post[:recipient][:address][:country] = address[:country] if address[:country]
+ end
+ end
+
+ def add_processing_data(post, options)
+ return unless options[:processing].is_a?(Hash)
+
+ post[:processing] = options[:processing]
+ end
+
+ def add_payment_sender_data(post, options)
+ return unless options[:sender].is_a?(Hash)
+
+ sender = options[:sender]
+
+ post[:sender] = {}
+ post[:sender][:type] = sender[:type] if sender[:type]
+ post[:sender][:first_name] = sender[:first_name] if sender[:first_name]
+ post[:sender][:last_name] = sender[:last_name] if sender[:last_name]
+ post[:sender][:dob] = sender[:dob] if sender[:dob]
+ post[:sender][:reference] = sender[:reference] if sender[:reference]
+ post[:sender][:company_name] = sender[:company_name] if sender[:company_name]
+
+ if address = sender[:address]
+ address1 = address[:address1] || address[:address_line1]
+ address2 = address[:address2] || address[:address_line2]
+
+ post[:sender][:address] = {}
+ post[:sender][:address][:address_line1] = address1 if address1
+ post[:sender][:address][:address_line2] = address2 if address2
+ post[:sender][:address][:city] = address[:city] if address[:city]
+ post[:sender][:address][:state] = address[:state] if address[:state]
+ post[:sender][:address][:zip] = address[:zip] if address[:zip]
+ post[:sender][:address][:country] = address[:country] if address[:country]
+ end
+
+ if identification = sender[:identification]
+ post[:sender][:identification] = {}
+ post[:sender][:identification][:type] = identification[:type] if identification[:type]
+ post[:sender][:identification][:number] = identification[:number] if identification[:number]
+ post[:sender][:identification][:issuing_country] = identification[:issuing_country] if identification[:issuing_country]
+ end
+ end
+
def add_authorization_type(post, options)
post[:authorization_type] = options[:authorization_type] if options[:authorization_type]
end
@@ -141,8 +236,10 @@ def add_metadata(post, options, payment_method = nil)
end
def add_payment_method(post, payment_method, options, key = :source)
+ # the key = :destination when this method is called in def credit
post[key] = {}
- if payment_method.is_a?(NetworkTokenizationCreditCard)
+ case payment_method
+ when NetworkTokenizationCreditCard
token_type = token_type_from(payment_method)
cryptogram = payment_method.payment_cryptogram
eci = payment_method.eci || options[:eci]
@@ -153,23 +250,43 @@ def add_payment_method(post, payment_method, options, key = :source)
post[key][:token_type] = token_type
post[key][:cryptogram] = cryptogram if cryptogram
post[key][:eci] = eci if eci
- elsif payment_method.is_a?(CreditCard)
+ when ->(pm) { pm.try(:credit_card?) }
post[key][:type] = 'card'
post[key][:name] = payment_method.name
post[key][:number] = payment_method.number
- post[key][:cvv] = payment_method.verification_value
+ post[key][:cvv] = payment_method.verification_value unless options[:funds_transfer_type]
post[key][:stored] = 'true' if options[:card_on_file] == true
+
+ # because of the way the key = is implemented in the method signature, some of the destination
+ # data will be added here, some in the destination specific method below.
+ # at first i was going to move this, but since this data is coming from the payment method
+ # i think it makes sense to leave it
if options[:account_holder_type]
post[key][:account_holder] = {}
post[key][:account_holder][:type] = options[:account_holder_type]
- post[key][:account_holder][:first_name] = payment_method.first_name if payment_method.first_name
- post[key][:account_holder][:last_name] = payment_method.last_name if payment_method.last_name
+
+ if options[:account_holder_type] == 'corporate' || options[:account_holder_type] == 'government'
+ post[key][:account_holder][:company_name] = payment_method.name if payment_method.respond_to?(:name)
+ else
+ post[key][:account_holder][:first_name] = payment_method.first_name if payment_method.first_name
+ post[key][:account_holder][:last_name] = payment_method.last_name if payment_method.last_name
+ end
else
post[key][:first_name] = payment_method.first_name if payment_method.first_name
post[key][:last_name] = payment_method.last_name if payment_method.last_name
end
end
- unless payment_method.is_a?(String)
+ if payment_method.is_a?(String)
+ if /tok/.match?(payment_method)
+ post[:type] = 'token'
+ post[:token] = payment_method
+ elsif /src/.match?(payment_method)
+ post[key][:type] = 'id'
+ post[key][:id] = payment_method
+ else
+ add_source(post, options)
+ end
+ elsif payment_method.try(:year)
post[key][:expiry_year] = format(payment_method.year, :four_digits)
post[key][:expiry_month] = format(payment_method.month, :two_digits)
end
@@ -197,6 +314,28 @@ def add_customer_data(post, options)
end
end
+ # created a separate method for these fields because they should not be included
+ # in all transaction types that include methods with source and customer fields
+ def add_extra_customer_data(post, payment_method, options)
+ post[:source][:phone] = {}
+ post[:source][:phone][:number] = options[:phone] || options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ post[:source][:phone][:country_code] = options[:phone_country_code] if options[:phone_country_code]
+ post[:customer][:name] = payment_method.name if payment_method.respond_to?(:name)
+ end
+
+ def add_shipping_address(post, options)
+ if address = options[:shipping_address]
+ post[:shipping] = {}
+ post[:shipping][:address] = {}
+ post[:shipping][:address][:address_line1] = address[:address1] unless address[:address1].blank?
+ post[:shipping][:address][:address_line2] = address[:address2] unless address[:address2].blank?
+ post[:shipping][:address][:city] = address[:city] unless address[:city].blank?
+ post[:shipping][:address][:state] = address[:state] unless address[:state].blank?
+ post[:shipping][:address][:country] = address[:country] unless address[:country].blank?
+ post[:shipping][: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
@@ -211,7 +350,7 @@ def merchant_initiated_override(post, options)
end
def add_stored_credentials_using_normalized_fields(post, options)
- if options[:stored_credential][:initial_transaction] == true
+ if options[:stored_credential][:initiator] == 'cardholder'
post[:merchant_initiated] = false
else
post[:source][:stored] = true
@@ -256,6 +395,82 @@ def add_processing_channel(post, options)
post[:processing_channel_id] = options[:processing_channel_id] if options[:processing_channel_id]
end
+ def add_instruction_data(post, options)
+ post[:instruction] = {}
+ post[:instruction][:funds_transfer_type] = options[:funds_transfer_type] || 'FD'
+ post[:instruction][:purpose] = options[:instruction_purpose] if options[:instruction_purpose]
+ end
+
+ def add_payout_sender_data(post, options)
+ return unless options[:payout] == true
+
+ post[:sender] = {
+ # options for type are individual, corporate, or government
+ type: options[:sender][:type],
+ # first and last name required if sent by type: individual
+ first_name: options[:sender][:first_name],
+ middle_name: options[:sender][:middle_name],
+ last_name: options[:sender][:last_name],
+ # company name required if sent by type: corporate or government
+ company_name: options[:sender][:company_name],
+ # these are required fields for payout, may not work if address is blank or different than cardholder(option for sender to be a company or government).
+ # may need to still include in GSF hash.
+
+ address: {
+ address_line1: options.dig(:sender, :address, :address1),
+ address_line2: options.dig(:sender, :address, :address2),
+ city: options.dig(:sender, :address, :city),
+ state: options.dig(:sender, :address, :state),
+ country: options.dig(:sender, :address, :country),
+ zip: options.dig(:sender, :address, :zip)
+ }.compact,
+ reference: options[:sender][:reference],
+ reference_type: options[:sender][:reference_type],
+ source_of_funds: options[:sender][:source_of_funds],
+ # identification object is conditional. required when card metadata issuer_country = AR, BR, CO, or PR
+ # checkout docs say PR (Peru), but PR is puerto rico and PE is Peru so yikes
+ identification: {
+ type: options.dig(:sender, :identification, :type),
+ number: options.dig(:sender, :identification, :number),
+ issuing_country: options.dig(:sender, :identification, :issuing_country),
+ date_of_expiry: options.dig(:sender, :identification, :date_of_expiry)
+ }.compact,
+ date_of_birth: options[:sender][:date_of_birth],
+ country_of_birth: options[:sender][:country_of_birth],
+ nationality: options[:sender][:nationality]
+ }.compact
+ end
+
+ def add_payout_destination_data(post, options)
+ return unless options[:payout] == true
+
+ post[:destination] ||= {}
+ post[:destination][:account_holder] ||= {}
+ post[:destination][:account_holder][:email] = options[:destination][:account_holder][:email] if options[:destination][:account_holder][:email]
+ post[:destination][:account_holder][:date_of_birth] = options[:destination][:account_holder][:date_of_birth] if options[:destination][:account_holder][:date_of_birth]
+ post[:destination][:account_holder][:country_of_birth] = options[:destination][:account_holder][:country_of_birth] if options[:destination][:account_holder][:country_of_birth]
+ # below fields only required during a card to card payout
+ post[:destination][:account_holder][:phone] = {}
+ post[:destination][:account_holder][:phone][:country_code] = options.dig(:destination, :account_holder, :phone, :country_code) if options.dig(:destination, :account_holder, :phone, :country_code)
+ post[:destination][:account_holder][:phone][:number] = options.dig(:destination, :account_holder, :phone, :number) if options.dig(:destination, :account_holder, :phone, :number)
+
+ post[:destination][:account_holder][:identification] = {}
+ post[:destination][:account_holder][:identification][:type] = options.dig(:destination, :account_holder, :identification, :type) if options.dig(:destination, :account_holder, :identification, :type)
+ post[:destination][:account_holder][:identification][:number] = options.dig(:destination, :account_holder, :identification, :number) if options.dig(:destination, :account_holder, :identification, :number)
+ post[:destination][:account_holder][:identification][:issuing_country] = options.dig(:destination, :account_holder, :identification, :issuing_country) if options.dig(:destination, :account_holder, :identification, :issuing_country)
+ post[:destination][:account_holder][:identification][:date_of_expiry] = options.dig(:destination, :account_holder, :identification, :date_of_expiry) if options.dig(:destination, :account_holder, :identification, :date_of_expiry)
+
+ if address = options[:billing_address] || options[:address] # destination address will come from the tokenized card billing address
+ post[:destination][:account_holder][:billing_address] = {}
+ post[:destination][:account_holder][:billing_address][:address_line1] = address[:address1] unless address[:address1].blank?
+ post[:destination][:account_holder][:billing_address][:address_line2] = address[:address2] unless address[:address2].blank?
+ post[:destination][:account_holder][:billing_address][:city] = address[:city] unless address[:city].blank?
+ post[:destination][:account_holder][:billing_address][:state] = address[:state] unless address[:state].blank?
+ post[:destination][:account_holder][:billing_address][:country] = address[:country] unless address[:country].blank?
+ post[:destination][:account_holder][:billing_address][:zip] = address[:zip] unless address[:zip].blank?
+ end
+ end
+
def add_marketplace_data(post, options)
if options[:marketplace]
post[:marketplace] = {}
@@ -274,18 +489,43 @@ def access_token_url
test? ? TEST_ACCESS_TOKEN_URL : LIVE_ACCESS_TOKEN_URL
end
+ def expires_date_with_extra_range(expires_in)
+ # Two minutes are subtracted from the expires_in time to generate the expires date
+ # in order to prevent any transaction from failing due to using an access_token
+ # that is very close to expiring.
+ # e.g. the access_token has one second left to expire and the lag when the transaction
+ # use an already expired access_token
+ (DateTime.now + (expires_in - 120).seconds).strftime('%Q').to_i
+ end
+
def setup_access_token
- request = 'grant_type=client_credentials'
- response = parse(ssl_post(access_token_url, request, access_token_header))
- response['access_token']
+ response = parse(ssl_post(access_token_url, 'grant_type=client_credentials', access_token_header))
+ @options[:access_token] = response['access_token']
+ @options[:expires] = expires_date_with_extra_range(response['expires_in']) if response['expires_in'] && response['expires_in'] > 0
+
+ Response.new(
+ access_token_valid?,
+ message_from(access_token_valid?, response, {}),
+ response.merge({ expires: @options[:expires] }),
+ test: test?,
+ error_code: error_code_from(access_token_valid?, response, {})
+ )
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ end
+
+ def access_token_valid?
+ @options[:access_token].present? && @options[:expires].to_i > DateTime.now.strftime('%Q').to_i
end
- def commit(action, post, authorization = nil)
+ def perform_request(action, post, options, authorization = nil, method = :post)
begin
- raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers))
+ raw_response = ssl_request(method, url(action, authorization), post.nil? || post.empty? ? nil : post.to_json, headers(action, options))
response = parse(raw_response)
response['id'] = response['_links']['payment']['href'].split('/')[-1] if action == :capture && response.key?('_links')
rescue ResponseError => e
+ @options[:access_token] = '' if e.response.code == '401' && !@options[:secret_key]
+
raise unless e.response.code.to_s =~ /4\d\d/
response = parse(e.response.body, error: e.response)
@@ -293,45 +533,68 @@ def commit(action, post, authorization = nil)
succeeded = success_from(action, response)
- response(action, succeeded, response)
+ response(action, succeeded, response, options)
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
+ def commit(action, post, options, authorization = nil, method = :post)
+ MultiResponse.run do |r|
+ r.process { setup_access_token } unless @options[:secret_key] || access_token_valid?
+ r.process { perform_request(action, post, options, authorization, method) }
+ end
+ end
+ def response(action, succeeded, response, options = {}, source_id = nil)
+ authorization = authorization_from(response) unless action == :unstore
+ body = action == :unstore ? { response_code: response.to_s } : response
Response.new(
succeeded,
- message_from(succeeded, response),
- response,
- authorization: authorization_from(response),
- error_code: error_code_from(succeeded, response),
+ message_from(succeeded, response, options),
+ body,
+ authorization: authorization,
+ error_code: error_code_from(succeeded, body, options),
test: test?,
- avs_result: avs_result,
- cvv_result: cvv_result
+ avs_result: avs_result(response),
+ cvv_result: cvv_result(response)
)
end
- def headers
- auth_token = @access_token ? "Bearer #{@access_token}" : @options[:secret_key]
- {
+ def headers(action, options)
+ auth_token = @options[:access_token] ? "Bearer #{@options[:access_token]}" : @options[:secret_key]
+ auth_token = @options[:public_key] if action == :tokens
+ headers = {
'Authorization' => auth_token,
'Content-Type' => 'application/json;charset=UTF-8'
}
+ headers['Cko-Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
+ headers
end
- def url(_post, action, authorization)
- if %i[authorize purchase credit].include?(action)
+ def tokenize(payment_method, options = {})
+ post = {}
+ add_authorization_type(post, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ commit(:tokens, post[:source], options)
+ end
+
+ def url(action, authorization)
+ case action
+ when :authorize, :purchase, :credit
"#{base_url}/payments"
- elsif action == :capture
+ when :unstore, :store
+ "#{base_url}/instruments/#{authorization}"
+ when :capture
"#{base_url}/payments/#{authorization}/captures"
- elsif action == :refund
+ when :refund
"#{base_url}/payments/#{authorization}/refunds"
- elsif action == :void
+ when :void
"#{base_url}/payments/#{authorization}/voids"
- elsif action == :incremental_authorize
+ when :incremental_authorize
"#{base_url}/payments/#{authorization}/authorizations"
+ when :tokens
+ "#{base_url}/tokens"
+ when :verify_payment
+ "#{base_url}/payments/#{authorization}"
else
"#{base_url}/payments/#{authorization}/#{action}"
end
@@ -342,11 +605,11 @@ def base_url
end
def avs_result(response)
- response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil
+ response.respond_to?(:dig) && response.dig('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
+ response.respond_to?(:dig) && response.dig('source', 'cvv_check') ? CVVResult.new(response['source']['cvv_check']) : nil
end
def parse(body, error: nil)
@@ -363,17 +626,27 @@ def parse(body, error: nil)
def success_from(action, response)
return response['status'] == 'Pending' if action == :credit
+ return true if action == :unstore && response == 204
+
+ store_response = response['token'] || response['id']
+ return true if store_response && ((action == :tokens && store_response.match(/tok/)) || (action == :store && store_response.match(/src_/)))
response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id')
end
- def message_from(succeeded, response)
+ def message_from(succeeded, response, options)
if succeeded
'Succeeded'
elsif response['error_type']
response['error_type'] + ': ' + response['error_codes'].first
else
- response['response_summary'] || response['response_code'] || response['status'] || response['message'] || 'Unable to read error message'
+ response_summary = if options[:threeds_response_message]
+ response['response_summary'] || response.dig('actions', 0, 'response_summary')
+ else
+ response['response_summary']
+ end
+
+ response_summary || response['response_code'] || response['status'] || response['message'] || 'Unable to read error message'
end
end
@@ -394,7 +667,7 @@ def authorization_from(raw)
raw['id']
end
- def error_code_from(succeeded, response)
+ def error_code_from(succeeded, response, options)
return if succeeded
if response['error_type'] && response['error_codes']
@@ -402,7 +675,13 @@ def error_code_from(succeeded, response)
elsif response['error_type']
response['error_type']
else
- STANDARD_ERROR_CODE_MAPPING[response['response_code']]
+ response_code = if options[:threeds_response_message]
+ response['response_code'] || response.dig('actions', 0, 'response_code')
+ else
+ response['response_code']
+ end
+
+ STANDARD_ERROR_CODE_MAPPING[response_code]
end
end
@@ -416,6 +695,16 @@ def token_type_from(payment_method)
'applepay'
end
end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code after unstore(delete instrument), because the body is nil
+ when 200...300
+ response.body || response.code
+ else
+ raise ResponseError.new(response)
+ end
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb
index 4db00a258e4..3bbd52925cc 100644
--- a/lib/active_merchant/billing/gateways/commerce_hub.rb
+++ b/lib/active_merchant/billing/gateways/commerce_hub.rb
@@ -18,7 +18,8 @@ class CommerceHubGateway < Gateway
'sale' => '/payments/v1/charges',
'void' => '/payments/v1/cancels',
'refund' => '/payments/v1/refunds',
- 'vault' => '/payments-vas/v1/tokens'
+ 'vault' => '/payments-vas/v1/tokens',
+ 'verify' => '/payments-vas/v1/accounts/verification'
}
def initialize(options = {})
@@ -29,7 +30,9 @@ def initialize(options = {})
def purchase(money, payment, options = {})
post = {}
options[:capture_flag] = true
- add_transaction_details(post, options)
+ options[:create_token] = false
+
+ add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
commit('sale', post, options)
@@ -38,7 +41,9 @@ def purchase(money, payment, options = {})
def authorize(money, payment, options = {})
post = {}
options[:capture_flag] = false
- add_transaction_details(post, options)
+ options[:create_token] = false
+
+ add_transaction_details(post, options, 'sale')
build_purchase_and_auth_request(post, money, payment, options)
commit('sale', post, options)
@@ -49,7 +54,8 @@ def capture(money, authorization, options = {})
options[:capture_flag] = true
add_invoice(post, money, options)
add_transaction_details(post, options, 'capture')
- add_reference_transaction_details(post, authorization, options, 'capture')
+ add_reference_transaction_details(post, authorization, options, :capture)
+ add_dynamic_descriptors(post, options)
commit('sale', post, options)
end
@@ -58,7 +64,16 @@ def refund(money, authorization, options = {})
post = {}
add_invoice(post, money, options) if money
add_transaction_details(post, options)
- add_reference_transaction_details(post, authorization, options)
+ add_reference_transaction_details(post, authorization, options, :refund)
+
+ commit('refund', post, options)
+ end
+
+ def credit(money, payment_method, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_transaction_interaction(post, options)
+ add_payment(post, payment_method, options)
commit('refund', post, options)
end
@@ -66,7 +81,7 @@ def refund(money, authorization, options = {})
def void(authorization, options = {})
post = {}
add_transaction_details(post, options)
- add_reference_transaction_details(post, authorization, options)
+ add_reference_transaction_details(post, authorization, options, :void)
commit('void', post, options)
end
@@ -82,10 +97,11 @@ def store(credit_card, options = {})
end
def verify(credit_card, options = {})
- verify_amount = options[:verify_amount] || 0
- options[:primary_transaction_type] = 'AUTH_ONLY'
- options[:account_verification] = true
- authorize(verify_amount, credit_card, options)
+ post = {}
+ add_payment(post, credit_card, options)
+ add_billing_address(post, credit_card, options)
+
+ commit('verify', post, options)
end
def supports_scrubbing?
@@ -103,21 +119,52 @@ def scrub(transcript)
private
+ def add_three_d_secure(post, payment, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post[:additionalData3DS] = {
+ dsTransactionId: three_d_secure[:ds_transaction_id],
+ authenticationStatus: three_d_secure[:authentication_response_status],
+ serviceProviderTransactionId: three_d_secure[:three_ds_server_trans_id],
+ acsTransactionId: three_d_secure[:acs_transaction_id],
+ mpiData: {
+ cavv: three_d_secure[:cavv],
+ eci: three_d_secure[:eci],
+ xid: three_d_secure[:xid]
+ }.compact,
+ versionData: { recommendedVersion: three_d_secure[:version] }
+ }.compact
+ end
+
def add_transaction_interaction(post, options)
post[:transactionInteraction] = {}
- post[:transactionInteraction][:origin] = options[:transaction_origin] || 'ECOM'
+ post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
post[:transactionInteraction][:eciIndicator] = options[:eci_indicator] || 'CHANNEL_ENCRYPTED'
post[:transactionInteraction][:posConditionCode] = options[:pos_condition_code] || 'CARD_NOT_PRESENT_ECOM'
+ post[:transactionInteraction][:posEntryMode] = (options[:pos_entry_mode] || 'MANUAL') unless options[:encryption_data].present?
+ post[:transactionInteraction][:additionalPosInformation] = {}
+ post[:transactionInteraction][:additionalPosInformation][:dataEntrySource] = options[:data_entry_source] || 'UNSPECIFIED'
end
def add_transaction_details(post, options, action = nil)
- post[:transactionDetails] = {}
- post[:transactionDetails][:captureFlag] = options[:capture_flag] unless options[:capture_flag].nil?
+ details = {
+ captureFlag: options[:capture_flag],
+ createToken: options[:create_token],
+ physicalGoodsIndicator: [true, 'true'].include?(options[:physical_goods_indicator])
+ }
+
+ if options[:order_id].present? && action == 'sale'
+ details[:merchantOrderId] = options[:order_id]
+ details[:merchantTransactionId] = options[:order_id]
+ end
+
if action != 'capture'
- post[:transactionDetails][:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
- post[:transactionDetails][:primaryTransactionType] = options[:primary_transaction_type] if options[:primary_transaction_type]
- post[:transactionDetails][:accountVerification] = options[:account_verification] unless options[:account_verification].nil?
+ details[:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
+ details[:primaryTransactionType] = options[:primary_transaction_type]
+ details[:accountVerification] = options[:account_verification]
end
+
+ post[:transactionDetails] = details.compact
end
def add_billing_address(post, payment, options)
@@ -167,21 +214,37 @@ def add_shipping_address(post, options)
end
def build_purchase_and_auth_request(post, money, payment, options)
+ add_three_d_secure(post, payment, options)
add_invoice(post, money, options)
add_payment(post, payment, options)
add_stored_credentials(post, options)
add_transaction_interaction(post, options)
add_billing_address(post, payment, options)
add_shipping_address(post, options)
+ add_dynamic_descriptors(post, options)
+ end
+
+ def add_dynamic_descriptors(post, options)
+ dynamic_descriptors_fields = %i[mcc merchant_name customer_service_number service_entitlement dynamic_descriptors_address]
+ return unless dynamic_descriptors_fields.any? { |key| options.include?(key) }
+
+ dynamic_descriptors = {}
+ dynamic_descriptors[:mcc] = options[:mcc] if options[:mcc]
+ dynamic_descriptors[:merchantName] = options[:merchant_name] if options[:merchant_name]
+ dynamic_descriptors[:customerServiceNumber] = options[:customer_service_number] if options[:customer_service_number]
+ dynamic_descriptors[:serviceEntitlement] = options[:service_entitlement] if options[:service_entitlement]
+ dynamic_descriptors[:address] = options[:dynamic_descriptors_address] if options[:dynamic_descriptors_address]
+
+ post[:dynamicDescriptors] = dynamic_descriptors
end
def add_reference_transaction_details(post, authorization, options, action = nil)
- post[:referenceTransactionDetails] = {}
- post[:referenceTransactionDetails][:referenceTransactionId] = authorization
- if action != 'capture'
- post[:referenceTransactionDetails][:referenceTransactionType] = options[:reference_transaction_type] || 'CHARGES'
- post[:referenceTransactionDetails][:referenceMerchantTransactionId] = options[:reference_merchant_transaction_id]
- end
+ reference_details = {}
+ _merchant_reference, transaction_id = authorization.include?('|') ? authorization.split('|') : [nil, authorization]
+
+ reference_details[:referenceTransactionId] = transaction_id
+ reference_details[:referenceTransactionType] = (options[:reference_transaction_type] || 'CHARGES') unless action == :capture
+ post[:referenceTransactionDetails] = reference_details.compact
end
def add_invoice(post, money, options)
@@ -198,7 +261,7 @@ def add_stored_credentials(post, options)
post[:storedCredentials][:sequence] = stored_credential[:initial_transaction] ? 'FIRST' : 'SUBSEQUENT'
post[:storedCredentials][:initiator] = stored_credential[:initiator] == 'merchant' ? 'MERCHANT' : 'CARD_HOLDER'
post[:storedCredentials][:scheduled] = SCHEDULED_REASON_TYPES.include?(stored_credential[:reason_type])
- post[:storedCredentials][:schemeReferenceTransactionId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ post[:storedCredentials][:schemeReferenceTransactionId] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id]
end
def add_credit_card(source, payment, options)
@@ -240,7 +303,12 @@ def add_payment(post, payment, options = {})
when NetworkTokenizationCreditCard
add_decrypted_wallet(source, payment, options)
when CreditCard
- add_credit_card(source, payment, options)
+ if options[:encryption_data].present?
+ source[:sourceType] = 'PaymentCard'
+ source[:encryptionData] = options[:encryption_data]
+ else
+ add_credit_card(source, payment, options)
+ end
when String
add_payment_token(source, payment, options)
end
@@ -257,7 +325,7 @@ def headers(request, options)
raw_signature = @options[:api_key] + client_request_id.to_s + time + request
hmac = OpenSSL::HMAC.digest('sha256', @options[:api_secret], raw_signature)
signature = Base64.strict_encode64(hmac.to_s).to_s
-
+ custom_headers = options.fetch(:headers_identifiers, {})
{
'Client-Request-Id' => client_request_id,
'Api-Key' => @options[:api_key],
@@ -267,7 +335,7 @@ def headers(request, options)
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => signature
- }
+ }.merge!(custom_headers)
end
def add_merchant_details(post)
@@ -282,12 +350,25 @@ def commit(action, parameters, options)
response = parse(ssl_post(url, parameters.to_json, headers(parameters.to_json, options)))
Response.new(
- success_from(response),
- message_from(response),
+ success_from(response, action),
+ message_from(response, action),
response,
- authorization: authorization_from(action, response),
+ authorization: authorization_from(action, response, options),
test: test?,
- error_code: error_code_from(response)
+ error_code: error_code_from(response, action),
+ avs_result: AVSResult.new(code: get_avs_cvv(response, 'avs')),
+ cvv_result: CVVResult.new(get_avs_cvv(response, 'cvv'))
+ )
+ end
+
+ def get_avs_cvv(response, type = 'avs')
+ response.dig(
+ 'paymentReceipt',
+ 'processorResponseDetails',
+ 'bankAssociationDetails',
+ 'avsSecurityCodeResponse',
+ 'association',
+ type == 'avs' ? 'avsCode' : 'securityCodeResponse'
)
end
@@ -300,21 +381,32 @@ def handle_response(response)
end
end
- def success_from(response)
+ def success_from(response, action = nil)
+ return message_from(response, action) == 'VERIFIED' if action == 'verify'
+
(response.dig('paymentReceipt', 'processorResponseDetails', 'responseCode') || response.dig('paymentTokens', 0, 'tokenResponseCode')) == '000'
end
- def message_from(response)
- response.dig('paymentReceipt', 'processorResponseDetails', 'responseMessage') || response.dig('error', 0, 'message') || response.dig('gatewayResponse', 'transactionType')
+ def message_from(response, action = nil)
+ return response.dig('error', 0, 'message') if response['error'].present?
+ return response.dig('gatewayResponse', 'transactionState') if action == 'verify'
+
+ response.dig('paymentReceipt', 'processorResponseDetails', 'responseMessage') || response.dig('gatewayResponse', 'transactionType')
end
- def authorization_from(action, response)
- return response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId') unless action == 'vault'
- return response.dig('paymentTokens', 0, 'tokenData') if action == 'vault'
+ def authorization_from(action, response, options)
+ case action
+ when 'vault'
+ response.dig('paymentTokens', 0, 'tokenData')
+ when 'sale'
+ [options[:order_id] || '', response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')].join('|')
+ else
+ response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
+ end
end
- def error_code_from(response)
- response.dig('error', 0, 'type') unless success_from(response)
+ def error_code_from(response, action)
+ response.dig('error', 0, 'code') unless success_from(response, action)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb
index 768f47cadc2..80b241616c0 100644
--- a/lib/active_merchant/billing/gateways/credorax.rb
+++ b/lib/active_merchant/billing/gateways/credorax.rb
@@ -30,7 +30,8 @@ class CredoraxGateway < Gateway
NETWORK_TOKENIZATION_CARD_SOURCE = {
'apple_pay' => 'applepay',
- 'google_pay' => 'googlepay'
+ 'google_pay' => 'googlepay',
+ 'network_token' => 'vts_mdes_token'
}
RESPONSE_MESSAGES = {
@@ -140,7 +141,7 @@ def initialize(options = {})
def purchase(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_3d_secure(post, options)
@@ -156,7 +157,7 @@ def purchase(amount, payment_method, options = {})
def authorize(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_3d_secure(post, options)
@@ -216,7 +217,7 @@ def refund(amount, authorization, options = {})
def credit(amount, payment_method, options = {})
post = {}
add_invoice(post, amount, options)
- add_payment_method(post, payment_method)
+ add_payment_method(post, payment_method, options)
add_customer_data(post, options)
add_email(post, options)
add_echo(post, options)
@@ -253,9 +254,7 @@ def add_3ds_2_optional_fields(post, options)
normalized_value = normalize(value)
next if normalized_value.nil?
- if key == :'3ds_homephonecountry'
- next unless options[:billing_address] && options[:billing_address][:phone]
- end
+ next if key == :'3ds_homephonecountry' && !(options[:billing_address] && options[:billing_address][:phone])
post[key] = normalized_value unless post[key]
end
@@ -282,9 +281,9 @@ def add_invoice(post, money, options)
'maestro' => '9'
}
- def add_payment_method(post, payment_method)
+ def add_payment_method(post, payment_method, options)
post[:c1] = payment_method&.name || ''
- post[:b21] = NETWORK_TOKENIZATION_CARD_SOURCE[payment_method.source.to_s] if payment_method.is_a? NetworkTokenizationCreditCard
+ add_network_tokenization_card(post, payment_method, options) if payment_method.is_a? NetworkTokenizationCreditCard
post[:b2] = CARD_TYPES[payment_method.brand] || ''
post[:b1] = payment_method.number
post[:b5] = payment_method.verification_value
@@ -292,6 +291,13 @@ def add_payment_method(post, payment_method)
post[:b3] = format(payment_method.month, :two_digits)
end
+ def add_network_tokenization_card(post, payment_method, options)
+ post[:b21] = NETWORK_TOKENIZATION_CARD_SOURCE[payment_method.source.to_s]
+ post[:token_eci] = post[:b21] == 'vts_mdes_token' ? '07' : nil
+ post[:token_eci] = options[:eci] || payment_method&.eci || (payment_method.brand.to_s == 'master' ? '00' : '07')
+ post[:token_crypto] = payment_method&.payment_cryptogram if payment_method.source.to_s == 'network_token'
+ end
+
def add_stored_credential(post, options)
add_transaction_type(post, options)
# if :transaction_type option is not passed, then check for :stored_credential options
@@ -300,20 +306,16 @@ def add_stored_credential(post, options)
if stored_credential[:initiator] == 'merchant'
case stored_credential[:reason_type]
when 'recurring'
- recurring_properties(post, stored_credential)
+ post[:a9] = stored_credential[:initial_transaction] ? '1' : '2'
when 'installment', 'unscheduled'
post[:a9] = '8'
end
+ post[:g6] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
else
post[:a9] = '9'
end
end
- def recurring_properties(post, stored_credential)
- post[:a9] = stored_credential[:initial_transaction] ? '1' : '2'
- post[:g6] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
- end
-
def add_customer_data(post, options)
post[:d1] = options[:ip] || '127.0.0.1'
if (billing_address = options[:billing_address])
@@ -503,7 +505,7 @@ def url
end
def parse(body)
- Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }]
+ CGI::parse(body).map { |k, v| [k.upcase, v.first] }.to_h
end
def success_from(response)
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index bc11b577e3c..78cc67b7d5d 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -33,6 +33,15 @@ class CyberSourceGateway < Gateway
discover: 'pb',
diners_club: 'pb'
}.freeze
+ THREEDS_EXEMPTIONS = {
+ authentication_outage: 'authenticationOutageExemptionIndicator',
+ corporate_card: 'secureCorporatePaymentIndicator',
+ delegated_authentication: 'delegatedAuthenticationExemptionIndicator',
+ low_risk: 'riskAnalysisExemptionIndicator',
+ low_value: 'lowValueExemptionIndicator',
+ stored_credential: 'stored_credential',
+ trusted_merchant: 'trustedMerchantExemptionIndicator'
+ }
DEFAULT_COLLECTION_INDICATOR = 2
self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro elo]
@@ -132,6 +141,16 @@ class CyberSourceGateway < Gateway
r703: 'Export hostname_country/ip_country match'
}
+ @@wallet_payment_solution = {
+ apple_pay: '001',
+ google_pay: '012'
+ }
+
+ NT_PAYMENT_SOLUTION = {
+ 'master' => '014',
+ 'visa' => '015'
+ }
+
# These are the options that can be used when creating a new CyberSource
# Gateway object.
#
@@ -156,9 +175,15 @@ def initialize(options = {})
super
end
- def authorize(money, creditcard_or_reference, options = {})
- setup_address_hash(options)
- commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, options)
+ def authorize(money, payment_method, options = {})
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_auth_request(money, payment_method, options), :authorize, money, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
def capture(money, authorization, options = {})
@@ -166,9 +191,15 @@ def capture(money, authorization, options = {})
commit(build_capture_request(money, authorization, options), :capture, money, options)
end
- def purchase(money, payment_method_or_reference, options = {})
- setup_address_hash(options)
- commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options)
+ def purchase(money, payment_method, options = {})
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_purchase_request(money, payment_method, options), :purchase, money, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
def void(identification, options = {})
@@ -201,8 +232,14 @@ def credit(money, creditcard_or_reference, options = {})
# To charge the card while creating a profile, pass
# options[:setup_fee] => money
def store(payment_method, options = {})
- setup_address_hash(options)
- commit(build_create_subscription_request(payment_method, options), :store, nil, options)
+ if valid_payment_method?(payment_method)
+ setup_address_hash(options)
+ commit(build_create_subscription_request(payment_method, options), :store, nil, options)
+ else
+ # this is for NetworkToken, ApplePay or GooglePay brands that aren't supported at CyberSource
+ payment_type = payment_method.source.to_s.gsub('_', ' ').titleize.gsub(' ', '')
+ Response.new(false, "#{card_brand(payment_method).capitalize} is not supported by #{payment_type} at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
+ end
end
# Updates a customer subscription/profile
@@ -267,6 +304,8 @@ def scrub(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
@@ -281,6 +320,12 @@ def verify_credentials
private
+ def valid_payment_method?(payment_method)
+ return true unless payment_method.is_a?(NetworkTokenizationCreditCard)
+
+ %w(visa master american_express).include?(card_brand(payment_method))
+ end
+
# Create all required address hash key value pairs
# If a value of nil is received, that value will be passed on to the gateway and will not be replaced with a default value
# Billing address fields received without an override value or with an empty string value will be replaced with the default_address values
@@ -312,16 +357,19 @@ def build_auth_request(money, creditcard_or_reference, options)
xml = Builder::XmlMarkup.new indent: 2
add_customer_id(xml, options)
add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
- add_other_tax(xml, options)
add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
+ add_mastercard_network_tokenization_ucaf_data(xml, creditcard_or_reference, options)
add_decision_manager_fields(xml, options)
+ add_other_tax(xml, options)
add_mdd_fields(xml, options)
add_auth_service(xml, creditcard_or_reference, options)
+ add_capture_service_fields_with_run_false(xml, options)
add_threeds_services(xml, options)
add_business_rules_data(xml, creditcard_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
- add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
+ add_payment_network_token(xml, creditcard_or_reference, options)
+ add_payment_solution(xml, creditcard_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
@@ -374,9 +422,10 @@ def build_purchase_request(money, payment_method_or_reference, options)
xml = Builder::XmlMarkup.new indent: 2
add_customer_id(xml, options)
add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
- add_other_tax(xml, options)
add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
+ add_mastercard_network_tokenization_ucaf_data(xml, payment_method_or_reference, options)
add_decision_manager_fields(xml, options)
+ add_other_tax(xml, options)
add_mdd_fields(xml, options)
if (!payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check') || reference_is_a_check?(payment_method_or_reference)
add_check_service(xml)
@@ -392,7 +441,8 @@ def build_purchase_request(money, payment_method_or_reference, options)
add_business_rules_data(xml, payment_method_or_reference, options)
add_airline_data(xml, options)
add_sales_slip_number(xml, options)
- add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
+ add_payment_network_token(xml, payment_method_or_reference, options)
+ add_payment_solution(xml, payment_method_or_reference)
add_tax_management_indicator(xml, options)
add_stored_credential_subsequent_auth(xml, options)
add_issuer_additional_data(xml, options)
@@ -480,7 +530,7 @@ def build_create_subscription_request(payment_method, options)
add_check_service(xml)
else
add_purchase_service(xml, payment_method, options)
- add_payment_network_token(xml) if network_tokenization?(payment_method)
+ add_payment_network_token(xml, payment_method, options)
end
end
add_subscription_create_service(xml, options)
@@ -519,11 +569,9 @@ def build_retrieve_subscription_request(reference, options)
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).to_s == 'true'
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
- end
+ xml.tag! 'businessRules' do
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
end
end
@@ -551,7 +599,7 @@ def add_line_item_data(xml, options)
end
def add_merchant_data(xml, options)
- xml.tag! 'merchantID', @options[:login]
+ xml.tag! 'merchantID', options[:merchant_id] || @options[:login]
xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id
xml.tag! 'clientLibrary', 'Ruby Active Merchant'
xml.tag! 'clientLibraryVersion', VERSION
@@ -672,6 +720,18 @@ def add_decision_manager_fields(xml, options)
end
end
+ def add_payment_solution(xml, payment_method)
+ return unless network_tokenization?(payment_method)
+
+ case payment_method.source
+ when :network_token
+ payment_solution = NT_PAYMENT_SOLUTION[payment_method.brand]
+ xml.tag! 'paymentSolution', payment_solution if payment_solution
+ when :apple_pay, :google_pay
+ xml.tag! 'paymentSolution', @@wallet_payment_solution[payment_method.source]
+ end
+ end
+
def add_issuer_additional_data(xml, options)
return unless options[:issuer_additional_data]
@@ -681,7 +741,7 @@ def add_issuer_additional_data(xml, options)
end
def add_other_tax(xml, options)
- return unless options[:local_tax_amount] || options[:national_tax_amount] || options[:national_tax_indicator]
+ return unless %i[vat_tax_rate local_tax_amount national_tax_amount national_tax_indicator].any? { |gsf| options.include?(gsf) }
xml.tag! 'otherTax' do
xml.tag! 'vatTaxRate', options[:vat_tax_rate] if options[:vat_tax_rate]
@@ -705,8 +765,8 @@ def add_mdd_fields(xml, options)
def add_check(xml, check, options)
xml.tag! 'check' do
xml.tag! 'accountNumber', check.account_number
- xml.tag! 'accountType', check.account_type[0]
- xml.tag! 'bankTransitNumber', check.routing_number
+ xml.tag! 'accountType', check.account_type == 'checking' ? 'C' : 'S'
+ xml.tag! 'bankTransitNumber', format_routing_number(check.routing_number, options)
xml.tag! 'secCode', options[:sec_code] if options[:sec_code]
end
end
@@ -720,21 +780,39 @@ def add_tax_service(xml)
def add_auth_service(xml, payment_method, options)
if network_tokenization?(payment_method)
- add_auth_network_tokenization(xml, payment_method, options)
+ if payment_method.source == :network_token
+ add_auth_network_tokenization(xml, payment_method, options)
+ else
+ add_auth_wallet(xml, payment_method, options)
+ end
else
xml.tag! 'ccAuthService', { 'run' => 'true' } do
if options[:three_d_secure]
add_normalized_threeds_2_data(xml, payment_method, options)
+ add_threeds_exemption_data(xml, options) if options[:three_ds_exemption_type]
else
indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
xml.tag!('commerceIndicator', indicator) if indicator
end
+ xml.tag!('aggregatorID', options[:aggregator_id]) if options[:aggregator_id]
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ xml.tag!('firstRecurringPayment', options[:first_recurring_payment]) if options[:first_recurring_payment]
xml.tag!('mobileRemotePaymentType', options[:mobile_remote_payment_type]) if options[:mobile_remote_payment_type]
end
end
end
+ def add_threeds_exemption_data(xml, options)
+ return unless options[:three_ds_exemption_type]
+
+ exemption = options[:three_ds_exemption_type].to_sym
+
+ case exemption
+ when :authentication_outage, :corporate_card, :delegated_authentication, :low_risk, :low_value, :trusted_merchant
+ xml.tag!(THREEDS_EXEMPTIONS[exemption], '1')
+ end
+ end
+
def add_incremental_auth_service(xml, authorization, options)
xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
xml.tag! 'authRequestID', authorization
@@ -796,26 +874,37 @@ def network_tokenization?(payment_method)
payment_method.is_a?(NetworkTokenizationCreditCard)
end
+ def subsequent_nt_apple_pay_auth(source, options)
+ return unless options[:stored_credential] || options[:stored_credential_overrides]
+ return unless @@wallet_payment_solution[source]
+
+ options.dig(:stored_credential_overrides, :subsequent_auth) || options.dig(:stored_credential, :initiator) == 'merchant'
+ end
+
def add_auth_network_tokenization(xml, payment_method, options)
- return unless network_tokenization?(payment_method)
+ xml.tag! 'ccAuthService', { 'run' => 'true' } do
+ xml.tag!('networkTokenCryptogram', payment_method.payment_cryptogram)
+ xml.tag!('commerceIndicator', 'internet')
+ xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ end
+ end
+
+ def add_auth_wallet(xml, payment_method, options)
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
brand = card_brand(payment_method).to_sym
case brand
when :visa
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('cavv', payment_method.payment_cryptogram)
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
- xml.tag!('xid', payment_method.payment_cryptogram)
+ xml.tag!('cavv', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
+ xml.tag!('xid', payment_method.payment_cryptogram) unless commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :master
- xml.tag! 'ucaf' do
- xml.tag!('authenticationData', payment_method.payment_cryptogram)
- xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
- end
xml.tag! 'ccAuthService', { 'run' => 'true' } do
- xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand])
+ xml.commerceIndicator commerce_indicator.nil? ? ECI_BRAND_MAPPING[brand] : commerce_indicator
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
when :american_express
@@ -826,14 +915,28 @@ def add_auth_network_tokenization(xml, payment_method, options)
xml.tag!('xid', Base64.encode64(cryptogram[20...40])) if cryptogram.bytes.count > 20
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
end
- else
- raise ArgumentError.new("Payment method #{brand} is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html")
end
end
- def add_payment_network_token(xml)
+ def add_mastercard_network_tokenization_ucaf_data(xml, payment_method, options)
+ return unless network_tokenization?(payment_method) && card_brand(payment_method).to_sym == :master
+ return if payment_method.source == :network_token
+
+ commerce_indicator = 'internet' if subsequent_nt_apple_pay_auth(payment_method.source, options)
+
+ xml.tag! 'ucaf' do
+ xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator
+ xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR)
+ end
+ end
+
+ def add_payment_network_token(xml, payment_method, options)
+ return unless network_tokenization?(payment_method)
+
+ transaction_type = payment_method.source == :network_token ? '3' : '1'
xml.tag! 'paymentNetworkToken' do
- xml.tag!('transactionType', '1')
+ xml.tag!('requestorID', options[:trid]) if transaction_type == '3' && options[:trid]
+ xml.tag!('transactionType', transaction_type)
end
end
@@ -846,10 +949,19 @@ def add_capture_service(xml, request_id, request_token, options)
end
end
+ def add_capture_service_fields_with_run_false(xml, options)
+ return unless options[:gratuity_amount]
+
+ xml.tag! 'ccCaptureService', { 'run' => 'false' } do
+ xml.tag! 'gratuityAmount', options[:gratuity_amount]
+ end
+ end
+
def add_purchase_service(xml, payment_method, options)
add_auth_service(xml, payment_method, options)
xml.tag! 'ccCaptureService', { 'run' => 'true' } do
xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
+ xml.tag!('gratuityAmount', options[:gratuity_amount]) if options[:gratuity_amount]
end
end
@@ -994,7 +1106,7 @@ def add_stored_credential_options(xml, options = {})
stored_credential_subsequent_auth_first = 'true' if options.dig(:stored_credential, :initial_transaction)
stored_credential_transaction_id = options.dig(:stored_credential, :network_transaction_id) if options.dig(:stored_credential, :initiator) == 'merchant'
- stored_credential_subsequent_auth_stored_cred = 'true' if options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction) || options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ stored_credential_subsequent_auth_stored_cred = 'true' if subsequent_cardholder_initiated_transaction?(options) || unscheduled_merchant_initiated_transaction?(options) || threeds_stored_credential_exemption?(options)
override_subsequent_auth_first = options.dig(:stored_credential_overrides, :subsequent_auth_first)
override_subsequent_auth_transaction_id = options.dig(:stored_credential_overrides, :subsequent_auth_transaction_id)
@@ -1005,6 +1117,18 @@ def add_stored_credential_options(xml, options = {})
xml.subsequentAuthStoredCredential override_subsequent_auth_stored_cred.nil? ? stored_credential_subsequent_auth_stored_cred : override_subsequent_auth_stored_cred
end
+ def subsequent_cardholder_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'cardholder' && !options.dig(:stored_credential, :initial_transaction)
+ end
+
+ def unscheduled_merchant_initiated_transaction?(options)
+ options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
+ end
+
+ def threeds_stored_credential_exemption?(options)
+ options[:three_ds_exemption_type] == THREEDS_EXEMPTIONS[:stored_credential]
+ end
+
def add_partner_solution_id(xml)
return unless application_id
@@ -1055,12 +1179,26 @@ def commit(request, action, amount, options)
message = message_from(response)
authorization = success || in_fraud_review?(response) ? authorization_from(response, action, amount, options) : nil
- Response.new(success, message, response,
+ message = auto_void?(authorization_from(response, action, amount, options), response, message, options)
+
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
fraud_review: in_fraud_review?(response),
avs_result: { code: response[:avsCode] },
- cvv_result: response[:cvCode])
+ cvv_result: response[:cvCode]
+ )
+ end
+
+ def auto_void?(authorization, response, message, options = {})
+ return message unless response[:reasonCode] == '230' && options[:auto_void_230]
+
+ response = void(authorization, options)
+ response&.success? ? message += ' - transaction has been auto-voided.' : message += ' - transaction could not be auto-voided.'
+ message
end
# Parse the SOAP response
@@ -1094,6 +1232,7 @@ def parse_element(reply, node)
parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
parent += '_'
end
+ reply[:reconciliationID2] = node.text if node.name == 'reconciliationID' && reply[:reconciliationID]
reply["#{parent}#{node.name}".to_sym] ||= node.text
end
return reply
@@ -1131,6 +1270,10 @@ def message_from(response)
def eligible_for_zero_auth?(payment_method, options = {})
payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
end
+
+ def format_routing_number(routing_number, options)
+ options[:currency] == 'CAD' && routing_number.length > 8 ? routing_number[-8..-1] : routing_number
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
new file mode 100644
index 00000000000..9e37a41fca7
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb
@@ -0,0 +1,36 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ module CyberSourceCommon
+ def check_billing_field_value(default, submitted)
+ if submitted.nil?
+ nil
+ elsif submitted.blank?
+ default
+ else
+ submitted
+ end
+ end
+
+ def address_names(address_name, payment_method)
+ names = split_names(address_name)
+ return names if names.any?(&:present?)
+
+ [
+ payment_method&.first_name,
+ payment_method&.last_name
+ ]
+ end
+
+ def lookup_country_code(country_field)
+ return unless country_field.present?
+
+ country_code = Country.find(country_field)
+ country_code&.code(:alpha2)
+ end
+
+ def eligible_for_zero_auth?(payment_method, options = {})
+ payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cyber_source_rest.rb b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
new file mode 100644
index 00000000000..8aa79675947
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb
@@ -0,0 +1,497 @@
+require 'active_merchant/billing/gateways/cyber_source/cyber_source_common'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CyberSourceRestGateway < Gateway
+ include ActiveMerchant::Billing::CyberSourceCommon
+
+ self.test_url = 'https://apitest.cybersource.com'
+ self.live_url = 'https://api.cybersource.com'
+
+ self.supported_countries = ActiveMerchant::Billing::CyberSourceGateway.supported_countries
+ self.default_currency = 'USD'
+ self.currencies_without_fractions = ActiveMerchant::Billing::CyberSourceGateway.currencies_without_fractions
+
+ self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada]
+
+ self.homepage_url = 'http://www.cybersource.com'
+ self.display_name = 'Cybersource REST'
+
+ CREDIT_CARD_CODES = {
+ american_express: '003',
+ cartes_bancaires: '036',
+ dankort: '034',
+ diners_club: '005',
+ discover: '004',
+ elo: '054',
+ jcb: '007',
+ maestro: '042',
+ master: '002',
+ unionpay: '062',
+ visa: '001'
+ }
+
+ WALLET_PAYMENT_SOLUTION = {
+ apple_pay: '001',
+ google_pay: '012'
+ }
+
+ NT_PAYMENT_SOLUTION = {
+ 'master' => '014',
+ 'visa' => '015'
+ }
+
+ def initialize(options = {})
+ requires!(options, :merchant_id, :public_key, :private_key)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ authorize(money, payment, options, true)
+ end
+
+ def authorize(money, payment, options = {}, capture = false)
+ post = build_auth_request(money, payment, options)
+ post[:processingInformation][:capture] = true if capture
+
+ commit('payments', post, options)
+ end
+
+ def capture(money, authorization, options = {})
+ payment = authorization.split('|').first
+ post = build_reference_request(money, options)
+
+ commit("payments/#{payment}/captures", post, options)
+ end
+
+ def refund(money, authorization, options = {})
+ payment = authorization.split('|').first
+ post = build_reference_request(money, options)
+ commit("payments/#{payment}/refunds", post, options)
+ end
+
+ def credit(money, payment, options = {})
+ post = build_credit_request(money, payment, options)
+ commit('credits', post)
+ end
+
+ def void(authorization, options = {})
+ payment, amount = authorization.split('|')
+ post = build_void_request(amount)
+ commit("payments/#{payment}/reversals", post)
+ end
+
+ def verify(credit_card, options = {})
+ amount = eligible_for_zero_auth?(credit_card, options) ? 0 : 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(\\?"routingNumber\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]').
+ gsub(/(\\?"cryptogram\\?":\\?")[^<]+/, '\1[FILTERED]').
+ gsub(/(signature=")[^"]*/, '\1[FILTERED]').
+ gsub(/(keyid=")[^"]*/, '\1[FILTERED]').
+ gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_level_2_data(post, options)
+ return unless options[:purchase_order_number]
+
+ post[:orderInformation][:invoiceDetails] ||= {}
+ post[:orderInformation][:invoiceDetails][:purchaseOrderNumber] = options[:purchase_order_number]
+ end
+
+ def add_level_3_data(post, options)
+ return unless options[:line_items]
+
+ post[:orderInformation][:lineItems] = options[:line_items]
+ post[:processingInformation][:purchaseLevel] = '3'
+ post[:orderInformation][:shipping_details] = { shipFromPostalCode: options[:ships_from_postal_code] }
+ post[:orderInformation][:amountDetails] ||= {}
+ post[:orderInformation][:amountDetails][:discountAmount] = options[:discount_amount]
+ end
+
+ def add_three_ds(post, payment_method, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post[:consumerAuthenticationInformation] ||= {}
+ if payment_method.brand == 'master'
+ post[:consumerAuthenticationInformation][:ucafAuthenticationData] = three_d_secure[:cavv]
+ post[:consumerAuthenticationInformation][:ucafCollectionIndicator] = '2'
+ else
+ post[:consumerAuthenticationInformation][:cavv] = three_d_secure[:cavv]
+ end
+ post[:consumerAuthenticationInformation][:cavvAlgorithm] = three_d_secure[:cavv_algorithm] if three_d_secure[:cavv_algorithm]
+ post[:consumerAuthenticationInformation][:paSpecificationVersion] = three_d_secure[:version] if three_d_secure[:version]
+ post[:consumerAuthenticationInformation][:directoryServerTransactionID] = three_d_secure[:ds_transaction_id] if three_d_secure[:ds_transaction_id]
+ post[:consumerAuthenticationInformation][:eciRaw] = three_d_secure[:eci] if three_d_secure[:eci]
+ if three_d_secure[:xid].present?
+ post[:consumerAuthenticationInformation][:xid] = three_d_secure[:xid]
+ else
+ post[:consumerAuthenticationInformation][:xid] = three_d_secure[:cavv]
+ end
+ post[:consumerAuthenticationInformation][:veresEnrolled] = three_d_secure[:enrolled] if three_d_secure[:enrolled]
+ post[:consumerAuthenticationInformation][:paresStatus] = three_d_secure[:authentication_response_status] if three_d_secure[:authentication_response_status]
+ post
+ end
+
+ def build_void_request(amount = nil)
+ { reversalInformation: { amountDetails: { totalAmount: nil } } }.tap do |post|
+ add_reversal_amount(post, amount.to_i) if amount.present?
+ end.compact
+ end
+
+ def build_auth_request(amount, payment, options)
+ { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
+ add_customer_id(post, options)
+ add_code(post, options)
+ add_payment(post, payment, options)
+ add_mdd_fields(post, options)
+ add_amount(post, amount, options)
+ add_address(post, payment, options[:billing_address], options, :billTo)
+ add_address(post, payment, options[:shipping_address], options, :shipTo)
+ add_business_rules_data(post, payment, options)
+ add_partner_solution_id(post)
+ add_stored_credentials(post, payment, options)
+ add_three_ds(post, payment, options)
+ add_level_2_data(post, options)
+ add_level_3_data(post, options)
+ end.compact
+ end
+
+ def build_reference_request(amount, options)
+ { clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
+ add_code(post, options)
+ add_mdd_fields(post, options)
+ add_amount(post, amount, options)
+ add_partner_solution_id(post)
+ end.compact
+ end
+
+ def build_credit_request(amount, payment, options)
+ { clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
+ add_code(post, options)
+ add_credit_card(post, payment)
+ add_mdd_fields(post, options)
+ add_amount(post, amount, options)
+ add_address(post, payment, options[:billing_address], options, :billTo)
+ add_merchant_description(post, options)
+ end.compact
+ end
+
+ def add_code(post, options)
+ return unless options[:order_id].present?
+
+ post[:clientReferenceInformation][:code] = options[:order_id]
+ end
+
+ def add_customer_id(post, options)
+ return unless options[:customer_id].present?
+
+ post[:paymentInformation][:customer] = { customerId: options[:customer_id] }
+ end
+
+ def add_reversal_amount(post, amount)
+ currency = options[:currency] || currency(amount)
+
+ post[:reversalInformation][:amountDetails] = {
+ totalAmount: localized_amount(amount, currency)
+ }
+ end
+
+ def add_amount(post, amount, options)
+ currency = options[:currency] || currency(amount)
+ post[:orderInformation][:amountDetails] = {
+ totalAmount: localized_amount(amount, currency),
+ currency: currency
+ }
+ end
+
+ def add_ach(post, payment)
+ post[:paymentInformation][:bank] = {
+ account: {
+ type: payment.account_type == 'checking' ? 'C' : 'S',
+ number: payment.account_number
+ },
+ routingNumber: payment.routing_number
+ }
+ end
+
+ def add_payment(post, payment, options)
+ post[:processingInformation] = {}
+ if payment.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_card(post, payment, options)
+ elsif payment.is_a?(Check)
+ add_ach(post, payment)
+ else
+ add_credit_card(post, payment)
+ end
+ end
+
+ def add_network_tokenization_card(post, payment, options)
+ post[:processingInformation][:commerceIndicator] = 'internet' unless options[:stored_credential] || card_brand(payment) == 'jcb'
+
+ post[:paymentInformation][:tokenizedCard] = {
+ number: payment.number,
+ expirationMonth: payment.month,
+ expirationYear: payment.year,
+ cryptogram: payment.payment_cryptogram,
+ type: CREDIT_CARD_CODES[card_brand(payment).to_sym],
+ transactionType: payment.source == :network_token ? '3' : '1'
+ }
+
+ if payment.source == :network_token && NT_PAYMENT_SOLUTION[payment.brand]
+ post[:processingInformation][:paymentSolution] = NT_PAYMENT_SOLUTION[payment.brand]
+ else
+ # Apple Pay / Google Pay
+ post[:processingInformation][:paymentSolution] = WALLET_PAYMENT_SOLUTION[payment.source]
+ if card_brand(payment) == 'master'
+ post[:consumerAuthenticationInformation] = {
+ ucafAuthenticationData: payment.payment_cryptogram,
+ ucafCollectionIndicator: '2'
+ }
+ else
+ post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram }
+ end
+ end
+ end
+
+ def add_credit_card(post, creditcard)
+ post[:paymentInformation][:card] = {
+ number: creditcard.number,
+ expirationMonth: format(creditcard.month, :two_digits),
+ expirationYear: format(creditcard.year, :four_digits),
+ securityCode: creditcard.verification_value,
+ type: CREDIT_CARD_CODES[card_brand(creditcard).to_sym]
+ }
+ end
+
+ def add_address(post, payment_method, address, options, address_type)
+ return unless address.present?
+
+ first_name, last_name = address_names(address[:name], payment_method)
+
+ post[:orderInformation][address_type] = {
+ firstName: first_name,
+ lastName: last_name,
+ address1: address[:address1],
+ address2: address[:address2],
+ locality: address[:city],
+ administrativeArea: address[:state],
+ postalCode: address[:zip],
+ country: lookup_country_code(address[:country])&.value,
+ email: options[:email].presence || 'null@cybersource.com',
+ phoneNumber: address[:phone]
+ # merchantTaxID: ship_to ? options[:merchant_tax_id] : nil,
+ # company: address[:company],
+ # companyTaxID: address[:companyTaxID],
+ # ipAddress: options[:ip],
+ # driversLicenseNumber: options[:drivers_license_number],
+ # driversLicenseState: options[:drivers_license_state],
+ }.compact
+ end
+
+ def add_merchant_description(post, options)
+ return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
+
+ merchant = post[:merchantInformation][:merchantDescriptor] = {}
+ merchant[:name] = options[:merchant_descriptor_name] if options[:merchant_descriptor_name]
+ merchant[:address1] = options[:merchant_descriptor_address1] if options[:merchant_descriptor_address1]
+ merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality]
+ end
+
+ def add_stored_credentials(post, payment, options)
+ return unless options[:stored_credential]
+
+ post[:processingInformation][:commerceIndicator] = commerce_indicator(options.dig(:stored_credential, :reason_type))
+ add_authorization_options(post, payment, options)
+ end
+
+ def commerce_indicator(reason_type)
+ case reason_type
+ when 'recurring'
+ 'recurring'
+ when 'installment'
+ 'install'
+ else
+ 'internet'
+ end
+ end
+
+ def add_authorization_options(post, payment, options)
+ initiator = options.dig(:stored_credential, :initiator) == 'cardholder' ? 'customer' : 'merchant'
+ authorization_options = {
+ authorizationOptions: {
+ initiator: {
+ type: initiator
+ }
+ }
+ }.compact
+
+ authorization_options[:authorizationOptions][:initiator][:storedCredentialUsed] = true if initiator == 'merchant'
+ authorization_options[:authorizationOptions][:initiator][:credentialStoredOnFile] = true if options.dig(:stored_credential, :initial_transaction)
+ authorization_options[:authorizationOptions][:initiator][:merchantInitiatedTransaction] ||= {}
+ unless options.dig(:stored_credential, :initial_transaction)
+ network_transaction_id = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id) || ''
+ authorization_options[:authorizationOptions][:initiator][:merchantInitiatedTransaction][:previousTransactionID] = network_transaction_id
+ authorization_options[:authorizationOptions][:initiator][:merchantInitiatedTransaction][:originalAuthorizedAmount] = post.dig(:orderInformation, :amountDetails, :totalAmount) if card_brand(payment) == 'discover'
+ end
+ authorization_options[:authorizationOptions][:initiator][:merchantInitiatedTransaction][:reason] = options[:reason_code] if options[:reason_code]
+ post[:processingInformation].merge!(authorization_options)
+ end
+
+ def network_transaction_id_from(response)
+ response.dig('processorInformation', 'networkTransactionId')
+ end
+
+ def url(action)
+ "#{test? ? test_url : live_url}/pts/v2/#{action}"
+ end
+
+ def host
+ URI.parse(url('')).host
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, post, options = {})
+ add_reconciliation_id(post, options)
+ add_sec_code(post, options)
+ add_invoice_number(post, options)
+ response = parse(ssl_post(url(action), post.to_json, auth_headers(action, options, post)))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')),
+ # cvv_result: CVVResult.new(response['some_cvv_response_key']),
+ network_transaction_id: network_transaction_id_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ActiveMerchant::ResponseError => e
+ response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } }
+ message = response.dig('response', 'rmsg') || response.dig('message')
+ Response.new(false, message, response, test: test?)
+ end
+
+ def success_from(response)
+ %w(AUTHORIZED PENDING REVERSED).include?(response['status'])
+ end
+
+ def message_from(response)
+ return response['status'] if success_from(response)
+
+ response['errorInformation']['message'] || response['message']
+ end
+
+ def authorization_from(response)
+ id = response['id']
+ has_amount = response['orderInformation'] && response['orderInformation']['amountDetails'] && response['orderInformation']['amountDetails']['authorizedAmount']
+ amount = response['orderInformation']['amountDetails']['authorizedAmount'].delete('.') if has_amount
+
+ return id if amount.blank?
+
+ [id, amount].join('|')
+ end
+
+ def error_code_from(response)
+ response['errorInformation']['reason'] unless success_from(response)
+ end
+
+ # This implementation follows the Cybersource guide on how create the request signature, see:
+ # https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/GenerateHeader/httpSignatureAuthentication.html
+ def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate)
+ string_to_sign = {
+ host: host,
+ date: gmtdatetime,
+ "request-target": "#{http_method} /pts/v2/#{resource}",
+ digest: digest,
+ "v-c-merchant-id": @options[:merchant_id]
+ }.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8)
+
+ {
+ keyid: @options[:public_key],
+ algorithm: 'HmacSHA256',
+ headers: "host date request-target#{digest.present? ? ' digest' : ''} v-c-merchant-id",
+ signature: sign_payload(string_to_sign)
+ }.map { |k, v| %{#{k}="#{v}"} }.join(', ')
+ end
+
+ def sign_payload(payload)
+ decoded_key = Base64.decode64(@options[:private_key])
+ Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload))
+ end
+
+ def auth_headers(action, options, post, http_method = 'post')
+ digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present?
+ date = Time.now.httpdate
+
+ {
+ 'Accept' => 'application/hal+json;charset=utf-8',
+ 'Content-Type' => 'application/json;charset=utf-8',
+ 'V-C-Merchant-Id' => options[:merchant_id] || @options[:merchant_id],
+ 'Date' => date,
+ 'Host' => host,
+ 'Signature' => get_http_signature(action, digest, http_method, date),
+ 'Digest' => digest
+ }
+ end
+
+ def add_business_rules_data(post, payment, options)
+ post[:processingInformation][:authorizationOptions] = {}
+ post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
+ post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
+ end
+
+ def add_mdd_fields(post, options)
+ mdd_fields = options.select { |k, v| k.to_s.start_with?('mdd_field') && v.present? }
+ return unless mdd_fields.present?
+
+ post[:merchantDefinedInformation] = mdd_fields.map do |key, value|
+ { key: key, value: value }
+ end
+ end
+
+ def add_reconciliation_id(post, options)
+ return unless options[:reconciliation_id].present?
+
+ post[:clientReferenceInformation][:reconciliationId] = options[:reconciliation_id]
+ end
+
+ def add_sec_code(post, options)
+ return unless options[:sec_code].present?
+
+ post[:processingInformation][:bankTransferOptions] = { secCode: options[:sec_code] }
+ end
+
+ def add_invoice_number(post, options)
+ return unless options[:invoice_number].present?
+
+ post[:orderInformation][:invoiceDetails] ||= {}
+ post[:orderInformation][:invoiceDetails][:invoiceNumber] = options[:invoice_number]
+ end
+
+ def add_partner_solution_id(post)
+ return unless application_id
+
+ post[:clientReferenceInformation][:partner] = { solutionId: application_id }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/d_local.rb b/lib/active_merchant/billing/gateways/d_local.rb
index 2f0b959f592..c98d551ceec 100644
--- a/lib/active_merchant/billing/gateways/d_local.rb
+++ b/lib/active_merchant/billing/gateways/d_local.rb
@@ -163,23 +163,19 @@ def add_card(post, card, action, options = {})
post[:card][:network_token] = card.number
post[:card][:cryptogram] = card.payment_cryptogram
post[:card][:eci] = card.eci
- # used case of Network Token: 'CARD_ON_FILE', 'SUBSCRIPTION', 'UNSCHEDULED_CARD_ON_FILE'
- if options.dig(:stored_credential, :reason_type) == 'unscheduled'
- if options.dig(:stored_credential, :initiator) == 'merchant'
- post[:card][:stored_credential_type] = 'UNSCHEDULED_CARD_ON_FILE'
- else
- post[:card][:stored_credential_type] = 'CARD_ON_FILE'
- end
- else
- post[:card][:stored_credential_type] = 'SUBSCRIPTION'
- end
- # required for MC debit recurrent in BR 'USED'(subsecuence Payments) . 'FIRST' an inital payment
- post[:card][:stored_credential_usage] = (options[:stored_credential][:initial_transaction] ? 'FIRST' : 'USED') if options[:stored_credential]
else
post[:card][:number] = card.number
post[:card][:cvv] = card.verification_value
end
+ if options[:stored_credential]
+ # required for MC debit recurrent in BR 'USED'(subsecuence Payments) . 'FIRST' an inital payment
+ post[:card][:stored_credential_usage] = (options[:stored_credential][:initial_transaction] ? 'FIRST' : 'USED')
+ post[:card][:network_payment_reference] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
+ # used case of Network Token: 'CARD_ON_FILE', 'SUBSCRIPTION', 'UNSCHEDULED_CARD_ON_FILE'
+ post[:card][:stored_credential_type] = fetch_stored_credential_type(options[:stored_credential])
+ end
+
post[:card][:holder_name] = card.name
post[:card][:expiration_month] = card.month
post[:card][:expiration_year] = card.year
@@ -188,6 +184,15 @@ def add_card(post, card, action, options = {})
post[:card][:installments] = options[:installments] if options[:installments]
post[:card][:installments_id] = options[:installments_id] if options[:installments_id]
post[:card][:force_type] = options[:force_type].to_s.upcase if options[:force_type]
+ post[:card][:save] = options[:save] if options[:save]
+ end
+
+ def fetch_stored_credential_type(stored_credential)
+ if stored_credential[:reason_type] == 'unscheduled'
+ stored_credential[:initiator] == 'merchant' ? 'UNSCHEDULED_CARD_ON_FILE' : 'CARD_ON_FILE'
+ else
+ 'SUBSCRIPTION'
+ end
end
def parse(body)
@@ -217,6 +222,7 @@ def commit(action, parameters, options = {})
message_from(action, response),
response,
authorization: authorization_from(response),
+ network_transaction_id: network_transaction_id_from(response),
avs_result: AVSResult.new(code: response['some_avs_response_key']),
cvv_result: CVVResult.new(response['some_cvv_response_key']),
test: test?,
@@ -241,6 +247,10 @@ def authorization_from(response)
response['id']
end
+ def network_transaction_id_from(response)
+ response.dig('card', 'network_tx_reference')
+ end
+
def error_code_from(action, response)
return if success_from(action, response)
@@ -249,7 +259,7 @@ def error_code_from(action, response)
end
def url(action, parameters, options = {})
- "#{(test? ? test_url : live_url)}/#{endpoint(action, parameters, options)}/"
+ "#{test? ? test_url : live_url}/#{endpoint(action, parameters, options)}/"
end
def endpoint(action, parameters, options)
diff --git a/lib/active_merchant/billing/gateways/data_cash.rb b/lib/active_merchant/billing/gateways/data_cash.rb
index 46058035510..b0bbe98f266 100644
--- a/lib/active_merchant/billing/gateways/data_cash.rb
+++ b/lib/active_merchant/billing/gateways/data_cash.rb
@@ -235,23 +235,23 @@ 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
@@ -260,9 +260,13 @@ def add_credit_card(xml, credit_card, address)
def commit(request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request))
- Response.new(response[:status] == '1', response[:reason], response,
+ Response.new(
+ response[:status] == '1',
+ response[:reason],
+ response,
test: test?,
- authorization: "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}")
+ authorization: "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}"
+ )
end
def format_date(month, year)
diff --git a/lib/active_merchant/billing/gateways/datatrans.rb b/lib/active_merchant/billing/gateways/datatrans.rb
new file mode 100644
index 00000000000..6d1a3c686d9
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/datatrans.rb
@@ -0,0 +1,228 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DatatransGateway < Gateway
+ self.test_url = 'https://api.sandbox.datatrans.com/v1/transactions/'
+ self.live_url = 'https://api.datatrans.com/v1/transactions/'
+
+ self.supported_countries = %w(CH GR US) # to confirm the countries supported.
+ self.default_currency = 'CHF'
+ self.currencies_without_fractions = %w(CHF EUR USD)
+ self.currencies_with_three_decimal_places = %w()
+ self.supported_cardtypes = %i[master visa american_express unionpay diners_club discover jcb maestro dankort]
+
+ self.money_format = :cents
+
+ self.homepage_url = 'https://www.datatrans.ch/'
+ self.display_name = 'Datatrans'
+
+ CREDIT_CARD_SOURCE = {
+ visa: 'VISA',
+ master: 'MASTERCARD'
+ }.with_indifferent_access
+
+ DEVICE_SOURCE = {
+ apple_pay: 'APPLE_PAY',
+ google_pay: 'GOOGLE_PAY'
+ }.with_indifferent_access
+
+ def initialize(options = {})
+ requires!(options, :merchant_id, :password)
+ @merchant_id, @password = options.values_at(:merchant_id, :password)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ authorize(money, payment, options.merge(auto_settle: true))
+ end
+
+ def authorize(money, payment, options = {})
+ post = { refno: options.fetch(:order_id, '') }
+ add_payment_method(post, payment)
+ add_3ds_data(post, payment, options)
+ add_currency_amount(post, money, options)
+ add_billing_address(post, options)
+ post[:autoSettle] = options[:auto_settle] if options[:auto_settle]
+ commit('authorize', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = { refno: options.fetch(:order_id, '') }
+ transaction_id = authorization.split('|').first
+ add_currency_amount(post, money, options)
+ commit('settle', post, { transaction_id: transaction_id })
+ end
+
+ def refund(money, authorization, options = {})
+ post = { refno: options.fetch(:order_id, '') }
+ transaction_id = authorization.split('|').first
+ add_currency_amount(post, money, options)
+ commit('credit', post, { transaction_id: transaction_id })
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ transaction_id = authorization.split('|').first
+ commit('cancel', post, { transaction_id: transaction_id })
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )[\w =]+), '\1[FILTERED]').
+ gsub(%r((\"number\\":\\")\d+), '\1[FILTERED]\2').
+ gsub(%r((\"cvv\\":\\")\d+), '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_payment_method(post, payment_method)
+ card = build_card(payment_method)
+ post[:card] = {
+ expiryMonth: format(payment_method.month, :two_digits),
+ expiryYear: format(payment_method.year, :two_digits)
+ }.merge(card)
+ end
+
+ def build_card(payment_method)
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
+ {
+ type: DEVICE_SOURCE[payment_method.source] ? 'DEVICE_TOKEN' : 'NETWORK_TOKEN',
+ tokenType: DEVICE_SOURCE[payment_method.source] || CREDIT_CARD_SOURCE[card_brand(payment_method)],
+ token: payment_method.number,
+ cryptogram: payment_method.payment_cryptogram
+ }
+ else
+ {
+ number: payment_method.number,
+ cvv: payment_method.verification_value.to_s
+ }
+ end
+ end
+
+ def add_3ds_data(post, payment_method, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ three_ds =
+ {
+ "3D":
+ {
+ eci: three_d_secure[:eci],
+ xid: three_d_secure[:xid],
+ threeDSTransactionId: three_d_secure[:ds_transaction_id],
+ cavv: three_d_secure[:cavv],
+ threeDSVersion: three_d_secure[:version],
+ cavvAlgorithm: three_d_secure[:cavv_algorithm],
+ directoryResponse: three_d_secure[:directory_response_status],
+ authenticationResponse: three_d_secure[:authentication_response_status],
+ transStatusReason: three_d_secure[:trans_status_reason]
+ }.compact
+ }
+
+ post[:card].merge!(three_ds)
+ end
+
+ def add_billing_address(post, options)
+ return unless billing_address = options[:billing_address]
+
+ post[:billing] = {
+ name: billing_address[:name],
+ street: billing_address[:address1],
+ street2: billing_address[:address2],
+ city: billing_address[:city],
+ country: Country.find(billing_address[:country]).code(:alpha3).value, # pass country alpha 2 to country alpha 3,
+ phoneNumber: billing_address[:phone],
+ zipCode: billing_address[:zip],
+ email: options[:email]
+ }.compact
+ end
+
+ def add_currency_amount(post, money, options)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:amount] = amount(money)
+ end
+
+ def commit(action, post, options = {})
+ response = parse(ssl_post(url(action, options), post.to_json, headers))
+ succeeded = success_from(action, response)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ResponseError => e
+ response = parse(e.response.body)
+ Response.new(false, message_from(false, response), response, test: test?, error_code: error_code_from(response))
+ end
+
+ def parse(response)
+ JSON.parse response
+ rescue JSON::ParserError
+ msg = 'Invalid JSON response received from Datatrans. Please contact them for support if you continue to receive this message.'
+ msg += " (The raw response returned by the API was #{response.inspect})"
+ {
+ 'successful' => false,
+ 'response' => {},
+ 'errors' => [msg]
+ }
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/json; charset=UTF-8',
+ 'Authorization' => "Basic #{Base64.strict_encode64("#{@merchant_id}:#{@password}")}"
+ }
+ end
+
+ def url(endpoint, options = {})
+ case endpoint
+ when 'settle', 'credit', 'cancel'
+ "#{test? ? test_url : live_url}#{options[:transaction_id]}/#{endpoint}"
+ else
+ "#{test? ? test_url : live_url}#{endpoint}"
+ end
+ end
+
+ def success_from(action, response)
+ case action
+ when 'authorize', 'credit'
+ true if response.include?('transactionId') && response.include?('acquirerAuthorizationCode')
+ when 'settle', 'cancel'
+ true if response.dig('response_code') == 204
+ else
+ false
+ end
+ end
+
+ def authorization_from(response)
+ auth = [response['transactionId'], response['acquirerAuthorizationCode']].join('|')
+ return auth unless auth == '|'
+ end
+
+ def message_from(succeeded, response)
+ return if succeeded
+
+ response.dig('error', 'message')
+ end
+
+ def error_code_from(response)
+ response.dig('error', 'code')
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ when 200...300
+ response.body || { response_code: response.code.to_i }.to_json
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/decidir.rb b/lib/active_merchant/billing/gateways/decidir.rb
index 38ce71dbab3..58167d5ede8 100644
--- a/lib/active_merchant/billing/gateways/decidir.rb
+++ b/lib/active_merchant/billing/gateways/decidir.rb
@@ -7,7 +7,7 @@ class DecidirGateway < Gateway
self.supported_countries = ['AR']
self.money_format = :cents
self.default_currency = 'ARS'
- self.supported_cardtypes = %i[visa master american_express diners_club naranja cabal]
+ self.supported_cardtypes = %i[visa master american_express diners_club naranja cabal tuya]
self.homepage_url = 'http://www.decidir.com'
self.display_name = 'Decidir'
@@ -127,6 +127,7 @@ def add_auth_purchase_params(post, money, credit_card, options)
add_payment(post, credit_card, options)
add_aggregate_data(post, options) if options[:aggregate_data]
add_sub_payments(post, options)
+ add_customer_data(post, options)
end
def add_payment_method_id(credit_card, options)
@@ -167,29 +168,55 @@ def add_amount(post, money, options)
post[:amount] = localized_amount(money, currency).to_i
end
- def add_payment(post, credit_card, options)
- card_data = {}
+ def add_payment(post, payment_method, options)
+ add_common_payment_data(post, payment_method, options)
+
+ case payment_method
+ when NetworkTokenizationCreditCard
+ add_network_token(post, payment_method, options)
+ else
+ add_credit_card(post, payment_method, options)
+ end
+ end
+
+ def add_common_payment_data(post, payment_method, options)
+ post[:card_data] = {}
+
+ data = post[:card_data]
+ data[:card_holder_identification] = {}
+ data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
+ data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
+ data[:card_holder_name] = payment_method.name if payment_method.name
+
+ # additional data used for Visa transactions
+ data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
+ data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
+ end
+
+ def add_network_token(post, payment_method, options)
+ post[:is_tokenized_payment] = true
+ post[:fraud_detection] ||= {}
+ post[:fraud_detection][:sent_to_cs] = false
+ post[:card_data][:last_four_digits] = options[:last_4]
+
+ post[:token_card_data] = {
+ token: payment_method.number,
+ eci: payment_method.eci,
+ cryptogram: payment_method.payment_cryptogram
+ }
+ end
+
+ def add_credit_card(post, credit_card, options)
+ card_data = post[: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
# the device_unique_id has to be sent in via the card data (as device_unique_identifier) no other fraud detection fields require this
- if options[:fraud_detection].present?
- card_data[:fraud_detection] = {} if (options[:fraud_detection][:device_unique_id]).present?
- card_data[:fraud_detection][:device_unique_identifier] = (options[:fraud_detection][:device_unique_id]) if (options[:fraud_detection][:device_unique_id]).present?
+ if (device_id = options.dig(:fraud_detection, :device_unique_id))
+ card_data[:fraud_detection] = { device_unique_identifier: device_id }
end
-
- # 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 add_aggregate_data(post, options)
@@ -215,6 +242,14 @@ def add_aggregate_data(post, options)
post[:aggregate_data] = aggregate_data
end
+ def add_customer_data(post, options = {})
+ return unless options[:customer_email] || options[:customer_id]
+
+ post[:customer] = {}
+ post[:customer][:id] = options[:customer_id] if options[:customer_id]
+ post[:customer][:email] = options[:customer_email] if options[:customer_email]
+ end
+
def add_sub_payments(post, options)
# sub_payments field is required for purchase transactions, even if empty
post[:sub_payments] = []
@@ -262,7 +297,7 @@ def headers(options = {})
end
def commit(method, endpoint, parameters, options = {})
- url = "#{(test? ? test_url : live_url)}/#{endpoint}"
+ url = "#{test? ? test_url : live_url}/#{endpoint}"
begin
raw_response = ssl_request(method, url, post_data(parameters), headers(options))
diff --git a/lib/active_merchant/billing/gateways/deepstack.rb b/lib/active_merchant/billing/gateways/deepstack.rb
new file mode 100644
index 00000000000..796f3d601c2
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/deepstack.rb
@@ -0,0 +1,382 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DeepstackGateway < Gateway
+ self.test_url = 'https://api.sandbox.deepstack.io'
+ self.live_url = 'https://api.deepstack.io'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[visa master american_express discover]
+ self.money_format = :cents
+
+ self.homepage_url = 'https://deepstack.io/'
+ self.display_name = 'Deepstack Gateway'
+
+ STANDARD_ERROR_CODE_MAPPING = {}
+
+ def initialize(options = {})
+ requires!(options, :publishable_api_key, :app_id, :shared_secret)
+ @publishable_api_key, @app_id, @shared_secret = options.values_at(:publishable_api_key, :app_id, :shared_secret)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ add_payment(post, payment, options)
+ add_order(post, money, options)
+ add_purchase_capture(post)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_payment(post, payment, options)
+ add_order(post, money, options)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ commit('auth', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, authorization, options)
+
+ commit('capture', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, authorization, options)
+ commit('refund', post)
+ end
+
+ def void(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, 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(0, r.authorization, options) }
+ end
+ end
+
+ def get_token(credit_card, options = {})
+ post = {}
+ add_payment_instrument(post, credit_card, options)
+ add_address_payment_instrument(post, credit_card, options)
+ commit('gettoken', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]').
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((Hmac: )[\w=]+), '\1[FILTERED]').
+ gsub(%r((\\"account_number\\":\\")[\w*]+), '\1[FILTERED]').
+ gsub(%r((\\"cvv\\":\\")\w+), '\1[FILTERED]').
+ gsub(%r((\\"expiration\\":\\")\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post[:meta] ||= {}
+
+ add_shipping(post, options) if options.key?(:shipping_address)
+ post[:meta][:client_customer_id] = options[:customer] if options[:customer]
+ post[:meta][:client_transaction_id] = options[:order_id] if options[:order_id]
+ post[:meta][:client_transaction_description] = options[:description] if options[:description]
+ post[:meta][:client_invoice_id] = options[:invoice] if options[:invoice]
+ post[:meta][:card_holder_ip_address] = options[:ip] if options[:ip]
+ end
+
+ def add_address(post, creditcard, options)
+ return post unless options.key?(:address) || options.key?(:billing_address)
+
+ billing_address = options[:address] || options[:billing_address]
+ post[:source] ||= {}
+
+ post[:source][:billing_contact] = {}
+ post[:source][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
+ post[:source][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
+ post[:source][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
+ post[:source][:billing_contact][:email] = options[:email] if options[:email]
+ post[:source][:billing_contact][:address] = {}
+ post[:source][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
+ post[:source][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
+ post[:source][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
+ post[:source][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
+ post[:source][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
+ post[:source][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
+ end
+
+ def add_address_payment_instrument(post, creditcard, options)
+ return post unless options.key?(:address) || options.key?(:billing_address)
+
+ billing_address = options[:address] || options[:billing_address]
+ post[:source] = {} unless post.key?(:payment_instrument)
+
+ post[:payment_instrument][:billing_contact] = {}
+ post[:payment_instrument][:billing_contact][:first_name] = billing_address[:first_name] if billing_address[:first_name]
+ post[:payment_instrument][:billing_contact][:last_name] = billing_address[:last_name] if billing_address[:last_name]
+ post[:payment_instrument][:billing_contact][:phone] = billing_address[:phone] if billing_address[:phone]
+ post[:payment_instrument][:billing_contact][:email] = billing_address[:email] if billing_address[:email]
+ post[:payment_instrument][:billing_contact][:address] = {}
+ post[:payment_instrument][:billing_contact][:address][:line_1] = billing_address[:address1] if billing_address[:address1]
+ post[:payment_instrument][:billing_contact][:address][:line_2] = billing_address[:address2] if billing_address[:address2]
+ post[:payment_instrument][:billing_contact][:address][:city] = billing_address[:city] if billing_address[:city]
+ post[:payment_instrument][:billing_contact][:address][:state] = billing_address[:state] if billing_address[:state]
+ post[:payment_instrument][:billing_contact][:address][:postal_code] = billing_address[:zip] if billing_address[:zip]
+ post[:payment_instrument][:billing_contact][:address][:country_code] = billing_address[:country] if billing_address[:country]
+ end
+
+ def add_shipping(post, options = {})
+ return post unless options.key?(:shipping_address)
+
+ shipping = options[:shipping_address]
+ post[:meta][:shipping_info] = {}
+ post[:meta][:shipping_info][:first_name] = shipping[:first_name] if shipping[:first_name]
+ post[:meta][:shipping_info][:last_name] = shipping[:last_name] if shipping[:last_name]
+ post[:meta][:shipping_info][:phone] = shipping[:phone] if shipping[:phone]
+ post[:meta][:shipping_info][:email] = shipping[:email] if shipping[:email]
+ post[:meta][:shipping_info][:address] = {}
+ post[:meta][:shipping_info][:address][:line_1] = shipping[:address1] if shipping[:address1]
+ post[:meta][:shipping_info][:address][:line_2] = shipping[:address2] if shipping[:address2]
+ post[:meta][:shipping_info][:address][:city] = shipping[:city] if shipping[:city]
+ post[:meta][:shipping_info][:address][:state] = shipping[:state] if shipping[:state]
+ post[:meta][:shipping_info][:address][:postal_code] = shipping[:zip] if shipping[:zip]
+ post[:meta][:shipping_info][:address][:country_code] = shipping[:country] if shipping[:country]
+ end
+
+ def add_invoice(post, money, authorization, options)
+ post[:amount] = amount(money)
+ post[:charge] = authorization
+ end
+
+ def add_payment(post, payment, options)
+ if payment.kind_of?(String)
+ post[:source] = {}
+ post[:source][:type] = 'card_on_file'
+ post[:source][:card_on_file] = {}
+ post[:source][:card_on_file][:id] = payment
+ post[:source][:card_on_file][:cvv] = options[:verification_value] || ''
+ post[:source][:card_on_file][:customer_id] = options[:customer_id] || ''
+ # credit card object
+ elsif payment.respond_to?(:number)
+ post[:source] = {}
+ post[:source][:type] = 'credit_card'
+ post[:source][:credit_card] = {}
+ post[:source][:credit_card][:account_number] = payment.number
+ post[:source][:credit_card][:cvv] = payment.verification_value || ''
+ post[:source][:credit_card][:expiration] = '%02d%02d' % [payment.month, payment.year % 100]
+ post[:source][:credit_card][:customer_id] = options[:customer_id] || ''
+ end
+ end
+
+ def add_payment_instrument(post, creditcard, options)
+ if creditcard.kind_of?(String)
+ post[:source] = creditcard
+ return post
+ end
+ return post unless creditcard.respond_to?(:number)
+
+ post[:payment_instrument] = {}
+ post[:payment_instrument][:type] = 'credit_card'
+ post[:payment_instrument][:credit_card] = {}
+ post[:payment_instrument][:credit_card][:account_number] = creditcard.number
+ post[:payment_instrument][:credit_card][:expiration] = '%02d%02d' % [creditcard.month, creditcard.year % 100]
+ post[:payment_instrument][:credit_card][:cvv] = creditcard.verification_value
+ end
+
+ def add_order(post, amount, options)
+ post[:transaction] ||= {}
+
+ post[:transaction][:amount] = amount
+ post[:transaction][:cof_type] = options.key?(:cof_type) ? options[:cof_type].upcase : 'UNSCHEDULED_CARDHOLDER'
+ post[:transaction][:capture] = false # Change this in the request (auth/charge)
+ post[:transaction][:currency_code] = (options[:currency] || currency(amount).upcase)
+ post[:transaction][:avs] = options[:avs] || true # default avs to true unless told otherwise
+ post[:transaction][:save_payment_instrument] = options[:save_payment_instrument] || false
+ end
+
+ def add_purchase_capture(post)
+ post[:transaction] ||= {}
+ post[:transaction][:capture] = true
+ end
+
+ def parse(body)
+ return {} if !body || body.empty?
+
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters, method = 'POST')
+ url = (test? ? test_url : live_url)
+ if no_hmac(action)
+ request_headers = headers.merge(create_basic(parameters, action))
+ else
+ request_headers = headers.merge(create_hmac(parameters, method))
+ end
+ request_url = url + get_url(action)
+ begin
+ response = parse(ssl_post(request_url, post_data(action, parameters), request_headers))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['avs_result']),
+ cvv_result: CVVResult.new(response['cvv_result']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ResponseError => e
+ Response.new(
+ false,
+ message_from_error(e.response.body),
+ response_error(e.response.body)
+ )
+ rescue JSON::ParserError
+ Response.new(
+ false,
+ message_from(response),
+ json_error(response)
+ )
+ end
+ end
+
+ def headers
+ {
+ 'Accept' => 'text/plain',
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def response_error(response)
+ parse(response)
+ rescue JSON::ParserError
+ json_error(response)
+ end
+
+ def json_error(response)
+ msg = 'Invalid response received from the Conekta API.'
+ msg += " (The raw response returned by the API was #{response.inspect})"
+ {
+ 'message' => msg
+ }
+ end
+
+ def success_from(response)
+ success = false
+ if response.key?('response_code')
+ success = response['response_code'] == '00'
+ # Hack because token/payment instrument methods do not return a response_code
+ elsif response.key?('id')
+ success = true if response['id'].start_with?('tok', 'card')
+ end
+
+ return success
+ end
+
+ def message_from(response)
+ response = JSON.parse(response) if response.is_a?(String)
+ if response.key?('message')
+ return response['message']
+ elsif response.key?('detail')
+ return response['detail']
+ end
+ end
+
+ def message_from_error(response)
+ if response.is_a?(String)
+ response.gsub!('\\"', '"')
+ response = JSON.parse(response)
+ end
+
+ if response.key?('detail')
+ return response['detail']
+ elsif response.key?('message')
+ return response['message']
+ end
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def post_data(action, parameters = {})
+ return JSON.generate(parameters)
+ end
+
+ def error_code_from(response)
+ error_code = nil
+ error_code = response['response_code'] unless success_from(response)
+ if error = response.dig('detail')
+ error_code = error
+ elsif error = response.dig('error')
+ error_code = error.dig('reason', 'id')
+ end
+ error_code
+ end
+
+ def get_url(action)
+ base = '/api/v1/'
+ case action
+ when 'sale'
+ return base + 'payments/charge'
+ when 'auth'
+ return base + 'payments/charge'
+ when 'capture'
+ return base + 'payments/capture'
+ when 'void'
+ return base + 'payments/refund'
+ when 'refund'
+ return base + 'payments/refund'
+ when 'gettoken'
+ return base + 'vault/token'
+ when 'vault'
+ return base + 'vault/payment-instrument/token'
+ else
+ return base + 'noaction'
+ end
+ end
+
+ def no_hmac(action)
+ case action
+ when 'gettoken'
+ return true
+ else
+ return false
+ end
+ end
+
+ def create_basic(post, method)
+ return { 'Authorization' => "Bearer #{@publishable_api_key}" }
+ end
+
+ def create_hmac(post, method)
+ # Need requestDate, requestMethod, Nonce, AppIDKey
+ app_id_key = @app_id
+ request_method = method.upcase
+ uuid = SecureRandom.uuid
+ request_time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
+
+ string_to_hash = "#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{JSON.generate(post)}"
+ signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), Base64.strict_decode64(@shared_secret), string_to_hash)
+ base64_signature = Base64.strict_encode64(signature)
+ hmac_header = Base64.strict_encode64("#{app_id_key}|#{request_method}|#{request_time}|#{uuid}|#{base64_signature}")
+ return { 'hmac' => hmac_header }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
index 11e5bb68945..4588eddb7f7 100644
--- a/lib/active_merchant/billing/gateways/ebanx.rb
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -4,30 +4,24 @@ class EbanxGateway < Gateway
self.test_url = 'https://sandbox.ebanxpay.com/ws/'
self.live_url = 'https://api.ebanxpay.com/ws/'
- self.supported_countries = %w(BR MX CO CL AR PE)
+ self.supported_countries = %w(BR MX CO CL AR PE BO EC)
self.default_currency = 'USD'
- self.supported_cardtypes = %i[visa master american_express discover diners_club]
+ self.supported_cardtypes = %i[visa master american_express discover diners_club elo hipercard]
self.homepage_url = 'http://www.ebanx.com/'
self.display_name = 'EBANX'
TAGS = ['Spreedly']
- 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'
+ store: 'token',
+ inquire: 'query',
+ verify: 'verifycard'
}
HTTP_METHOD = {
@@ -36,16 +30,9 @@ class EbanxGateway < Gateway
capture: :get,
refund: :post,
void: :get,
- store: :post
- }
-
- VERIFY_AMOUNT_PER_COUNTRY = {
- 'br' => 100,
- 'ar' => 100,
- 'co' => 50000,
- 'pe' => 300,
- 'mx' => 2000,
- 'cl' => 80000
+ store: :post,
+ inquire: :get,
+ verify: :post
}
def initialize(options = {})
@@ -113,17 +100,30 @@ def void(authorization, options = {})
def store(credit_card, options = {})
post = {}
add_integration_key(post)
- add_payment_details(post, credit_card)
- post[:country] = customer_country(options)
+ customer_country(post, options)
+ add_payment_type(post)
+ post[:creditcard] = payment_details(credit_card)
commit(:store, post)
end
def verify(credit_card, options = {})
- MultiResponse.run(:use_first_response) do |r|
- r.process { authorize(VERIFY_AMOUNT_PER_COUNTRY[customer_country(options)], credit_card, options) }
- r.process(:ignore_result) { void(r.authorization, options) }
- end
+ post = {}
+ add_integration_key(post)
+ add_payment_type(post)
+ customer_country(post, options)
+ post[:card] = payment_details(credit_card)
+ post[:device_id] = options[:device_id] if options[:device_id]
+
+ commit(:verify, post)
+ end
+
+ def inquire(authorization, options = {})
+ post = {}
+ add_integration_key(post)
+ add_authorization(post, authorization)
+
+ commit(:inquire, post)
end
def supports_scrubbing?
@@ -153,7 +153,7 @@ def add_authorization(post, authorization)
def add_customer_data(post, payment, options)
post[:payment][:name] = customer_name(payment, options)
- post[:payment][:email] = options[:email] || 'unspecified@example.com'
+ post[:payment][:email] = options[:email]
post[:payment][:document] = options[:document]
post[:payment][:birth_date] = options[:birth_date] if options[:birth_date]
end
@@ -189,15 +189,14 @@ def add_invoice(post, money, options)
end
def add_card_or_token(post, payment, options)
- payment, brand = payment.split('|') if payment.is_a?(String)
- post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym]
+ payment = payment.split('|')[0] if payment.is_a?(String)
+ add_payment_type(post[:payment])
post[:payment][:creditcard] = payment_details(payment)
post[:payment][:creditcard][:soft_descriptor] = options[:soft_descriptor] if options[:soft_descriptor]
end
- def add_payment_details(post, payment)
- post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
- post[:creditcard] = payment_details(payment)
+ def add_payment_type(post)
+ post[:payment_type_code] = 'creditcard'
end
def payment_details(payment)
@@ -235,7 +234,7 @@ def commit(action, parameters)
Response.new(
success,
- message_from(response),
+ message_from(action, response),
response,
authorization: authorization_from(action, parameters, response),
test: test?,
@@ -257,28 +256,41 @@ def add_processing_type_to_commit_headers(commit_headers, processing_type)
end
def success_from(action, response)
- if %i[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'
+ status = response.dig('payment', 'status')
+
+ case action
+ when :purchase, :capture, :refund
+ status == 'CO'
+ when :authorize
+ status == 'PE'
+ when :void
+ status == 'CA'
+ when :verify
+ response.dig('card_verification', 'transaction_status', 'code') == 'OK'
+ when :store, :inquire
+ response.dig('status') == 'SUCCESS'
else
false
end
end
- def message_from(response)
+ def message_from(action, response)
return response['status_message'] if response['status'] == 'ERROR'
- response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'description')
+ if action == :verify
+ response.dig('card_verification', 'transaction_status', 'description')
+ else
+ response.dig('payment', 'transaction_status', 'description')
+ end
end
def authorization_from(action, parameters, response)
if action == :store
- "#{response.try(:[], 'token')}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}"
+ if success_from(action, response)
+ "#{response.try(:[], 'token')}|#{response['payment_type_code']}"
+ else
+ response.try(:[], 'token')
+ end
else
response.try(:[], 'payment').try(:[], 'hash')
end
@@ -298,7 +310,7 @@ def url_for(hostname, action, parameters)
end
def requires_http_get(action)
- return true if %i[capture void].include?(action)
+ return true if %i[capture void inquire].include?(action)
false
end
@@ -319,9 +331,9 @@ def error_code_from(response, success)
end
end
- def customer_country(options)
+ def customer_country(post, options)
if country = options[:country] || (options[:billing_address][:country] if options[:billing_address])
- country.downcase
+ post[:country] = country.downcase
end
end
diff --git a/lib/active_merchant/billing/gateways/efsnet.rb b/lib/active_merchant/billing/gateways/efsnet.rb
index 00a5579c61f..d3ec02270ce 100644
--- a/lib/active_merchant/billing/gateways/efsnet.rb
+++ b/lib/active_merchant/billing/gateways/efsnet.rb
@@ -145,11 +145,15 @@ def add_creditcard(post, creditcard)
def commit(action, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml'))
- Response.new(success?(response), message_from(response[:result_message]), response,
+ Response.new(
+ success?(response),
+ message_from(response[:result_message]),
+ response,
test: test?,
authorization: authorization_from(response, parameters),
avs_result: { code: response[:avs_response_code] },
- cvv_result: response[:cvv_response_code])
+ cvv_result: response[:cvv_response_code]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/elavon.rb b/lib/active_merchant/billing/gateways/elavon.rb
index 3085354dc8d..f7f5e678575 100644
--- a/lib/active_merchant/billing/gateways/elavon.rb
+++ b/lib/active_merchant/billing/gateways/elavon.rb
@@ -43,12 +43,7 @@ def purchase(money, payment_method, options = {})
xml.ssl_transaction_type self.actions[:purchase]
xml.ssl_amount amount(money)
- if payment_method.is_a?(String)
- add_token(xml, payment_method)
- else
- add_creditcard(xml, payment_method)
- end
-
+ add_payment(xml, payment_method, options)
add_invoice(xml, options)
add_salestax(xml, options)
add_currency(xml, money, options)
@@ -62,15 +57,14 @@ def purchase(money, payment_method, options = {})
commit(request)
end
- def authorize(money, creditcard, options = {})
+ def authorize(money, payment_method, options = {})
request = build_xml_request do |xml|
xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id]
xml.ssl_transaction_type self.actions[:authorize]
xml.ssl_amount amount(money)
-
add_salestax(xml, options)
add_invoice(xml, options)
- add_creditcard(xml, creditcard)
+ add_payment(xml, payment_method, options)
add_currency(xml, money, options)
add_address(xml, options)
add_customer_email(xml, options)
@@ -200,6 +194,16 @@ def scrub(transcript)
private
+ def add_payment(xml, payment, options)
+ if payment.is_a?(String)
+ xml.ssl_token payment
+ elsif payment.is_a?(NetworkTokenizationCreditCard)
+ add_network_token(xml, payment)
+ else
+ add_creditcard(xml, payment)
+ end
+ end
+
def add_invoice(xml, options)
xml.ssl_invoice_number url_encode_truncate((options[:order_id] || options[:invoice]), 25)
xml.ssl_description url_encode_truncate(options[:description], 255)
@@ -213,6 +217,16 @@ def add_txn_id(xml, authorization)
xml.ssl_txn_id authorization.split(';').last
end
+ def add_network_token(xml, payment_method)
+ payment = payment_method.payment_data&.gsub('=>', ':')
+ case payment_method.source
+ when :apple_pay
+ xml.ssl_applepay_web url_encode(payment)
+ when :google_pay
+ xml.ssl_google_pay url_encode(payment)
+ end
+ end
+
def add_creditcard(xml, creditcard)
xml.ssl_card_number creditcard.number
xml.ssl_exp_date expdate(creditcard)
@@ -451,8 +465,8 @@ def url_encode(value)
if value.is_a?(String)
encoded = CGI.escape(value)
encoded = encoded.tr('+', ' ') # don't encode spaces
- encoded = encoded.gsub('%26', '%26amp;') # account for Elavon's weird '&' handling
- encoded
+ encoded.gsub('%26', '%26amp;') # account for Elavon's weird '&' handling
+
else
value.to_s
end
diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb
index f9fe19149bf..b685c7bab9c 100644
--- a/lib/active_merchant/billing/gateways/element.rb
+++ b/lib/active_merchant/billing/gateways/element.rb
@@ -37,6 +37,7 @@ def purchase(money, payment, options = {})
add_transaction(xml, money, options)
add_terminal(xml, options)
add_address(xml, options)
+ add_lodging(xml, options)
end
end
@@ -51,6 +52,7 @@ def authorize(money, payment, options = {})
add_transaction(xml, money, options)
add_terminal(xml, options)
add_address(xml, options)
+ add_lodging(xml, options)
end
end
@@ -222,16 +224,44 @@ def market_code(money, options)
options[:market_code] || 'Default'
end
+ def add_lodging(xml, options)
+ if lodging = options[:lodging]
+ xml.extendedParameters do
+ xml.ExtendedParameters do
+ xml.Key 'Lodging'
+ xml.Value('xsi:type' => 'Lodging') do
+ xml.LodgingAgreementNumber lodging[:agreement_number] if lodging[:agreement_number]
+ xml.LodgingCheckInDate lodging[:check_in_date] if lodging[:check_in_date]
+ xml.LodgingCheckOutDate lodging[:check_out_date] if lodging[:check_out_date]
+ xml.LodgingRoomAmount lodging[:room_amount] if lodging[:room_amount]
+ xml.LodgingRoomTax lodging[:room_tax] if lodging[:room_tax]
+ xml.LodgingNoShowIndicator lodging[:no_show_indicator] if lodging[:no_show_indicator]
+ xml.LodgingDuration lodging[:duration] if lodging[:duration]
+ xml.LodgingCustomerName lodging[:customer_name] if lodging[:customer_name]
+ xml.LodgingClientCode lodging[:client_code] if lodging[:client_code]
+ xml.LodgingExtraChargesDetail lodging[:extra_charges_detail] if lodging[:extra_charges_detail]
+ xml.LodgingExtraChargesAmounts lodging[:extra_charges_amounts] if lodging[:extra_charges_amounts]
+ xml.LodgingPrestigiousPropertyCode lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ xml.LodgingSpecialProgramCode lodging[:special_program_code] if lodging[:special_program_code]
+ xml.LodgingChargeType lodging[:charge_type] if lodging[:charge_type]
+ end
+ end
+ end
+ end
+ end
+
def add_terminal(xml, options)
xml.terminal do
xml.TerminalID options[:terminal_id] || '01'
+ xml.TerminalType options[:terminal_type] if options[:terminal_type]
xml.CardPresentCode options[:card_present_code] || 'UseDefault'
- xml.CardholderPresentCode 'UseDefault'
- xml.CardInputCode 'UseDefault'
- xml.CVVPresenceCode 'UseDefault'
- xml.TerminalCapabilityCode 'UseDefault'
- xml.TerminalEnvironmentCode 'UseDefault'
+ xml.CardholderPresentCode options[:card_holder_present_code] || 'UseDefault'
+ xml.CardInputCode options[:card_input_code] || 'UseDefault'
+ xml.CVVPresenceCode options[:cvv_presence_code] || 'UseDefault'
+ xml.TerminalCapabilityCode options[:terminal_capability_code] || 'UseDefault'
+ xml.TerminalEnvironmentCode options[:terminal_environment_code] || 'UseDefault'
xml.MotoECICode 'NonAuthenticatedSecureECommerceTransaction'
+ xml.PartialApprovedFlag options[:partial_approved_flag] if options[:partial_approved_flag]
end
end
@@ -240,7 +270,7 @@ def add_credit_card(xml, payment)
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.CardholderName "#{payment.first_name} #{payment.last_name}"
xml.CVV payment.verification_value
end
end
@@ -363,7 +393,6 @@ def build_soap_request
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
diff --git a/lib/active_merchant/billing/gateways/epay.rb b/lib/active_merchant/billing/gateways/epay.rb
index d26382cfe3c..83c35088833 100644
--- a/lib/active_merchant/billing/gateways/epay.rb
+++ b/lib/active_merchant/billing/gateways/epay.rb
@@ -174,17 +174,21 @@ def commit(action, params)
response = send("do_#{action}", params)
if action == :authorize
- Response.new response['accept'].to_i == 1,
+ Response.new(
+ response['accept'].to_i == 1,
response['errortext'],
response,
test: test?,
authorization: response['tid']
+ )
else
- Response.new response['result'] == 'true',
+ Response.new(
+ response['result'] == 'true',
messages(response['epay'], response['pbs']),
response,
test: test?,
authorization: params[:transaction]
+ )
end
end
diff --git a/lib/active_merchant/billing/gateways/evo_ca.rb b/lib/active_merchant/billing/gateways/evo_ca.rb
index 8aeabf72977..cd9848eb2a7 100644
--- a/lib/active_merchant/billing/gateways/evo_ca.rb
+++ b/lib/active_merchant/billing/gateways/evo_ca.rb
@@ -279,11 +279,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/eway.rb b/lib/active_merchant/billing/gateways/eway.rb
index 3032bb2d4fe..c6e21c658af 100644
--- a/lib/active_merchant/billing/gateways/eway.rb
+++ b/lib/active_merchant/billing/gateways/eway.rb
@@ -111,11 +111,13 @@ def commit(url, money, parameters)
raw_response = ssl_post(url, post_data(parameters))
response = parse(raw_response)
- Response.new(success?(response),
+ Response.new(
+ success?(response),
message_from(response[:ewaytrxnerror]),
response,
authorization: response[:ewaytrxnnumber],
- test: test?)
+ test: test?
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/eway_managed.rb b/lib/active_merchant/billing/gateways/eway_managed.rb
index 02cd5b5cf6f..c65ad5206b0 100644
--- a/lib/active_merchant/billing/gateways/eway_managed.rb
+++ b/lib/active_merchant/billing/gateways/eway_managed.rb
@@ -222,9 +222,13 @@ def commit(action, post)
end
response = parse(raw)
- EwayResponse.new(response[:success], response[:message], response,
+ EwayResponse.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
- authorization: response[:auth_code])
+ authorization: response[:auth_code]
+ )
end
# Where we build the full SOAP 1.2 request using builder
diff --git a/lib/active_merchant/billing/gateways/exact.rb b/lib/active_merchant/billing/gateways/exact.rb
index 144e3dc1359..6b99cd66e2a 100644
--- a/lib/active_merchant/billing/gateways/exact.rb
+++ b/lib/active_merchant/billing/gateways/exact.rb
@@ -158,11 +158,15 @@ def expdate(credit_card)
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,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs] },
- cvv_result: response[:cvv2])
+ cvv_result: response[:cvv2]
+ )
rescue ResponseError => e
case e.response.code
when '401'
diff --git a/lib/active_merchant/billing/gateways/fat_zebra.rb b/lib/active_merchant/billing/gateways/fat_zebra.rb
index 91b4e23bb68..0d534db434c 100644
--- a/lib/active_merchant/billing/gateways/fat_zebra.rb
+++ b/lib/active_merchant/billing/gateways/fat_zebra.rb
@@ -28,6 +28,7 @@ def purchase(money, creditcard, options = {})
add_order_id(post, options)
add_ip(post, options)
add_metadata(post, options)
+ add_three_ds(post, options)
commit(:post, 'purchases', post)
end
@@ -41,6 +42,7 @@ def authorize(money, creditcard, options = {})
add_order_id(post, options)
add_ip(post, options)
add_metadata(post, options)
+ add_three_ds(post, options)
post[:capture] = false
@@ -125,16 +127,42 @@ def add_creditcard(post, creditcard, options = {})
def add_extra_options(post, options)
extra = {}
extra[:ecm] = '32' if options[:recurring]
- extra[:cavv] = options[:cavv] || options.dig(:three_d_secure, :cavv) if options[:cavv] || options.dig(:three_d_secure, :cavv)
- extra[:xid] = options[:xid] || options.dig(:three_d_secure, :xid) if options[:xid] || options.dig(:three_d_secure, :xid)
- extra[:sli] = options[:sli] || options.dig(:three_d_secure, :eci) if options[:sli] || options.dig(:three_d_secure, :eci)
extra[:name] = options[:merchant] if options[:merchant]
extra[:location] = options[:merchant_location] if options[:merchant_location]
extra[:card_on_file] = options.dig(:extra, :card_on_file) if options.dig(:extra, :card_on_file)
extra[:auth_reason] = options.dig(:extra, :auth_reason) if options.dig(:extra, :auth_reason)
+
+ unless options[:three_d_secure].present?
+ extra[:sli] = options[:sli] if options[:sli]
+ extra[:xid] = options[:xid] if options[:xid]
+ extra[:cavv] = options[:cavv] if options[:cavv]
+ end
+
post[:extra] = extra if extra.any?
end
+ def add_three_ds(post, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post[:extra] = {
+ sli: three_d_secure[:eci],
+ xid: three_d_secure[:xid],
+ cavv: three_d_secure[:cavv],
+ par: three_d_secure[:authentication_response_status],
+ ver: formatted_enrollment(three_d_secure[:enrolled]),
+ threeds_version: three_d_secure[:version],
+ ds_transaction_id: three_d_secure[:ds_transaction_id]
+ }.compact
+ end
+
+ def formatted_enrollment(val)
+ case val
+ when 'Y', 'N', 'U' then val
+ when true, 'true' then 'Y'
+ when false, 'false' then 'N'
+ end
+ end
+
def add_order_id(post, options)
post[:reference] = options[:order_id] || SecureRandom.hex(15)
end
diff --git a/lib/active_merchant/billing/gateways/federated_canada.rb b/lib/active_merchant/billing/gateways/federated_canada.rb
index 9399db829f5..43460286317 100644
--- a/lib/active_merchant/billing/gateways/federated_canada.rb
+++ b/lib/active_merchant/billing/gateways/federated_canada.rb
@@ -121,11 +121,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/first_pay.rb b/lib/active_merchant/billing/gateways/first_pay.rb
index e6f92b3cdd8..daec309819e 100644
--- a/lib/active_merchant/billing/gateways/first_pay.rb
+++ b/lib/active_merchant/billing/gateways/first_pay.rb
@@ -1,181 +1,15 @@
-require 'nokogiri'
+require 'active_merchant/billing/gateways/first_pay/first_pay_xml'
+require 'active_merchant/billing/gateways/first_pay/first_pay_json'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class FirstPayGateway < Gateway
- self.live_url = 'https://secure.goemerchant.com/secure/gateway/xmlgateway.aspx'
+ self.abstract_class = true
- self.supported_countries = ['US']
- self.default_currency = 'USD'
- self.money_format = :dollars
- self.supported_cardtypes = %i[visa master american_express discover]
+ def self.new(options = {})
+ return FirstPayJsonGateway.new(options) if options[:merchant_key]
- self.homepage_url = 'http://1stpaygateway.net/'
- self.display_name = '1stPayGateway.Net'
-
- def initialize(options = {})
- requires!(options, :transaction_center_id, :gateway_id)
- super
- end
-
- 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', post)
- end
-
- 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)
-
- commit('auth', post)
- end
-
- def capture(money, authorization, options = {})
- post = {}
- add_reference(post, 'settle', money, authorization)
- commit('settle', post)
- end
-
- def refund(money, authorization, options = {})
- post = {}
- add_reference(post, 'credit', money, authorization)
- commit('credit', post)
- end
-
- 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_authentication(post, options)
- post[:transaction_center_id] = options[:transaction_center_id]
- post[:gateway_id] = options[:gateway_id]
- end
-
- 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, money, options)
- post[:order_id] = options[:order_id]
- post[:total] = amount(money)
- end
-
- 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 add_reference(post, action, money, authorization)
- post[:"#{action}_amount1"] = amount(money) if money
- post[:total_number_transactions] = 1
- post[:reference_number1] = authorization
- end
-
- def parse(xml)
- response = {}
-
- doc = Nokogiri::XML(xml)
- doc.root&.xpath('//RESPONSE/FIELDS/FIELD')&.each do |field|
- response[field['KEY']] = field.text
- end
-
- response
- end
-
- def commit(action, parameters)
- response = parse(ssl_post(live_url, post_data(action, parameters)))
-
- Response.new(
- success_from(response),
- message_from(response),
- response,
- authorization: authorization_from(response),
- error_code: error_code_from(response),
- test: test?
- )
- end
-
- def success_from(response)
- (
- (response['status'] == '1') ||
- (response['status1'] == '1')
- )
- 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
-
- def error_code_from(response)
- response['error']
- end
-
- def authorization_from(response)
- response['reference_number'] || response['reference_number1']
- end
-
- def post_data(action, parameters = {})
- parameters[:transaction_center_id] = @options[:transaction_center_id]
- parameters[:gateway_id] = @options[:gateway_id]
-
- parameters[:operation_type] = action
-
- 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!
+ FirstPayXmlGateway.new(options)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/first_pay/first_pay_common.rb b/lib/active_merchant/billing/gateways/first_pay/first_pay_common.rb
new file mode 100644
index 00000000000..f74a6609f5d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/first_pay/first_pay_common.rb
@@ -0,0 +1,15 @@
+module FirstPayCommon
+ def self.included(base)
+ base.supported_countries = ['US']
+ base.default_currency = 'USD'
+ base.money_format = :dollars
+ base.supported_cardtypes = %i[visa master american_express discover]
+
+ base.homepage_url = 'http://1stpaygateway.net/'
+ base.display_name = '1stPayGateway.Net'
+ end
+
+ def supports_scrubbing?
+ true
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/first_pay/first_pay_json.rb b/lib/active_merchant/billing/gateways/first_pay/first_pay_json.rb
new file mode 100644
index 00000000000..464aad139de
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/first_pay/first_pay_json.rb
@@ -0,0 +1,190 @@
+require 'active_merchant/billing/gateways/first_pay/first_pay_common'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class FirstPayJsonGateway < Gateway
+ include FirstPayCommon
+
+ ACTIONS = {
+ purchase: 'Sale',
+ authorize: 'Auth',
+ capture: 'Settle',
+ refund: 'Refund',
+ void: 'Void'
+ }.freeze
+
+ WALLET_TYPES = {
+ apple_pay: 'ApplePay',
+ google_pay: 'GooglePay'
+ }.freeze
+
+ self.test_url = 'https://secure-v.1stPaygateway.net/secure/RestGW/Gateway/Transaction/'
+ self.live_url = 'https://secure.1stPaygateway.net/secure/RestGW/Gateway/Transaction/'
+
+ # Creates a new FirstPayJsonGateway
+ #
+ # The gateway requires two values for connection to be passed
+ # in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * :merchant_key -- FirstPay's merchant_key (REQUIRED)
+ # * :processor_id -- FirstPay's processor_id or processorId (REQUIRED)
+ def initialize(options = {})
+ requires!(options, :merchant_key, :processor_id)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment, options)
+ add_address(post, payment, options)
+
+ commit(:purchase, post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment, options)
+ add_address(post, payment, options)
+
+ commit(:authorize, post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_reference(post, authorization)
+
+ commit(:capture, post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_reference(post, authorization)
+
+ commit(:refund, post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ add_reference(post, authorization)
+
+ commit(:void, post)
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(("processorId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("merchantKey\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cardNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("paymentCryptogram\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv\\?"\s*:\s*\\?)[^,]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_address(post, creditcard, options)
+ if address = options[:billing_address] || options[:address]
+ post[:ownerName] = address[:name]
+ post[:ownerStreet] = address[:address1]
+ post[:ownerCity] = address[:city]
+ post[:ownerState] = address[:state]
+ post[:ownerZip] = address[:zip]
+ post[:ownerCountry] = address[:country]
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:orderId] = options[:order_id]
+ post[:transactionAmount] = amount(money)
+ end
+
+ def add_payment(post, payment, options)
+ post[:cardNumber] = payment.number
+ post[:cardExpMonth] = payment.month
+ post[:cardExpYear] = format(payment.year, :two_digits)
+ post[:cvv] = payment.verification_value
+ post[:recurring] = options[:recurring] if options[:recurring]
+ post[:recurringStartDate] = options[:recurring_start_date] if options[:recurring_start_date]
+ post[:recurringEndDate] = options[:recurring_end_date] if options[:recurring_end_date]
+
+ case payment
+ when NetworkTokenizationCreditCard
+ post[:walletType] = WALLET_TYPES[payment.source]
+ other_fields = post[:otherFields] = {}
+ other_fields[:paymentCryptogram] = payment.payment_cryptogram
+ other_fields[:eciIndicator] = payment.eci || '07'
+ when CreditCard
+ post[:cvv] = payment.verification_value
+ end
+ end
+
+ def add_reference(post, authorization)
+ post[:refNumber] = authorization
+ end
+
+ def commit(action, parameters)
+ response = parse(api_request(base_url + ACTIONS[action], post_data(parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(response),
+ test: test?
+ )
+ end
+
+ def base_url
+ test? ? self.test_url : self.live_url
+ end
+
+ def api_request(url, data)
+ ssl_post(url, data, headers)
+ rescue ResponseError => e
+ e.response.body
+ end
+
+ def parse(data)
+ JSON.parse data
+ end
+
+ def headers
+ { 'Content-Type' => 'application/json' }
+ end
+
+ def format_messages(messages)
+ return unless messages.present?
+
+ messages.map { |message| message['message'] || message }.join('; ')
+ end
+
+ def success_from(response)
+ response['isSuccess']
+ end
+
+ def message_from(response)
+ format_messages(response['errorMessages'] + response['validationFailures']) || response['data']['authResponse']
+ end
+
+ def error_code_from(response)
+ return 'isError' if response['isError']
+
+ return 'validationHasFailed' if response['validationHasFailed']
+ end
+
+ def authorization_from(response)
+ response.dig('data', 'referenceNumber') || ''
+ end
+
+ def post_data(params)
+ params.merge({ processorId: @options[:processor_id], merchantKey: @options[:merchant_key] }).to_json
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/first_pay/first_pay_xml.rb b/lib/active_merchant/billing/gateways/first_pay/first_pay_xml.rb
new file mode 100644
index 00000000000..fb111949920
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/first_pay/first_pay_xml.rb
@@ -0,0 +1,183 @@
+require 'active_merchant/billing/gateways/first_pay/first_pay_common'
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class FirstPayXmlGateway < Gateway
+ include FirstPayCommon
+
+ self.live_url = 'https://secure.goemerchant.com/secure/gateway/xmlgateway.aspx'
+
+ # Creates a new FirstPayXmlGateway
+ #
+ # The gateway requires two values for connection to be passed
+ # in the +options+ hash
+ #
+ # ==== Options
+ #
+ # * :gateway_id -- FirstPay's gateway_id (REQUIRED)
+ # * :transaction_center_id -- FirstPay's transaction_center_id or processorId (REQUIRED)
+ # Otherwise, perform transactions against the production server.
+ def initialize(options = {})
+ requires!(options, :gateway_id, :transaction_center_id)
+ super
+ end
+
+ 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', post)
+ end
+
+ 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)
+
+ commit('auth', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_reference(post, 'settle', money, authorization)
+ commit('settle', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_reference(post, 'credit', money, authorization)
+ commit('credit', post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ add_reference(post, 'void', nil, authorization)
+ commit('void', post)
+ 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_authentication(post, options)
+ post[:transaction_center_id] = options[:transaction_center_id]
+ post[:gateway_id] = options[:gateway_id]
+ end
+
+ 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, money, options)
+ post[:order_id] = options[:order_id]
+ post[:total] = amount(money)
+ end
+
+ 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 add_reference(post, action, money, authorization)
+ post[:"#{action}_amount1"] = amount(money) if money
+ post[:total_number_transactions] = 1
+ post[:reference_number1] = authorization
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.root&.xpath('//RESPONSE/FIELDS/FIELD')&.each do |field|
+ response[field['KEY']] = field.text
+ end
+
+ response
+ end
+
+ def commit(action, parameters)
+ response = parse(ssl_post(live_url, post_data(action, parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(response),
+ test: test?
+ )
+ end
+
+ def success_from(response)
+ (
+ (response['status'] == '1') ||
+ (response['status1'] == '1')
+ )
+ 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
+
+ def error_code_from(response)
+ response['error']
+ end
+
+ def authorization_from(response)
+ response['reference_number'] || response['reference_number1']
+ end
+
+ def post_data(action, parameters = {})
+ parameters[:transaction_center_id] = @options[:transaction_center_id]
+ parameters[:gateway_id] = @options[:gateway_id]
+
+ parameters[:operation_type] = action
+
+ 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
index aa2cb1e39c8..35191eeee72 100755
--- a/lib/active_merchant/billing/gateways/firstdata_e4.rb
+++ b/lib/active_merchant/billing/gateways/firstdata_e4.rb
@@ -354,12 +354,16 @@ def commit(action, request, credit_card = nil)
response = parse_error(e.response)
end
- Response.new(successful?(response), message_from(response), response,
+ 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))
+ error_code: standard_error_code(response)
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
index e6c438916d5..caf783770ac 100644
--- a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
+++ b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
@@ -316,7 +316,7 @@ def add_address(xml, options)
def strip_line_breaks(address)
return unless address.is_a?(Hash)
- Hash[address.map { |k, s| [k, s&.tr("\r\n", ' ')&.strip] }]
+ address.map { |k, s| [k, s&.tr("\r\n", ' ')&.strip] }.to_h
end
def add_invoice(xml, options)
@@ -380,12 +380,16 @@ def commit(action, data, credit_card = nil)
response = parse_error(e.response)
end
- Response.new(successful?(response), message_from(response), response,
+ 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))
+ error_code: standard_error_code(response)
+ )
end
def headers(method, url, request)
diff --git a/lib/active_merchant/billing/gateways/flex_charge.rb b/lib/active_merchant/billing/gateways/flex_charge.rb
new file mode 100644
index 00000000000..b3ff85061b1
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/flex_charge.rb
@@ -0,0 +1,296 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class FlexChargeGateway < Gateway
+ self.test_url = 'https://api-sandbox.flex-charge.com/v1/'
+ self.live_url = 'https://api.flex-charge.com/v1/'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[visa master american_express discover]
+ self.money_format = :cents
+ self.homepage_url = 'https://www.flex-charge.com/'
+ self.display_name = 'FlexCharge'
+
+ ENDPOINTS_MAPPING = {
+ authenticate: 'oauth2/token',
+ purchase: 'evaluate',
+ sync: 'outcome',
+ refund: 'orders/%s/refund',
+ store: 'tokenize',
+ inquire: 'orders/%s'
+ }
+
+ SUCCESS_MESSAGES = %w(APPROVED CHALLENGE SUBMITTED SUCCESS PROCESSING).freeze
+
+ def initialize(options = {})
+ requires!(options, :app_key, :app_secret, :site_id, :mid)
+ super
+ end
+
+ def purchase(money, credit_card, options = {})
+ post = {}
+ address = options[:billing_address] || options[:address]
+ add_merchant_data(post, options)
+ add_base_data(post, options)
+ add_invoice(post, money, credit_card, options)
+ add_mit_data(post, options)
+ add_payment_method(post, credit_card, address, options)
+ add_address(post, credit_card, address)
+ add_customer_data(post, options)
+ add_three_ds(post, options)
+
+ commit(:purchase, post)
+ end
+
+ def refund(money, authorization, options = {})
+ commit(:refund, { amountToRefund: (money.to_f / 100).round(2) }, authorization)
+ end
+
+ def store(credit_card, options = {})
+ address = options[:billing_address] || options[:address] || {}
+ first_name, last_name = address_names(address[:name], credit_card)
+
+ post = {
+ payment_method: {
+ credit_card: {
+ first_name: first_name,
+ last_name: last_name,
+ month: credit_card.month,
+ year: credit_card.year,
+ number: credit_card.number,
+ verification_value: credit_card.verification_value
+ }.compact
+ }
+ }
+ commit(:store, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer )[a-zA-Z0-9._-]+)i, '\1[FILTERED]').
+ gsub(%r(("AppKey\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("AppSecret\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("accessToken\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("mid\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("siteId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("environment\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cardNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("verification_value\\?":\\?")\d+), '\1[FILTERED]')
+ end
+
+ def inquire(authorization, options = {})
+ commit(:inquire, {}, authorization, :get)
+ end
+
+ private
+
+ def add_three_ds(post, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post[:threeDSecure] = {
+ threeDsVersion: three_d_secure[:version],
+ EcommerceIndicator: three_d_secure[:eci],
+ authenticationValue: three_d_secure[:cavv],
+ directoryServerTransactionId: three_d_secure[:ds_transaction_id],
+ xid: three_d_secure[:xid],
+ authenticationValueAlgorithm: three_d_secure[:cavv_algorithm],
+ directoryResponseStatus: three_d_secure[:directory_response_status],
+ authenticationResponseStatus: three_d_secure[:authentication_response_status],
+ enrolled: three_d_secure[:enrolled]
+ }
+ end
+
+ def add_merchant_data(post, options)
+ post[:siteId] = @options[:site_id]
+ post[:mid] = @options[:mid]
+ end
+
+ def add_base_data(post, options)
+ post[:isDeclined] = cast_bool(options[:is_declined])
+ post[:orderId] = options[:order_id]
+ post[:idempotencyKey] = options[:idempotency_key] || options[:order_id]
+ end
+
+ def add_mit_data(post, options)
+ return if options[:is_mit].nil?
+
+ post[:isMIT] = cast_bool(options[:is_mit])
+ post[:isRecurring] = cast_bool(options[:is_recurring])
+ post[:expiryDateUtc] = options[:mit_expiry_date_utc]
+ end
+
+ def add_customer_data(post, options)
+ post[:payer] = { email: options[:email] || 'NA', phone: phone_from(options) }.compact
+ end
+
+ def add_address(post, payment, address)
+ first_name, last_name = address_names(address[:name], payment)
+
+ post[:billingInformation] = {
+ firstName: first_name,
+ lastName: last_name,
+ country: address[:country],
+ phone: address[:phone],
+ countryCode: address[:country],
+ addressLine1: address[:address1],
+ state: address[:state],
+ city: address[:city],
+ zipCode: address[:zip]
+ }.compact
+ end
+
+ def add_invoice(post, money, credit_card, options)
+ post[:transaction] = {
+ id: options[:order_id],
+ dynamicDescriptor: options[:description],
+ timestamp: Time.now.utc.iso8601,
+ timezoneUtcOffset: options[:timezone_utc_offset],
+ amount: money,
+ currency: (options[:currency] || currency(money)),
+ responseCode: options[:response_code],
+ responseCodeSource: options[:response_code_source] || '',
+ avsResultCode: options[:avs_result_code],
+ cvvResultCode: options[:cvv_result_code],
+ cavvResultCode: options[:cavv_result_code],
+ cardNotPresent: credit_card.is_a?(String) ? false : credit_card.verification_value.blank?
+ }.compact
+ end
+
+ def add_payment_method(post, credit_card, address, options)
+ payment_method = case credit_card
+ when String
+ { Token: true, cardNumber: credit_card }
+ else
+ {
+ holderName: credit_card.name,
+ cardType: 'CREDIT',
+ cardBrand: credit_card.brand&.upcase,
+ cardCountry: address[:country],
+ expirationMonth: credit_card.month,
+ expirationYear: credit_card.year,
+ cardBinNumber: credit_card.number[0..5],
+ cardLast4Digits: credit_card.number[-4..-1],
+ cardNumber: credit_card.number,
+ Token: false
+ }
+ end
+ post[:paymentMethod] = payment_method.compact
+ end
+
+ def address_names(address_name, payment_method)
+ split_names(address_name).tap do |names|
+ names[0] = payment_method&.first_name unless names[0].present?
+ names[1] = payment_method&.last_name unless names[1].present?
+ end
+ end
+
+ def phone_from(options)
+ options[:phone] || options.dig(:billing_address, :phone_number)
+ end
+
+ def access_token_valid?
+ @options[:access_token].present? && @options.fetch(:token_expires, 0) > DateTime.now.strftime('%Q').to_i
+ end
+
+ def fetch_access_token
+ params = { AppKey: @options[:app_key], AppSecret: @options[:app_secret] }
+ response = parse(ssl_post(url(:authenticate), params.to_json, headers))
+
+ @options[:access_token] = response[:accessToken]
+ @options[:token_expires] = response[:expires]
+ @options[:new_credentials] = true
+
+ Response.new(
+ response[:accessToken].present?,
+ message_from(response),
+ response,
+ test: test?,
+ error_code: response[:statusCode]
+ )
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ end
+
+ def url(action, id = nil)
+ "#{test? ? test_url : live_url}#{ENDPOINTS_MAPPING[action] % id}"
+ end
+
+ def headers
+ { 'Content-Type' => 'application/json' }.tap do |headers|
+ headers['Authorization'] = "Bearer #{@options[:access_token]}" if @options[:access_token]
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body).with_indifferent_access
+ rescue JSON::ParserError
+ {
+ errors: body,
+ status: 'Unable to parse JSON response'
+ }.with_indifferent_access
+ end
+
+ def commit(action, post, authorization = nil, method = :post)
+ MultiResponse.run do |r|
+ r.process { fetch_access_token } unless access_token_valid?
+ r.process do
+ api_request(action, post, authorization, method).tap do |response|
+ response.params.merge!(@options.slice(:access_token, :token_expires)) if @options[:new_credentials]
+ end
+ end
+ end
+ end
+
+ def api_request(action, post, authorization = nil, method = :post)
+ response = parse ssl_request(method, url(action, authorization), post.to_json, headers)
+
+ Response.new(
+ success_from(action, response),
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response),
+ test: test?,
+ error_code: error_code_from(action, response)
+ )
+ rescue ResponseError => e
+ response = parse(e.response.body)
+ # if current access_token is invalid then clean it
+ if e.response.code == '401'
+ @options[:access_token] = ''
+ @options[:new_credentials] = true
+ end
+ Response.new(false, message_from(response), response, test: test?)
+ end
+
+ def success_from(action, response)
+ case action
+ when :store then response.dig(:transaction, :payment_method, :token).present?
+ when :inquire then response[:id].present? && SUCCESS_MESSAGES.include?(response[:statusName])
+ else
+ response[:success] && SUCCESS_MESSAGES.include?(response[:status])
+ end
+ end
+
+ def message_from(response)
+ response[:title] || response[:responseMessage] || response[:statusName] || response[:status]
+ end
+
+ def authorization_from(action, response)
+ action == :store ? response.dig(:transaction, :payment_method, :token) : response[:orderId]
+ end
+
+ def error_code_from(action, response)
+ (response[:statusName] || response[:status]) unless success_from(action, response)
+ end
+
+ def cast_bool(value)
+ ![false, 0, '', '0', 'f', 'F', 'false', 'FALSE'].include?(value)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/garanti.rb b/lib/active_merchant/billing/gateways/garanti.rb
index 2bdd1071981..57a78d4104e 100644
--- a/lib/active_merchant/billing/gateways/garanti.rb
+++ b/lib/active_merchant/billing/gateways/garanti.rb
@@ -219,11 +219,13 @@ def commit(money, request)
success = success?(response)
- Response.new(success,
+ Response.new(
+ success,
success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})",
response,
test: test?,
- authorization: response[:order_id])
+ authorization: response[:order_id]
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index c1a44930a29..466b2be2de5 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -2,18 +2,22 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class GlobalCollectGateway < Gateway
class_attribute :preproduction_url
+ class_attribute :ogone_direct_test
+ class_attribute :ogone_direct_live
- self.display_name = 'GlobalCollect'
+ self.display_name = 'Worldline (formerly GlobalCollect)'
self.homepage_url = 'http://www.globalcollect.com/'
self.test_url = 'https://eu.sandbox.api-ingenico.com'
- self.preproduction_url = 'https://world.preprod.api-ingenico.com'
- self.live_url = 'https://world.api-ingenico.com'
+ self.preproduction_url = 'https://api.preprod.connect.worldline-solutions.com'
+ self.live_url = 'https://api.connect.worldline-solutions.com'
+ self.ogone_direct_test = 'https://payment.preprod.direct.worldline-solutions.com'
+ self.ogone_direct_live = 'https://payment.direct.worldline-solutions.com'
self.supported_countries = %w[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 = %i[visa master american_express discover naranja cabal]
+ self.supported_cardtypes = %i[visa master american_express discover naranja cabal tuya]
def initialize(options = {})
requires!(options, :merchant_id, :api_key_id, :secret_api_key)
@@ -36,6 +40,7 @@ def authorize(money, payment, options = {})
add_creator_info(post, options)
add_fraud_fields(post, options)
add_external_cardholder_authentication_data(post, options)
+ add_threeds_exemption_data(post, options)
commit(:post, :authorize, post, options: options)
end
@@ -97,8 +102,8 @@ def scrub(transcript)
'diners_club' => '132',
'cabal' => '135',
'naranja' => '136',
- 'apple_pay': '302',
- 'google_pay': '320'
+ apple_pay: '302',
+ google_pay: '320'
}
def add_order(post, money, options, capture: false)
@@ -114,7 +119,7 @@ def add_order(post, money, options, capture: false)
post['order']['references']['invoiceData'] = {
'invoiceNumber' => options[:invoice]
}
- add_airline_data(post, options)
+ add_airline_data(post, options) unless ogone_direct?
add_lodging_data(post, options)
add_number_of_installments(post, options) if options[:number_of_installments]
end
@@ -134,6 +139,7 @@ def add_airline_data(post, options)
airline_data['isThirdParty'] = options[:airline_data][:is_third_party] if options[:airline_data][:is_third_party]
airline_data['issueDate'] = options[:airline_data][:issue_date] if options[:airline_data][:issue_date]
airline_data['merchantCustomerId'] = options[:airline_data][:merchant_customer_id] if options[:airline_data][:merchant_customer_id]
+ airline_data['agentNumericCode'] = options[:airline_data][:agent_numeric_code] if options[:airline_data][:agent_numeric_code]
airline_data['flightLegs'] = add_flight_legs(airline_options)
airline_data['passengers'] = add_passengers(airline_options)
@@ -248,9 +254,10 @@ def add_creator_info(post, options)
end
def add_amount(post, money, options = {})
+ currency_ogone = 'EUR' if ogone_direct?
post['amountOfMoney'] = {
'amount' => amount(money),
- 'currencyCode' => options[:currency] || currency(money)
+ 'currencyCode' => options[:currency] || currency_ogone || currency(money)
}
end
@@ -262,7 +269,7 @@ def add_payment(post, payment, options)
product_id = options[:payment_product_id] || BRAND_MAP[payment.brand]
specifics_inputs = {
'paymentProductId' => product_id,
- 'skipAuthentication' => 'true', # refers to 3DSecure
+ 'skipAuthentication' => options[:skip_authentication] || 'true', # refers to 3DSecure
'skipFraudService' => 'true',
'authorizationMode' => pre_authorization
}
@@ -270,7 +277,7 @@ def add_payment(post, payment, options)
if payment.is_a?(NetworkTokenizationCreditCard)
add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
elsif payment.is_a?(CreditCard)
- options[:google_pay_pan_only] ? add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate) : add_credit_card(post, payment, specifics_inputs, expirydate)
+ add_credit_card(post, payment, specifics_inputs, expirydate)
end
end
@@ -286,31 +293,32 @@ def add_credit_card(post, payment, specifics_inputs, expirydate)
end
def add_mobile_credit_card(post, payment, options, specifics_inputs, expirydate)
- specifics_inputs['paymentProductId'] = options[:google_pay_pan_only] ? BRAND_MAP[:google_pay] : BRAND_MAP[payment.source]
+ specifics_inputs['paymentProductId'] = BRAND_MAP[payment.source]
post['mobilePaymentMethodSpecificInput'] = specifics_inputs
- add_decrypted_payment_data(post, payment, options, expirydate)
+
+ if options[:use_encrypted_payment_data]
+ post['mobilePaymentMethodSpecificInput']['encryptedPaymentData'] = payment.payment_data
+ else
+ add_decrypted_payment_data(post, payment, options, expirydate)
+ end
end
def add_decrypted_payment_data(post, payment, options, expirydate)
- if payment.is_a?(NetworkTokenizationCreditCard) && payment.payment_cryptogram
- data = {
- 'cardholderName' => payment.name,
- 'cryptogram' => payment.payment_cryptogram,
- 'eci' => payment.eci,
- 'expiryDate' => expirydate,
- 'dpan' => payment.number
- }
- data['paymentMethod'] = 'TOKENIZED_CARD' if payment.source == :google_pay
- # else case when google payment is an ONLY_PAN, doesn't have cryptogram or eci.
- elsif options[:google_pay_pan_only]
- data = {
- 'cardholderName' => payment.name,
- 'expiryDate' => expirydate,
- 'pan' => payment.number,
- 'paymentMethod' => 'CARD'
- }
- end
- post['mobilePaymentMethodSpecificInput']['decryptedPaymentData'] = data if data
+ data_type = payment.source == :apple_pay ? 'decrypted' : 'encrypted'
+ data = case payment.source
+ when :apple_pay
+ {
+ 'cardholderName' => payment.name,
+ 'cryptogram' => payment.payment_cryptogram,
+ 'eci' => payment.eci,
+ 'expiryDate' => expirydate,
+ 'dpan' => payment.number
+ }
+ when :google_pay
+ payment.payment_data
+ end
+
+ post['mobilePaymentMethodSpecificInput']["#{data_type}PaymentData"] = data if data
end
def add_customer_data(post, options, payment = nil)
@@ -321,8 +329,8 @@ def add_customer_data(post, options, payment = nil)
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]
+ if address = options[:billing_address] || options[:address] && (address[:phone])
+ post['order']['customer']['contactDetails']['phoneNumber'] = address[:phone]
end
end
@@ -332,8 +340,8 @@ def add_refund_customer_data(post, options)
'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]
+ if address = options[:billing_address] || options[:address] && (address[:phone])
+ post['customer']['contactDetails']['phoneNumber'] = address[:phone]
end
end
end
@@ -342,7 +350,8 @@ def add_address(post, creditcard, options)
shipping_address = options[:shipping_address]
if billing_address = options[:billing_address] || options[:address]
post['order']['customer']['billingAddress'] = {
- 'street' => truncate(billing_address[:address1], 50),
+ 'street' => truncate(split_address(billing_address[:address1])[1], 50),
+ 'houseNumber' => split_address(billing_address[:address1])[0],
'additionalInfo' => truncate(billing_address[:address2], 50),
'zip' => billing_address[:zip],
'city' => billing_address[:city],
@@ -352,7 +361,8 @@ def add_address(post, creditcard, options)
end
if shipping_address
post['order']['customer']['shippingAddress'] = {
- 'street' => truncate(shipping_address[:address1], 50),
+ 'street' => truncate(split_address(shipping_address[:address1])[1], 50),
+ 'houseNumber' => split_address(shipping_address[:address1])[0],
'additionalInfo' => truncate(shipping_address[:address2], 50),
'zip' => shipping_address[:zip],
'city' => shipping_address[:city],
@@ -369,7 +379,6 @@ def add_address(post, creditcard, options)
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
@@ -377,21 +386,33 @@ def add_fraud_fields(post, options)
def add_external_cardholder_authentication_data(post, options)
return unless threeds_2_options = options[:three_d_secure]
- authentication_data = {}
- authentication_data[:acsTransactionId] = threeds_2_options[:acs_transaction_id] if threeds_2_options[:acs_transaction_id]
- authentication_data[:cavv] = threeds_2_options[:cavv] if threeds_2_options[:cavv]
- authentication_data[:cavvAlgorithm] = threeds_2_options[:cavv_algorithm] if threeds_2_options[:cavv_algorithm]
- authentication_data[:directoryServerTransactionId] = threeds_2_options[:ds_transaction_id] if threeds_2_options[:ds_transaction_id]
- authentication_data[:eci] = threeds_2_options[:eci] if threeds_2_options[:eci]
- authentication_data[:threeDSecureVersion] = threeds_2_options[:version] if threeds_2_options[:version]
- authentication_data[:validationResult] = threeds_2_options[:authentication_response_status] if threeds_2_options[:authentication_response_status]
- authentication_data[:xid] = threeds_2_options[:xid] if threeds_2_options[:xid]
+ authentication_data = {
+ priorThreeDSecureData: { acsTransactionId: threeds_2_options[:acs_transaction_id] }.compact,
+ cavv: threeds_2_options[:cavv],
+ cavvAlgorithm: threeds_2_options[:cavv_algorithm],
+ directoryServerTransactionId: threeds_2_options[:ds_transaction_id],
+ eci: threeds_2_options[:eci],
+ threeDSecureVersion: threeds_2_options[:version] || options[:three_ds_version],
+ validationResult: threeds_2_options[:authentication_response_status],
+ xid: threeds_2_options[:xid],
+ acsTransactionId: threeds_2_options[:acs_transaction_id],
+ flow: threeds_2_options[:flow]
+ }.compact
post['cardPaymentMethodSpecificInput'] ||= {}
post['cardPaymentMethodSpecificInput']['threeDSecure'] ||= {}
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['merchantFraudRate'] = threeds_2_options[:merchant_fraud_rate]
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['exemptionRequest'] = threeds_2_options[:exemption_request]
+ post['cardPaymentMethodSpecificInput']['threeDSecure']['secureCorporatePayment'] = threeds_2_options[:secure_corporate_payment]
post['cardPaymentMethodSpecificInput']['threeDSecure']['externalCardholderAuthenticationData'] = authentication_data unless authentication_data.empty?
end
+ def add_threeds_exemption_data(post, options)
+ return unless options[:three_ds_exemption_type]
+
+ post['cardPaymentMethodSpecificInput']['transactionChannel'] = 'MOTO' if options[:three_ds_exemption_type] == 'moto'
+ end
+
def add_number_of_installments(post, options)
post['order']['additionalInput']['numberOfInstallments'] = options[:number_of_installments] if options[:number_of_installments]
end
@@ -402,17 +423,28 @@ def parse(body)
def url(action, authorization)
return preproduction_url + uri(action, authorization) if @options[:url_override].to_s == 'preproduction'
+ return ogone_direct_url(action, authorization) if ogone_direct?
(test? ? test_url : live_url) + uri(action, authorization)
end
+ def ogone_direct_url(action, authorization)
+ (test? ? ogone_direct_test : ogone_direct_live) + uri(action, authorization)
+ end
+
+ def ogone_direct?
+ @options[:url_override].to_s == 'ogone_direct'
+ end
+
def uri(action, authorization)
- uri = "/v1/#{@options[:merchant_id]}/"
+ version = ogone_direct? ? 'v2' : 'v1'
+ uri = "/#{version}/#{@options[:merchant_id]}/"
case action
when :authorize
uri + 'payments'
when :capture
- uri + "payments/#{authorization}/approve"
+ capture_name = ogone_direct? ? 'capture' : 'approve'
+ uri + "payments/#{authorization}/#{capture_name}"
when :refund
uri + "payments/#{authorization}/refund"
when :void
@@ -423,7 +455,7 @@ def uri(action, authorization)
end
def idempotency_key_for_signature(options)
- "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key] && !ogone_direct?
end
def commit(method, action, post, authorization: nil, options: {})
@@ -461,7 +493,7 @@ def headers(method, action, post, authorization = nil, options = {})
'Date' => date
}
- headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key] && !ogone_direct?
headers
end
@@ -474,13 +506,13 @@ def auth_digest(method, action, post, authorization = nil, options = {})
#{uri(action, authorization)}
REQUEST
data = data.each_line.reject { |line| line.strip == '' }.join
- digest = OpenSSL::Digest.new('sha256')
+ 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))}"
+ "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data)).strip}"
end
def date
- @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
+ @date ||= Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
end
def content_type
@@ -490,8 +522,6 @@ def content_type
def success_from(action, response)
return false if response['errorId'] || response['error_message']
- return %w(CAPTURED CAPTURE_REQUESTED).include?(response.dig('payment', 'status')) if response.dig('payment', 'paymentOutput', 'paymentMethod') == 'mobile'
-
case action
when :authorize
response.dig('payment', 'statusOutput', 'isAuthorized')
diff --git a/lib/active_merchant/billing/gateways/hi_pay.rb b/lib/active_merchant/billing/gateways/hi_pay.rb
new file mode 100644
index 00000000000..30700ebc9f4
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/hi_pay.rb
@@ -0,0 +1,286 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class HiPayGateway < Gateway
+ # to add more check => payment_product_list: https://developer.hipay.com/api-explorer/api-online-payments#/payments/generateHostedPaymentPage
+ PAYMENT_PRODUCT = {
+ 'visa' => 'visa',
+ 'master' => 'mastercard'
+ }
+
+ DEVICE_CHANEL = {
+ app: 1,
+ browser: 2,
+ three_ds_requestor_initiaded: 3
+ }
+
+ self.test_url = 'https://stage-secure-gateway.hipay-tpp.com/rest'
+ self.live_url = 'https://secure-gateway.hipay-tpp.com/rest'
+
+ self.supported_countries = %w[FR]
+ self.default_currency = 'EUR'
+ self.money_format = :dollars
+ self.supported_cardtypes = %i[visa master american_express]
+
+ self.homepage_url = 'https://hipay.com/'
+ self.display_name = 'HiPay'
+
+ def initialize(options = {})
+ requires!(options, :username, :password)
+ @username = options[:username]
+ @password = options[:password]
+ super
+ end
+
+ def purchase(money, payment_method, options = {})
+ authorize(money, payment_method, options.merge({ operation: 'Sale' }))
+ end
+
+ def authorize(money, payment_method, options = {})
+ MultiResponse.run do |r|
+ if payment_method.is_a?(CreditCard)
+ response = r.process { tokenize(payment_method, options) }
+ card_token = response.params['token']
+ elsif payment_method.is_a?(String)
+ _transaction_ref, card_token, payment_product = payment_method.split('|') if payment_method.split('|').size == 3
+ card_token, payment_product = payment_method.split('|') if payment_method.split('|').size == 2
+ end
+
+ payment_product = payment_method.is_a?(CreditCard) ? PAYMENT_PRODUCT[payment_method.brand] : PAYMENT_PRODUCT[payment_product&.downcase]
+
+ post = {
+ payment_product: payment_product,
+ operation: options[:operation] || 'Authorization',
+ cardtoken: card_token
+ }
+ add_address(post, options)
+ add_product_data(post, options)
+ add_invoice(post, money, options)
+ add_3ds(post, options)
+ r.process { commit('order', post) }
+ end
+ end
+
+ def capture(money, authorization, options)
+ reference_operation(money, authorization, options.merge({ operation: 'capture' }))
+ end
+
+ def store(payment_method, options = {})
+ tokenize(payment_method, options.merge({ multiuse: '1' }))
+ end
+
+ def unstore(authorization, options = {})
+ _transaction_ref, card_token, _payment_product = authorization.split('|') if authorization.split('|').size == 3
+ card_token, _payment_product = authorization.split('|') if authorization.split('|').size == 2
+ commit('unstore', { card_token: card_token }, options, :delete)
+ end
+
+ def refund(money, authorization, options)
+ reference_operation(money, authorization, options.merge({ operation: 'refund' }))
+ end
+
+ def void(authorization, options)
+ reference_operation(nil, authorization, options.merge({ operation: 'cancel' }))
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )[\w =]+), '\1[FILTERED]').
+ gsub(%r((card_number=)\w+), '\1[FILTERED]\2').
+ gsub(%r((cvc=)\w+), '\1[FILTERED]\2')
+ end
+
+ private
+
+ def reference_operation(money, authorization, options)
+ post = {}
+ post[:operation] = options[:operation]
+ post[:currency] = (options[:currency] || currency(money))
+ post[:amount] = amount(money) if options[:operation] == 'refund' || options[:operation] == 'capture'
+ commit(options[:operation], post, { transaction_reference: authorization.split('|').first })
+ end
+
+ def add_product_data(post, options)
+ post[:orderid] = options[:order_id] if options[:order_id]
+ post[:description] = options[:description]
+ end
+
+ def add_invoice(post, money, options)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:amount] = amount(money)
+ end
+
+ def add_credit_card(post, credit_card)
+ post[:card_number] = credit_card.number
+ post[:card_expiry_month] = credit_card.month
+ post[:card_expiry_year] = credit_card.year
+ post[:card_holder] = credit_card.name
+ post[:cvc] = credit_card.verification_value
+ end
+
+ def add_address(post, options)
+ return unless billing_address = options[:billing_address]
+
+ post[:streetaddress] = billing_address[:address1] if billing_address[:address1]
+ post[:streetaddress2] = billing_address[:address2] if billing_address[:address2]
+ post[:city] = billing_address[:city] if billing_address[:city]
+ post[:recipient_info] = billing_address[:company] if billing_address[:company]
+ post[:state] = billing_address[:state] if billing_address[:state]
+ post[:country] = billing_address[:country] if billing_address[:country]
+ post[:zipcode] = billing_address[:zip] if billing_address[:zip]
+ post[:country] = billing_address[:country] if billing_address[:country]
+ post[:phone] = billing_address[:phone] if billing_address[:phone]
+ end
+
+ def tokenize(payment_method, options = {})
+ post = {}
+ add_credit_card(post, payment_method)
+ post[:multi_use] = options[:multiuse] ? '1' : '0'
+ post[:generate_request_id] = '0'
+ commit('store', post, options)
+ end
+
+ def add_3ds(post, options)
+ return unless options.has_key?(:execute_threed)
+
+ browser_info_3ds = options[:three_ds_2][:browser_info]
+
+ browser_info_hash = {
+ java_enabled: browser_info_3ds[:java],
+ javascript_enabled: (browser_info_3ds[:javascript] || false),
+ ipaddr: options[:ip],
+ http_accept: '*\\/*',
+ http_user_agent: browser_info_3ds[:user_agent],
+ language: browser_info_3ds[:language],
+ color_depth: browser_info_3ds[:depth],
+ screen_height: browser_info_3ds[:height],
+ screen_width: browser_info_3ds[:width],
+ timezone: browser_info_3ds[:timezone]
+ }
+
+ browser_info_hash['device_fingerprint'] = options[:device_fingerprint] if options[:device_fingerprint]
+ post[:browser_info] = browser_info_hash.to_json
+ post.to_json
+
+ post[:accept_url] = options[:accept_url] if options[:accept_url]
+ post[:decline_url] = options[:decline_url] if options[:decline_url]
+ post[:pending_url] = options[:pending_url] if options[:pending_url]
+ post[:exception_url] = options[:exception_url] if options[:exception_url]
+ post[:cancel_url] = options[:cancel_url] if options[:cancel_url]
+ post[:notify_url] = browser_info_3ds[:notification_url] if browser_info_3ds[:notification_url]
+ post[:authentication_indicator] = DEVICE_CHANEL[options[:three_ds_2][:channel]] || 0
+ end
+
+ def parse(body)
+ return {} if body.blank?
+
+ JSON.parse(body)
+ end
+
+ def commit(action, post, options = {}, method = :post)
+ raw_response = begin
+ ssl_request(method, url(action, options), post_data(post), request_headers)
+ rescue ResponseError => e
+ e.response.body
+ end
+
+ response = parse(raw_response)
+
+ Response.new(
+ success_from(action, response),
+ message_from(action, response),
+ response,
+ authorization: authorization_from(action, response),
+ test: test?,
+ error_code: error_code_from(action, response)
+ )
+ end
+
+ def error_code_from(action, response)
+ (response['code'] || response.dig('reason', 'code')).to_s unless success_from(action, response)
+ end
+
+ def success_from(action, response)
+ case action
+ when 'order'
+ response['state'] == 'completed' || (response['state'] == 'forwarding' && response['status'] == '140')
+ when 'capture'
+ response['status'] == '118' && response['message'] == 'Captured'
+ when 'refund'
+ response['status'] == '124' && response['message'] == 'Refund Requested'
+ when 'cancel'
+ response['status'] == '175' && response['message'] == 'Authorization Cancellation requested'
+ when 'store'
+ response.include? 'token'
+ when 'unstore'
+ response['code'] == '204'
+ else
+ false
+ end
+ end
+
+ def message_from(action, response)
+ response['message']
+ end
+
+ def authorization_from(action, response)
+ authorization_string(response['transactionReference'], response['token'], response['brand'])
+ end
+
+ def authorization_string(*args)
+ args.flatten.compact.reject(&:empty?).join('|')
+ end
+
+ def post_data(params)
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def url(action, options = {})
+ case action
+ when 'store'
+ "#{token_url}/create"
+ when 'unstore'
+ token_url
+ when 'capture', 'refund', 'cancel'
+ endpoint = "maintenance/transaction/#{options[:transaction_reference]}"
+ base_url(endpoint)
+ else
+ base_url(action)
+ end
+ end
+
+ def base_url(endpoint)
+ "#{test? ? test_url : live_url}/v1/#{endpoint}"
+ end
+
+ def token_url
+ "https://#{'stage-' if test?}secure2-vault.hipay-tpp.com/rest/v2/token"
+ end
+
+ def basic_auth
+ Base64.strict_encode64("#{@username}:#{@password}")
+ end
+
+ def request_headers
+ {
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Authorization' => "Basic #{basic_auth}"
+ }
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code after unstore(delete instrument), because the body is nil
+ when 200...300
+ response.body || { code: response.code }.to_json
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/hps.rb b/lib/active_merchant/billing/gateways/hps.rb
index 5bd92b80e02..a4a6370b992 100644
--- a/lib/active_merchant/billing/gateways/hps.rb
+++ b/lib/active_merchant/billing/gateways/hps.rb
@@ -330,7 +330,7 @@ def build_request(action)
} do
xml.SOAP :Body do
xml.hps :PosRequest do
- xml.hps 'Ver1.0'.to_sym do
+ xml.hps :"Ver1.0" do
xml.hps :Header do
xml.hps :SecretAPIKey, @options[:secret_api_key]
xml.hps :DeveloperID, @options[:developer_id] if @options[:developer_id]
diff --git a/lib/active_merchant/billing/gateways/iats_payments.rb b/lib/active_merchant/billing/gateways/iats_payments.rb
index b8e6303f57d..ee758ea55fd 100644
--- a/lib/active_merchant/billing/gateways/iats_payments.rb
+++ b/lib/active_merchant/billing/gateways/iats_payments.rb
@@ -185,8 +185,13 @@ def creditcard_brand(brand)
end
def commit(action, parameters)
- response = parse(ssl_post(url(action), post_data(action, parameters),
- { 'Content-Type' => 'application/soap+xml; charset=utf-8' }))
+ response = parse(
+ ssl_post(
+ url(action),
+ post_data(action, parameters),
+ { 'Content-Type' => 'application/soap+xml; charset=utf-8' }
+ )
+ )
Response.new(
success_from(response),
diff --git a/lib/active_merchant/billing/gateways/inspire.rb b/lib/active_merchant/billing/gateways/inspire.rb
index 61f6f8c4b85..0347d97ec25 100644
--- a/lib/active_merchant/billing/gateways/inspire.rb
+++ b/lib/active_merchant/billing/gateways/inspire.rb
@@ -172,11 +172,14 @@ def commit(action, money, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(response['response'] == '1', message_from(response), response,
+ Response.new(
+ response['response'] == '1',
+ message_from(response), response,
authorization: response['transactionid'],
test: test?,
cvv_result: response['cvvresponse'],
- avs_result: { code: response['avsresponse'] })
+ avs_result: { code: response['avsresponse'] }
+ )
end
def message_from(response)
@@ -196,8 +199,7 @@ 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('&')
end
def determine_funding_source(source)
diff --git a/lib/active_merchant/billing/gateways/instapay.rb b/lib/active_merchant/billing/gateways/instapay.rb
index 4ca11852dd8..76e9de5d556 100644
--- a/lib/active_merchant/billing/gateways/instapay.rb
+++ b/lib/active_merchant/billing/gateways/instapay.rb
@@ -140,10 +140,14 @@ 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])
+ cvv_result: response[:cvv_result]
+ )
end
def post_data(action, parameters = {})
@@ -151,8 +155,7 @@ def post_data(action, parameters = {})
post[:acctid] = @options[:login]
post[:merchantpin] = @options[:password] if @options[:password]
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('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/ipg.rb b/lib/active_merchant/billing/gateways/ipg.rb
index 46052cfd1e7..10b3bfbaaed 100644
--- a/lib/active_merchant/billing/gateways/ipg.rb
+++ b/lib/active_merchant/billing/gateways/ipg.rb
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class IpgGateway < Gateway
self.test_url = 'https://test.ipg-online.com/ipgapi/services'
- self.live_url = 'https://www5.ipg-online.com'
+ self.live_url = 'https://www5.ipg-online.com/ipgapi/services'
self.supported_countries = %w(AR)
self.default_currency = 'ARS'
@@ -18,7 +18,7 @@ class IpgGateway < Gateway
ACTION_REQUEST_ITEMS = %w(vault unstore)
def initialize(options = {})
- requires!(options, :store_id, :user_id, :password, :pem, :pem_password)
+ requires!(options, :user_id, :password, :pem, :pem_password)
@credentials = options
@hosted_data_id = nil
super
@@ -86,8 +86,7 @@ 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')
end
private
@@ -273,7 +272,7 @@ def add_payment(xml, money, payment, options)
xml.tag!('v1:SubTotal', options[:sub_total]) if options[:sub_total]
xml.tag!('v1:ValueAddedTax', options[:value_added_tax]) if options[:value_added_tax]
xml.tag!('v1:DeliveryAmount', options[:delivery_amount]) if options[:delivery_amount]
- xml.tag!('v1:ChargeTotal', money)
+ xml.tag!('v1:ChargeTotal', amount(money))
xml.tag!('v1:Currency', CURRENCY_CODES[options[:currency]])
xml.tag!('v1:numberOfInstallments', options[:number_of_installments]) if options[:number_of_installments]
end
@@ -317,7 +316,10 @@ def build_header
end
def encoded_credentials
- Base64.encode64("WS#{@credentials[:store_id]}._.#{@credentials[:user_id]}:#{@credentials[:password]}").delete("\n")
+ # We remove 'WS' and add it back on the next line because the ipg docs are a little confusing.
+ # Some merchants will likely add it to their user_id and others won't.
+ user_id = @credentials[:user_id].sub(/^WS/, '')
+ Base64.encode64("WS#{user_id}:#{@credentials[:password]}").delete("\n")
end
def envelope_namespaces
@@ -344,6 +346,8 @@ def ipg_action_namespaces
end
def override_store_id(options)
+ raise ArgumentError, 'store_id must be provieded' if @credentials[:store_id].blank? && options[:store_id].blank?
+
@credentials[:store_id] = options[:store_id] if options[:store_id].present?
end
@@ -396,7 +400,7 @@ def parse_element(reply, node)
end
def message_from(response)
- response[:TransactionResult]
+ [response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
end
def authorization_from(action, response)
diff --git a/lib/active_merchant/billing/gateways/iridium.rb b/lib/active_merchant/billing/gateways/iridium.rb
index d2f3beff909..d139643f992 100644
--- a/lib/active_merchant/billing/gateways/iridium.rb
+++ b/lib/active_merchant/billing/gateways/iridium.rb
@@ -376,22 +376,32 @@ def add_merchant_data(xml, options)
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' }))
+ 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'
+ }
+ )
+ )
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
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
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] ])
+ cvv_result: CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ]
+ )
end
def parse(xml)
diff --git a/lib/active_merchant/billing/gateways/itransact.rb b/lib/active_merchant/billing/gateways/itransact.rb
index 2a881e1939b..7ad416dc906 100644
--- a/lib/active_merchant/billing/gateways/itransact.rb
+++ b/lib/active_merchant/billing/gateways/itransact.rb
@@ -387,11 +387,15 @@ def commit(payload)
# the Base64 encoded payload signature!
response = parse(ssl_post(self.live_url, post_data(payload), 'Content-Type' => 'text/xml'))
- Response.new(successful?(response), response[:error_message], response,
+ Response.new(
+ successful?(response),
+ response[:error_message],
+ response,
test: test?,
authorization: response[:xid],
avs_result: { code: response[:avs_response] },
- cvv_result: response[:cvv_response])
+ cvv_result: response[:cvv_response]
+ )
end
def post_data(payload)
diff --git a/lib/active_merchant/billing/gateways/iveri.rb b/lib/active_merchant/billing/gateways/iveri.rb
index 87993b01baf..c2cb8aa141a 100644
--- a/lib/active_merchant/billing/gateways/iveri.rb
+++ b/lib/active_merchant/billing/gateways/iveri.rb
@@ -218,10 +218,10 @@ def parse_element(parsed, node)
end
end
- if !node.elements.empty?
- node.elements.each { |e| parse_element(parsed, e) }
- else
+ if node.elements.empty?
parsed[underscore(node.name)] = node.text
+ else
+ node.elements.each { |e| parse_element(parsed, e) }
end
end
diff --git a/lib/active_merchant/billing/gateways/ixopay.rb b/lib/active_merchant/billing/gateways/ixopay.rb
index db928d445c9..71e299e726e 100644
--- a/lib/active_merchant/billing/gateways/ixopay.rb
+++ b/lib/active_merchant/billing/gateways/ixopay.rb
@@ -286,8 +286,8 @@ def commit(request)
response =
begin
parse(ssl_post(url, request, headers(request)))
- rescue StandardError => error
- parse(error.response.body)
+ rescue StandardError => e
+ parse(e.response.body)
end
Response.new(
diff --git a/lib/active_merchant/billing/gateways/jetpay.rb b/lib/active_merchant/billing/gateways/jetpay.rb
index 94b6d0bb224..c2b28b5968e 100644
--- a/lib/active_merchant/billing/gateways/jetpay.rb
+++ b/lib/active_merchant/billing/gateways/jetpay.rb
@@ -284,13 +284,15 @@ def commit(money, request, token = nil)
response = parse(ssl_post(url, request))
success = success?(response)
- Response.new(success,
+ Response.new(
+ success,
success ? 'APPROVED' : message_from(response),
response,
test: test?,
authorization: authorization_from(response, money, token),
avs_result: { code: response[:avs] },
- cvv_result: response[:cvv2])
+ cvv_result: response[:cvv2]
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/jetpay_v2.rb b/lib/active_merchant/billing/gateways/jetpay_v2.rb
index 19ff95a7e99..b852215f181 100644
--- a/lib/active_merchant/billing/gateways/jetpay_v2.rb
+++ b/lib/active_merchant/billing/gateways/jetpay_v2.rb
@@ -295,14 +295,16 @@ def commit(money, request, token = nil)
response = parse(ssl_post(url, request))
success = success?(response)
- Response.new(success,
+ 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))
+ error_code: success ? nil : error_code_from(response)
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
index 6125ea41c35..6e10b0876a2 100644
--- a/lib/active_merchant/billing/gateways/kushki.rb
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -7,7 +7,7 @@ class KushkiGateway < Gateway
self.test_url = 'https://api-uat.kushkipagos.com/'
self.live_url = 'https://api.kushkipagos.com/'
- self.supported_countries = %w[CL CO EC MX PE]
+ self.supported_countries = %w[BR CL CO EC MX PE]
self.default_currency = 'USD'
self.money_format = :dollars
self.supported_cardtypes = %i[visa master american_express discover diners_club alia]
@@ -20,14 +20,14 @@ def initialize(options = {})
def purchase(amount, payment_method, options = {})
MultiResponse.run() do |r|
r.process { tokenize(amount, payment_method, options) }
- r.process { charge(amount, r.authorization, options) }
+ r.process { charge(amount, r.authorization, options, payment_method) }
end
end
def authorize(amount, payment_method, options = {})
MultiResponse.run() do |r|
r.process { tokenize(amount, payment_method, options) }
- r.process { preauthorize(amount, r.authorization, options) }
+ r.process { preauthorize(amount, r.authorization, options, payment_method) }
end
end
@@ -48,8 +48,9 @@ def refund(amount, authorization, options = {})
post = {}
post[:ticketNumber] = authorization
add_full_response(post, options)
+ add_invoice(action, post, amount, options)
- commit(action, post)
+ commit(action, post, options)
end
def void(authorization, options = {})
@@ -83,11 +84,13 @@ def tokenize(amount, payment_method, options)
add_payment_method(post, payment_method, options)
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
commit(action, post)
end
- def charge(amount, authorization, options)
+ def charge(amount, authorization, options, payment_method = {})
action = 'charge'
post = {}
@@ -96,11 +99,15 @@ def charge(amount, authorization, options)
add_contact_details(post, options[:contact_details]) if options[:contact_details]
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
+ add_three_d_secure(post, payment_method, options)
+ add_product_details(post, options)
commit(action, post)
end
- def preauthorize(amount, authorization, options)
+ def preauthorize(amount, authorization, options, payment_method = {})
action = 'preAuthorization'
post = {}
@@ -108,6 +115,9 @@ def preauthorize(amount, authorization, options)
add_invoice(action, post, amount, options)
add_full_response(post, options)
add_metadata(post, options)
+ add_months(post, options)
+ add_deferred(post, options)
+ add_three_d_secure(post, payment_method, options)
commit(action, post)
end
@@ -127,9 +137,9 @@ def add_invoice(action, post, money, options)
end
def add_amount_defaults(sum, money, options)
- sum[:subtotalIva] = amount(money).to_f
+ sum[:subtotalIva] = 0
sum[:iva] = 0
- sum[:subtotalIva0] = 0
+ sum[:subtotalIva0] = amount(money).to_f
sum[:ice] = 0 if sum[:currency] != 'COP'
end
@@ -177,13 +187,81 @@ def add_contact_details(post, contact_details_options)
end
def add_full_response(post, options)
- post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
+ # this is the only currently accepted value for this field, previously it was 'true'
+ post[:fullResponse] = 'v2' unless options[:full_response] == 'false' || options[:full_response].blank?
end
def add_metadata(post, options)
post[:metadata] = options[:metadata] if options[:metadata]
end
+ def add_months(post, options)
+ post[:months] = options[:months] if options[:months]
+ end
+
+ def add_deferred(post, options)
+ return unless options[:deferred_grace_months] && options[:deferred_credit_type] && options[:deferred_months]
+
+ post[:deferred] = {
+ graceMonths: options[:deferred_grace_months],
+ creditType: options[:deferred_credit_type],
+ months: options[:deferred_months]
+ }
+ end
+
+ def add_product_details(post, options)
+ return unless options[:product_details]
+
+ product_items_array = []
+ options[:product_details].each do |item|
+ product_items_obj = {}
+
+ product_items_obj[:id] = item[:id] if item[:id]
+ product_items_obj[:title] = item[:title] if item[:title]
+ product_items_obj[:price] = item[:price].to_i if item[:price]
+ product_items_obj[:sku] = item[:sku] if item[:sku]
+ product_items_obj[:quantity] = item[:quantity].to_i if item[:quantity]
+
+ product_items_array << product_items_obj
+ end
+
+ product_items = {
+ product: product_items_array
+ }
+
+ post[:productDetails] = product_items
+ end
+
+ def add_three_d_secure(post, payment_method, options)
+ three_d_secure = options[:three_d_secure]
+ return unless three_d_secure.present?
+
+ post[:threeDomainSecure] = {
+ eci: three_d_secure[:eci],
+ specificationVersion: three_d_secure[:version]
+ }
+
+ if payment_method.brand == 'master'
+ post[:threeDomainSecure][:acceptRisk] = three_d_secure[:eci] == '00'
+ post[:threeDomainSecure][:ucaf] = three_d_secure[:cavv]
+ post[:threeDomainSecure][:directoryServerTransactionID] = three_d_secure[:ds_transaction_id]
+ case three_d_secure[:eci]
+ when '07'
+ post[:threeDomainSecure][:collectionIndicator] = '0'
+ when '06'
+ post[:threeDomainSecure][:collectionIndicator] = '1'
+ else
+ post[:threeDomainSecure][:collectionIndicator] = '2'
+ end
+ elsif payment_method.brand == 'visa'
+ post[:threeDomainSecure][:acceptRisk] = three_d_secure[:eci] == '07'
+ post[:threeDomainSecure][:cavv] = three_d_secure[:cavv]
+ post[:threeDomainSecure][:xid] = three_d_secure[:xid] if three_d_secure[:xid].present?
+ else
+ raise ArgumentError.new 'Kushki supports 3ds2 authentication for only Visa and Mastercard brands.'
+ end
+ end
+
ENDPOINT = {
'tokenize' => 'tokens',
'charge' => 'charges',
@@ -193,10 +271,10 @@ def add_metadata(post, options)
'capture' => 'capture'
}
- def commit(action, params)
+ def commit(action, params, options = {})
response =
begin
- parse(ssl_invoke(action, params))
+ parse(ssl_invoke(action, params, options))
rescue ResponseError => e
parse(e.response.body)
end
@@ -213,9 +291,11 @@ def commit(action, params)
)
end
- def ssl_invoke(action, params)
+ def ssl_invoke(action, params, options)
if %w[void refund].include?(action)
- ssl_request(:delete, url(action, params), nil, headers(action))
+ # removes ticketNumber from request for partial refunds because gateway will reject if included in request body
+ data = options[:partial_refund] == true ? post_data(params.except(:ticketNumber)) : nil
+ ssl_request(:delete, url(action, params), data, headers(action))
else
ssl_post(url(action, params), post_data(params), headers(action))
end
diff --git a/lib/active_merchant/billing/gateways/linkpoint.rb b/lib/active_merchant/billing/gateways/linkpoint.rb
index dc5bf64e56f..11c1b95dc3d 100644
--- a/lib/active_merchant/billing/gateways/linkpoint.rb
+++ b/lib/active_merchant/billing/gateways/linkpoint.rb
@@ -263,11 +263,15 @@ def scrub(transcript)
def commit(money, creditcard, options = {})
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(money, creditcard, options)))
- Response.new(successful?(response), response[:message], response,
+ 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])
+ cvv_result: response[:avs].to_s[3, 1]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb
index be6e34e9562..49b4eed8e44 100644
--- a/lib/active_merchant/billing/gateways/litle.rb
+++ b/lib/active_merchant/billing/gateways/litle.rb
@@ -5,9 +5,10 @@ module Billing #:nodoc:
class LitleGateway < Gateway
SCHEMA_VERSION = '9.14'
- class_attribute :postlive_url
+ class_attribute :postlive_url, :prelive_url
self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online'
+ self.prelive_url = 'https://payments.vantivprelive.com/vap/communicator/online'
self.postlive_url = 'https://payments.vantivpostlive.com/vap/communicator/online'
self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online'
@@ -110,14 +111,15 @@ def add_line_item_information_for_level_three_visa(doc, payment_method, level_3_
doc.lineItemData do
level_3_data[:line_items].each do |line_item|
doc.itemSequenceNumber(line_item[:item_sequence_number]) if line_item[:item_sequence_number]
- doc.commodityCode(line_item[:commodity_code]) if line_item[:commodity_code]
doc.itemDescription(line_item[:item_description]) if line_item[:item_description]
doc.productCode(line_item[:product_code]) if line_item[:product_code]
doc.quantity(line_item[:quantity]) if line_item[:quantity]
doc.unitOfMeasure(line_item[:unit_of_measure]) if line_item[:unit_of_measure]
doc.taxAmount(line_item[:tax_amount]) if line_item[:tax_amount]
- doc.itemDiscountAmount(line_item[:discount_per_line_item]) unless line_item[:discount_per_line_item] < 0
- doc.unitCost(line_item[:unit_cost]) unless line_item[:unit_cost] < 0
+ doc.lineItemTotal(line_item[:line_item_total]) if line_item[:line_item_total]
+ doc.itemDiscountAmount(line_item[:discount_per_line_item].to_i) unless line_item[:discount_per_line_item].to_i < 0
+ doc.commodityCode(line_item[:commodity_code]) if line_item[:commodity_code]
+ doc.unitCost(line_item[:unit_cost].to_i) unless line_item[:unit_cost].to_i < 0
doc.detailTax do
doc.taxIncludedInTotal(line_item[:tax_included_in_total]) if line_item[:tax_included_in_total]
doc.taxAmount(line_item[:tax_amount]) if line_item[:tax_amount]
@@ -309,7 +311,15 @@ def add_authentication(doc)
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)
+
+ if options.dig(:stored_credential, :initial_transaction) == false
+ # orderSource needs to be added at the top of doc and
+ # processingType near the end
+ source_for_subsequent_stored_credential_txns(doc, options)
+ else
+ add_order_source(doc, payment_method, options)
+ end
+
add_billing_address(doc, payment_method, options)
add_shipping_address(doc, payment_method, options)
add_payment_method(doc, payment_method, options)
@@ -381,8 +391,9 @@ def add_payment_method(doc, payment_method, options)
doc.track(payment_method.track_data)
end
elsif check?(payment_method)
+ account_type = payment_method.account_type || payment_method.account_holder_type
doc.echeck do
- doc.accType(payment_method.account_type.capitalize)
+ doc.accType(account_type&.capitalize)
doc.accNum(payment_method.account_number)
doc.routingNum(payment_method.routing_number)
doc.checkNum(payment_method.number) if payment_method.number
@@ -408,16 +419,17 @@ def add_payment_method(doc, payment_method, options)
end
def add_stored_credential_params(doc, options = {})
- return unless options[:stored_credential]
+ return unless stored_credential = options[:stored_credential]
- if options[:stored_credential][:initial_transaction]
- add_stored_credential_params_initial(doc, options)
+ if stored_credential[:initial_transaction]
+ add_stored_credential_for_initial_txn(doc, options)
else
- add_stored_credential_params_used(doc, options)
+ doc.processingType("#{stored_credential[:initiator]}InitiatedCOF") if stored_credential[:reason_type] == 'unscheduled'
+ doc.originalNetworkTransactionId(stored_credential[:network_transaction_id]) if stored_credential[:initiator] == 'merchant'
end
end
- def add_stored_credential_params_initial(doc, options)
+ def add_stored_credential_for_initial_txn(doc, options)
case options[:stored_credential][:reason_type]
when 'unscheduled'
doc.processingType('initialCOF')
@@ -428,15 +440,15 @@ def add_stored_credential_params_initial(doc, options)
end
end
- 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
+ def source_for_subsequent_stored_credential_txns(doc, options)
+ case options[:stored_credential][:reason_type]
+ when 'unscheduled'
+ doc.orderSource('ecommerce')
+ when 'installment'
+ doc.orderSource('installment')
+ when 'recurring'
+ doc.orderSource('recurring')
end
- doc.originalNetworkTransactionId(options[:stored_credential][:network_transaction_id])
end
def add_billing_address(doc, payment_method, options)
@@ -478,8 +490,7 @@ def add_address(doc, address)
end
def add_order_source(doc, payment_method, options)
- order_source = order_source(options)
- if order_source
+ if order_source = options[:order_source]
doc.orderSource(order_source)
elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
doc.orderSource('applepay')
@@ -492,31 +503,6 @@ def add_order_source(doc, payment_method, options)
end
end
- 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
-
- order_source
- end
-
def add_pos(doc, payment_method)
return unless payment_method.respond_to?(:track_data) && payment_method.track_data.present?
@@ -571,15 +557,26 @@ def commit(kind, request, money = nil)
cvv_result: parsed[:fraudResult_cardValidationResult]
}
- Response.new(success_from(kind, parsed), parsed[:message], parsed, options)
+ Response.new(success_from(kind, parsed), message_from(parsed), parsed, options)
end
def success_from(kind, parsed)
- return (parsed[:response] == '000') unless kind == :registerToken
+ return %w(000 001 010 141 142).any?(parsed[:response]) unless kind == :registerToken
%w(000 801 802).include?(parsed[:response])
end
+ def message_from(parsed)
+ case parsed[:response]
+ when '010'
+ return "#{parsed[:message]}: The authorized amount is less than the requested amount."
+ when '001'
+ return "#{parsed[:message]}: This is sent to acknowledge that the submitted transaction has been received."
+ else
+ parsed[:message]
+ end
+ end
+
def authorization_from(kind, parsed, money)
kind == :registerToken ? parsed[:litleToken] : "#{parsed[:litleTxnId]};#{kind};#{money}"
end
@@ -606,16 +603,15 @@ def root_attributes
}
end
- def build_xml_request
+ def build_xml_request(&block)
builder = Nokogiri::XML::Builder.new
- builder.__send__('litleOnlineRequest', root_attributes) do |doc|
- yield(doc)
- end
+ builder.__send__('litleOnlineRequest', root_attributes, &block)
builder.doc.root.to_xml
end
def url
return postlive_url if @options[:url_override].to_s == 'postlive'
+ return prelive_url if @options[:url_override].to_s == 'prelive'
test? ? test_url : live_url
end
diff --git a/lib/active_merchant/billing/gateways/mastercard.rb b/lib/active_merchant/billing/gateways/mastercard.rb
index be18bda516f..894ad2be3f5 100644
--- a/lib/active_merchant/billing/gateways/mastercard.rb
+++ b/lib/active_merchant/billing/gateways/mastercard.rb
@@ -10,7 +10,7 @@ def purchase(amount, payment_method, options = {})
if options[:pay_mode]
post = new_post
add_invoice(post, amount, options)
- add_reference(post, *new_authorization)
+ add_reference(post, *new_authorization(options))
add_payment_method(post, payment_method)
add_customer_data(post, payment_method, options)
add_3dsecure_id(post, options)
@@ -27,7 +27,7 @@ def purchase(amount, payment_method, options = {})
def authorize(amount, payment_method, options = {})
post = new_post
add_invoice(post, amount, options)
- add_reference(post, *new_authorization)
+ add_reference(post, *new_authorization(options))
add_payment_method(post, payment_method)
add_customer_data(post, payment_method, options)
add_3dsecure_id(post, options)
@@ -218,14 +218,7 @@ def build_url(orderid, transactionid)
def base_url
if test?
- case @options[:region]
- when 'asia_pacific'
- test_ap_url
- when 'europe'
- test_eu_url
- when 'north_america', nil
- test_na_url
- end
+ test_url
else
case @options[:region]
when 'asia_pacific'
@@ -271,9 +264,9 @@ def split_authorization(authorization)
authorization.split('|')
end
- def new_authorization
+ def new_authorization(options)
# Must be unique within a merchant id.
- orderid = SecureRandom.uuid
+ orderid = options[:order_id] || SecureRandom.uuid
# Must be unique within an order id.
transactionid = '1'
diff --git a/lib/active_merchant/billing/gateways/maxipago.rb b/lib/active_merchant/billing/gateways/maxipago.rb
index c22ceaeaf01..57c9bca9763 100644
--- a/lib/active_merchant/billing/gateways/maxipago.rb
+++ b/lib/active_merchant/billing/gateways/maxipago.rb
@@ -79,8 +79,8 @@ def scrub(transcript)
private
- def commit(action)
- request = build_xml_request(action) { |doc| yield(doc) }
+ def commit(action, &block)
+ request = build_xml_request(action, &block)
response = parse(ssl_post(url, request, 'Content-Type' => 'text/xml'))
Response.new(
diff --git a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
index 6c6b2bc85c8..86cdb5c8bc6 100644
--- a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
+++ b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
@@ -180,7 +180,7 @@ def parse(body)
def commit(action, money, parameters)
url = test? ? self.test_url : self.live_url
- parameters[:transaction_amount] = amount(money) if money unless action == 'V'
+ parameters[:transaction_amount] = amount(money) if !(action == 'V') && money
response =
begin
@@ -189,11 +189,15 @@ def commit(action, money, parameters)
{ 'error_code' => '404', 'auth_response_text' => e.to_s }
end
- Response.new(success_from(response), message_from(response), response,
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
authorization: authorization_from(response),
test: test?,
cvv_result: response['cvv2_result'],
- avs_result: { code: response['avs_result'] })
+ avs_result: { code: response['avs_result'] }
+ )
end
def authorization_from(response)
@@ -220,8 +224,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('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/merchant_ware.rb b/lib/active_merchant/billing/gateways/merchant_ware.rb
index 1781a301968..cc934aa6fd7 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware.rb
@@ -290,19 +290,26 @@ def url(v4 = false)
def commit(action, request, v4 = false)
begin
- data = ssl_post(url(v4), request,
+ data = ssl_post(
+ url(v4),
+ request,
'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => soap_action(action, v4))
+ 'SOAPAction' => soap_action(action, v4)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response)
end
- Response.new(response[:success], response[:message], response,
+ Response.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response['AVSResponse'] },
- cvv_result: response['CVResponse'])
+ cvv_result: response['CVResponse']
+ )
end
def authorization_from(response)
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 36635dd0f2a..9657a5631ed 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
@@ -261,19 +261,26 @@ def url
def commit(action, request)
begin
- data = ssl_post(url, request,
+ data = ssl_post(
+ url,
+ request,
'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => soap_action(action))
+ 'SOAPAction' => soap_action(action)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response, action)
end
- Response.new(response[:success], response[:message], response,
+ Response.new(
+ response[:success],
+ response[:message],
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response['AvsResponse'] },
- cvv_result: response['CvResponse'])
+ cvv_result: response['CvResponse']
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb
index 337b18be643..18be04361f1 100644
--- a/lib/active_merchant/billing/gateways/merchant_warrior.rb
+++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb
@@ -32,6 +32,7 @@ def authorize(money, payment_method, options = {})
add_payment_method(post, payment_method)
add_recurring_flag(post, options)
add_soft_descriptors(post, options)
+ add_three_ds(post, options)
commit('processAuth', post)
end
@@ -43,6 +44,7 @@ def purchase(money, payment_method, options = {})
add_payment_method(post, payment_method)
add_recurring_flag(post, options)
add_soft_descriptors(post, options)
+ add_three_ds(post, options)
commit('processCard', post)
end
@@ -184,6 +186,18 @@ def void_verification_hash(transaction_id)
)
end
+ def add_three_ds(post, options)
+ return unless three_d_secure = options[:three_d_secure]
+
+ post.merge!({
+ threeDSEci: three_d_secure[:eci],
+ threeDSXid: three_d_secure[:xid] || three_d_secure[:ds_transaction_id],
+ threeDSCavv: three_d_secure[:cavv],
+ threeDSStatus: three_d_secure[:authentication_response_status],
+ threeDSV2Version: three_d_secure[:version]
+ }.compact)
+ end
+
def parse(body)
xml = REXML::Document.new(body)
diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb
index fcbca035e0c..5648640d734 100644
--- a/lib/active_merchant/billing/gateways/mercury.rb
+++ b/lib/active_merchant/billing/gateways/mercury.rb
@@ -302,12 +302,16 @@ def commit(action, request)
success = SUCCESS_CODES.include?(response[:cmd_status])
message = success ? 'Success' : message_from(response)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs_result] },
cvv_result: response[:cvv_result],
- error_code: success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]])
+ error_code: success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/metrics_global.rb b/lib/active_merchant/billing/gateways/metrics_global.rb
index 5aac5401d4b..cb3ea1ad26a 100644
--- a/lib/active_merchant/billing/gateways/metrics_global.rb
+++ b/lib/active_merchant/billing/gateways/metrics_global.rb
@@ -175,12 +175,16 @@ def commit(action, money, parameters)
# (TESTMODE) Successful Sale
test_mode = test? || message =~ /TESTMODE/
- Response.new(success?(response), message, response,
+ 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])
+ cvv_result: response[:card_code]
+ )
end
def success?(response)
@@ -194,7 +198,7 @@ def fraud_review?(response)
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],
@@ -202,7 +206,6 @@ def parse(body)
transaction_id: fields[TRANSACTION_ID],
card_code: fields[CARD_CODE_RESPONSE_CODE]
}
- results
end
def post_data(action, parameters = {})
@@ -218,8 +221,7 @@ def post_data(action, parameters = {})
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('&')
end
def add_invoice(post, options)
diff --git a/lib/active_merchant/billing/gateways/migs.rb b/lib/active_merchant/billing/gateways/migs.rb
index 50a254e49a3..f50b3d29de5 100644
--- a/lib/active_merchant/billing/gateways/migs.rb
+++ b/lib/active_merchant/billing/gateways/migs.rb
@@ -281,12 +281,16 @@ def response_object(response)
cvv_result_code = response[:CSCResultCode]
cvv_result_code = 'P' if cvv_result_code == 'Unsupported'
- Response.new(success?(response), response[:Message], response,
+ Response.new(
+ success?(response),
+ response[:Message],
+ response,
test: test?,
authorization: response[:TransactionNo],
fraud_review: fraud_review?(response),
avs_result: { code: avs_response_code },
- cvv_result: cvv_result_code)
+ cvv_result: cvv_result_code
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/migs/migs_codes.rb b/lib/active_merchant/billing/gateways/migs/migs_codes.rb
index 32929ed8abe..dff303a5b81 100644
--- a/lib/active_merchant/billing/gateways/migs/migs_codes.rb
+++ b/lib/active_merchant/billing/gateways/migs/migs_codes.rb
@@ -71,6 +71,7 @@ module MigsCodes
class CreditCardType
attr_accessor :am_code, :migs_code, :migs_long_code, :name
+
def initialize(am_code, migs_code, migs_long_code, name)
@am_code = am_code
@migs_code = migs_code
diff --git a/lib/active_merchant/billing/gateways/mit.rb b/lib/active_merchant/billing/gateways/mit.rb
index 74b7bf6beab..2e2d6a97013 100644
--- a/lib/active_merchant/billing/gateways/mit.rb
+++ b/lib/active_merchant/billing/gateways/mit.rb
@@ -7,6 +7,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class MitGateway < Gateway
self.live_url = 'https://wpy.mitec.com.mx/ModuloUtilWS/activeCDP.htm'
+ self.test_url = 'https://scqa.mitec.com.mx/ModuloUtilWS/activeCDP.htm'
self.supported_countries = ['MX']
self.default_currency = 'MXN'
@@ -41,7 +42,7 @@ def decrypt(val, keyinhex)
# original message
full_data = unpacked[0].bytes.slice(16, unpacked[0].bytes.length)
# Creates the engine
- engine = OpenSSL::Cipher::AES128.new(:CBC)
+ engine = OpenSSL::Cipher.new('aes-128-cbc')
# Set engine as decrypt mode
engine.decrypt
# Converts the key from hex to bytes
@@ -54,7 +55,7 @@ def decrypt(val, keyinhex)
def encrypt(val, keyinhex)
# Creates the engine motor
- engine = OpenSSL::Cipher::AES128.new(:CBC)
+ engine = OpenSSL::Cipher.new('aes-128-cbc')
# Set engine as encrypt mode
engine.encrypt
# Converts the key from hex to bytes
@@ -93,8 +94,7 @@ def authorize(money, payment, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('sale', json_post)
end
@@ -114,8 +114,7 @@ def capture(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('capture', json_post)
end
@@ -136,8 +135,7 @@ def refund(money, authorization, options = {})
post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
final_post = '' + post_to_json_encrypt + '' + @options[:user] + ''
- json_post = {}
- json_post[:payload] = final_post
+ json_post = final_post
commit('refund', json_post)
end
@@ -145,10 +143,18 @@ def supports_scrubbing?
true
end
+ def extract_mit_responses_from_transcript(transcript)
+ groups = transcript.scan(/reading \d+ bytes(.*?)read \d+ bytes/m)
+ groups.map do |group|
+ group.first.scan(/-> "(.*?)"/).flatten.map(&:strip).join('')
+ end
+ end
+
def scrub(transcript)
ret_transcript = transcript
auth_origin = ret_transcript[/(.*?)<\/authorization>/, 1]
unless auth_origin.nil?
+ auth_origin = auth_origin.gsub('\n', '')
auth_decrypted = decrypt(auth_origin, @options[:key_session])
auth_json = JSON.parse(auth_decrypted)
auth_json['card'] = '[FILTERED]'
@@ -162,6 +168,7 @@ def scrub(transcript)
cap_origin = ret_transcript[/(.*?)<\/capture>/, 1]
unless cap_origin.nil?
+ cap_origin = cap_origin.gsub('\n', '')
cap_decrypted = decrypt(cap_origin, @options[:key_session])
cap_json = JSON.parse(cap_decrypted)
cap_json['apikey'] = '[FILTERED]'
@@ -173,6 +180,7 @@ def scrub(transcript)
ref_origin = ret_transcript[/(.*?)<\/refund>/, 1]
unless ref_origin.nil?
+ ref_origin = ref_origin.gsub('\n', '')
ref_decrypted = decrypt(ref_origin, @options[:key_session])
ref_json = JSON.parse(ref_decrypted)
ref_json['apikey'] = '[FILTERED]'
@@ -182,15 +190,10 @@ def scrub(transcript)
ret_transcript = ret_transcript.gsub(/(.*?)<\/refund>/, ref_tagged)
end
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
- loop do
- break if res_origin.nil?
-
- resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
- resp_decrypted = decrypt(resp_origin, @options[:key_session])
- ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
- ret_transcript = ret_transcript.sub('reading ', 'response: ')
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
+ groups = extract_mit_responses_from_transcript(transcript)
+ groups.each do |group|
+ group_decrypted = decrypt(group, @options[:key_session])
+ ret_transcript = ret_transcript.gsub('Conn close', "\n" + group_decrypted + "\nConn close")
end
ret_transcript
@@ -218,10 +221,12 @@ def add_payment(post, payment)
post[:name_client] = [payment.first_name, payment.last_name].join(' ')
end
+ def url
+ test? ? test_url : live_url
+ end
+
def commit(action, parameters)
- json_str = JSON.generate(parameters)
- cleaned_str = json_str.gsub('\n', '')
- raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
+ raw_response = ssl_post(url, parameters, { 'Content-type' => 'text/plain' })
response = JSON.parse(decrypt(raw_response, @options[:key_session]))
Response.new(
diff --git a/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
index d5d6f9b2a27..9a8f760ec8e 100644
--- a/lib/active_merchant/billing/gateways/modern_payments_cim.rb
+++ b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
@@ -111,13 +111,12 @@ def add_credit_card(post, credit_card)
end
def build_request(action, params)
+ envelope_obj = { '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' }
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!
- xml.tag! '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' } do
-
+ xml.tag! 'env:Envelope', envelope_obj do
xml.tag! 'env:Body' do
xml.tag! action, { 'xmlns' => xmlns(action) } do
xml.tag! 'clientId', @options[:login]
@@ -146,15 +145,24 @@ def url(action)
end
def commit(action, params)
- data = ssl_post(url(action), build_request(action, params),
- { 'Content-Type' => 'text/xml; charset=utf-8',
- 'SOAPAction' => "#{xmlns(action)}#{action}" })
+ data = ssl_post(
+ url(action),
+ build_request(action, params),
+ {
+ '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,
+ Response.new(
+ successful?(action, response),
+ message_from(action, response),
+ response,
test: test?,
authorization: authorization_from(action, response),
- avs_result: { code: response[:avs_code] })
+ avs_result: { code: response[:avs_code] }
+ )
end
def authorization_from(action, response)
diff --git a/lib/active_merchant/billing/gateways/monei.rb b/lib/active_merchant/billing/gateways/monei.rb
index c7e1a5b9b5a..f9bb672fcd6 100755
--- a/lib/active_merchant/billing/gateways/monei.rb
+++ b/lib/active_merchant/billing/gateways/monei.rb
@@ -337,7 +337,7 @@ def commit(request, action, options)
endpoint = translate_action_endpoint(action, options)
headers = {
'Content-Type': 'application/json;charset=UTF-8',
- 'Authorization': @options[:api_key],
+ Authorization: @options[:api_key],
'User-Agent': 'MONEI/Shopify/0.1.0'
}
diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb
index 1491e1315fa..2df428bb68c 100644
--- a/lib/active_merchant/billing/gateways/moneris.rb
+++ b/lib/active_merchant/billing/gateways/moneris.rb
@@ -9,6 +9,8 @@ module Billing #:nodoc:
# Response Values", available at Moneris' {eSelect Plus Documentation
# Centre}[https://www3.moneris.com/connect/en/documents/index.html].
class MonerisGateway < Gateway
+ WALLETS = %w(APP GPP)
+
self.test_url = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
self.live_url = 'https://www3.moneris.com/gateway2/servlet/MpgRequest'
@@ -47,11 +49,12 @@ def authorize(money, creditcard_or_datakey, options = {})
post = {}
add_payment_source(post, creditcard_or_datakey, options)
post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
+ post[:order_id] = format_order_id(post[:wallet_indicator], options[:order_id])
post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
add_external_mpi_fields(post, options)
add_stored_credential(post, options)
+ add_cust_id(post, options)
action = if post[:cavv] || options[:three_d_secure]
'cavv_preauth'
elsif post[:data_key].blank?
@@ -71,11 +74,12 @@ def purchase(money, creditcard_or_datakey, options = {})
post = {}
add_payment_source(post, creditcard_or_datakey, options)
post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
+ post[:order_id] = format_order_id(post[:wallet_indicator], options[:order_id])
post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
add_external_mpi_fields(post, options)
add_stored_credential(post, options)
+ add_cust_id(post, options)
action = if post[:cavv] || options[:three_d_secure]
'cavv_purchase'
elsif post[:data_key].blank?
@@ -246,6 +250,10 @@ def add_cof(post, options)
post[:payment_information] = options[:payment_information] if options[:payment_information]
end
+ def add_cust_id(post, options)
+ post[:cust_id] = options[:cust_id] if options[:cust_id]
+ end
+
def add_stored_credential(post, options)
add_cof(post, options)
# if any of :issuer_id, :payment_information, or :payment_indicator is not passed,
@@ -438,6 +446,18 @@ def wallet_indicator(token_source)
}[token_source]
end
+ def format_order_id(wallet_indicator_code, order_id = nil)
+ # Truncate (max 100 characters) order id for
+ # google pay and apple pay (specific wallets / token sources)
+ return truncate_order_id(order_id) if WALLETS.include?(wallet_indicator_code)
+
+ order_id
+ end
+
+ def truncate_order_id(order_id = nil)
+ order_id.present? ? order_id[0, 100] : SecureRandom.alphanumeric(100)
+ end
+
def message_from(message)
return 'Unspecified error' if message.blank?
@@ -453,8 +473,8 @@ def actions
'indrefund' => %i[order_id cust_id amount pan expdate crypt_type],
'completion' => %i[order_id comp_amount txn_number crypt_type],
'purchasecorrection' => %i[order_id txn_number crypt_type],
- 'cavv_preauth' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator],
- 'cavv_purchase' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator],
+ 'cavv_preauth' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator threeds_version threeds_server_trans_id ds_trans_id],
+ 'cavv_purchase' => %i[order_id cust_id amount pan expdate cavv crypt_type wallet_indicator threeds_version threeds_server_trans_id ds_trans_id],
'card_verification' => %i[order_id cust_id pan expdate crypt_type avs_info cvd_info cof_info],
'transact' => %i[order_id cust_id amount pan expdate crypt_type],
'Batchcloseall' => [],
diff --git a/lib/active_merchant/billing/gateways/money_movers.rb b/lib/active_merchant/billing/gateways/money_movers.rb
index 0a16b4ea5dc..2ba6c35cae7 100644
--- a/lib/active_merchant/billing/gateways/money_movers.rb
+++ b/lib/active_merchant/billing/gateways/money_movers.rb
@@ -113,11 +113,15 @@ def commit(action, money, parameters)
response = parse(data)
message = message_from(response)
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response['transactionid'],
avs_result: { code: response['avsresponse'] },
- cvv_result: response['cvvresponse'])
+ cvv_result: response['cvvresponse']
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb
index 723063781a0..7c81d230bcf 100644
--- a/lib/active_merchant/billing/gateways/nab_transact.rb
+++ b/lib/active_merchant/billing/gateways/nab_transact.rb
@@ -233,16 +233,24 @@ def build_unstore_request(identification, options)
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,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
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,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/net_registry.rb b/lib/active_merchant/billing/gateways/net_registry.rb
index bb3c11d63f3..7052581b7ba 100644
--- a/lib/active_merchant/billing/gateways/net_registry.rb
+++ b/lib/active_merchant/billing/gateways/net_registry.rb
@@ -144,8 +144,12 @@ def commit(action, params)
# get gateway response
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))
+ Response.new(
+ response['status'] == 'approved',
+ message_from(response),
+ response,
+ authorization: authorization_from(response, action)
+ )
end
def post_data(action, params)
diff --git a/lib/active_merchant/billing/gateways/netbanx.rb b/lib/active_merchant/billing/gateways/netbanx.rb
index b50053583df..fd0a493a30e 100644
--- a/lib/active_merchant/billing/gateways/netbanx.rb
+++ b/lib/active_merchant/billing/gateways/netbanx.rb
@@ -233,7 +233,7 @@ def map_address(address)
end
def map_3ds(three_d_secure_options)
- mapped = {
+ {
eci: three_d_secure_options[:eci],
cavv: three_d_secure_options[:cavv],
xid: three_d_secure_options[:xid],
@@ -241,8 +241,6 @@ def map_3ds(three_d_secure_options)
threeDSecureVersion: three_d_secure_options[:version],
directoryServerTransactionId: three_d_secure_options[:ds_transaction_id]
}
-
- mapped
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/netbilling.rb b/lib/active_merchant/billing/gateways/netbilling.rb
index 08f0e57a398..dfa479a495d 100644
--- a/lib/active_merchant/billing/gateways/netbilling.rb
+++ b/lib/active_merchant/billing/gateways/netbilling.rb
@@ -193,11 +193,15 @@ def parse(body)
def commit(action, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test_response?(response),
authorization: response[:trans_id],
avs_result: { code: response[:avs_code] },
- cvv_result: response[:cvv2_code])
+ cvv_result: response[:cvv2_code]
+ )
rescue ActiveMerchant::ResponseError => e
raise unless e.response.code =~ /^[67]\d\d$/
diff --git a/lib/active_merchant/billing/gateways/network_merchants.rb b/lib/active_merchant/billing/gateways/network_merchants.rb
index 4c1c2ef16d5..a72f133dce0 100644
--- a/lib/active_merchant/billing/gateways/network_merchants.rb
+++ b/lib/active_merchant/billing/gateways/network_merchants.rb
@@ -200,11 +200,15 @@ def commit(action, parameters)
authorization = authorization_from(success, parameters, raw)
- Response.new(success, raw['responsetext'], raw,
+ Response.new(
+ success,
+ raw['responsetext'],
+ raw,
test: test?,
authorization: authorization,
avs_result: { code: raw['avsresponse'] },
- cvv_result: raw['cvvresponse'])
+ cvv_result: raw['cvvresponse']
+ )
end
def build_request(action, parameters)
diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb
index 0268ae4bb23..bb53c39eedf 100644
--- a/lib/active_merchant/billing/gateways/nmi.rb
+++ b/lib/active_merchant/billing/gateways/nmi.rb
@@ -8,7 +8,7 @@ class NmiGateway < Gateway
self.test_url = self.live_url = 'https://secure.networkmerchants.com/api/transact.php'
self.default_currency = 'USD'
self.money_format = :dollars
- self.supported_countries = ['US']
+ self.supported_countries = %w[US CA]
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'http://nmi.com/'
self.display_name = 'NMI'
@@ -149,6 +149,7 @@ def add_level3_fields(post, options)
def add_invoice(post, money, options)
post[:amount] = amount(money)
+ post[:surcharge] = options[:surcharge] if options[:surcharge]
post[:orderid] = options[:order_id]
post[:orderdescription] = options[:description]
post[:currency] = options[:currency] || currency(money)
@@ -211,7 +212,8 @@ def add_stored_credential(post, options)
else
post[:stored_credential_indicator] = 'used'
# should only send :initial_transaction_id if it is a MIT
- post[:initial_transaction_id] = stored_credential[:network_transaction_id] if post[:initiated_by] == 'merchant'
+ ntid = options[:network_transaction_id] || stored_credential[:network_transaction_id]
+ post[:initial_transaction_id] = ntid if post[:initiated_by] == 'merchant'
end
end
@@ -232,6 +234,9 @@ def add_customer_data(post, options)
end
if (shipping_address = options[:shipping_address])
+ first_name, last_name = split_names(shipping_address[:name])
+ post[:shipping_firstname] = first_name if first_name
+ post[:shipping_lastname] = last_name if last_name
post[:shipping_company] = shipping_address[:company]
post[:shipping_address1] = shipping_address[:address1]
post[:shipping_address2] = shipping_address[:address2]
@@ -240,6 +245,7 @@ def add_customer_data(post, options)
post[:shipping_country] = shipping_address[:country]
post[:shipping_zip] = shipping_address[:zip]
post[:shipping_phone] = shipping_address[:phone]
+ post[:shipping_email] = options[:shipping_email] if options[:shipping_email]
end
if (descriptor = options[:descriptors])
@@ -329,8 +335,7 @@ def split_authorization(authorization)
end
def headers
- headers = { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
- headers
+ { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
end
def post_data(action, params)
@@ -342,7 +347,7 @@ def url
end
def parse(body)
- Hash[CGI::parse(body).map { |k, v| [k.intern, v.first] }]
+ CGI::parse(body).map { |k, v| [k.intern, v.first] }.to_h
end
def success_from(response)
diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb
index 8c0fc958222..03b969d8df8 100644
--- a/lib/active_merchant/billing/gateways/ogone.rb
+++ b/lib/active_merchant/billing/gateways/ogone.rb
@@ -263,7 +263,7 @@ 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]
+ add_d3d(post, options) if options[:d3d] || three_d_secure(options)
if payment_source.is_a?(String)
add_alias(post, payment_source, options[:alias_operation])
add_eci(post, options[:eci] || '9')
@@ -460,7 +460,7 @@ def calculate_signature(signed_parameters, algorithm, secret)
raise "Unknown signature algorithm #{algorithm}"
end
- filtered_params = signed_parameters.compact
+ filtered_params = signed_parameters.reject { |_k, v| v.nil? || v == '' }
sha_encryptor.hexdigest(
filtered_params.sort_by { |k, _v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('')
).upcase
@@ -494,6 +494,10 @@ def convert_attributes_to_hash(rexml_attributes)
end
response_hash
end
+
+ def three_d_secure(options)
+ options[:three_d_secure] ? options[:three_d_secure][:required] : false
+ end
end
class OgoneResponse < Response
diff --git a/lib/active_merchant/billing/gateways/openpay.rb b/lib/active_merchant/billing/gateways/openpay.rb
index 7b648f75e94..e655a5b599a 100644
--- a/lib/active_merchant/billing/gateways/openpay.rb
+++ b/lib/active_merchant/billing/gateways/openpay.rb
@@ -201,11 +201,13 @@ def commit(method, resource, parameters, options = {})
response = http_request(method, resource, parameters, options)
success = !error?(response)
- Response.new(success,
+ Response.new(
+ success,
(success ? response['error_code'] : response['description']),
response,
test: test?,
- authorization: response['id'])
+ authorization: response['id']
+ )
end
def http_request(method, resource, parameters = {}, options = {})
diff --git a/lib/active_merchant/billing/gateways/opp.rb b/lib/active_merchant/billing/gateways/opp.rb
index 8dc6acf83ff..38d1f3b3e18 100644
--- a/lib/active_merchant/billing/gateways/opp.rb
+++ b/lib/active_merchant/billing/gateways/opp.rb
@@ -125,8 +125,7 @@ def initialize(options = {})
def purchase(money, payment, options = {})
# debit
options[:registrationId] = payment if payment.is_a?(String)
- execute_dbpa(options[:risk_workflow] ? 'PA.CP' : 'DB',
- money, payment, options)
+ execute_dbpa(options[:risk_workflow] ? 'PA.CP' : 'DB', money, payment, options)
end
def authorize(money, payment, options = {})
diff --git a/lib/active_merchant/billing/gateways/optimal_payment.rb b/lib/active_merchant/billing/gateways/optimal_payment.rb
index d6832282535..8ec37ff413d 100644
--- a/lib/active_merchant/billing/gateways/optimal_payment.rb
+++ b/lib/active_merchant/billing/gateways/optimal_payment.rb
@@ -121,11 +121,15 @@ def commit(action, money, post)
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),
+ Response.new(
+ successful?(response),
+ message_from(response),
+ hash_from_xml(response),
test: test?,
authorization: authorization_from(response),
avs_result: { code: avs_result_from(response) },
- cvv_result: cvv_result_from(response))
+ cvv_result: cvv_result_from(response)
+ )
end
# The upstream is picky and so we can't use CGI.escape like we want to
diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb
index c43a9f51b2e..872fda576c7 100644
--- a/lib/active_merchant/billing/gateways/orbital.rb
+++ b/lib/active_merchant/billing/gateways/orbital.rb
@@ -30,7 +30,7 @@ module Billing #:nodoc:
class OrbitalGateway < Gateway
include Empty
- API_VERSION = '9.0'
+ API_VERSION = '9.5'
POST_HEADERS = {
'MIME-Version' => '1.1',
@@ -193,6 +193,10 @@ class OrbitalGateway < Gateway
'checking' => 'C'
}
+ # safetech token flags
+ GET_TOKEN = 'GT'
+ USE_TOKEN = 'UT'
+
def initialize(options = {})
requires!(options, :merchant_id)
requires!(options, :login, :password) unless options[:ip_authentication]
@@ -253,6 +257,11 @@ def credit(money, payment_method, options = {})
commit(order, :refund, options[:retry_logic], options[:trace_number])
end
+ # Orbital save a payment method if the TokenTxnType is 'GT', that's why we use this as the default value for store
+ def store(creditcard, options = {})
+ authorize(0, creditcard, options.merge({ token_txn_type: GET_TOKEN }))
+ end
+
def void(authorization, options = {}, deprecated = {})
if !options.kind_of?(Hash)
ActiveMerchant.deprecated('Calling the void method with an amount parameter is deprecated and will be removed in a future version.')
@@ -487,6 +496,35 @@ def add_line_items(xml, options = {})
end
end
+ def add_level2_card_and_more_tax(xml, options = {})
+ if (level2 = options[:level_2_data])
+ xml.tag! :PCardRequestorName, byte_limit(level2[:requestor_name], 38) if level2[:requestor_name]
+ xml.tag! :PCardLocalTaxRate, byte_limit(level2[:local_tax_rate], 5) if level2[:local_tax_rate]
+ # Canadian Merchants Only
+ xml.tag! :PCardNationalTax, byte_limit(level2[:national_tax], 12) if level2[:national_tax]
+ xml.tag! :PCardPstTaxRegNumber, byte_limit(level2[:pst_tax_reg_number], 15) if level2[:pst_tax_reg_number]
+ xml.tag! :PCardCustomerVatRegNumber, byte_limit(level2[:customer_vat_reg_number], 13) if level2[:customer_vat_reg_number]
+ # Canadian Merchants Only
+ xml.tag! :PCardMerchantVatRegNumber, byte_limit(level2[:merchant_vat_reg_number], 20) if level2[:merchant_vat_reg_number]
+ xml.tag! :PCardTotalTaxAmount, byte_limit(level2[:total_tax_amount], 12) if level2[:total_tax_amount]
+ end
+ end
+
+ def add_card_commodity_code(xml, options = {})
+ if (level2 = options[:level_2_data]) && (level2[:commodity_code])
+ xml.tag! :PCardCommodityCode, byte_limit(level2[:commodity_code], 4)
+ end
+ end
+
+ def add_level3_vat_fields(xml, options = {})
+ if (level3 = options[:level_3_data])
+ xml.tag! :PC3InvoiceDiscTreatment, byte_limit(level3[:invoice_discount_treatment], 1) if level3[:invoice_discount_treatment]
+ xml.tag! :PC3TaxTreatment, byte_limit(level3[:tax_treatment], 1) if level3[:tax_treatment]
+ xml.tag! :PC3UniqueVATInvoiceRefNum, byte_limit(level3[:unique_vat_invoice_ref], 15) if level3[:unique_vat_invoice_ref]
+ xml.tag! :PC3ShipVATRate, byte_limit(level3[:ship_vat_rate], 4) if level3[:ship_vat_rate]
+ end
+ end
+
#=====ADDRESS FIELDS=====
def add_address(xml, payment_source, options)
@@ -536,9 +574,9 @@ def add_customer_address(xml, options)
end
def billing_name(payment_source, options)
- if payment_source&.name.present?
+ if !payment_source.is_a?(String) && payment_source&.name.present?
payment_source.name[0..29]
- elsif options[:billing_address][:name].present?
+ elsif options[:billing_address] && options[:billing_address][:name].present?
options[:billing_address][:name][0..29]
end
end
@@ -555,10 +593,17 @@ def get_address(options)
options[:billing_address] || options[:address]
end
+ def add_safetech_token_data(xml, payment_source, options)
+ payment_source_token = split_authorization(payment_source).values_at(2).first
+ xml.tag! :CardBrand, options[:card_brand]
+ xml.tag! :AccountNum, payment_source_token
+ end
+
#=====PAYMENT SOURCE FIELDS=====
# Payment can be done through either Credit Card or Electronic Check
def add_payment_source(xml, payment_source, options = {})
+ add_safetech_token_data(xml, payment_source, options) if payment_source.is_a?(String)
payment_source.is_a?(Check) ? add_echeck(xml, payment_source, options) : add_credit_card(xml, payment_source, options)
end
@@ -576,10 +621,10 @@ def add_echeck(xml, check, options = {})
end
def add_credit_card(xml, credit_card, options)
- xml.tag! :AccountNum, credit_card.number if credit_card
- xml.tag! :Exp, expiry_date(credit_card) if credit_card
+ xml.tag! :AccountNum, credit_card.number if credit_card.is_a?(CreditCard)
+ xml.tag! :Exp, expiry_date(credit_card) if credit_card.is_a?(CreditCard)
add_currency_fields(xml, options[:currency])
- add_verification_value(xml, credit_card) if credit_card
+ add_verification_value(xml, credit_card) if credit_card.is_a?(CreditCard)
end
def add_verification_value(xml, credit_card)
@@ -594,7 +639,7 @@ def add_verification_value(xml, credit_card)
# Null-fill this attribute OR
# Do not submit the attribute at all.
# - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
- xml.tag! :CardSecValInd, '1' if %w(visa discover diners_club).include?(credit_card.brand) && bin == '000001'
+ xml.tag! :CardSecValInd, '1' if %w(visa discover diners_club).include?(credit_card.brand)
xml.tag! :CardSecVal, credit_card.verification_value
end
@@ -886,13 +931,19 @@ def commit(order, message_type, retry_logic = nil, trace_number = nil)
request.call(remote_url(:secondary))
end
- Response.new(success?(response, message_type), message_from(response), response,
+ authorization = authorization_string(response[:tx_ref_num], response[:order_id], response[:safetech_token], response[:card_brand])
+
+ Response.new(
+ success?(response, message_type),
+ message_from(response),
+ response,
{
- authorization: authorization_string(response[:tx_ref_num], response[:order_id]),
+ authorization: authorization,
test: self.test?,
avs_result: OrbitalGateway::AVSResult.new(response[:avs_resp_code]),
cvv_result: OrbitalGateway::CVVResult.new(response[:cvv2_resp_code])
- })
+ }
+ )
end
def remote_url(url = :primary)
@@ -981,35 +1032,34 @@ def build_new_order_xml(action, money, payment_source, parameters = {})
xml.tag! :OrderID, format_order_id(parameters[:order_id])
xml.tag! :Amount, amount(money)
xml.tag! :Comments, parameters[:comments] if parameters[:comments]
-
add_level2_tax(xml, parameters)
add_level2_advice_addendum(xml, parameters)
-
add_aav(xml, payment_source, three_d_secure)
# CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here.
-
add_soft_descriptors(xml, parameters[:soft_descriptors])
- add_payment_action_ind(xml, parameters[:payment_action_ind])
- add_dpanind(xml, payment_source, parameters[:industry_type])
- add_aevv(xml, payment_source, three_d_secure)
- add_digital_token_cryptogram(xml, payment_source, three_d_secure)
-
- xml.tag! :ECPSameDayInd, parameters[:same_day] if parameters[:same_day] && payment_source.is_a?(Check)
-
set_recurring_ind(xml, parameters)
# Append Transaction Reference Number at the end for Refund transactions
add_tx_ref_num(xml, parameters[:authorization]) if action == REFUND && payment_source.nil?
-
add_level2_purchase(xml, parameters)
add_level3_purchase(xml, parameters)
add_level3_tax(xml, parameters)
add_line_items(xml, parameters) if parameters[:line_items]
- add_ecp_details(xml, payment_source, parameters) if payment_source.is_a?(Check)
add_card_indicators(xml, parameters)
+ add_payment_action_ind(xml, parameters[:payment_action_ind])
+ add_dpanind(xml, payment_source, parameters[:industry_type])
+ add_aevv(xml, payment_source, three_d_secure)
+ add_level2_card_and_more_tax(xml, parameters)
+ add_digital_token_cryptogram(xml, payment_source, three_d_secure)
+ xml.tag! :ECPSameDayInd, parameters[:same_day] if parameters[:same_day] && payment_source.is_a?(Check)
+ add_ecp_details(xml, payment_source, parameters) if payment_source.is_a?(Check)
+
add_stored_credentials(xml, parameters)
add_pymt_brand_program_code(xml, payment_source, three_d_secure)
+ xml.tag! :TokenTxnType, parameters[:token_txn_type] if parameters[:token_txn_type]
add_mastercard_fields(xml, payment_source, parameters, three_d_secure) if mastercard?(payment_source)
+ add_card_commodity_code(xml, parameters)
+ add_level3_vat_fields(xml, parameters)
end
end
xml.target!
@@ -1031,6 +1081,9 @@ def build_mark_for_capture_xml(money, authorization, parameters = {})
add_level3_purchase(xml, parameters)
add_level3_tax(xml, parameters)
add_line_items(xml, parameters) if parameters[:line_items]
+ add_level2_card_and_more_tax(xml, parameters)
+ add_card_commodity_code(xml, parameters)
+ add_level3_vat_fields(xml, parameters)
end
end
xml.target!
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 5b7f4517a69..bedc1b942b2 100644
--- a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb
+++ b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb
@@ -33,9 +33,7 @@ def validate
errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format'] if !empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2)
%i[merchant_email merchant_url].each do |attr|
- unless self.send(attr).blank?
- errors << [attr, 'is required to be 13 bytes or less'] if self.send(attr).bytesize > 13
- end
+ errors << [attr, 'is required to be 13 bytes or less'] if !self.send(attr).blank? && (self.send(attr).bytesize > 13)
end
errors_hash(errors)
diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb
index 2cb24b07107..67b4a9a1ddb 100644
--- a/lib/active_merchant/billing/gateways/pac_net_raven.rb
+++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb
@@ -121,7 +121,10 @@ def commit(action, money, parameters)
test_mode = test? || message =~ /TESTMODE/
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test_mode,
authorization: response['TrackingNumber'],
fraud_review: fraud_review?(response),
@@ -129,7 +132,8 @@ def commit(action, money, parameters)
postal_match: AVS_POSTAL_CODES[response['AVSPostalResponseCode']],
street_match: AVS_ADDRESS_CODES[response['AVSAddressResponseCode']]
},
- cvv_result: CVV2_CODES[response['CVV2ResponseCode']])
+ cvv_result: CVV2_CODES[response['CVV2ResponseCode']]
+ )
end
def url(action)
@@ -178,8 +182,7 @@ def post_data(action, parameters = {})
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
+ post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def timestamp
diff --git a/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
index 7d97405afac..a39c79f33b9 100644
--- a/lib/active_merchant/billing/gateways/pay_gate_xml.rb
+++ b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
@@ -264,9 +264,13 @@ def parse(action, body)
def commit(action, request, authorization = nil)
response = parse(action, ssl_post(self.live_url, request))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization || response[:tid])
+ authorization: authorization || response[:tid]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/pay_hub.rb b/lib/active_merchant/billing/gateways/pay_hub.rb
index 6f66cefaac1..b6dbb6cb695 100644
--- a/lib/active_merchant/billing/gateways/pay_hub.rb
+++ b/lib/active_merchant/billing/gateways/pay_hub.rb
@@ -182,14 +182,16 @@ def commit(post)
response = json_error(raw_response)
end
- Response.new(success,
+ 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'])
+ authorization: response['TRANSACTION_ID']
+ )
end
def response_error(raw_response)
diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb
index 0017dfe99ae..ce8d66fe60b 100644
--- a/lib/active_merchant/billing/gateways/pay_junction.rb
+++ b/lib/active_merchant/billing/gateways/pay_junction.rb
@@ -337,9 +337,13 @@ def commit(action, parameters)
response = parse(ssl_post(url, post_data(action, parameters)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response[:transaction_id] || parameters[:transaction_id])
+ authorization: response[:transaction_id] || parameters[:transaction_id]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/pay_secure.rb b/lib/active_merchant/billing/gateways/pay_secure.rb
index db7de9bdb4b..3337fa24cf3 100644
--- a/lib/active_merchant/billing/gateways/pay_secure.rb
+++ b/lib/active_merchant/billing/gateways/pay_secure.rb
@@ -68,9 +68,13 @@ def add_credit_card(post, credit_card)
def commit(action, money, parameters)
response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test_response?(response),
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb
index 53203d51f96..d4a159d1d87 100644
--- a/lib/active_merchant/billing/gateways/pay_trace.rb
+++ b/lib/active_merchant/billing/gateways/pay_trace.rb
@@ -1,7 +1,7 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PayTraceGateway < Gateway
- self.test_url = 'https://api.paytrace.com'
+ self.test_url = 'https://api.sandbox.paytrace.com'
self.live_url = 'https://api.paytrace.com'
self.supported_countries = ['US']
@@ -46,7 +46,7 @@ class PayTraceGateway < Gateway
def initialize(options = {})
requires!(options, :username, :password, :integrator_id)
super
- acquire_access_token
+ acquire_access_token unless options[:access_token]
end
def purchase(money, payment_or_customer_id, options = {})
@@ -169,28 +169,35 @@ def scrub(transcript)
transcript.
gsub(%r((Authorization: Bearer )[a-zA-Z0-9:_]+), '\1[FILTERED]').
gsub(%r(("credit_card\\?":{\\?"number\\?":\\?")\d+), '\1[FILTERED]').
- gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r(("csc\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("username\\?":\\?")\w+@+\w+.+\w+), '\1[FILTERED]').
+ gsub(%r(("username\\?":\\?")\w+), '\1[FILTERED]').
gsub(%r(("password\\?":\\?")\w+), '\1[FILTERED]').
gsub(%r(("integrator_id\\?":\\?")\w+), '\1[FILTERED]')
end
def acquire_access_token
post = {}
+ base_url = (test? ? test_url : live_url)
post[:grant_type] = 'password'
post[:username] = @options[:username]
post[:password] = @options[:password]
data = post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
- url = live_url + '/oauth/token'
+ url = base_url + '/oauth/token'
oauth_headers = {
'Accept' => '*/*',
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
- json_response = JSON.parse(response)
+ json_response = parse(response)
- @options[:access_token] = json_response['access_token'] if json_response['access_token']
- response
+ if json_response.include?('error')
+ oauth_response = Response.new(false, json_response['error_description'])
+ raise OAuthResponseError.new(oauth_response)
+ else
+ @options[:access_token] = json_response['access_token'] if json_response['access_token']
+ response
+ end
end
private
@@ -237,11 +244,11 @@ def visa_or_mastercard?(options)
end
def customer_id?(payment_or_customer_id)
- payment_or_customer_id.class == String
+ payment_or_customer_id.instance_of?(String)
end
def string_literal_to_boolean(value)
- return value unless value.class == String
+ return value unless value.instance_of?(String)
if value.casecmp('true').zero?
true
@@ -258,15 +265,16 @@ def add_customer_data(post, options)
end
def add_address(post, creditcard, options)
- return unless options[:billing_address] || options[:address]
-
- address = options[:billing_address] || options[:address]
post[:billing_address] = {}
+
+ if (address = options[:billing_address] || options[:address])
+ post[:billing_address][:street_address] = address[:address1]
+ post[:billing_address][:city] = address[:city]
+ post[:billing_address][:state] = address[:state]
+ post[:billing_address][:zip] = address[:zip]
+ end
+
post[:billing_address][:name] = creditcard.name
- post[:billing_address][:street_address] = address[:address1]
- post[:billing_address][:city] = address[:city]
- post[:billing_address][:state] = address[:state]
- post[:billing_address][:zip] = address[:zip]
end
def add_amount(post, money, options)
@@ -283,6 +291,7 @@ def add_payment(post, payment)
post[:credit_card][:number] = payment.number
post[:credit_card][:expiration_month] = payment.month
post[:credit_card][:expiration_year] = payment.year
+ post[:csc] = payment.verification_value
end
end
@@ -373,6 +382,12 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
+ handle_final_response(action, response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def handle_final_response(action, response)
success = success_from(response)
Response.new(
@@ -385,8 +400,6 @@ def commit(action, parameters)
test: test?,
error_code: success ? nil : error_code_from(response)
)
- rescue JSON::ParserError
- unparsable_response(raw_response)
end
def unparsable_response(raw_response)
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
index b27eb7edac2..50248894423 100644
--- a/lib/active_merchant/billing/gateways/payeezy.rb
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -35,12 +35,15 @@ def purchase(amount, payment_method, options = {})
add_invoice(params, options)
add_reversal_id(params, options)
+ add_customer_ref(params, options)
+ add_reference_3(params, options)
add_payment_method(params, payment_method, options)
add_address(params, options)
add_amount(params, amount, options)
add_soft_descriptors(params, options)
add_level2_data(params, options)
add_stored_credentials(params, options)
+ add_external_three_ds(params, payment_method, options)
commit(params, options)
end
@@ -50,12 +53,15 @@ def authorize(amount, payment_method, options = {})
add_invoice(params, options)
add_reversal_id(params, options)
+ add_customer_ref(params, options)
+ add_reference_3(params, options)
add_payment_method(params, payment_method, options)
add_address(params, options)
add_amount(params, amount, options)
add_soft_descriptors(params, options)
add_level2_data(params, options)
add_stored_credentials(params, options)
+ add_external_three_ds(params, payment_method, options)
commit(params, options)
end
@@ -140,6 +146,26 @@ def scrub(transcript)
private
+ def add_external_three_ds(params, payment_method, options)
+ return unless three_ds = options[:three_d_secure]
+
+ params[:'3DS'] = {
+ program_protocol: three_ds[:version][0],
+ directory_server_transaction_id: three_ds[:ds_transaction_id],
+ cardholder_name: payment_method.name,
+ card_number: payment_method.number,
+ exp_date: format_exp_date(payment_method.month, payment_method.year),
+ cvv: payment_method.verification_value,
+ xid: three_ds[:acs_transaction_id],
+ cavv: three_ds[:cavv],
+ wallet_provider_id: 'NO_WALLET',
+ type: 'D'
+ }.compact
+
+ params[:eci_indicator] = options[:three_d_secure][:eci]
+ params[:method] = '3DS'
+ end
+
def add_invoice(params, options)
params[:merchant_ref] = options[:order_id]
end
@@ -148,6 +174,14 @@ def add_reversal_id(params, options)
params[:reversal_id] = options[:reversal_id] if options[:reversal_id]
end
+ def add_customer_ref(params, options)
+ params[:customer_ref] = options[:customer_ref] if options[:customer_ref]
+ end
+
+ def add_reference_3(params, options)
+ params[:reference_3] = options[:reference_3] if options[:reference_3]
+ end
+
def amount_from_authorization(authorization)
authorization.split('|').last.to_i
end
@@ -155,6 +189,8 @@ def amount_from_authorization(authorization)
def add_authorization_info(params, authorization, options = {})
transaction_id, transaction_tag, method, = authorization.split('|')
params[:method] = method == 'token' ? 'credit_card' : method
+ # If the previous transaction `method` value was 3DS, it needs to be set to `credit_card` on follow up transactions
+ params[:method] = 'credit_card' if method == '3DS'
if options[:reversal_id]
params[:reversal_id] = options[:reversal_id]
@@ -168,7 +204,7 @@ 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[:credit_card] = add_card_data(payment_method, options)
params[:auth] = 'false'
end
@@ -184,7 +220,7 @@ def add_payment_method(params, payment_method, options)
elsif payment_method.is_a? NetworkTokenizationCreditCard
add_network_tokenization(params, payment_method, options)
else
- add_creditcard(params, payment_method)
+ add_creditcard(params, payment_method, options)
end
end
@@ -195,7 +231,7 @@ def add_echeck(params, echeck, options)
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[:accountholder_name] = name_from_payment_method(echeck)
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]
@@ -221,17 +257,17 @@ def add_token(params, payment_method, options)
params[:token] = token
end
- def add_creditcard(params, creditcard)
- credit_card = add_card_data(creditcard)
+ def add_creditcard(params, creditcard, options)
+ credit_card = add_card_data(creditcard, options)
params[:method] = 'credit_card'
params[:credit_card] = credit_card
end
- def add_card_data(payment_method)
+ def add_card_data(payment_method, options = {})
card = {}
card[:type] = CREDIT_CARD_BRAND[payment_method.brand]
- card[:cardholder_name] = payment_method.name
+ card[:cardholder_name] = name_from_payment_method(payment_method) || name_from_address(options)
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?
@@ -241,17 +277,17 @@ def add_card_data(payment_method)
def add_network_tokenization(params, payment_method, options)
nt_card = {}
nt_card[:type] = 'D'
- nt_card[:cardholder_name] = payment_method.first_name || name_from_address(options)
+ nt_card[:cardholder_name] = name_from_payment_method(payment_method) || name_from_address(options)
nt_card[:card_number] = payment_method.number
nt_card[:exp_date] = format_exp_date(payment_method.month, payment_method.year)
nt_card[:cvv] = payment_method.verification_value
- nt_card[:xid] = payment_method.payment_cryptogram
- nt_card[:cavv] = payment_method.payment_cryptogram
+ nt_card[:xid] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty? || payment_method.brand.include?('american_express')
+ nt_card[:cavv] = payment_method.payment_cryptogram unless payment_method.payment_cryptogram.empty?
nt_card[:wallet_provider_id] = 'APPLE_PAY'
params['3DS'] = nt_card
params[:method] = '3DS'
- params[:eci_indicator] = payment_method.eci
+ params[:eci_indicator] = payment_method.eci.nil? ? '5' : payment_method.eci
end
def format_exp_date(month, year)
@@ -263,6 +299,12 @@ def name_from_address(options)
return address[:name] if address[:name]
end
+ def name_from_payment_method(payment_method)
+ return unless payment_method.first_name && payment_method.last_name
+
+ return "#{payment_method.first_name} #{payment_method.last_name}"
+ end
+
def add_address(params, options)
address = options[:billing_address]
return unless address
@@ -305,8 +347,7 @@ def add_stored_credentials(params, options)
end
def original_transaction_id(options)
- return options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id]
- return options[:stored_credential][:network_transaction_id] if options.dig(:stored_credential, :network_transaction_id)
+ return options[:cardbrand_original_transaction_id] || options.dig(:stored_credential, :network_transaction_id)
end
def initiator(options)
@@ -383,8 +424,7 @@ def generate_hmac(nonce, current_timestamp, payload)
@options[:token],
payload
].join('')
- hash = Base64.strict_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:apisecret], message))
- hash
+ Base64.strict_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:apisecret], message))
end
def headers(payload)
diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb
index 3d63b8957a0..c1449672e4b 100644
--- a/lib/active_merchant/billing/gateways/payex.rb
+++ b/lib/active_merchant/billing/gateways/payex.rb
@@ -385,11 +385,13 @@ def commit(soap_action, request)
'Content-Length' => request.size.to_s
}
response = parse(ssl_post(url, request, headers))
- Response.new(success?(response),
+ Response.new(
+ success?(response),
message_from(response),
response,
test: test?,
- authorization: build_authorization(response))
+ authorization: build_authorization(response)
+ )
end
def build_authorization(response)
diff --git a/lib/active_merchant/billing/gateways/payflow.rb b/lib/active_merchant/billing/gateways/payflow.rb
index 23f66fd65e9..949a42a2721 100644
--- a/lib/active_merchant/billing/gateways/payflow.rb
+++ b/lib/active_merchant/billing/gateways/payflow.rb
@@ -192,9 +192,7 @@ def build_credit_card_request(action, money, credit_card, options)
xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
end
- if %i(authorization purchase).include? action
- add_mpi_3ds(xml, options[:three_d_secure]) if options[:three_d_secure]
- end
+ add_mpi_3ds(xml, options[:three_d_secure]) if %i(authorization purchase).include?(action) && (options[:three_d_secure])
xml.tag! 'Tender' do
add_credit_card(xml, credit_card, options)
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 ef51f210ece..7fe5009259a 100644
--- a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb
+++ b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb
@@ -99,7 +99,7 @@ def build_request(body, options = {})
end
xml.tag! 'RequestAuth' do
xml.tag! 'UserPass' do
- xml.tag! 'User', !@options[:user].blank? ? @options[:user] : @options[:login]
+ xml.tag! 'User', @options[:user].blank? ? @options[:login] : @options[:user]
xml.tag! 'Password', @options[:password]
end
end
diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb
index 81369f2b432..51517b9277d 100644
--- a/lib/active_merchant/billing/gateways/payment_express.rb
+++ b/lib/active_merchant/billing/gateways/payment_express.rb
@@ -20,8 +20,8 @@ class PaymentExpressGateway < Gateway
self.homepage_url = 'https://www.windcave.com/'
self.display_name = 'Windcave (formerly PaymentExpress)'
- self.live_url = 'https://sec.paymentexpress.com/pxpost.aspx'
- self.test_url = 'https://uat.paymentexpress.com/pxpost.aspx'
+ self.live_url = 'https://sec.windcave.com/pxpost.aspx'
+ self.test_url = 'https://uat.windcave.com/pxpost.aspx'
APPROVED = '1'
@@ -306,9 +306,13 @@ def commit(action, request)
response = parse(ssl_post(url, request.to_s))
# Return a response
- PaymentExpressResponse.new(response[:success] == APPROVED, message_from(response), response,
+ PaymentExpressResponse.new(
+ response[:success] == APPROVED,
+ message_from(response),
+ response,
test: response[:test_mode] == '1',
- authorization: authorization_from(action, response))
+ authorization: authorization_from(action, response)
+ )
end
# Response XML documentation: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#XMLTxnOutput
diff --git a/lib/active_merchant/billing/gateways/paymentez.rb b/lib/active_merchant/billing/gateways/paymentez.rb
index 3e71b1e1989..19677797ff2 100644
--- a/lib/active_merchant/billing/gateways/paymentez.rb
+++ b/lib/active_merchant/billing/gateways/paymentez.rb
@@ -34,7 +34,7 @@ class PaymentezGateway < Gateway #:nodoc:
28 => :card_declined
}.freeze
- SUCCESS_STATUS = ['success', 'pending', 1, 0]
+ SUCCESS_STATUS = ['APPROVED', 'PENDING', 'pending', 'success', 1, 0]
CARD_MAPPING = {
'visa' => 'vi',
@@ -137,6 +137,10 @@ def unstore(identification, options = {})
commit_card('delete', post)
end
+ def inquire(authorization, options = {})
+ commit_transaction('inquire', authorization)
+ end
+
def supports_scrubbing?
true
end
@@ -214,7 +218,7 @@ def add_external_mpi_fields(extra_params, options)
xid: three_d_secure_options[:xid],
eci: three_d_secure_options[:eci],
version: three_d_secure_options[:version],
- reference_id: three_d_secure_options[:three_ds_server_trans_id],
+ reference_id: three_d_secure_options[:ds_transaction_id],
status: three_d_secure_options[:authentication_response_status] || three_d_secure_options[:directory_response_status]
}.compact
@@ -232,12 +236,20 @@ def 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
+ if action == 'inquire'
+ url = "#{test? ? test_url : live_url}#{object}/#{parameters}"
+ begin
+ raw_response = ssl_get(url, headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+ else
+ 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
end
begin
@@ -250,7 +262,7 @@ def commit_raw(object, action, parameters)
def commit_transaction(action, parameters)
response = commit_raw('transaction', action, parameters)
Response.new(
- success_from(response),
+ success_from(response, action),
message_from(response),
response,
authorization: authorization_from(response),
@@ -278,10 +290,22 @@ def headers
}
end
- def success_from(response)
- return false if response.include?('error')
+ def success_from(response, action = nil)
+ transaction_current_status = response.dig('transaction', 'current_status')
+ request_status = response['status']
+ transaction_status = response.dig('transaction', 'status')
+ default_response = SUCCESS_STATUS.include?(transaction_current_status || request_status || transaction_status)
- SUCCESS_STATUS.include?(response['status'] || response['transaction']['status'])
+ case action
+ when 'refund'
+ if transaction_current_status && request_status
+ transaction_current_status&.upcase == 'CANCELLED' && request_status&.downcase == 'success'
+ else
+ default_response
+ end
+ else
+ default_response
+ end
end
def card_success_from(response)
@@ -302,10 +326,10 @@ def message_from(response)
end
def card_message_from(response)
- if !response.include?('error')
- response['message'] || response['card']['message']
- else
+ if response.include?('error')
response['error']['type']
+ else
+ response['message'] || response['card']['message']
end
end
diff --git a/lib/active_merchant/billing/gateways/paypal_express.rb b/lib/active_merchant/billing/gateways/paypal_express.rb
index aaa65cec7e2..597cfeda9c3 100644
--- a/lib/active_merchant/billing/gateways/paypal_express.rb
+++ b/lib/active_merchant/billing/gateways/paypal_express.rb
@@ -108,6 +108,7 @@ def build_sale_or_authorization_request(action, money, options)
xml.tag! 'n2:PaymentAction', action
xml.tag! 'n2:Token', options[:token]
xml.tag! 'n2:PayerID', options[:payer_id]
+ xml.tag! 'n2:MsgSubID', options[:idempotency_key] if options[:idempotency_key]
add_payment_details(xml, money, currency_code, options)
end
end
@@ -251,6 +252,7 @@ def build_reference_transaction_request(action, money, options)
add_payment_details(xml, money, currency_code, options)
xml.tag! 'n2:IPAddress', options[:ip]
xml.tag! 'n2:MerchantSessionId', options[:merchant_session_id] if options[:merchant_session_id].present?
+ xml.tag! 'n2:MsgSubID', options[:idempotency_key] if options[:idempotency_key]
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paysafe.rb b/lib/active_merchant/billing/gateways/paysafe.rb
index d228a7ca774..a7cff9fe813 100644
--- a/lib/active_merchant/billing/gateways/paysafe.rb
+++ b/lib/active_merchant/billing/gateways/paysafe.rb
@@ -124,12 +124,13 @@ def add_billing_address(post, options)
return unless address = options[:billing_address] || options[:address]
post[:billingDetails] = {}
- post[:billingDetails][:street] = address[:address1]
- post[:billingDetails][:city] = address[:city]
- post[:billingDetails][:state] = address[:state]
+ post[:billingDetails][:street] = truncate(address[:address1], 50)
+ post[:billingDetails][:street2] = truncate(address[:address2], 50)
+ post[:billingDetails][:city] = truncate(address[:city], 40)
+ post[:billingDetails][:state] = truncate(address[:state], 40)
post[:billingDetails][:country] = address[:country]
- post[:billingDetails][:zip] = address[:zip]
- post[:billingDetails][:phone] = address[:phone]
+ post[:billingDetails][:zip] = truncate(address[:zip], 10)
+ post[:billingDetails][:phone] = truncate(address[:phone], 40)
end
# The add_address_for_vaulting method is applicable to the store method, as the APIs address
@@ -138,12 +139,12 @@ def add_address_for_vaulting(post, options)
return unless address = options[:billing_address] || options[:address]
post[:card][:billingAddress] = {}
- post[:card][:billingAddress][:street] = address[:address1]
- post[:card][:billingAddress][:street2] = address[:address2]
- post[:card][:billingAddress][:city] = address[:city]
- post[:card][:billingAddress][:zip] = address[:zip]
+ post[:card][:billingAddress][:street] = truncate(address[:address1], 50)
+ post[:card][:billingAddress][:street2] = truncate(address[:address2], 50)
+ post[:card][:billingAddress][:city] = truncate(address[:city], 40)
+ post[:card][:billingAddress][:zip] = truncate(address[:zip], 10)
post[:card][:billingAddress][:country] = address[:country]
- post[:card][:billingAddress][:state] = address[:state] if address[:state]
+ post[:card][:billingAddress][:state] = truncate(address[:state], 40) if address[:state]
end
# This data is specific to creating a profile at the gateway's vault level
@@ -401,7 +402,7 @@ def get_id_from_store_auth(authorization)
def post_data(parameters = {}, options = {})
return unless parameters.present?
- parameters[:merchantRefNum] = options[:merchant_ref_num] || SecureRandom.hex(16).to_s
+ parameters[:merchantRefNum] = options[:merchant_ref_num] || options[:order_id] || SecureRandom.hex(16).to_s
parameters.to_json
end
diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb
index 4c2f418a8c8..ff0ab53d361 100644
--- a/lib/active_merchant/billing/gateways/payscout.rb
+++ b/lib/active_merchant/billing/gateways/payscout.rb
@@ -115,12 +115,16 @@ def commit(action, money, parameters)
message = message_from(response)
test_mode = (test? || message =~ /TESTMODE/)
- Response.new(success?(response), message, response,
+ 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'])
+ cvv_result: response['cvvresponse']
+ )
end
def message_from(response)
@@ -151,8 +155,7 @@ def post_data(action, parameters = {})
post[:password] = @options[:password]
post[:type] = 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('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paystation.rb b/lib/active_merchant/billing/gateways/paystation.rb
index e7557ce5419..ddea5f241bc 100644
--- a/lib/active_merchant/billing/gateways/paystation.rb
+++ b/lib/active_merchant/billing/gateways/paystation.rb
@@ -177,9 +177,13 @@ def commit(post)
response = parse(data)
message = message_from(response)
- PaystationResponse.new(success?(response), message, response,
- test: (response[:tm]&.casecmp('t')&.zero?),
- authorization: response[:paystation_transaction_id])
+ PaystationResponse.new(
+ success?(response),
+ message,
+ response,
+ test: response[:tm]&.casecmp('t')&.zero?,
+ authorization: response[:paystation_transaction_id]
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/payu_latam.rb b/lib/active_merchant/billing/gateways/payu_latam.rb
index 89e52e494a9..3ac30eec018 100644
--- a/lib/active_merchant/billing/gateways/payu_latam.rb
+++ b/lib/active_merchant/billing/gateways/payu_latam.rb
@@ -455,7 +455,7 @@ def error_from(action, response)
when 'verify_credentials'
response['error'] || 'FAILED'
else
- response['transactionResponse']['errorCode'] || response['transactionResponse']['responseCode'] if response['transactionResponse']
+ response['transactionResponse']['paymentNetworkResponseCode'] || response['transactionResponse']['errorCode'] if response['transactionResponse']
end
end
diff --git a/lib/active_merchant/billing/gateways/payway.rb b/lib/active_merchant/billing/gateways/payway.rb
index e5980c8ce04..c48373ea608 100644
--- a/lib/active_merchant/billing/gateways/payway.rb
+++ b/lib/active_merchant/billing/gateways/payway.rb
@@ -192,9 +192,13 @@ def commit(action, post)
success = (params[:summary_code] ? (params[:summary_code] == '0') : (params[:response_code] == '00'))
- Response.new(success, message, params,
+ Response.new(
+ success,
+ message,
+ params,
test: (@options[:merchant].to_s == 'TEST'),
- authorization: post[:order_number])
+ authorization: post[:order_number]
+ )
rescue ActiveMerchant::ResponseError => e
raise unless e.response.code == '403'
diff --git a/lib/active_merchant/billing/gateways/payway_dot_com.rb b/lib/active_merchant/billing/gateways/payway_dot_com.rb
index 06f6d919360..d3a9fa61c8c 100644
--- a/lib/active_merchant/billing/gateways/payway_dot_com.rb
+++ b/lib/active_merchant/billing/gateways/payway_dot_com.rb
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaywayDotComGateway < Gateway
self.test_url = 'https://paywaywsdev.com/PaywayWS/Payment/CreditCard'
- self.live_url = 'https://paywayws.com/PaywayWS/Payment/CreditCard'
+ self.live_url = 'https://paywayws.net/PaywayWS/Payment/CreditCard'
self.supported_countries = %w[US CA]
self.default_currency = 'USD'
@@ -48,7 +48,7 @@ class PaywayDotComGateway < Gateway
'I5' => 'M', # +4 and Address Match
'I6' => 'W', # +4 Match
'I7' => 'A', # Address Match
- 'I8' => 'C', # No Match
+ 'I8' => 'C' # No Match
}
PAYWAY_WS_SUCCESS = '5000'
@@ -224,7 +224,7 @@ def success_from(response)
def error_code_from(response)
return '' if success_from(response)
- error = !STANDARD_ERROR_CODE_MAPPING[response['paywayCode']].nil? ? STANDARD_ERROR_CODE_MAPPING[response['paywayCode']] : STANDARD_ERROR_CODE[:processing_error]
+ error = STANDARD_ERROR_CODE_MAPPING[response['paywayCode']].nil? ? STANDARD_ERROR_CODE[:processing_error] : STANDARD_ERROR_CODE_MAPPING[response['paywayCode']]
return error
end
diff --git a/lib/active_merchant/billing/gateways/pin.rb b/lib/active_merchant/billing/gateways/pin.rb
index b054ed3b7a6..0562ff14134 100644
--- a/lib/active_merchant/billing/gateways/pin.rb
+++ b/lib/active_merchant/billing/gateways/pin.rb
@@ -31,6 +31,7 @@ def purchase(money, creditcard, options = {})
add_capture(post, options)
add_metadata(post, options)
add_3ds(post, options)
+ add_platform_adjustment(post, options)
commit(:post, 'charges', post, options)
end
@@ -175,6 +176,10 @@ def add_metadata(post, options)
post[:metadata] = options[:metadata] if options[:metadata]
end
+ def add_platform_adjustment(post, options)
+ post[:platform_adjustment] = options[:platform_adjustment] if options[:platform_adjustment]
+ end
+
def add_3ds(post, options)
if options[:three_d_secure]
post[:three_d_secure] = {}
diff --git a/lib/active_merchant/billing/gateways/plexo.rb b/lib/active_merchant/billing/gateways/plexo.rb
index f4558e1c4df..d0bf2448ffc 100644
--- a/lib/active_merchant/billing/gateways/plexo.rb
+++ b/lib/active_merchant/billing/gateways/plexo.rb
@@ -67,6 +67,7 @@ def void(authorization, options = {})
def verify(credit_card, options = {})
post = {}
post[:ReferenceId] = options[:reference_id] || generate_unique_id
+ post[:Flow] = 'direct'
post[:MerchantId] = options[:merchant_id] || @credentials[:merchant_id]
post[:StatementDescriptor] = options[:statement_descriptor] if options[:statement_descriptor]
post[:CustomerId] = options[:customer_id] if options[:customer_id]
@@ -76,6 +77,7 @@ def verify(credit_card, options = {})
add_metadata(post, options[:metadata])
add_amount(money, post, options)
add_browser_details(post, options)
+ add_invoice_number(post, options)
commit('/verify', post, options)
end
@@ -90,7 +92,8 @@ def scrub(transcript)
gsub(%r(("Number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
gsub(%r(("Cvc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
gsub(%r(("InvoiceNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
- gsub(%r(("MerchantId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
+ gsub(%r(("MerchantId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("Cryptogram\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
end
private
@@ -105,12 +108,14 @@ def build_auth_purchase_request(money, post, payment, options)
post[:Installments] = options[:installments] if options[:installments]
post[:StatementDescriptor] = options[:statement_descriptor] if options[:statement_descriptor]
post[:CustomerId] = options[:customer_id] if options[:customer_id]
+ post[:Flow] = 'direct'
add_payment_method(post, payment, options)
add_items(post, options[:items])
add_metadata(post, options[:metadata])
add_amount(money, post, options)
add_browser_details(post, options)
+ add_invoice_number(post, options)
end
def header(parameters = {})
@@ -186,6 +191,10 @@ def add_browser_details(post, browser_details)
post[:BrowserDetails][:IpAddress] = browser_details[:ip] if browser_details[:ip]
end
+ def add_invoice_number(post, options)
+ post[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
+ end
+
def add_payment_method(post, payment, options)
post[:paymentMethod] = {}
@@ -199,6 +208,7 @@ def add_payment_method(post, payment, options)
add_card_holder(post[:paymentMethod][:Card], payment, options)
end
+ post[:paymentMethod][:Card][:Cryptogram] = payment.payment_cryptogram if payment&.is_a?(NetworkTokenizationCreditCard)
end
def add_card_holder(card, payment, options)
diff --git a/lib/active_merchant/billing/gateways/plugnpay.rb b/lib/active_merchant/billing/gateways/plugnpay.rb
index e7343efde03..e383c0d4ef7 100644
--- a/lib/active_merchant/billing/gateways/plugnpay.rb
+++ b/lib/active_merchant/billing/gateways/plugnpay.rb
@@ -178,11 +178,15 @@ def commit(action, post)
success = SUCCESS_CODES.include?(response[:finalstatus])
message = success ? 'Success' : message_from(response)
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: response[:orderid],
avs_result: { code: response[:avs_code] },
- cvv_result: response[:cvvresp])
+ cvv_result: response[:cvvresp]
+ )
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/priority.rb b/lib/active_merchant/billing/gateways/priority.rb
index cddde41395c..762a7675726 100644
--- a/lib/active_merchant/billing/gateways/priority.rb
+++ b/lib/active_merchant/billing/gateways/priority.rb
@@ -55,7 +55,8 @@ def purchase(amount, credit_card, options = {})
add_merchant_id(params)
add_amount(params, amount, options)
- add_auth_purchase_params(params, credit_card, options)
+ add_auth_purchase_params(params, options)
+ add_credit_card(params, credit_card, 'purchase', options)
commit('purchase', params: params)
end
@@ -67,7 +68,8 @@ def authorize(amount, credit_card, options = {})
add_merchant_id(params)
add_amount(params, amount, options)
- add_auth_purchase_params(params, credit_card, options)
+ add_auth_purchase_params(params, options)
+ add_credit_card(params, credit_card, 'purchase', options)
commit('purchase', params: params)
end
@@ -100,7 +102,7 @@ def capture(amount, authorization, options = {})
add_merchant_id(params)
add_amount(params, amount, options)
params['paymentToken'] = payment_token(authorization) || options[:payment_token]
- params['tenderType'] = options[:tender_type].present? ? options[:tender_type] : 'Card'
+ add_auth_purchase_params(params, options)
commit('capture', params: params)
end
@@ -150,9 +152,8 @@ def add_merchant_id(params)
params['merchantId'] = @options[:merchant_id]
end
- def add_auth_purchase_params(params, credit_card, options)
+ def add_auth_purchase_params(params, options)
add_replay_id(params, options)
- add_credit_card(params, credit_card, 'purchase', options)
add_purchases_data(params, options)
add_shipping_data(params, options)
add_pos_data(params, options)
diff --git a/lib/active_merchant/billing/gateways/psigate.rb b/lib/active_merchant/billing/gateways/psigate.rb
index 6fc9c8905e4..c383ddd0cd9 100644
--- a/lib/active_merchant/billing/gateways/psigate.rb
+++ b/lib/active_merchant/billing/gateways/psigate.rb
@@ -102,11 +102,15 @@ def scrub(transcript)
def commit(money, creditcard, options = {})
response = parse(ssl_post(url, post_data(money, creditcard, options)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
authorization: build_authorization(response),
avs_result: { code: response[:avsresult] },
- cvv_result: response[:cardidresult])
+ cvv_result: response[:cardidresult]
+ )
end
def url
diff --git a/lib/active_merchant/billing/gateways/psl_card.rb b/lib/active_merchant/billing/gateways/psl_card.rb
index ec688861457..11f20a2a1ad 100644
--- a/lib/active_merchant/billing/gateways/psl_card.rb
+++ b/lib/active_merchant/billing/gateways/psl_card.rb
@@ -259,11 +259,15 @@ def parse(body)
def commit(request)
response = parse(ssl_post(self.live_url, post_data(request)))
- Response.new(response[:ResponseCode] == APPROVED, response[:Message], response,
+ Response.new(
+ response[:ResponseCode] == APPROVED,
+ response[:Message],
+ response,
test: test?,
authorization: response[:CrossReference],
cvv_result: CVV_CODE[response[:AVSCV2Check]],
- avs_result: { code: AVS_CODE[response[:AVSCV2Check]] })
+ avs_result: { code: AVS_CODE[response[:AVSCV2Check]] }
+ )
end
# Put the passed data into a format that can be submitted to PSL
diff --git a/lib/active_merchant/billing/gateways/qbms.rb b/lib/active_merchant/billing/gateways/qbms.rb
index c709905a293..354928930aa 100644
--- a/lib/active_merchant/billing/gateways/qbms.rb
+++ b/lib/active_merchant/billing/gateways/qbms.rb
@@ -142,12 +142,16 @@ def commit(action, money, parameters)
response = parse(type, data)
message = (response[:status_message] || '').strip
- Response.new(success?(response), message, response,
+ Response.new(
+ success?(response),
+ message,
+ response,
test: test?,
authorization: response[:credit_card_trans_id],
fraud_review: fraud_review?(response),
avs_result: { code: avs_result(response) },
- cvv_result: cvv_result(response))
+ cvv_result: cvv_result(response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/quantum.rb b/lib/active_merchant/billing/gateways/quantum.rb
index 104148b662b..693bcdb9b7a 100644
--- a/lib/active_merchant/billing/gateways/quantum.rb
+++ b/lib/active_merchant/billing/gateways/quantum.rb
@@ -215,11 +215,15 @@ def commit(request, options)
authorization = success ? authorization_for(response) : nil
end
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
avs_result: { code: response[:AVSResponseCode] },
- cvv_result: response[:CVV2ResponseCode])
+ cvv_result: response[:CVV2ResponseCode]
+ )
end
# Parse the SOAP response
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
index 402637475e5..308a873b7ca 100644
--- a/lib/active_merchant/billing/gateways/quickbooks.rb
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -42,10 +42,10 @@ class QuickbooksGateway < Gateway
'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.
+ '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']
+ FRAUD_WARNING_CODES = %w(PMT-1000 PMT-1001 PMT-1002 PMT-1003)
def initialize(options = {})
# Quickbooks is deprecating OAuth 1.0 on December 17, 2019.
@@ -128,8 +128,8 @@ def scrub(transcript)
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]').
+ gsub(%r((number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((cvc\\\":\\\")\d+), '\1[FILTERED]').
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r((access_token\\?":\\?")[\w\-\.]+)i, '\1[FILTERED]').
gsub(%r((refresh_token\\?":\\?")\w+), '\1[FILTERED]').
@@ -263,7 +263,7 @@ def headers(method, uri)
oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.delete("\n"))
# prepare Authorization header string
- oauth_parameters = Hash[oauth_parameters.sort_by { |k, _| k }]
+ oauth_parameters = oauth_parameters.sort_by { |k, _| k }.to_h
oauth_headers = ["OAuth realm=\"#{@options[:realm]}\""]
oauth_headers += oauth_parameters.map { |k, v| "#{k}=\"#{v}\"" }
@@ -358,6 +358,7 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end
+
response_error.response.body
end
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
index 91eeeff564e..3061ba3305c 100644
--- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
@@ -153,9 +153,13 @@ def commit(action, params = {})
response = json_error(response)
end
- Response.new(success, message_from(success, response), response,
+ Response.new(
+ success,
+ message_from(success, response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def authorization_from(response)
@@ -251,7 +255,7 @@ def map_address(address)
requires!(address, :name, :address1, :city, :zip, :country)
country = Country.find(address[:country])
- mapped = {
+ {
name: address[:name],
street: address[:address1],
city: address[:city],
@@ -259,7 +263,6 @@ def map_address(address)
zip_code: address[:zip],
country_code: country.code(:alpha3).value
}
- mapped
end
def format_order_id(order_id)
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
index bb00258a3f6..c4daca39787 100644
--- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
@@ -164,9 +164,13 @@ def add_finalize(post, options)
def commit(action, params)
response = parse(ssl_post(self.live_url, post_data(action, params)))
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response[:transaction])
+ authorization: response[:transaction]
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb
index 6cdd07b2792..e99b8c10eb7 100644
--- a/lib/active_merchant/billing/gateways/rapyd.rb
+++ b/lib/active_merchant/billing/gateways/rapyd.rb
@@ -1,16 +1,23 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class RapydGateway < Gateway
+ class_attribute :payment_redirect_test, :payment_redirect_live
+
self.test_url = 'https://sandboxapi.rapyd.net/v1/'
self.live_url = 'https://api.rapyd.net/v1/'
+ self.payment_redirect_test = 'https://sandboxpayment-redirect.rapyd.net/v1/'
+ self.payment_redirect_live = 'https://payment-redirect.rapyd.net/v1/'
+
self.supported_countries = %w(CA CL CO DO SV PE PT VI AU HK IN ID JP MY NZ PH SG KR TW TH VN AD AT BE BA BG HR CY CZ DK EE FI FR GE DE GI GR GL HU IS IE IL IT LV LI LT LU MK MT MD MC ME NL GB NO PL RO RU SM SK SI ZA ES SE CH TR VA)
self.default_currency = 'USD'
- self.supported_cardtypes = %i[visa master american_express discover]
+ self.supported_cardtypes = %i[visa master american_express discover verve]
self.homepage_url = 'https://www.rapyd.net/'
self.display_name = 'Rapyd Gateway'
+ USA_PAYMENT_METHODS = %w[us_debit_discover_card us_debit_mastercard_card us_debit_visa_card us_ach_bank]
+
STANDARD_ERROR_CODE_MAPPING = {}
def initialize(options = {})
@@ -61,11 +68,11 @@ def verify(credit_card, options = {})
def store(payment, options = {})
post = {}
add_payment(post, payment, options)
- add_customer_object(post, payment, options)
+ add_customer_data(post, payment, options, 'store')
add_metadata(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
- add_payment_urls(post, options)
+ add_payment_urls(post, options, 'store')
add_address(post, payment, options)
commit(:post, 'customers', post)
end
@@ -98,13 +105,18 @@ def add_reference(authorization)
def add_auth_purchase(post, money, payment, options)
add_invoice(post, money, options)
add_payment(post, payment, options)
+ add_customer_data(post, payment, options)
add_3ds(post, payment, options)
add_address(post, payment, options)
add_metadata(post, options)
add_ewallet(post, options)
add_payment_fields(post, options)
add_payment_urls(post, options)
- add_customer_id(post, options)
+ add_idempotency(options)
+ end
+
+ def add_idempotency(options)
+ @options[:idempotency] = options[:idempotency_key] if options[:idempotency_key]
end
def add_address(post, creditcard, options)
@@ -125,6 +137,10 @@ def add_address(post, creditcard, options)
def add_invoice(post, money, options)
post[:amount] = money.zero? ? 0 : amount(money).to_f.to_s
post[:currency] = (options[:currency] || currency(money))
+ post[:merchant_reference_id] = options[:merchant_reference_id] || options[:order_id]
+ post[:requested_currency] = options[:requested_currency] if options[:requested_currency].present?
+ post[:fixed_side] = options[:fixed_side] if options[:fixed_side].present?
+ post[:expiration] = (options[:expiration_days] || 7).to_i.days.from_now.to_i if options[:fixed_side].present?
end
def add_payment(post, payment, options)
@@ -133,15 +149,27 @@ def add_payment(post, payment, options)
elsif payment.is_a?(Check)
add_ach(post, payment, options)
else
- add_token(post, payment, options)
+ add_tokens(post, payment, options)
end
end
def add_stored_credential(post, options)
- return unless stored_credential = options[:stored_credential]
+ add_network_reference_id(post, options)
+ add_initiation_type(post, options)
+ end
+
+ def add_network_reference_id(post, options)
+ return unless (options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring') || options[:network_transaction_id]
+
+ network_transaction_id = options[:network_transaction_id] || options[:stored_credential][:network_transaction_id]
+ post[:payment_method][:fields][:network_reference_id] = network_transaction_id unless network_transaction_id&.empty?
+ end
+
+ def add_initiation_type(post, options)
+ return unless options[:stored_credential] || options[:initiation_type]
- post[:payment_method][:fields][:network_reference_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
- post[:initiation_type] = stored_credential[:reason_type] if stored_credential[:reason_type]
+ initiation_type = options[:initiation_type] || options[:stored_credential][:reason_type]
+ post[:initiation_type] = initiation_type if initiation_type
end
def add_creditcard(post, payment, options)
@@ -151,14 +179,23 @@ def add_creditcard(post, payment, options)
post[:payment_method][:type] = options[:pm_type]
pm_fields[:number] = payment.number
- pm_fields[:expiration_month] = payment.month.to_s
- pm_fields[:expiration_year] = payment.year.to_s
+ pm_fields[:expiration_month] = format(payment.month, :two_digits).to_s
+ pm_fields[:expiration_year] = format(payment.year, :two_digits).to_s
pm_fields[:name] = "#{payment.first_name} #{payment.last_name}"
- pm_fields[:cvv] = payment.verification_value.to_s
-
+ pm_fields[:cvv] = payment.verification_value.to_s unless valid_network_transaction_id?(options) || payment.verification_value.blank?
+ pm_fields[:recurrence_type] = options[:recurrence_type] if options[:recurrence_type]
add_stored_credential(post, options)
end
+ def send_customer_object?(options)
+ options[:stored_credential] && options[:stored_credential][:reason_type] == 'recurring'
+ end
+
+ def valid_network_transaction_id?(options)
+ network_transaction_id = options[:network_tansaction_id] || options.dig(:stored_credential_options, :network_transaction_id) || options.dig(:stored_credential, :network_transaction_id)
+ return network_transaction_id.present?
+ end
+
def add_ach(post, payment, options)
post[:payment_method] = {}
post[:payment_method][:fields] = {}
@@ -172,22 +209,27 @@ def add_ach(post, payment, options)
post[:payment_method][:fields][:payment_purpose] = options[:payment_purpose] if options[:payment_purpose]
end
- def add_token(post, payment, options)
- return unless token = payment.split('|')[1]
+ def add_tokens(post, payment, options)
+ return unless payment.respond_to?(:split)
- post[:payment_method] = token
+ customer_id, card_id = payment.split('|')
+
+ post[:customer] = customer_id unless send_customer_object?(options)
+ post[:payment_method] = card_id
end
def add_3ds(post, payment, options)
- return unless three_d_secure = options[:three_d_secure]
-
- post[:payment_method_options] = {}
- post[:payment_method_options]['3d_required'] = three_d_secure[:required]
- post[:payment_method_options]['3d_version'] = three_d_secure[:version]
- post[:payment_method_options][:cavv] = three_d_secure[:cavv]
- post[:payment_method_options][:eci] = three_d_secure[:eci]
- post[:payment_method_options][:xid] = three_d_secure[:xid]
- post[:payment_method_options][:ds_trans_id] = three_d_secure[:ds_transaction_id]
+ if options[:execute_threed] == true
+ post[:payment_method_options] = { '3d_required' => true } if options[:force_3d_secure].to_s == 'true'
+ elsif three_d_secure = options[:three_d_secure]
+ post[:payment_method_options] = {}
+ post[:payment_method_options]['3d_required'] = three_d_secure[:required]
+ post[:payment_method_options]['3d_version'] = three_d_secure[:version]
+ post[:payment_method_options][:cavv] = three_d_secure[:cavv]
+ post[:payment_method_options][:eci] = three_d_secure[:eci]
+ post[:payment_method_options][:xid] = three_d_secure[:xid]
+ post[:payment_method_options][:ds_trans_id] = three_d_secure[:ds_transaction_id]
+ end
end
def add_metadata(post, options)
@@ -195,25 +237,66 @@ def add_metadata(post, options)
end
def add_ewallet(post, options)
- post[:ewallet_id] = options[:ewallet_id] if options[:ewallet_id]
+ post[:ewallet] = options[:ewallet_id] if options[:ewallet_id]
end
def add_payment_fields(post, options)
- post[:payment] = {}
+ post[:description] = options[:description] if options[:description]
+ post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
+ end
- post[:payment][:description] = options[:description] if options[:description]
- post[:payment][:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
+ def add_payment_urls(post, options, action = '')
+ if action == 'store'
+ url_location = post[:payment_method]
+ else
+ url_location = post
+ end
+
+ url_location[:complete_payment_url] = options[:complete_payment_url] if options[:complete_payment_url]
+ url_location[:error_payment_url] = options[:error_payment_url] if options[:error_payment_url]
+ end
+
+ def add_customer_data(post, payment, options, action = '')
+ phone_number = options.dig(:billing_address, :phone) || options.dig(:billing_address, :phone_number)
+ post[:phone_number] = phone_number.gsub(/\D/, '') unless phone_number.nil?
+ post[:receipt_email] = options[:email] if payment.is_a?(String) && options[:customer_id].present? && !send_customer_object?(options)
+
+ return if payment.is_a?(String)
+ return add_customer_id(post, options) if options[:customer_id]
+
+ if action == 'store'
+ post.merge!(customer_fields(payment, options))
+ else
+ post[:customer] = customer_fields(payment, options) unless send_customer_object?(options)
+ end
end
- def add_payment_urls(post, options)
- post[:complete_payment_url] = options[:complete_payment_url] if options[:complete_payment_url]
- post[:error_payment_url] = options[:error_payment_url] if options[:error_payment_url]
+ def customer_fields(payment, options)
+ return if options[:customer_id]
+
+ customer_address = address(options)
+ customer_data = {}
+ customer_data[:name] = "#{payment.first_name} #{payment.last_name}" unless payment.is_a?(String)
+ customer_data[:email] = options[:email] unless payment.is_a?(String) && options[:customer_id].blank?
+ customer_data[:addresses] = [customer_address] if customer_address
+ customer_data
end
- def add_customer_object(post, payment, options)
- post[:name] = "#{payment.first_name} #{payment.last_name}"
- post[:phone_number] = options[:billing_address][:phone].gsub(/\D/, '') if options[:billing_address]
- post[:email] = options[:email] if options[:email]
+ def address(options)
+ return unless address = options[:billing_address]
+
+ formatted_address = {}
+
+ formatted_address[:name] = address[:name] if address[:name]
+ formatted_address[:line_1] = address[:address1] if address[:address1]
+ formatted_address[:line_2] = address[:address2] if address[:address2]
+ formatted_address[:city] = address[:city] if address[:city]
+ formatted_address[:state] = address[:state] if address[:state]
+ formatted_address[:country] = address[:country] if address[:country]
+ formatted_address[:zip] = address[:zip] if address[:zip]
+ formatted_address[:phone_number] = address[:phone].gsub(/\D/, '') if address[:phone]
+
+ formatted_address
end
def add_customer_id(post, options)
@@ -223,13 +306,21 @@ def add_customer_id(post, options)
def parse(body)
return {} if body.empty? || body.nil?
- JSON.parse(body)
+ parsed = JSON.parse(body)
+ parsed.is_a?(Hash) ? parsed : { 'status' => { 'status' => parsed } }
+ end
+
+ def url(action, url_override = nil)
+ if url_override.to_s == 'payment_redirect' && action == 'payments'
+ (self.test? ? self.payment_redirect_test : self.payment_redirect_live) + action.to_s
+ else
+ (self.test? ? self.test_url : self.live_url) + action.to_s
+ end
end
def commit(method, action, parameters)
- url = (test? ? test_url : live_url) + action.to_s
rel_path = "#{method}/v1/#{action}"
- response = api_request(method, url, rel_path, parameters)
+ response = api_request(method, url(action, @options[:url_override]), rel_path, parameters)
Response.new(
success_from(response),
@@ -241,10 +332,25 @@ def commit(method, action, parameters)
test: test?,
error_code: error_code_from(response)
)
+ rescue ActiveMerchant::ResponseError => e
+ response = e.response.body.present? ? parse(e.response.body) : { 'status' => { 'response_code' => e.response.msg } }
+ message = response['status'].slice('message', 'response_code').values.select(&:present?).first || ''
+ Response.new(false, message, response, test: test?, error_code: error_code_from(response))
+ end
+
+ # We need to revert the work of ActiveSupport JSON encoder to prevent discrepancies
+ # Between the signature and the actual request body
+ def revert_json_html_encoding!(string)
+ {
+ '\\u003e' => '>',
+ '\\u003c' => '<',
+ '\\u0026' => '&'
+ }.each { |k, v| string.gsub! k, v }
end
def api_request(method, url, rel_path, params)
params == {} ? body = '' : body = params.to_json
+ revert_json_html_encoding!(body) if defined?(ActiveSupport::JSON::Encoding) && ActiveSupport::JSON::Encoding.escape_html_entities_in_json
parse(ssl_request(method, url, body, headers(rel_path, body)))
end
@@ -256,14 +362,14 @@ def headers(rel_path, payload)
'access_key' => @options[:access_key],
'salt' => salt,
'timestamp' => timestamp,
- 'signature' => generate_hmac(rel_path, salt, timestamp, payload)
- }
+ 'signature' => generate_hmac(rel_path, salt, timestamp, payload),
+ 'idempotency' => @options[:idempotency]
+ }.delete_if { |_, value| value.nil? }
end
def generate_hmac(rel_path, salt, timestamp, payload)
signature = "#{rel_path}#{salt}#{timestamp}#{@options[:access_key]}#{@options[:secret_key]}#{payload}"
- hash = Base64.urlsafe_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:secret_key], signature))
- hash
+ Base64.urlsafe_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:secret_key], signature))
end
def avs_result(response)
@@ -298,7 +404,7 @@ def authorization_from(response)
end
def error_code_from(response)
- response.dig('status', 'error_code') unless success_from(response)
+ response.dig('status', 'error_code') || response.dig('status', 'response_code') || ''
end
def handle_response(response)
diff --git a/lib/active_merchant/billing/gateways/reach.rb b/lib/active_merchant/billing/gateways/reach.rb
index 092a19de698..5d6b9547b8d 100644
--- a/lib/active_merchant/billing/gateways/reach.rb
+++ b/lib/active_merchant/billing/gateways/reach.rb
@@ -4,7 +4,14 @@ class ReachGateway < Gateway
self.test_url = 'https://checkout.rch.how/'
self.live_url = 'https://checkout.rch.io/'
- self.supported_countries = ['US']
+ self.supported_countries = %w(AE AG AL AM AT AU AW AZ BA BB BD BE BF BG BH BJ BM BN BO BR BS BW BZ CA CD CF
+ CH CI CL CM CN CO CR CU CV CY CZ DE DJ DK DM DO DZ EE EG ES ET FI FJ FK FR GA
+ GB GD GE GG GH GI GN GR GT GU GW GY HK HN HR HU ID IE IL IM IN IS IT JE JM JO
+ JP KE KG KH KM KN KR KW KY KZ LA LC LK LR LT LU LV LY MA MD MK ML MN MO MR MS
+ MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NZ OM PA PE PF PG PH PK PL PT PY
+ QA RO RS RW SA SB SC SE SG SH SI SK SL SN SO SR ST SV SY SZ TD TG TH TN TO TR
+ TT TV TW TZ UG US UY UZ VC VN VU WF WS YE ZM)
+
self.default_currency = 'USD'
self.supported_cardtypes = %i[visa diners_club american_express jcb master discover maestro]
@@ -71,10 +78,10 @@ def supports_scrubbing?
def scrub(transcript)
transcript.
- gsub(%r(((MerchantId)[% \w]+[%]\d{2})[\w -]+), '\1[FILTERED]').
+ gsub(%r(((MerchantId)[% \w]+%\d{2})[\w -]+), '\1[FILTERED]').
gsub(%r((signature=)[\w%]+), '\1[FILTERED]\2').
- gsub(%r((Number%22%3A%22)[\d]+), '\1[FILTERED]\2').
- gsub(%r((VerificationCode%22%3A)[\d]+), '\1[FILTERED]\2')
+ gsub(%r((Number%22%3A%22)\d+), '\1[FILTERED]\2').
+ gsub(%r((VerificationCode%22%3A)\d+), '\1[FILTERED]\2')
end
def refund(amount, authorization, options = {})
diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb
index 00239bf2ee2..a733b77bcdc 100644
--- a/lib/active_merchant/billing/gateways/redsys.rb
+++ b/lib/active_merchant/billing/gateways/redsys.rb
@@ -39,7 +39,7 @@ class RedsysGateway < Gateway
self.live_url = 'https://sis.redsys.es/sis/operaciones'
self.test_url = 'https://sis-t.redsys.es:25443/sis/operaciones'
- self.supported_countries = ['ES']
+ self.supported_countries = %w[ES FR GB IT PL PT]
self.default_currency = 'EUR'
self.money_format = :cents
@@ -266,14 +266,7 @@ def refund(money, authorization, options = {})
end
def verify(creditcard, options = {})
- if options[:sca_exemption_behavior_override] == 'endpoint_and_ntid'
- purchase(0, creditcard, options)
- else
- MultiResponse.run(:use_first_response) do |r|
- r.process { authorize(100, creditcard, options) }
- r.process(:ignore_result) { void(r.authorization, options) }
- end
- end
+ purchase(0, creditcard, options)
end
def supports_scrubbing
@@ -538,6 +531,7 @@ def build_merchant_data(xml, data, options = {})
xml.DS_MERCHANT_COF_INI data[:DS_MERCHANT_COF_INI]
xml.DS_MERCHANT_COF_TYPE data[:DS_MERCHANT_COF_TYPE]
xml.DS_MERCHANT_COF_TXNID data[:DS_MERCHANT_COF_TXNID] if data[:DS_MERCHANT_COF_TXNID]
+ xml.DS_MERCHANT_DIRECTPAYMENT 'false' if options[:stored_credential][:initial_transaction]
end
end
end
@@ -695,8 +689,7 @@ def encrypt(key, order_id)
order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros
- output = cipher.update(order_id) + cipher.final
- output
+ cipher.update(order_id) + cipher.final
end
def mac256(key, data)
diff --git a/lib/active_merchant/billing/gateways/redsys_rest.rb b/lib/active_merchant/billing/gateways/redsys_rest.rb
new file mode 100644
index 00000000000..3e8de87ed68
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/redsys_rest.rb
@@ -0,0 +1,465 @@
+# coding: utf-8
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # = Redsys Merchant Gateway
+ #
+ # Gateway support for the Spanish "Redsys" payment gateway system. This is
+ # used by many banks in Spain and is particularly well supported by
+ # Catalunya Caixa's ecommerce department.
+ #
+ # Redsys requires an order_id be provided with each transaction and it must
+ # follow a specific format. The rules are as follows:
+ #
+ # * 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.
+ #
+ # Written by Piers Chambers (Varyonic.com)
+ #
+ # *** SHA256 Authentication Update ***
+ #
+ # Redsys has dropped support for the SHA1 authentication method.
+ # Developer documentation: https://pagosonline.redsys.es/desarrolladores.html
+ class RedsysRestGateway < Gateway
+ self.test_url = 'https://sis-t.redsys.es:25443/sis/rest/'
+ self.live_url = 'https://sis.redsys.es/sis/rest/'
+
+ self.supported_countries = ['ES']
+ self.default_currency = 'EUR'
+ self.money_format = :cents
+ # Not all card types may be activated by the bank!
+ self.supported_cardtypes = %i[visa master american_express jcb diners_club unionpay]
+ self.homepage_url = 'http://www.redsys.es/'
+ self.display_name = 'Redsys (REST)'
+
+ CURRENCY_CODES = {
+ '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.
+ # More operations are supported by the gateway itself, but
+ # are not supported in this library.
+ SUPPORTED_TRANSACTIONS = {
+ purchase: '0',
+ authorize: '1',
+ capture: '2',
+ refund: '3',
+ cancel: '9',
+ verify: '7'
+ }
+
+ # These are the text meanings sent back by the acquirer when
+ # a card has been rejected. Syntax or general request errors
+ # are not covered here.
+ RESPONSE_TEXTS = {
+ 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',
+ 195 => 'Requires SCA authentication',
+
+ 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'
+ }
+
+ # Expected values as per documentation
+ THREE_DS_V2 = '2.1.0'
+
+ # Creates a new instance
+ #
+ # Redsys requires a login and secret_key, and optionally also accepts a
+ # non-default terminal.
+ #
+ # ==== Options
+ #
+ # * :login -- The Redsys Merchant ID (REQUIRED)
+ # * :secret_key -- The Redsys Secret Key. (REQUIRED)
+ # * :terminal -- The Redsys Terminal. Defaults to 1. (OPTIONAL)
+ # * :test -- +true+ or +false+. Defaults to +false+. (OPTIONAL)
+ def initialize(options = {})
+ requires!(options, :login, :secret_key)
+ options[:terminal] ||= 1
+ options[:signature_algorithm] = 'sha256'
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :purchase, options)
+ add_amount(post, money, options)
+ add_order(post, options[:order_id])
+ add_payment(post, payment)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
+ end
+
+ def authorize(money, payment, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :authorize, options)
+ add_amount(post, money, options)
+ add_order(post, options[:order_id])
+ add_payment(post, payment)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_action(post, :capture)
+ add_amount(post, money, options)
+ order_id, = split_authorization(authorization)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def void(authorization, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :cancel)
+ order_id, amount, currency = split_authorization(authorization)
+ add_amount(post, amount, currency: currency)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def refund(money, authorization, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :refund)
+ add_amount(post, money, options)
+ order_id, = split_authorization(authorization)
+ add_order(post, order_id)
+ add_description(post, options)
+
+ commit(post, options)
+ end
+
+ def verify(creditcard, options = {})
+ requires!(options, :order_id)
+
+ post = {}
+ add_action(post, :verify, options)
+ add_amount(post, 0, options)
+ add_order(post, options[:order_id])
+ add_payment(post, creditcard)
+ add_description(post, options)
+ add_direct_payment(post, options)
+ add_threeds(post, options)
+
+ commit(post, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((PAN\"=>\")(\d+)), '\1[FILTERED]').
+ gsub(%r((CVV2\"=>\")(\d+)), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_direct_payment(post, options)
+ # Direct payment skips 3DS authentication. We should only apply this if execute_threed is false
+ # or authentication data is not present. Authentication data support to be added in the future.
+ return if options[:execute_threed] || options[:authentication_data]
+
+ post[:DS_MERCHANT_DIRECTPAYMENT] = true
+ end
+
+ def add_threeds(post, options)
+ return unless options[:execute_threed] || options[:three_ds_2]
+
+ post[:DS_MERCHANT_EMV3DS] = if options[:execute_threed]
+ { threeDSInfo: 'CardData' }
+ else
+ add_browser_info(post, options)
+ end
+ end
+
+ def add_browser_info(post, options)
+ return unless browser_info = options.dig(:three_ds_2, :browser_info)
+
+ {
+ browserAcceptHeader: browser_info[:accept_header],
+ browserUserAgent: browser_info[:user_agent],
+ browserJavaEnabled: browser_info[:java],
+ browserJavascriptEnabled: browser_info[:java],
+ browserLanguage: browser_info[:language],
+ browserColorDepth: browser_info[:depth],
+ browserScreenHeight: browser_info[:height],
+ browserScreenWidth: browser_info[:width],
+ browserTZ: browser_info[:timezone]
+ }
+ end
+
+ def add_action(post, action, options = {})
+ post[:DS_MERCHANT_TRANSACTIONTYPE] = transaction_code(action)
+ end
+
+ def add_amount(post, money, options)
+ post[:DS_MERCHANT_AMOUNT] = amount(money).to_s
+ post[:DS_MERCHANT_CURRENCY] = currency_code(options[:currency] || currency(money))
+ end
+
+ def add_description(post, options)
+ post[:DS_MERCHANT_PRODUCTDESCRIPTION] = CGI.escape(options[:description]) if options[:description]
+ end
+
+ def add_order(post, order_id)
+ post[:DS_MERCHANT_ORDER] = clean_order_id(order_id)
+ end
+
+ def add_payment(post, card)
+ name = [card.first_name, card.last_name].join(' ').slice(0, 60)
+ year = sprintf('%.4i', card.year)
+ month = sprintf('%.2i', card.month)
+ post['DS_MERCHANT_TITULAR'] = CGI.escape(name)
+ post['DS_MERCHANT_PAN'] = card.number
+ post['DS_MERCHANT_EXPIRYDATE'] = "#{year[2..3]}#{month}"
+ post['DS_MERCHANT_CVV2'] = card.verification_value if card.verification_value.present?
+ end
+
+ def determine_action(options)
+ # If execute_threed is true, we need to use iniciaPeticionREST to set up authentication
+ # Otherwise we are skipping 3DS or we should have 3DS authentication results
+ options[:execute_threed] ? 'iniciaPeticionREST' : 'trataPeticionREST'
+ end
+
+ def commit(post, options)
+ url = (test? ? test_url : live_url)
+ action = determine_action(options)
+ raw_response = parse(ssl_post(url + action, post_data(post, options)))
+ payload = raw_response['Ds_MerchantParameters']
+ return Response.new(false, "#{raw_response['errorCode']} ERROR") unless payload
+
+ response = JSON.parse(Base64.decode64(payload)).transform_keys!(&:downcase).with_indifferent_access
+ return Response.new(false, 'Unable to verify response') unless validate_signature(payload, raw_response['Ds_Signature'], response[:ds_order])
+
+ succeeded = success_from(response, options)
+ Response.new(
+ succeeded,
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: succeeded ? nil : response[:ds_response]
+ )
+ end
+
+ def post_data(post, options)
+ add_authentication(post, options)
+ merchant_parameters = JSON.generate(post)
+ encoded_parameters = Base64.strict_encode64(merchant_parameters)
+ post_data = PostData.new
+ post_data['Ds_SignatureVersion'] = 'HMAC_SHA256_V1'
+ post_data['Ds_MerchantParameters'] = encoded_parameters
+ post_data['Ds_Signature'] = sign_request(encoded_parameters, post[:DS_MERCHANT_ORDER])
+ post_data.to_post_data
+ end
+
+ def add_authentication(post, options)
+ post[:DS_MERCHANT_TERMINAL] = options[:terminal] || @options[:terminal]
+ post[:DS_MERCHANT_MERCHANTCODE] = @options[:login]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response, options)
+ return true if response[:ds_emv3ds] && options[:execute_threed]
+
+ # Need to get updated for 3DS support
+ if code = response[:ds_response]
+ (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
+ else
+ false
+ end
+ end
+
+ def message_from(response)
+ return response.dig(:ds_emv3ds, :threeDSInfo) if response[:ds_emv3ds]
+
+ code = response[:ds_response]&.to_i
+ code = 0 if code < 100
+ RESPONSE_TEXTS[code] || 'Unknown code, please check in manual'
+ end
+
+ def validate_signature(data, signature, order_number)
+ key = encrypt(@options[:secret_key], order_number)
+ Base64.urlsafe_encode64(mac256(key, data)) == signature
+ end
+
+ def authorization_from(response)
+ # Need to get updated for 3DS support
+ [response[:ds_order], response[:ds_amount], response[:ds_currency]].join('|')
+ end
+
+ def split_authorization(authorization)
+ 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
+
+ def transaction_code(type)
+ SUPPORTED_TRANSACTIONS[type]
+ end
+
+ def clean_order_id(order_id)
+ cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
+ if /^\d{4}/.match?(cleansed)
+ cleansed[0..11]
+ else
+ '%04d' % [rand(0..9999)] + cleansed[0...8]
+ end
+ end
+
+ def sign_request(encoded_parameters, order_id)
+ raise(ArgumentError, 'missing order_id') unless order_id
+
+ key = encrypt(@options[:secret_key], order_id)
+ Base64.strict_encode64(mac256(key, encoded_parameters))
+ end
+
+ def encrypt(key, order_id)
+ block_length = 8
+ cipher = OpenSSL::Cipher.new('DES3')
+ cipher.encrypt
+
+ cipher.key = Base64.urlsafe_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
+
+ cipher.update(order_id) + cipher.final
+ end
+
+ def mac256(key, data)
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/s5.rb b/lib/active_merchant/billing/gateways/s5.rb
index 4dc62423313..51acce11b6b 100644
--- a/lib/active_merchant/billing/gateways/s5.rb
+++ b/lib/active_merchant/billing/gateways/s5.rb
@@ -128,9 +128,7 @@ def add_payment(xml, money, action, options)
end
def add_account(xml, payment_method)
- if !payment_method.respond_to?(:number)
- xml.Account(registration: payment_method)
- else
+ if payment_method.respond_to?(:number)
xml.Account do
xml.Number payment_method.number
xml.Holder "#{payment_method.first_name} #{payment_method.last_name}"
@@ -138,6 +136,8 @@ def add_account(xml, payment_method)
xml.Expiry(year: payment_method.year, month: payment_method.month)
xml.Verification payment_method.verification_value
end
+ else
+ xml.Account(registration: payment_method)
end
end
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
index 49554a31454..3f522c0a1c0 100644
--- a/lib/active_merchant/billing/gateways/safe_charge.rb
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -73,10 +73,10 @@ def refund(money, authorization, options = {})
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
+ post[:sg_TransactionID] = transaction_id unless options[:unreferenced_refund]
commit(post)
end
@@ -86,8 +86,9 @@ def credit(money, payment, options = {})
add_payment(post, payment, options)
add_transaction_data('Credit', post, money, options)
+ add_customer_details(post, payment, options)
- post[:sg_CreditType] = 1
+ options[:unreferenced_refund].to_s == 'true' ? post[:sg_CreditType] = 2 : post[:sg_CreditType] = 1
commit(post)
end
@@ -148,26 +149,46 @@ def add_transaction_data(trans_type, post, money, options)
end
def add_payment(post, payment, options = {})
- post[:sg_ExpMonth] = format(payment.month, :two_digits)
- post[:sg_ExpYear] = format(payment.year, :two_digits)
- post[:sg_CardNumber] = payment.number
-
- if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
- post[:sg_CAVV] = payment.payment_cryptogram
- post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
- post[:sg_IsExternalMPI] = 1
- post[:sg_ExternalTokenProvider] = 5
- else
- post[:sg_CVV2] = payment.verification_value
- post[:sg_NameOnCard] = payment.name
- post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ case payment
+ when String
+ add_token(post, payment)
+ when CreditCard
+ post[:sg_ExpMonth] = format(payment.month, :two_digits)
+ post[:sg_ExpYear] = format(payment.year, :two_digits)
+ post[:sg_CardNumber] = payment.number
+
+ if payment.is_a?(NetworkTokenizationCreditCard) && payment.source == :network_token
+ add_network_token(post, payment, options)
+ else
+ add_credit_card(post, payment, options)
+ end
end
end
+ def add_token(post, payment)
+ _, transaction_id, token = payment.split('|')
+
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ end
+
+ def add_credit_card(post, payment, options)
+ post[:sg_CVV2] = payment.verification_value
+ post[:sg_NameOnCard] = payment.name
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ end
+
+ def add_network_token(post, payment, options)
+ post[:sg_CAVV] = payment.payment_cryptogram
+ post[:sg_ECI] = options[:three_d_secure] && options[:three_d_secure][:eci] || '05'
+ post[:sg_IsExternalMPI] = 1
+ post[:sg_ExternalTokenProvider] = 5
+ 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_FirstName] = payment.first_name if payment.respond_to?(:first_name)
+ post[:sg_LastName] = payment.last_name if payment.respond_to?(: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]
diff --git a/lib/active_merchant/billing/gateways/sage.rb b/lib/active_merchant/billing/gateways/sage.rb
index b0c56ee02e8..25817aa99f9 100644
--- a/lib/active_merchant/billing/gateways/sage.rb
+++ b/lib/active_merchant/billing/gateways/sage.rb
@@ -260,11 +260,15 @@ 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,
+ 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])
+ cvv_result: response[:cvv_result]
+ )
end
def url(params, source)
@@ -380,8 +384,12 @@ def commit(action, request)
message = success ? 'Succeeded' : 'Failed'
end
- Response.new(success, message, response,
- authorization: response[:guid])
+ Response.new(
+ success,
+ message,
+ response,
+ authorization: response[:guid]
+ )
end
ENVELOPE_NAMESPACES = {
diff --git a/lib/active_merchant/billing/gateways/sage_pay.rb b/lib/active_merchant/billing/gateways/sage_pay.rb
index 24fff459089..5e38cf6e300 100644
--- a/lib/active_merchant/billing/gateways/sage_pay.rb
+++ b/lib/active_merchant/billing/gateways/sage_pay.rb
@@ -6,8 +6,8 @@ class SagePayGateway < Gateway
class_attribute :simulator_url
- self.test_url = 'https://test.sagepay.com/gateway/service'
- self.live_url = 'https://live.sagepay.com/gateway/service'
+ self.test_url = 'https://sandbox.opayo.eu.elavon.com/gateway/service'
+ self.live_url = 'https://live.opayo.eu.elavon.com/gateway/service'
self.simulator_url = 'https://test.sagepay.com/Simulator'
APPROVED = 'OK'
@@ -78,6 +78,7 @@ class SagePayGateway < Gateway
def initialize(options = {})
requires!(options, :login)
+ @protocol_version = options.fetch(:protocol_version, '3.00')
super
end
@@ -86,6 +87,9 @@ def purchase(money, payment_method, options = {})
post = {}
+ add_override_protocol_version(options)
+ add_three_ds_data(post, options)
+ add_stored_credentials_data(post, options)
add_amount(post, money, options)
add_invoice(post, options)
add_payment_method(post, payment_method, options)
@@ -101,6 +105,9 @@ def authorize(money, payment_method, options = {})
post = {}
+ add_three_ds_data(post, options)
+ add_stored_credentials_data(post, options)
+ add_override_protocol_version(options)
add_amount(post, money, options)
add_invoice(post, options)
add_payment_method(post, payment_method, options)
@@ -115,6 +122,7 @@ def authorize(money, payment_method, options = {})
def capture(money, identification, options = {})
post = {}
+ add_override_protocol_version(options)
add_reference(post, identification)
add_release_amount(post, money, options)
@@ -124,6 +132,7 @@ def capture(money, identification, options = {})
def void(identification, options = {})
post = {}
+ add_override_protocol_version(options)
add_reference(post, identification)
action = abort_or_void_from(identification)
@@ -136,6 +145,7 @@ def refund(money, identification, options = {})
post = {}
+ add_override_protocol_version(options)
add_related_reference(post, identification)
add_amount(post, money, options)
add_invoice(post, options)
@@ -150,6 +160,7 @@ def credit(money, identification, options = {})
def store(credit_card, options = {})
post = {}
+ add_override_protocol_version(options)
add_credit_card(post, credit_card)
add_currency(post, 0, options)
@@ -158,6 +169,7 @@ def store(credit_card, options = {})
def unstore(token, options = {})
post = {}
+ add_override_protocol_version(options)
add_token(post, token)
commit(:unstore, post)
end
@@ -182,6 +194,58 @@ def scrub(transcript)
private
+ def add_override_protocol_version(options)
+ @protocol_version = options[:protocol_version] if options[:protocol_version]
+ end
+
+ def add_three_ds_data(post, options)
+ return unless @protocol_version == '4.00'
+ return unless three_ds_2_options = options[:three_ds_2]
+
+ add_pair(post, :ThreeDSNotificationURL, three_ds_2_options[:notification_url])
+ return unless three_ds_2_options[:browser_info]
+
+ add_browser_info(post, three_ds_2_options[:browser_info])
+ end
+
+ def add_browser_info(post, browser_info)
+ add_pair(post, :BrowserAcceptHeader, browser_info[:accept_header])
+ add_pair(post, :BrowserColorDepth, browser_info[:depth])
+ add_pair(post, :BrowserJavascriptEnabled, format_boolean(browser_info[:java]))
+ add_pair(post, :BrowserJavaEnabled, format_boolean(browser_info[:java]))
+ add_pair(post, :BrowserLanguage, browser_info[:language])
+ add_pair(post, :BrowserScreenHeight, browser_info[:height])
+ add_pair(post, :BrowserScreenWidth, browser_info[:width])
+ add_pair(post, :BrowserTZ, browser_info[:timezone])
+ add_pair(post, :BrowserUserAgent, browser_info[:user_agent])
+ add_pair(post, :ChallengeWindowSize, browser_info[:browser_size])
+ end
+
+ def add_stored_credentials_data(post, options)
+ return unless @protocol_version == '4.00'
+ return unless stored_credential = options[:stored_credential]
+
+ initiator = stored_credential[:initiator] == 'cardholder' ? 'CIT' : 'MIT'
+ cof_usage = if stored_credential[:initial_transaction] && initiator == 'CIT'
+ 'FIRST'
+ elsif !stored_credential[:initial_transaction] && initiator == 'MIT'
+ 'SUBSEQUENT'
+ end
+
+ add_pair(post, :COFUsage, cof_usage) if cof_usage
+ add_pair(post, :InitiatedTYPE, initiator)
+ add_pair(post, :SchemeTraceID, stored_credential[:network_transaction_id]) if stored_credential[:network_transaction_id]
+
+ reasoning = stored_credential[:reason_type] == 'installment' ? 'instalment' : stored_credential[:reason_type]
+ add_pair(post, :MITType, reasoning.upcase)
+
+ if %w(instalment recurring).any?(reasoning)
+ add_pair(post, :RecurringExpiry, options[:recurring_expiry])
+ add_pair(post, :RecurringFrequency, options[:recurring_frequency])
+ add_pair(post, :PurchaseInstalData, options[:installment_data])
+ end
+ end
+
def truncate(value, max_size)
return nil unless value
return value.to_s if CGI.escape(value.to_s).length <= max_size
@@ -346,14 +410,18 @@ def format_date(month, year)
def commit(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_CODE[response['AddressResult']],
postal_match: AVS_CODE[response['PostCodeResult']]
},
- cvv_result: CVV_CODE[response['CV2Result']])
+ cvv_result: CVV_CODE[response['CV2Result']]
+ )
end
def authorization_from(response, params, action)
@@ -401,7 +469,7 @@ def post_data(action, parameters = {})
parameters.update(
Vendor: @options[:login],
TxType: TRANSACTIONS[action],
- VPSProtocol: @options.fetch(:protocol_version, '3.00')
+ VPSProtocol: @protocol_version
)
parameters.update(ReferrerID: application_id) if application_id && (application_id != Gateway.application_id)
@@ -409,6 +477,12 @@ def post_data(action, parameters = {})
parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
+ def format_boolean(value)
+ return if value.nil?
+
+ value ? '1' : '0'
+ end
+
# SagePay returns data in the following format
# Key1=value1
# Key2=value2
diff --git a/lib/active_merchant/billing/gateways/sallie_mae.rb b/lib/active_merchant/billing/gateways/sallie_mae.rb
index af13651eae0..fa7f1f9f8c3 100644
--- a/lib/active_merchant/billing/gateways/sallie_mae.rb
+++ b/lib/active_merchant/billing/gateways/sallie_mae.rb
@@ -120,9 +120,13 @@ def commit(action, money, parameters)
end
response = parse(ssl_post(self.live_url, parameters.to_post_data) || '')
- Response.new(successful?(response), message_from(response), response,
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: response['refcode'])
+ authorization: response['refcode']
+ )
end
def successful?(response)
diff --git a/lib/active_merchant/billing/gateways/secure_net.rb b/lib/active_merchant/billing/gateways/secure_net.rb
index 474babbd600..a82a8bc2ec4 100644
--- a/lib/active_merchant/billing/gateways/secure_net.rb
+++ b/lib/active_merchant/billing/gateways/secure_net.rb
@@ -79,11 +79,15 @@ def commit(request)
data = ssl_post(url, xml, 'Content-Type' => 'text/xml')
response = parse(data)
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
authorization: build_authorization(response),
avs_result: { code: response[:avs_result_code] },
- cvv_result: response[:card_code_response_code])
+ cvv_result: response[:card_code_response_code]
+ )
end
def build_request(request)
diff --git a/lib/active_merchant/billing/gateways/secure_pay.rb b/lib/active_merchant/billing/gateways/secure_pay.rb
index 2ad96517097..e20a326e6c7 100644
--- a/lib/active_merchant/billing/gateways/secure_pay.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay.rb
@@ -56,12 +56,16 @@ def commit(action, money, parameters)
message = message_from(response)
- Response.new(success?(response), message, 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])
+ cvv_result: response[:card_code]
+ )
end
def success?(response)
@@ -75,7 +79,7 @@ def fraud_review?(response)
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],
@@ -85,7 +89,6 @@ def parse(body)
authorization_code: fields[AUTHORIZATION_CODE],
cardholder_authentication_code: fields[CARDHOLDER_AUTH_CODE]
}
- results
end
def post_data(action, parameters = {})
@@ -101,8 +104,7 @@ def post_data(action, parameters = {})
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('&')
end
def add_currency_code(post, money, options)
diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb
index fc993767a80..13f37c96254 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_au.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb
@@ -183,9 +183,13 @@ def build_request(action, body)
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,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def build_periodic_item(action, money, credit_card, options)
@@ -239,9 +243,13 @@ def commit_periodic(request)
my_request = build_periodic_request(request)
response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request))
- Response.new(success?(response), message_from(response), response,
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
test: test?,
- authorization: authorization_from(response))
+ authorization: authorization_from(response)
+ )
end
def success?(response)
diff --git a/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
index a866bddb17b..80f26087b9c 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_tech.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
@@ -84,9 +84,13 @@ def parse(body)
def commit(action, post)
response = parse(ssl_post(self.live_url, post_data(action, post)))
- Response.new(response[:result_code] == 1, message_from(response), response,
+ Response.new(
+ response[:result_code] == 1,
+ message_from(response),
+ response,
test: test?,
- authorization: response[:merchant_transaction_reference])
+ authorization: response[:merchant_transaction_reference]
+ )
end
def message_from(result)
diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb
index 8b63029b6c0..3d7225f37da 100644
--- a/lib/active_merchant/billing/gateways/securion_pay.rb
+++ b/lib/active_merchant/billing/gateways/securion_pay.rb
@@ -193,7 +193,8 @@ def add_creditcard(post, creditcard, options)
post[:card] = card
add_address(post, options)
elsif creditcard.kind_of?(String)
- post[:card] = creditcard
+ key = creditcard.match(/^pm_/) ? :paymentMethod : :card
+ post[key] = creditcard
else
raise ArgumentError.new("Unhandled payment method #{creditcard.class}.")
end
@@ -223,24 +224,37 @@ def commit(url, parameters = nil, options = {}, method = nil)
end
response = api_request(url, parameters, options, method)
- success = !response.key?('error')
+ success = success?(response)
- Response.new(success,
+ 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']]))
+ authorization: authorization_from(url, response),
+ error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']])
+ )
+ end
+
+ def authorization_from(action, response)
+ if action == 'customers' && success?(response) && response['cards'].present?
+ response['cards'].first['id']
+ else
+ success?(response) ? response['id'] : (response.dig('error', 'charge') || response.dig('error', 'chargeId'))
+ end
+ end
+
+ def success?(response)
+ !response.key?('error')
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)
@@ -287,8 +301,8 @@ def api_request(endpoint, parameters = nil, options = {}, method = nil)
response
end
- def json_error(raw_response)
- msg = 'Invalid response received from the SecurionPay API.'
+ def json_error(raw_response, gateway_name = 'SecurionPay')
+ msg = "Invalid response received from the #{gateway_name} API."
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{
'error' => {
@@ -298,7 +312,7 @@ def json_error(raw_response)
end
def test?
- (@options[:secret_key]&.include?('_test_'))
+ @options[:secret_key]&.include?('_test_')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb
index 7d638542355..407ca4253d3 100644
--- a/lib/active_merchant/billing/gateways/shift4.rb
+++ b/lib/active_merchant/billing/gateways/shift4.rb
@@ -20,22 +20,6 @@ class Shift4Gateway < Gateway
'add' => 'tokens',
'verify' => 'cards'
}
- 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, :client_guid, :auth_token)
@@ -91,7 +75,7 @@ def capture(money, authorization, options = {})
commit(action, post, options)
end
- def refund(money, authorization, options = {})
+ def refund(money, payment_method, options = {})
post = {}
action = 'refund'
@@ -99,12 +83,15 @@ def refund(money, authorization, options = {})
add_invoice(post, money, options)
add_clerk(post, options)
add_transaction(post, options)
- add_card(action, post, get_card_token(authorization), options)
+ card_token = payment_method.is_a?(CreditCard) ? get_card_token(payment_method) : payment_method
+ add_card(action, post, card_token, options)
add_card_present(post, options)
commit(action, post, options)
end
+ alias credit refund
+
def void(authorization, options = {})
options[:invoice] = get_invoice(authorization)
commit('invoice', {}, options)
@@ -144,7 +131,7 @@ def scrub(transcript)
gsub(%r(("expirationDate\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
gsub(%r(("FirstName\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
gsub(%r(("LastName\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
- gsub(%r(("securityCode\\?":{\\?"[\w]+\\?":[\d]+,\\?"value\\?":\\?")[\d]*)i, '\1[FILTERED]')
+ gsub(%r(("securityCode\\?":{\\?"\w+\\?":\d+,\\?"value\\?":\\?")\d*)i, '\1[FILTERED]')
end
def setup_access_token
@@ -153,6 +140,8 @@ def setup_access_token
add_datetime(post, options)
response = commit('accesstoken', post, request_headers('accesstoken', options))
+ raise OAuthResponseError.new(response, response.params.fetch('result', [{}]).first.dig('error', 'longText')) unless response.success?
+
response.params['result'].first['credential']['accessToken']
end
@@ -183,6 +172,7 @@ def add_transaction(post, options)
post[:transaction] = {}
post[:transaction][:invoice] = options[:invoice] || Time.new.to_i.to_s[1..3] + rand.to_s[2..7]
post[:transaction][:notes] = options[:notes] if options[:notes].present?
+ post[:transaction][:vendorReference] = options[:order_id]
add_purchase_card(post[:transaction], options)
add_card_on_file(post[:transaction], options)
@@ -257,6 +247,7 @@ def commit(action, parameters, option)
message_from(action, response),
response,
authorization: authorization_from(action, response),
+ avs_result: avs_result_from(response),
test: test?,
error_code: error_code_from(action, response)
)
@@ -278,13 +269,25 @@ def parse(body)
end
def message_from(action, response)
- success_from(action, response) ? 'Transaction successful' : (error(response)&.dig('longText') || 'Transaction declined')
+ success_from(action, response) ? 'Transaction successful' : (error(response)&.dig('longText') || response['result'].first&.dig('transaction', 'hostResponse', 'reasonDescription') || 'Transaction declined')
end
def error_code_from(action, response)
- return unless success_from(action, response)
+ code = response['result'].first&.dig('transaction', 'responseCode')
+ primary_code = response['result'].first['error'].present?
+ return unless code == 'D' || primary_code == true || success_from(action, response)
+
+ if response['result'].first&.dig('transaction', 'hostResponse')
+ response['result'].first&.dig('transaction', 'hostResponse', 'reasonCode')
+ elsif response['result'].first['error']
+ response['result'].first&.dig('error', 'primaryCode')
+ else
+ response['result'].first&.dig('transaction', 'responseCode')
+ end
+ end
- STANDARD_ERROR_CODE_MAPPING[response['primaryCode']]
+ def avs_result_from(response)
+ AVSResult.new(code: response['result'].first&.dig('transaction', 'avs', 'result')) if response['result'].first&.dig('transaction', 'avs')
end
def authorization_from(action, response)
@@ -306,7 +309,7 @@ def get_invoice(authorization)
def request_headers(action, options)
headers = {
- 'Content-Type' => 'application/x-www-form-urlencoded'
+ 'Content-Type' => 'application/json'
}
headers['AccessToken'] = @access_token
headers['Invoice'] = options[:invoice] if action != 'capture' && options[:invoice].present?
diff --git a/lib/active_merchant/billing/gateways/shift4_v2.rb b/lib/active_merchant/billing/gateways/shift4_v2.rb
new file mode 100644
index 00000000000..7af6542ec27
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/shift4_v2.rb
@@ -0,0 +1,117 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class Shift4V2Gateway < SecurionPayGateway
+ # same endpont for testing
+ self.live_url = 'https://api.shift4.com/'
+ self.display_name = 'Shift4'
+ self.homepage_url = 'https://dev.shift4.com/us/'
+
+ def credit(money, payment, options = {})
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ commit('credits', post, options)
+ end
+
+ def store(payment_method, options = {})
+ post = case payment_method
+ when CreditCard
+ cc = {}.tap { |card| add_creditcard(card, payment_method, options) }[:card]
+ options[:customer_id].blank? ? { email: options[:email], card: cc } : cc
+ when Check
+ bank_account_object(payment_method, options)
+ else
+ raise ArgumentError.new("Unhandled payment method #{payment_method.class}.")
+ end
+
+ commit url_for_store(payment_method, options), post, options
+ end
+
+ def url_for_store(payment_method, options = {})
+ case payment_method
+ when CreditCard
+ options[:customer_id].blank? ? 'customers' : "customers/#{options[:customer_id]}/cards"
+ when Check then 'payment-methods'
+ end
+ end
+
+ def unstore(reference, options = {})
+ commit("customers/#{options[:customer_id]}/cards/#{reference}", nil, options, :delete)
+ end
+
+ def create_post_for_auth_or_purchase(money, payment, options)
+ super.tap do |post|
+ add_stored_credentials(post, options)
+ end
+ end
+
+ def add_stored_credentials(post, options)
+ return unless options[:stored_credential].present?
+
+ initiator = options.dig(:stored_credential, :initiator)
+ reason_type = options.dig(:stored_credential, :reason_type)
+
+ post_type = {
+ %w[cardholder recurring] => 'first_recurring',
+ %w[merchant recurring] => 'subsequent_recurring',
+ %w[cardholder unscheduled] => 'customer_initiated',
+ %w[merchant installment] => 'merchant_initiated'
+ }[[initiator, reason_type]]
+ post[:type] = post_type if post_type
+ end
+
+ def headers(options = {})
+ super.tap do |headers|
+ headers['User-Agent'] = "Shift4/v2 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
+ end
+ end
+
+ def scrub(transcript)
+ super.
+ gsub(%r((card\[expMonth\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[expYear\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[cardholderName\]=)\w+[^ ]\w+), '\1[FILTERED]')
+ end
+
+ def json_error(raw_response)
+ super(raw_response, 'Shift4 V2')
+ end
+
+ def add_amount(post, money, options, include_currency = false)
+ super
+ post[:currency]&.upcase!
+ end
+
+ def add_creditcard(post, payment_method, options)
+ return super unless payment_method.is_a?(Check)
+
+ post[:paymentMethod] = bank_account_object(payment_method, options)
+ end
+
+ def bank_account_object(payment_method, options)
+ {
+ type: :ach,
+ fraudCheckData: {
+ ipAddress: options[:ip],
+ email: options[:email]
+ }.compact,
+ billing: {
+ name: payment_method.name,
+ address: { country: options.dig(:billing_address, :country) }
+ }.compact,
+ ach: {
+ account: {
+ routingNumber: payment_method.routing_number,
+ accountNumber: payment_method.account_number,
+ accountType: get_account_type(payment_method)
+ },
+ verificationProvider: :external
+ }
+ }
+ end
+
+ def get_account_type(check)
+ holder = (check.account_holder_type || '').match(/business/i) ? :corporate : :personal
+ "#{holder}_#{check.account_type}"
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb
index f8c34654200..877a2cdcf30 100644
--- a/lib/active_merchant/billing/gateways/simetrik.rb
+++ b/lib/active_merchant/billing/gateways/simetrik.rb
@@ -44,7 +44,7 @@ class SimetrikGateway < Gateway
def initialize(options = {})
requires!(options, :client_id, :client_secret)
super
- @access_token = {}
+ @access_token = options[:access_token] || {}
sign_access_token()
end
@@ -280,12 +280,12 @@ def parse(body)
def commit(action, parameters, url_params = {})
begin
response = JSON.parse ssl_post(url(action, url_params), post_data(parameters), authorized_headers())
- rescue ResponseError => exception
- case exception.response.code.to_i
+ rescue ResponseError => e
+ case e.response.code.to_i
when 400...499
- response = JSON.parse exception.response.body
+ response = JSON.parse e.response.body
else
- raise exception
+ raise e
end
end
@@ -318,7 +318,7 @@ def message_from(response)
end
def url(action, url_params)
- "#{(test? ? test_url : live_url)}/#{url_params[:token_acquirer]}/#{action}"
+ "#{test? ? test_url : live_url}/#{url_params[:token_acquirer]}/#{action}"
end
def post_data(data = {})
@@ -356,12 +356,18 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
- response = parse(ssl_post(auth_url(), login_info.to_json, {
- 'content-Type' => 'application/json'
- }))
- @access_token[:access_token] = response['access_token']
- @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ begin
+ raw_response = ssl_post(auth_url(), login_info.to_json, {
+ 'content-Type' => 'application/json'
+ })
+ rescue ResponseError => e
+ raise OAuthResponseError.new(e)
+ else
+ response = parse(raw_response)
+ @access_token[:access_token] = response['access_token']
+ @access_token[:expires_at] = Time.new.to_i + response['expires_in']
+ 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 8f8851808e1..effe78d837b 100644
--- a/lib/active_merchant/billing/gateways/skip_jack.rb
+++ b/lib/active_merchant/billing/gateways/skip_jack.rb
@@ -263,11 +263,15 @@ def commit(action, money, parameters)
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,
+ Response.new(
+ response[:success],
+ message_from(response, action),
+ response,
test: test?,
authorization: response[:szTransactionFileName] || parameters[:szTransactionId],
avs_result: { code: response[:szAVSResponseCode] },
- cvv_result: response[:szCVV2ResponseCode])
+ cvv_result: response[:szCVV2ResponseCode]
+ )
end
def url_for(action)
diff --git a/lib/active_merchant/billing/gateways/smart_ps.rb b/lib/active_merchant/billing/gateways/smart_ps.rb
index 1c63532b9e0..d8180010709 100644
--- a/lib/active_merchant/billing/gateways/smart_ps.rb
+++ b/lib/active_merchant/billing/gateways/smart_ps.rb
@@ -226,11 +226,15 @@ def parse(body)
def commit(action, money, parameters)
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,
+ 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'] })
+ avs_result: { code: response['avsresponse'] }
+ )
end
def expdate(creditcard)
@@ -257,8 +261,7 @@ 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('&')
end
def determine_funding_source(source)
diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb
index 16b5ef79297..f13a3e6d4cc 100644
--- a/lib/active_merchant/billing/gateways/so_easy_pay.rb
+++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb
@@ -161,11 +161,13 @@ def commit(soap_action, soap, options)
'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',
+ return Response.new(
+ response['errorcode'] == '000',
response['errormessage'],
response,
test: test?,
- authorization: response['transaction_id'])
+ authorization: response['transaction_id']
+ )
end
def build_soap(request)
diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb
index a286892086f..1e2a4715a3c 100644
--- a/lib/active_merchant/billing/gateways/spreedly_core.rb
+++ b/lib/active_merchant/billing/gateways/spreedly_core.rb
@@ -271,11 +271,9 @@ def childnode_to_response(response, node, childnode)
end
end
- def build_xml_request(root)
+ def build_xml_request(root, &block)
builder = Nokogiri::XML::Builder.new
- builder.__send__(root) do |doc|
- yield(doc)
- end
+ builder.__send__(root, &block)
builder.to_xml
end
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index e85d0ece147..17bc8c5035c 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -7,14 +7,16 @@ module Billing #:nodoc:
class StripeGateway < Gateway
self.live_url = 'https://api.stripe.com/v1/'
+ # Docs on AVS codes: https://en.wikipedia.org/w/index.php?title=Address_verification_service&_ga=2.97570079.1027215965.1655989706-2008268124.1655989706#AVS_response_codes
+ # possible response values: https://stripe.com/docs/api/payment_methods/object#payment_method_object-card-checks
AVS_CODE_TRANSLATOR = {
- 'line1: pass, zip: pass' => 'Y',
'line1: pass, zip: fail' => 'A',
'line1: pass, zip: unchecked' => 'B',
- 'line1: fail, zip: pass' => 'Z',
+ 'line1: unchecked, zip: unchecked' => 'I',
'line1: fail, zip: fail' => 'N',
'line1: unchecked, zip: pass' => 'P',
- 'line1: unchecked, zip: unchecked' => 'I'
+ 'line1: pass, zip: pass' => 'Y',
+ 'line1: fail, zip: pass' => 'Z'
}
CVC_CODE_TRANSLATOR = {
@@ -292,7 +294,9 @@ def scrub(transcript)
gsub(%r(((\[card\]|card)\[number\]=)\d+), '\1[FILTERED]').
gsub(%r(((\[card\]|card)\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\3').
gsub(%r(((\[bank_account\]|bank_account)\[account_number\]=)\d+), '\1[FILTERED]').
- gsub(%r(((\[payment_method_data\]|payment_method_data)\[card\]\[token\]=)[^&]+(&?)), '\1[FILTERED]\3')
+ gsub(%r(((\[payment_method_data\]|payment_method_data)\[card\]\[token\]=)[^&]+(&?)), '\1[FILTERED]\3').
+ gsub(%r(((\[payment_method_data\]|payment_method_data)\[card\]\[network_token\]\[number\]=)\d+), '\1[FILTERED]').
+ gsub(%r(((\[payment_method_options\]|payment_method_options)\[card\]\[network_token\]\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]')
end
def supports_network_tokenization?
@@ -577,7 +581,7 @@ def add_shipping_address(post, payment, options = {})
def add_source_owner(post, creditcard, options)
post[:owner] = {}
- post[:owner][:name] = creditcard.name if creditcard.name
+ post[:owner][:name] = creditcard.name if creditcard.respond_to?(:name) && creditcard.name
post[:owner][:email] = options[:email] if options[:email]
if address = options[:billing_address] || options[:address]
@@ -652,18 +656,19 @@ def flatten_array(flattened, array, prefix)
end
end
- def headers(options = {})
- key = options[:key] || @api_key
- idempotency_key = options[:idempotency_key]
+ def key(options = {})
+ options[:key] || @api_key
+ end
+ def headers(options = {})
headers = {
- 'Authorization' => 'Basic ' + Base64.strict_encode64(key.to_s + ':').strip,
+ 'Authorization' => 'Basic ' + Base64.strict_encode64(key(options).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['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
headers['Stripe-Account'] = options[:stripe_account] if options[:stripe_account]
headers
end
@@ -694,31 +699,46 @@ def api_request(method, endpoint, parameters = nil, options = {})
def commit(method, url, parameters = nil, options = {})
add_expand_parameters(parameters, options) if parameters
+ return Response.new(false, 'Invalid API Key provided') unless key_valid?(options)
+
response = api_request(method, url, parameters, options)
response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
success = success_from(response, options)
- 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']]
- Response.new(success,
+ card_checks = card_from_response(response)
+ avs_code = AVS_CODE_TRANSLATOR["line1: #{card_checks['address_line1_check']}, zip: #{card_checks['address_zip_check'] || card_checks['address_postal_code_check']}"]
+ cvc_code = CVC_CODE_TRANSLATOR[card_checks['cvc_check']]
+ Response.new(
+ success,
message_from(success, response),
response,
test: response_is_test?(response),
- authorization: authorization_from(success, url, method, response),
+ authorization: authorization_from(success, url, method, response, options),
avs_result: { code: avs_code },
cvv_result: cvc_code,
emv_authorization: emv_authorization_from_response(response),
- error_code: success ? nil : error_code_from(response))
+ error_code: success ? nil : error_code_from(response)
+ )
+ end
+
+ def key_valid?(options)
+ return true unless test?
+
+ %w(sk rk).each do |k|
+ return false if key(options).start_with?(k) && !key(options).start_with?("#{k}_test")
+ end
+
+ true
end
- def authorization_from(success, url, method, response)
- return response.fetch('error', {})['charge'] unless success
+ def authorization_from(success, url, method, response, options)
+ return response.dig('error', 'charge') || response.dig('error', 'setup_intent', 'id') || response['id'] 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('|')
+ elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/) || options[:action] == :store)
+ response_id = options[:action] == :store ? response['payment_method'] : response['id']
+ [response['customer'], response_id].join('|')
else
response['id']
end
@@ -767,7 +787,10 @@ def quickchip_payment?(payment)
end
def card_from_response(response)
- response['card'] || response['active_card'] || response['source'] || {}
+ # StripePI puts the AVS and CVC check significantly deeper into the response object
+ response['card'] || response['active_card'] || response['source'] ||
+ response.dig('charges', 'data', 0, 'payment_method_details', 'card', 'checks') ||
+ response.dig('latest_attempt', 'payment_method_details', 'card', 'checks') || {}
end
def emv_authorization_from_response(response)
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
index 7135cf7d2a0..4a9582aced1 100644
--- a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -11,10 +11,15 @@ class StripePaymentIntentsGateway < StripeGateway
CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session]
UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor_suffix statement_descriptor receipt_email setup_future_usage]
DEFAULT_API_VERSION = '2020-08-27'
+ DIGITAL_WALLETS = {
+ apple_pay: 'apple_pay',
+ google_pay: 'google_pay_dpan',
+ untokenized_google_pay: 'google_pay_ecommerce_token'
+ }
def create_intent(money, payment_method, options = {})
MultiResponse.run do |r|
- if payment_method.is_a?(NetworkTokenizationCreditCard)
+ if payment_method.is_a?(NetworkTokenizationCreditCard) && digital_wallet_payment_method?(payment_method) && options[:new_ap_gp_route] != true
r.process { tokenize_apple_google(payment_method, options) }
payment_method = (r.params['token']['id']) if r.success?
end
@@ -25,24 +30,32 @@ def create_intent(money, payment_method, options = {})
add_confirmation_method(post, options)
add_customer(post, options)
- result = add_payment_method_token(post, payment_method, options)
- return result if result.is_a?(ActiveMerchant::Billing::Response)
+ if new_apple_google_pay_flow(payment_method, options)
+ add_digital_wallet(post, payment_method, options)
+ add_billing_address(post, payment_method, options)
+ else
+ result = add_payment_method_token(post, payment_method, options)
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
+ end
+ add_network_token_cryptogram_and_eci(post, payment_method)
add_external_three_d_secure_auth_data(post, options)
add_metadata(post, options)
add_return_url(post, options)
add_connected_account(post, options)
add_radar_data(post, options)
add_shipping_address(post, options)
+ add_stored_credentials(post, options)
setup_future_usage(post, options)
add_exemption(post, options)
- add_stored_credentials(post, options)
add_ntid(post, options)
add_claim_without_transaction_id(post, options)
add_error_on_requires_action(post, options)
add_fulfillment_date(post, options)
request_three_d_secure(post, options)
add_level_three(post, options)
+ add_card_brand(post, options)
+ post[:expand] = ['charges.data.balance_transaction']
CREATE_INTENT_ATTRIBUTES.each do |attribute|
add_whitelisted_attribute(post, options, attribute)
@@ -56,10 +69,19 @@ def show_intent(intent_id, options)
commit(:get, "payment_intents/#{intent_id}", nil, options)
end
+ def create_test_customer
+ response = api_request(:post, 'customers')
+ response['id']
+ end
+
def confirm_intent(intent_id, payment_method, options = {})
post = {}
- result = add_payment_method_token(post, payment_method, options)
- return result if result.is_a?(ActiveMerchant::Billing::Response)
+ if new_apple_google_pay_flow(payment_method, options)
+ add_digital_wallet(post, payment_method, options)
+ else
+ result = add_payment_method_token(post, payment_method, options)
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
+ end
add_payment_method_types(post, options)
CONFIRM_INTENT_ATTRIBUTES.each do |attribute|
@@ -71,22 +93,33 @@ def confirm_intent(intent_id, payment_method, options = {})
def create_payment_method(payment_method, options = {})
post_data = add_payment_method_data(payment_method, options)
-
options = format_idempotency_key(options, 'pm')
commit(:post, 'payment_methods', post_data, options)
end
+ def new_apple_google_pay_flow(payment_method, options)
+ return false unless options[:new_ap_gp_route]
+
+ payment_method.is_a?(NetworkTokenizationCreditCard) && digital_wallet_payment_method?(payment_method)
+ end
+
def add_payment_method_data(payment_method, options = {})
- post_data = {}
- post_data[:type] = 'card'
- post_data[:card] = {}
- post_data[:card][:number] = payment_method.number
- post_data[:card][:exp_month] = payment_method.month
- post_data[:card][:exp_year] = payment_method.year
- post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
- add_billing_address(post_data, options)
- add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
- post_data
+ post = {
+ type: 'card',
+ card: {
+ exp_month: payment_method.month,
+ exp_year: payment_method.year
+ }
+ }
+ post[:card][:number] = payment_method.number unless adding_network_token_card_data?(payment_method)
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
+ if billing = options[:billing_address] || options[:address]
+ post[:billing_details] = add_address(billing, options)
+ end
+
+ add_name_only(post, payment_method) if post[:billing_details].nil?
+ add_network_token_data(post, payment_method, options)
+ post
end
def add_payment_method_card_data_token(post_data, payment_method)
@@ -100,8 +133,12 @@ def update_intent(money, intent_id, payment_method, options = {})
post = {}
add_amount(post, money, options)
- result = add_payment_method_token(post, payment_method, options)
- return result if result.is_a?(ActiveMerchant::Billing::Response)
+ if new_apple_google_pay_flow(payment_method, options)
+ add_digital_wallet(post, payment_method, options)
+ else
+ result = add_payment_method_token(post, payment_method, options)
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
+ end
add_payment_method_types(post, options)
add_customer(post, options)
@@ -117,20 +154,32 @@ def update_intent(money, intent_id, payment_method, options = {})
end
def create_setup_intent(payment_method, options = {})
- post = {}
- add_customer(post, options)
- result = add_payment_method_token(post, payment_method, options)
- return result if result.is_a?(ActiveMerchant::Billing::Response)
+ MultiResponse.run do |r|
+ r.process do
+ post = {}
+ add_customer(post, options)
- add_metadata(post, options)
- add_return_url(post, options)
- add_fulfillment_date(post, options)
- request_three_d_secure(post, options)
- post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
- post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
- post[:description] = options[:description] if options[:description]
+ if new_apple_google_pay_flow(payment_method, options)
+ add_digital_wallet(post, payment_method, options)
+ add_billing_address(post, payment_method, options)
+ else
+ result = add_payment_method_token(post, payment_method, options, r)
+ return result if result.is_a?(ActiveMerchant::Billing::Response)
+ end
- commit(:post, 'setup_intents', post, options)
+ add_metadata(post, options)
+ add_return_url(post, options)
+ add_fulfillment_date(post, options)
+ request_three_d_secure(post, options)
+ add_card_brand(post, options)
+ post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
+ post[:usage] = options[:usage] if %w(on_session off_session).include?(options[:usage])
+ post[:description] = options[:description] if options[:description]
+ post[:expand] = ['latest_attempt']
+
+ commit(:post, 'setup_intents', post, options)
+ end
+ end
end
def retrieve_setup_intent(setup_intent_id, options = {})
@@ -196,23 +245,16 @@ def refund(money, intent_id, options = {})
# 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)
+ if new_apple_google_pay_flow(payment_method, options)
+ options[:customer] = customer(payment_method, options).params['id'] unless options[:customer]
+ verify(payment_method, options.merge!(action: :store))
+ elsif payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
result = add_payment_method_token(params, payment_method, options)
return result if result.is_a?(ActiveMerchant::Billing::Response)
- if options[:customer]
- customer_id = options[:customer]
- else
- post[:description] = options[:description] if options[:description]
- post[:email] = options[:email] if options[:email]
- options = format_idempotency_key(options, 'customer')
- post[:expand] = [:sources]
- customer = commit(:post, 'customers', post, options)
- customer_id = customer.params['id']
- end
+ customer_id = options[:customer] || customer(payment_method, options).params['id']
options = format_idempotency_key(options, 'attach')
attach_parameters = { customer: customer_id }
attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
@@ -222,6 +264,24 @@ def store(payment_method, options = {})
end
end
+ def customer(payment, options)
+ post = {}
+ post[:description] = options[:description] if options[:description]
+ post[:expand] = [:sources]
+ post[:email] = options[:email]
+
+ if billing = options[:billing_address] || options[:address]
+ post.merge!(add_address(billing, options))
+ end
+
+ if shipping = options[:shipping_address]
+ post[:shipping] = add_address(shipping, options).except(:email)
+ end
+
+ options = format_idempotency_key(options, 'customer')
+ commit(:post, 'customers', post, options)
+ end
+
def unstore(identification, options = {}, deprecated_options = {})
if identification.include?('pm_')
_, payment_method = identification.split('|')
@@ -232,7 +292,7 @@ def unstore(identification, options = {}, deprecated_options = {})
end
def verify(payment_method, options = {})
- create_setup_intent(payment_method, options.merge!(confirm: true))
+ create_setup_intent(payment_method, options.merge!({ confirm: true, verify: true }))
end
def setup_purchase(money, options = {})
@@ -245,8 +305,22 @@ def setup_purchase(money, options = {})
commit(:post, 'payment_intents', post, options)
end
+ def supports_network_tokenization?
+ true
+ end
+
private
+ def digital_wallet_payment_method?(payment_method)
+ payment_method.source == :google_pay || payment_method.source == :apple_pay
+ end
+
+ def adding_network_token_card_data?(payment_method)
+ return true if payment_method.is_a?(ActiveMerchant::Billing::NetworkTokenizationCreditCard) && payment_method.source == :network_token
+
+ false
+ end
+
def off_session_request?(options = {})
(options[:off_session] || options[:setup_future_usage]) && options[:confirm] == true
end
@@ -285,6 +359,14 @@ def add_metadata(post, options = {})
post[:metadata][:event_type] = options[:event_type] if options[:event_type]
end
+ def add_card_brand(post, options)
+ return unless options[:card_brand]
+
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:network] = options[:card_brand] if options[:card_brand]
+ end
+
def add_level_three(post, options = {})
level_three = {}
@@ -305,7 +387,7 @@ def add_return_url(post, options)
post[:return_url] = options[:return_url] if options[:return_url]
end
- def add_payment_method_token(post, payment_method, options)
+ def add_payment_method_token(post, payment_method, options, responses = [])
case payment_method
when StripePaymentToken
post[:payment_method_data] = {
@@ -318,7 +400,75 @@ def add_payment_method_token(post, payment_method, options)
when String
extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
when ActiveMerchant::Billing::CreditCard
- get_payment_method_data_from_card(post, payment_method, options)
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) if options[:verify]
+
+ get_payment_method_data_from_card(post, payment_method, options, responses)
+ when ActiveMerchant::Billing::NetworkTokenizationCreditCard
+ get_payment_method_data_from_card(post, payment_method, options, responses)
+ end
+ end
+
+ def add_network_token_data(post_data, payment_method, options)
+ return unless adding_network_token_card_data?(payment_method)
+
+ post_data[:card] ||= {}
+ post_data[:card][:last4] = options[:last_4]
+ post_data[:card][:network_token] = {}
+ post_data[:card][:network_token][:number] = payment_method.number
+ post_data[:card][:network_token][:exp_month] = payment_method.month
+ post_data[:card][:network_token][:exp_year] = payment_method.year
+ post_data[:card][:network_token][:payment_account_reference] = options[:payment_account_reference] if options[:payment_account_reference]
+
+ post_data
+ end
+
+ def add_network_token_cryptogram_and_eci(post, payment_method)
+ return unless adding_network_token_card_data?(payment_method)
+
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:network_token] ||= {}
+ post[:payment_method_options][:card][:network_token][:cryptogram] = payment_method.payment_cryptogram if payment_method.payment_cryptogram
+ post[:payment_method_options][:card][:network_token][:electronic_commerce_indicator] = payment_method.eci if payment_method.eci
+ end
+
+ def add_digital_wallet(post, payment_method, options)
+ post[:payment_method_data] = {
+ type: 'card',
+ card: {
+ last4: options[:last_4] || payment_method.number[-4..],
+ exp_month: payment_method.month,
+ exp_year: payment_method.year,
+ network_token: {
+ number: payment_method.number,
+ exp_month: payment_method.month,
+ exp_year: payment_method.year
+ }
+ }
+ }
+
+ add_cryptogram_and_eci(post, payment_method, options) unless options[:wallet_type]
+ source = payment_method.respond_to?(:source) ? payment_method.source : options[:wallet_type]
+ post[:payment_method_data][:card][:network_token][:tokenization_method] = DIGITAL_WALLETS[source]
+ end
+
+ def add_cryptogram_and_eci(post, payment_method, options)
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:network_token] ||= {}
+ post[:payment_method_options][:card][:network_token] = {
+ cryptogram: payment_method.respond_to?(:payment_cryptogram) ? payment_method.payment_cryptogram : options[:cryptogram],
+ electronic_commerce_indicator: format_eci(payment_method, options)
+ }.compact
+ end
+
+ def format_eci(payment_method, options)
+ eci_value = payment_method.respond_to?(:eci) ? payment_method.eci : options[:eci]
+
+ if eci_value&.length == 1
+ "0#{eci_value}"
+ else
+ eci_value
end
end
@@ -347,6 +497,7 @@ def tokenize_apple_google(payment, options = {})
cryptogram: payment.payment_cryptogram
}
}
+ add_billing_address_for_card_tokenization(post, options) if %i(apple_pay android_pay).include?(tokenization_method)
token_response = api_request(:post, 'tokens', post, options)
success = token_response['error'].nil?
if success && token_response['id']
@@ -358,16 +509,17 @@ def tokenize_apple_google(payment, options = {})
end
end
- def get_payment_method_data_from_card(post, payment_method, options)
- return create_payment_method_and_extract_token(post, payment_method, options) unless off_session_request?(options)
+ def get_payment_method_data_from_card(post, payment_method, options, responses)
+ return create_payment_method_and_extract_token(post, payment_method, options, responses) unless off_session_request?(options) || adding_network_token_card_data?(payment_method)
post[:payment_method_data] = add_payment_method_data(payment_method, options)
end
- def create_payment_method_and_extract_token(post, payment_method, options)
+ def create_payment_method_and_extract_token(post, payment_method, options, responses)
payment_method_response = create_payment_method(payment_method, options)
return payment_method_response if payment_method_response.failure?
+ responses << payment_method_response
add_payment_method_token(post, payment_method_response.params['id'], options)
end
@@ -386,24 +538,78 @@ def add_exemption(post, options = {})
post[:payment_method_options][:card][:moto] = true if options[:moto]
end
- # Stripe Payment Intents does not pass any parameters for cardholder/merchant initiated
- # it also does not support installments for any country other than Mexico (reason for this is unknown)
- # The only thing that Stripe PI requires for stored credentials to work currently is the network_transaction_id
- # network_transaction_id is created when the card is authenticated using the field `setup_for_future_usage` with the value `off_session` see def setup_future_usage below
+ # Stripe Payment Intents now supports specifying on a transaction level basis stored credential information.
+ # The feature is currently gated but is listed as `stored_credential_transaction_type` inside the
+ # `post[:payment_method_options][:card]` hash. Since this is a beta field adding an extra check to use
+ # the existing logic by default. To be able to utilize this field, you must reach out to Stripe.
def add_stored_credentials(post, options = {})
- return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
-
stored_credential = options[:stored_credential]
+ return unless stored_credential && !stored_credential.values.all?(&:nil?)
+
post[:payment_method_options] ||= {}
post[:payment_method_options][:card] ||= {}
- post[:payment_method_options][:card][:mit_exemption] = {}
+
+ card_options = post[:payment_method_options][:card]
+ card_options[:mit_exemption] = {}
# Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
# The network_transaction_id can be sent in nested under stored credentials OR as its own field (add_ntid handles when it is sent in on its own)
# If it is sent is as its own field AND under stored credentials, the value sent under its own field is what will send.
- post[:payment_method_options][:card][:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
- post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ card_options[:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
+ card_options[:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if !(options[:setup_future_usage] == 'off_session') && (stored_credential[:network_transaction_id])
+
+ add_stored_credential_transaction_type(post, options)
+ end
+
+ def add_stored_credential_transaction_type(post, options = {})
+ return unless options[:stored_credential_transaction_type]
+
+ stored_credential = options[:stored_credential]
+ # Do not add anything unless these are present.
+ return unless stored_credential[:reason_type] && stored_credential[:initiator]
+
+ # Not compatible with off_session parameter.
+ options.delete(:off_session)
+
+ stored_credential_type = if stored_credential[:initial_transaction]
+ return unless stored_credential[:initiator] == 'cardholder'
+
+ initial_transaction_stored_credential(post, stored_credential)
+ else
+ subsequent_transaction_stored_credential(post, stored_credential)
+ end
+
+ card_options = post[:payment_method_options][:card]
+ card_options[:stored_credential_transaction_type] = stored_credential_type
+ card_options[:mit_exemption].delete(:network_transaction_id) if stored_credential_type == 'setup_on_session'
+ end
+
+ def initial_transaction_stored_credential(post, stored_credential)
+ case stored_credential[:reason_type]
+ when 'unscheduled'
+ # Charge on-session and store card for future one-off payment use
+ 'setup_off_session_unscheduled'
+ when 'recurring'
+ # Charge on-session and store card for future recurring payment use
+ 'setup_off_session_recurring'
+ else
+ # Charge on-session and store card for future on-session payment use.
+ 'setup_on_session'
+ end
+ end
+
+ def subsequent_transaction_stored_credential(post, stored_credential)
+ if stored_credential[:initiator] == 'cardholder'
+ # Charge on-session customer using previously stored card.
+ 'stored_on_session'
+ elsif stored_credential[:reason_type] == 'recurring'
+ # Charge off-session customer using previously stored card for recurring transaction
+ 'stored_off_session_recurring'
+ else
+ # Charge off-session customer using previously stored card for one-off transaction
+ 'stored_off_session_unscheduled'
+ end
end
def add_ntid(post, options = {})
@@ -413,7 +619,7 @@ def add_ntid(post, options = {})
post[:payment_method_options][:card] ||= {}
post[:payment_method_options][:card][:mit_exemption] = {}
- post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id] if options[:network_transaction_id]
+ post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id]
end
def add_claim_without_transaction_id(post, options = {})
@@ -429,6 +635,16 @@ def add_claim_without_transaction_id(post, options = {})
post[:payment_method_options][:card][:mit_exemption][:claim_without_transaction_id] = options[:claim_without_transaction_id]
end
+ def add_billing_address_for_card_tokenization(post, options = {})
+ return unless (billing = options[:billing_address] || options[:address])
+
+ billing = add_address(billing, options)
+ billing[:address].transform_keys! { |k| k == :postal_code ? :address_zip : k.to_s.prepend('address_').to_sym }
+
+ post[:card][:name] = billing[:name]
+ post[:card].merge!(billing[:address])
+ end
+
def add_error_on_requires_action(post, options = {})
return unless options[:confirm]
@@ -462,52 +678,51 @@ def setup_future_usage(post, options = {})
post
end
- def add_billing_address(post, options = {})
- return unless billing = options[:billing_address] || options[:address]
-
- email = billing[:email] || options[:email]
-
- 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] = email if email
- post[:billing_details][:name] = billing[:name] if billing[:name]
- post[:billing_details][:phone] = billing[:phone] if billing[:phone]
- end
+ def add_billing_address(post, payment_method, options = {})
+ return if payment_method.nil? || payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(String)
- def add_name_only(post, payment_method)
- post[:billing_details] = {} unless post[:billing_details]
+ post[:payment_method_data] ||= {}
+ if billing = options[:billing_address] || options[:address]
+ post[:payment_method_data][:billing_details] = add_address(billing, options)
+ end
- name = [payment_method.first_name, payment_method.last_name].compact.join(' ')
- post[:billing_details][:name] = name
+ unless post[:payment_method_data][:billing_details]
+ name = [payment_method.first_name, payment_method.last_name].compact.join(' ')
+ post[:payment_method_data][:billing_details] = { name: name }
+ end
end
def add_shipping_address(post, options = {})
return unless shipping = options[:shipping_address]
- post[:shipping] = {}
-
- # fields required by Stripe PI
- post[:shipping][:address] = {}
- post[:shipping][:address][:line1] = shipping[:address1]
- post[:shipping][:name] = shipping[:name]
-
- # fields considered optional by Stripe PI
- post[:shipping][:address][:city] = shipping[:city] if shipping[:city]
- post[:shipping][:address][:country] = shipping[:country] if shipping[:country]
- post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2]
- post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip]
- post[:shipping][:address][:state] = shipping[:state] if shipping[:state]
- post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number]
+ post[:shipping] = add_address(shipping, options).except(:email)
post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
end
+ def add_address(address, options)
+ {
+ address: {
+ city: address[:city],
+ country: address[:country],
+ line1: address[:address1],
+ line2: address[:address2],
+ postal_code: address[:zip],
+ state: address[:state]
+ }.compact,
+ email: address[:email] || options[:email],
+ phone: address[:phone] || address[:phone_number],
+ name: address[:name]
+ }.compact
+ end
+
+ def add_name_only(post, payment_method)
+ post[:billing_details] = {} unless post[:billing_details]
+
+ name = [payment_method.first_name, payment_method.last_name].compact.join(' ')
+ post[:billing_details][:name] = name
+ end
+
def format_idempotency_key(options, suffix)
return options unless options[:idempotency_key]
diff --git a/lib/active_merchant/billing/gateways/sum_up.rb b/lib/active_merchant/billing/gateways/sum_up.rb
new file mode 100644
index 00000000000..85018165efb
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/sum_up.rb
@@ -0,0 +1,223 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SumUpGateway < Gateway
+ self.live_url = 'https://api.sumup.com/v0.1/'
+
+ self.supported_countries = %w(AT BE BG BR CH CL CO CY CZ DE DK EE ES FI FR
+ GB GR HR HU IE IT LT LU LV MT NL NO PL PT RO
+ SE SI SK US)
+ self.currencies_with_three_decimal_places = %w(EUR BGN BRL CHF CZK DKK GBP
+ HUF NOK PLN SEK USD)
+ self.default_currency = 'USD'
+
+ self.homepage_url = 'https://www.sumup.com/'
+ self.display_name = 'SumUp'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ multiple_invalid_parameters: 'MULTIPLE_INVALID_PARAMETERS'
+ }
+
+ def initialize(options = {})
+ requires!(options, :access_token, :pay_to_email)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ MultiResponse.run do |r|
+ r.process { create_checkout(money, payment, options) } unless options[:checkout_id]
+ r.process { complete_checkout(options[:checkout_id] || r.params['id'], payment, options) }
+ end
+ end
+
+ def refund(money, authorization, options = {})
+ transaction_id = authorization.split('#').last
+ post = money ? { amount: amount(money) } : {}
+ add_merchant_data(post, options)
+
+ commit('me/refund/' + transaction_id, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]').
+ gsub(%r(("pay_to_email\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def create_checkout(money, payment, options)
+ post = {}
+
+ add_merchant_data(post, options)
+ add_invoice(post, money, options)
+ add_address(post, options)
+ add_customer_data(post, payment, options)
+ add_3ds_data(post, options)
+
+ commit('checkouts', post)
+ end
+
+ def complete_checkout(checkout_id, payment, options = {})
+ post = {}
+
+ add_payment(post, payment, options)
+
+ commit('checkouts/' + checkout_id, post, :put)
+ end
+
+ def add_customer_data(post, payment, options)
+ post[:customer_id] = options[:customer_id]
+ post[:personal_details] = {
+ email: options[:email],
+ first_name: payment&.first_name,
+ last_name: payment&.last_name,
+ tax_id: options[:tax_id]
+ }
+ end
+
+ def add_merchant_data(post, options)
+ # Required field: pay_to_email
+ # Description: Email address of the merchant to whom the payment is made.
+ post[:pay_to_email] = @options[:pay_to_email]
+ end
+
+ def add_address(post, options)
+ post[:personal_details] ||= {}
+ if address = (options[:billing_address] || options[:shipping_address] || options[:address])
+ post[:personal_details][:address] = {
+ city: address[:city],
+ state: address[:state],
+ country: address[:country],
+ line_1: address[:address1],
+ postal_code: address[:zip]
+ }
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:checkout_reference] = options[:order_id]
+ post[:amount] = amount(money)
+ post[:currency] = options[:currency] || currency(money)
+ post[:description] = options[:description]
+ end
+
+ def add_payment(post, payment, options)
+ post[:payment_type] = options[:payment_type] || 'card'
+
+ post[:card] = {
+ name: payment.name,
+ number: payment.number,
+ expiry_month: format(payment.month, :two_digits),
+ expiry_year: payment.year,
+ cvv: payment.verification_value
+ }
+ end
+
+ def add_3ds_data(post, options)
+ post[:redirect_url] = options[:redirect_url] if options[:redirect_url]
+ end
+
+ def commit(action, post, method = :post)
+ response = api_request(action, post.compact, method)
+ succeeded = success_from(response)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ action.include?('refund') ? { response_code: response.to_s } : response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(succeeded, response)
+ )
+ end
+
+ def api_request(action, post, method)
+ raw_response =
+ begin
+ ssl_request(method, live_url + action, post.to_json, auth_headers)
+ rescue ResponseError => e
+ e.response.body
+ end
+ response = parse(raw_response)
+ response = response.is_a?(Hash) ? response.symbolize_keys : response
+
+ return format_errors(response) if raw_response.include?('error_code') && response.is_a?(Array)
+
+ response
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ (response.is_a?(Hash) && response[:next_step]) ||
+ response == 204 ||
+ %w(PENDING PAID).include?(response[:status]) ||
+ response[:transactions]&.all? { |transaction| transaction.symbolize_keys[:status] == 'SUCCESSFUL' }
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ return 'Succeeded' if (response.is_a?(Hash) && response[:next_step]) || response == 204
+
+ return response[:status]
+ end
+
+ response[:message] || response[:error_message] || response[:status]
+ end
+
+ def authorization_from(response)
+ return nil if response.is_a?(Integer)
+
+ return response[:id] unless response[:transaction_id]
+
+ [response[:id], response[:transaction_id]].join('#')
+ end
+
+ def auth_headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{options[:access_token]}"
+ }
+ end
+
+ def error_code_from(succeeded, response)
+ response[:error_code] unless succeeded
+ end
+
+ def format_error(error, key)
+ {
+ :error_code => error['error_code'],
+ key => error['param']
+ }
+ end
+
+ def format_errors(errors)
+ return format_error(errors.first, :message) if errors.size == 1
+
+ return {
+ error_code: STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters],
+ message: 'Validation error',
+ errors: errors.map { |error| format_error(error, :param) }
+ }
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ # to get the response code (204) when the body is nil
+ when 200...300
+ response.body || response.code
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/swipe_checkout.rb b/lib/active_merchant/billing/gateways/swipe_checkout.rb
index f80621d0233..e274ce2d918 100644
--- a/lib/active_merchant/billing/gateways/swipe_checkout.rb
+++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb
@@ -104,12 +104,14 @@ def commit(action, money, parameters)
result = response['data']['result']
success = (result == 'accepted' || (test? && result == 'test-accepted'))
- Response.new(success,
+ Response.new(
+ success,
success ?
TRANSACTION_APPROVED_MSG :
TRANSACTION_DECLINED_MSG,
response,
- test: test?)
+ test: test?
+ )
else
build_error_response(message, response)
end
diff --git a/lib/active_merchant/billing/gateways/telr.rb b/lib/active_merchant/billing/gateways/telr.rb
index 76c47c1dba4..620ea242f54 100644
--- a/lib/active_merchant/billing/gateways/telr.rb
+++ b/lib/active_merchant/billing/gateways/telr.rb
@@ -162,9 +162,9 @@ def lookup_country_code(code)
country.code(:alpha2)
end
- def commit(action, amount = nil, currency = nil)
+ def commit(action, amount = nil, currency = nil, &block)
currency = default_currency if currency == nil
- request = build_xml_request { |doc| yield(doc) }
+ request = build_xml_request(&block)
response = ssl_post(live_url, request, headers)
parsed = parse(response)
@@ -231,8 +231,7 @@ def parse(xml)
def authorization_from(action, response, amount, currency)
auth = response[:tranref]
- auth = [auth, amount, currency].join('|')
- auth
+ [auth, amount, currency].join('|')
end
def split_authorization(authorization)
diff --git a/lib/active_merchant/billing/gateways/tns.rb b/lib/active_merchant/billing/gateways/tns.rb
index 15c47eadb82..b84da916ef5 100644
--- a/lib/active_merchant/billing/gateways/tns.rb
+++ b/lib/active_merchant/billing/gateways/tns.rb
@@ -8,13 +8,10 @@ class TnsGateway < Gateway
VERSION = '52'
self.live_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
-
self.live_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
-
self.live_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
- self.test_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
+
+ self.test_url = "https://secure.uat.tnspayments.com/api/rest/version/#{VERSION}/"
self.display_name = 'TNS'
self.homepage_url = 'http://www.tnsi.com/'
diff --git a/lib/active_merchant/billing/gateways/trans_first.rb b/lib/active_merchant/billing/gateways/trans_first.rb
index 55215e8c0e0..6cf3756843e 100644
--- a/lib/active_merchant/billing/gateways/trans_first.rb
+++ b/lib/active_merchant/billing/gateways/trans_first.rb
@@ -219,8 +219,7 @@ def post_data(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('&')
end
def add_pair(post, key, value, options = {})
diff --git a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb
index 36a5d43084d..b9f3b237277 100644
--- a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb
+++ b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb
@@ -438,29 +438,21 @@ def refund_type(action)
end
# -- request methods ---------------------------------------------------
- def build_xml_transaction_request
- build_xml_request('SendTranRequest') do |doc|
- yield doc
- end
+ def build_xml_transaction_request(&block)
+ build_xml_request('SendTranRequest', &block)
end
- def build_xml_payment_storage_request
- build_xml_request('UpdtRecurrProfRequest') do |doc|
- yield doc
- end
+ def build_xml_payment_storage_request(&block)
+ build_xml_request('UpdtRecurrProfRequest', &block)
end
- def build_xml_payment_update_request
+ def build_xml_payment_update_request(&block)
merchant_product_type = 5 # credit card
- build_xml_request('UpdtRecurrProfRequest', merchant_product_type) do |doc|
- yield doc
- end
+ build_xml_request('UpdtRecurrProfRequest', merchant_product_type, &block)
end
- def build_xml_payment_search_request
- build_xml_request('FndRecurrProfRequest') do |doc|
- yield doc
- end
+ def build_xml_payment_search_request(&block)
+ build_xml_request('FndRecurrProfRequest', &block)
end
def build_xml_request(wrapper, merchant_product_type = nil)
diff --git a/lib/active_merchant/billing/gateways/transact_pro.rb b/lib/active_merchant/billing/gateways/transact_pro.rb
index bda2602c49d..4f017bd525e 100644
--- a/lib/active_merchant/billing/gateways/transact_pro.rb
+++ b/lib/active_merchant/billing/gateways/transact_pro.rb
@@ -172,7 +172,7 @@ def parse(body)
{ status: 'success', id: m[2] } :
{ status: 'failure', message: m[2] }
else
- Hash[status: body]
+ { status: body }
end
end
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index 3b805e94657..88529d90c5d 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -248,6 +248,12 @@ def void(authorization, options = {})
commit(action, parameters)
end
+ def verify(credit_card, options = {})
+ parameters = {}
+ add_creditcard(parameters, credit_card)
+ commit('verify', parameters)
+ end
+
# recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
# hosted customer billing info database.
#
@@ -444,11 +450,15 @@ def commit(action, parameters)
# to be considered successful, transaction status must be either "approved" or "accepted"
success = SUCCESS_TYPES.include?(data['status'])
message = message_from(data)
- Response.new(success, message, data,
+ Response.new(
+ success,
+ message,
+ data,
test: test?,
authorization: authorization_from(action, data),
cvv_result: data['cvv'],
- avs_result: { code: data['avs'] })
+ avs_result: { code: data['avs'] }
+ )
end
def parse(body)
@@ -476,9 +486,14 @@ def message_from(data)
end
def authorization_from(action, data)
- authorization = data['transid']
- authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action)
- authorization
+ case action
+ when 'store'
+ data['billingid']
+ when *VOIDABLE_ACTIONS
+ "#{data['transid']}|#{action}"
+ else
+ data['transid']
+ end
end
def split_authorization(authorization)
diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
index 20a985a4fdd..dd16d383583 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
@@ -1030,15 +1030,17 @@ def get_account_details
# Build soap header, etc.
def build_request(action, options = {})
- soap = Builder::XmlMarkup.new
- soap.instruct!(:xml, version: '1.0', encoding: 'utf-8')
- soap.tag! 'SOAP-ENV:Envelope',
+ envelope_obj = {
'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-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/'
+ }
+ soap = Builder::XmlMarkup.new
+ soap.instruct!(:xml, version: '1.0', encoding: 'utf-8')
+ soap.tag! 'SOAP-ENV:Envelope', envelope_obj do
soap.tag! 'SOAP-ENV:Body' do
send("build_#{action}", soap, options)
end
@@ -1335,8 +1337,7 @@ def build_credit_card_or_check(soap, payment_method)
case
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.to_s[-2..-1]}"
+ build_tag soap, :string, 'CardExpiration', "#{'%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]
@@ -1520,8 +1521,8 @@ def commit(action, request)
begin
soap = ssl_post(url, request, 'Content-Type' => 'text/xml')
- rescue ActiveMerchant::ResponseError => error
- soap = error.response.body
+ rescue ActiveMerchant::ResponseError => e
+ soap = e.response.body
end
build_response(action, soap)
diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
index c012758b305..1faa8291fb2 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
@@ -329,12 +329,16 @@ def commit(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,
+ Response.new(
+ approved,
+ message_from(response),
+ response,
test: test?,
authorization: authorization_from(action, response),
cvv_result: response[:cvv2_result_code],
avs_result: { code: response[:avs_result_code] },
- error_code: error_code)
+ error_code: error_code
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/vanco.rb b/lib/active_merchant/billing/gateways/vanco.rb
index f909e84f55d..09d0bbe9519 100644
--- a/lib/active_merchant/billing/gateways/vanco.rb
+++ b/lib/active_merchant/billing/gateways/vanco.rb
@@ -281,11 +281,9 @@ def login_request
end
end
- def build_xml_request
+ def build_xml_request(&block)
builder = Nokogiri::XML::Builder.new
- builder.__send__('VancoWS') do |doc|
- yield(doc)
- end
+ builder.__send__('VancoWS', &block)
builder.to_xml
end
diff --git a/lib/active_merchant/billing/gateways/vantiv_express.rb b/lib/active_merchant/billing/gateways/vantiv_express.rb
new file mode 100644
index 00000000000..4bbf3160414
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/vantiv_express.rb
@@ -0,0 +1,587 @@
+require 'nokogiri'
+require 'securerandom'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class VantivExpressGateway < Gateway
+ self.test_url = 'https://certtransaction.elementexpress.com'
+ self.live_url = 'https://transaction.elementexpress.com'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[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'
+ SERVICE_LIVE_URL = 'https://services.elementexpress.com'
+
+ NETWORK_TOKEN_TYPE = {
+ apple_pay: 2,
+ google_pay: 1
+ }
+
+ CARD_PRESENT_CODE = {
+ 'Unknown' => 1,
+ 'Present' => 2,
+ 'NotPresent' => 3
+ }
+
+ MARKET_CODE = {
+ 'AutoRental' => 1,
+ 'DirectMarketing' => 2,
+ 'ECommerce' => 3,
+ 'FoodRestaurant' => 4,
+ 'HotelLodging' => 5,
+ 'Petroleum' => 6,
+ 'Retail' => 7,
+ 'QSR' => 8,
+ 'Grocery' => 9
+ }
+
+ PAYMENT_TYPE = {
+ 'NotUsed' => 0,
+ 'Recurring' => 1,
+ 'Installment' => 2,
+ 'CardHolderInitiated' => 3,
+ 'CredentialOnFile' => 4
+ }
+
+ REVERSAL_TYPE = {
+ 'System' => 0,
+ 'Full' => 1,
+ 'Partial' => 2
+ }
+
+ SUBMISSION_TYPE = {
+ 'NotUsed' => 0,
+ 'Initial' => 1,
+ 'Subsequent' => 2,
+ 'Resubmission' => 3,
+ 'ReAuthorization' => 4,
+ 'DelayedCharges' => 5,
+ 'NoShow' => 6
+ }
+
+ LODGING_PPC = {
+ 'NonParticipant' => 0,
+ 'DollarLimit500' => 1,
+ 'DollarLimit1000' => 2,
+ 'DollarLimit1500' => 3
+ }
+
+ LODGING_SPC = {
+ 'Default' => 0,
+ 'Sale' => 1,
+ 'NoShow' => 2,
+ 'AdvanceDeposit' => 3
+ }
+
+ LODGING_CHARGE_TYPE = {
+ 'Default' => 0,
+ 'Restaurant' => 1,
+ 'GiftShop' => 2
+ }
+
+ TERMINAL_TYPE = {
+ 'Unknown' => 0,
+ 'PointOfSale' => 1,
+ 'ECommerce' => 2,
+ 'MOTO' => 3,
+ 'FuelPump' => 4,
+ 'ATM' => 5,
+ 'Voice' => 6,
+ 'Mobile' => 7,
+ 'WebSiteGiftCard' => 8
+ }
+
+ CARD_HOLDER_PRESENT_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'Present' => 2,
+ 'NotPresent' => 3,
+ 'MailOrder' => 4,
+ 'PhoneOrder' => 5,
+ 'StandingAuth' => 6,
+ 'ECommerce' => 7
+ }
+
+ CARD_INPUT_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'MagstripeRead' => 2,
+ 'ContactlessMagstripeRead' => 3,
+ 'ManualKeyed' => 4,
+ 'ManualKeyedMagstripeFailure' => 5,
+ 'ChipRead' => 6,
+ 'ContactlessChipRead' => 7,
+ 'ManualKeyedChipReadFailure' => 8,
+ 'MagstripeReadChipReadFailure' => 9,
+ 'MagstripeReadNonTechnicalFallback' => 10
+ }
+
+ CVV_PRESENCE_CODE = {
+ 'UseDefault' => 0,
+ 'NotProvided' => 1,
+ 'Provided' => 2,
+ 'Illegible' => 3,
+ 'CustomerIllegible' => 4
+ }
+
+ TERMINAL_CAPABILITY_CODE = {
+ 'Default' => 0,
+ 'Unknown' => 1,
+ 'NoTerminal' => 2,
+ 'MagstripeReader' => 3,
+ 'ContactlessMagstripeReader' => 4,
+ 'KeyEntered' => 5,
+ 'ChipReader' => 6,
+ 'ContactlessChipReader' => 7
+ }
+
+ TERMINAL_ENVIRONMENT_CODE = {
+ 'Default' => 0,
+ 'NoTerminal' => 1,
+ 'LocalAttended' => 2,
+ 'LocalUnattended' => 3,
+ 'RemoteAttended' => 4,
+ 'RemoteUnattended' => 5,
+ 'ECommerce' => 6
+ }
+
+ 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'
+ eci = parse_eci(payment)
+
+ request = build_xml_request do |xml|
+ xml.send(action, xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ add_lodging(xml, options)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def authorize(money, payment, options = {})
+ eci = parse_eci(payment)
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAuthorization(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ add_lodging(xml, options)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def capture(money, authorization, options = {})
+ trans_id, _, eci = authorization.split('|')
+ options[:trans_id] = trans_id
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAuthorizationCompletion(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money)
+ end
+
+ def refund(money, authorization, options = {})
+ trans_id, _, eci = authorization.split('|')
+ options[:trans_id] = trans_id
+
+ request = build_xml_request do |xml|
+ xml.CreditCardReturn(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money)
+ end
+
+ def credit(money, payment, options = {})
+ eci = parse_eci(payment)
+
+ request = build_xml_request do |xml|
+ xml.CreditCardCredit(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, money, payment)
+ end
+
+ def void(authorization, options = {})
+ trans_id, trans_amount, eci = authorization.split('|')
+ options.merge!({ trans_id: trans_id, trans_amount: trans_amount, reversal_type: 1 })
+
+ request = build_xml_request do |xml|
+ xml.CreditCardReversal(xmlns: live_url) do
+ add_credentials(xml)
+ add_transaction(xml, trans_amount, options, eci)
+ add_terminal(xml, options, eci)
+ end
+ end
+
+ commit(request, trans_amount)
+ end
+
+ def store(payment, options = {})
+ request = build_xml_request do |xml|
+ xml.PaymentAccountCreate(xmlns: SERVICE_LIVE_URL) 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(request, payment, nil, :store)
+ end
+
+ def verify(payment, options = {})
+ eci = parse_eci(payment)
+
+ request = build_xml_request do |xml|
+ xml.CreditCardAVSOnly(xmlns: live_url) do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, 0, options, eci)
+ add_terminal(xml, options, eci)
+ add_address(xml, options)
+ end
+ end
+
+ commit(request)
+ 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)
+ elsif payment.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_card(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.PaymentAccount do
+ xml.PaymentAccountID payment
+ end
+ end
+
+ def add_transaction(xml, money, options = {}, network_token_eci = nil)
+ xml.Transaction do
+ xml.ReversalType REVERSAL_TYPE[options[:reversal_type]] || 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 market_code(money, options, network_token_eci) if options[:market_code] || money
+ xml.ReferenceNumber options[:order_id].present? ? options[:order_id][0, 50] : SecureRandom.hex(20)
+ xml.TicketNumber options[:ticket_number] || rand(1..999999)
+ xml.MerchantSuppliedTransactionID options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
+ xml.PaymentType PAYMENT_TYPE[options[:payment_type]] || options[:payment_type] if options[:payment_type]
+ xml.SubmissionType SUBMISSION_TYPE[options[:submission_type]] || options[:submission_type] if options[:submission_type]
+ xml.DuplicateCheckDisableFlag 1 if options[:duplicate_check_disable_flag].to_s == 'true' || options[:duplicate_override_flag].to_s == 'true'
+ end
+ end
+
+ def parse_eci(payment)
+ return nil unless payment.is_a?(NetworkTokenizationCreditCard)
+
+ if (eci = payment.eci)
+ eci = eci[0] == '0' ? eci.sub!(/^0/, '') : eci
+ return eci
+ else
+ payment.brand == 'american_express' ? '9' : '6'
+ end
+ end
+
+ def market_code(money, options, network_token_eci)
+ return 3 if network_token_eci
+
+ MARKET_CODE[options[:market_code]] || options[:market_code] || 0
+ end
+
+ def add_lodging(xml, options)
+ if options[:lodging]
+ lodging = parse_lodging(options[:lodging])
+ xml.ExtendedParameters do
+ xml.Lodging do
+ xml.LodgingAgreementNumber lodging[:agreement_number] if lodging[:agreement_number]
+ xml.LodgingCheckInDate lodging[:check_in_date] if lodging[:check_in_date]
+ xml.LodgingCheckOutDate lodging[:check_out_date] if lodging[:check_out_date]
+ xml.LodgingRoomAmount lodging[:room_amount] if lodging[:room_amount]
+ xml.LodgingRoomTax lodging[:room_tax] if lodging[:room_tax]
+ xml.LodgingNoShowIndicator lodging[:no_show_indicator] if lodging[:no_show_indicator]
+ xml.LodgingDuration lodging[:duration] if lodging[:duration]
+ xml.LodgingCustomerName lodging[:customer_name] if lodging[:customer_name]
+ xml.LodgingClientCode lodging[:client_code] if lodging[:client_code]
+ xml.LodgingExtraChargesDetail lodging[:extra_charges_detail] if lodging[:extra_charges_detail]
+ xml.LodgingExtraChargesAmounts lodging[:extra_charges_amounts] if lodging[:extra_charges_amounts]
+ xml.LodgingPrestigiousPropertyCode lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ xml.LodgingSpecialProgramCode lodging[:special_program_code] if lodging[:special_program_code]
+ xml.LodgingChargeType lodging[:charge_type] if lodging[:charge_type]
+ end
+ end
+ end
+ end
+
+ def add_terminal(xml, options, network_token_eci = nil)
+ options = parse_terminal(options)
+
+ xml.Terminal do
+ xml.TerminalID options[:terminal_id] || '01'
+ xml.TerminalType options[:terminal_type] if options[:terminal_type]
+ xml.CardPresentCode options[:card_present_code] || 0
+ xml.CardholderPresentCode options[:card_holder_present_code] || 0
+ xml.CardInputCode options[:card_input_code] || 0
+ xml.CVVPresenceCode options[:cvv_presence_code] || 0
+ xml.TerminalCapabilityCode options[:terminal_capability_code] || 0
+ xml.TerminalEnvironmentCode options[:terminal_environment_code] || 0
+ xml.MotoECICode network_token_eci || 7
+ xml.PartialApprovedFlag options[:partial_approved_flag] if options[:partial_approved_flag]
+ 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 == 'checking' ? 0 : 1
+ end
+ end
+
+ def add_network_tokenization_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.Cryptogram payment.payment_cryptogram
+ xml.WalletType NETWORK_TOKEN_TYPE[payment.source]
+ end
+ end
+
+ def add_address(xml, options)
+ address = address = options[:billing_address] || options[:address]
+ shipping_address = options[:shipping_address]
+
+ if address || shipping_address
+ xml.Address do
+ if address
+ address[:email] ||= options[:email]
+
+ 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
+
+ if shipping_address
+ 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
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.remove_namespaces!
+ root = doc.root.xpath('//response/*')
+
+ root = doc.root.xpath('//Response/*') if root.empty?
+
+ root.each do |node|
+ if node.elements.empty?
+ response[node.name.downcase] = node.text
+ else
+ node_name = node.name.downcase
+ response[node_name] = {}
+
+ node.elements.each do |childnode|
+ response[node_name][childnode.name.downcase] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def parse_lodging(lodging)
+ lodging[:prestigious_property_code] = LODGING_PPC[lodging[:prestigious_property_code]] || lodging[:prestigious_property_code] if lodging[:prestigious_property_code]
+ lodging[:special_program_code] = LODGING_SPC[lodging[:special_program_code]] || lodging[:special_program_code] if lodging[:special_program_code]
+ lodging[:charge_type] = LODGING_CHARGE_TYPE[lodging[:charge_type]] || lodging[:charge_type] if lodging[:charge_type]
+
+ lodging
+ end
+
+ def parse_terminal(options)
+ options[:terminal_type] = TERMINAL_TYPE[options[:terminal_type]] || options[:terminal_type]
+ options[:card_present_code] = CARD_PRESENT_CODE[options[:card_present_code]] || options[:card_present_code]
+ options[:card_holder_present_code] = CARD_HOLDER_PRESENT_CODE[options[:card_holder_present_code]] || options[:card_holder_present_code]
+ options[:card_input_code] = CARD_INPUT_CODE[options[:card_input_code]] || options[:card_input_code]
+ options[:cvv_presence_code] = CVV_PRESENCE_CODE[options[:cvv_presence_code]] || options[:cvv_presence_code]
+ options[:terminal_capability_code] = TERMINAL_CAPABILITY_CODE[options[:terminal_capability_code]] || options[:terminal_capability_code]
+ options[:terminal_environment_code] = TERMINAL_ENVIRONMENT_CODE[options[:terminal_environment_code]] || options[:terminal_environment_code]
+
+ options
+ end
+
+ def commit(xml, amount = nil, payment = nil, action = nil)
+ response = parse(ssl_post(url(action), xml, headers))
+ success = success_from(response)
+
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response, amount, payment),
+ avs_result: success ? avs_from(response) : nil,
+ cvv_result: success ? cvv_from(response) : nil,
+ test: test?
+ )
+ end
+
+ def authorization_from(action, response, amount, payment)
+ return response.dig('paymentaccount', 'paymentaccountid') if action == :store
+
+ if response['transaction']
+ authorization = "#{response.dig('transaction', 'transactionid')}|#{amount}"
+ authorization << "|#{parse_eci(payment)}" if parse_eci(payment)
+ authorization
+ 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 build_xml_request(&block)
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8', &block)
+
+ builder.to_xml
+ end
+
+ def payment_account_type(payment)
+ return 0 unless payment.is_a?(Check)
+
+ if payment.account_type == 'checking'
+ 1
+ elsif payment.account_type == 'savings'
+ 2
+ else
+ 3
+ end
+ end
+
+ def url(action)
+ if action == :store
+ test? ? SERVICE_TEST_URL : SERVICE_LIVE_URL
+ else
+ test? ? test_url : live_url
+ end
+ 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 a11a6fcc279..5df036a5a77 100644
--- a/lib/active_merchant/billing/gateways/verifi.rb
+++ b/lib/active_merchant/billing/gateways/verifi.rb
@@ -194,11 +194,15 @@ def commit(trx_type, money, post)
response = parse(ssl_post(self.live_url, post_data(trx_type, post)))
- Response.new(response[:response].to_i == SUCCESS, message_from(response), response,
+ Response.new(
+ response[:response].to_i == SUCCESS,
+ message_from(response),
+ response,
test: test?,
authorization: response[:transactionid],
avs_result: { code: response[:avsresponse] },
- cvv_result: response[:cvvresponse])
+ cvv_result: response[:cvvresponse]
+ )
end
def message_from(response)
diff --git a/lib/active_merchant/billing/gateways/viaklix.rb b/lib/active_merchant/billing/gateways/viaklix.rb
index 76bfc8e46ca..dd49530a77f 100644
--- a/lib/active_merchant/billing/gateways/viaklix.rb
+++ b/lib/active_merchant/billing/gateways/viaklix.rb
@@ -136,11 +136,15 @@ def commit(action, money, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)))
- Response.new(response['result'] == APPROVED, message_from(response), response,
+ Response.new(
+ response['result'] == APPROVED,
+ message_from(response),
+ response,
test: @options[:test] || test?,
authorization: authorization_from(response),
avs_result: { code: response['avs_response'] },
- cvv_result: response['cvv2_response'])
+ cvv_result: response['cvv2_response']
+ )
end
def authorization_from(response)
diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb
index 25889cccd00..5c98fadfb2f 100644
--- a/lib/active_merchant/billing/gateways/visanet_peru.rb
+++ b/lib/active_merchant/billing/gateways/visanet_peru.rb
@@ -143,12 +143,12 @@ def split_authorization(authorization)
end
def generate_purchase_number_stamp
- Time.now.to_f.to_s.delete('.')[1..10] + rand(99).to_s
+ rand(('9' * 12).to_i).to_s.center(12, rand(9).to_s)
end
def commit(action, params, options = {})
raw_response = ssl_request(method(action), url(action, params, options), params.to_json, headers)
- response = parse(raw_response)
+ response = parse(raw_response).merge('purchaseNumber' => params[:purchaseNumber])
rescue ResponseError => e
raw_response = e.response.body
response_error(raw_response, options, action)
diff --git a/lib/active_merchant/billing/gateways/vpos.rb b/lib/active_merchant/billing/gateways/vpos.rb
index be259e3b7ca..3389637e965 100644
--- a/lib/active_merchant/billing/gateways/vpos.rb
+++ b/lib/active_merchant/billing/gateways/vpos.rb
@@ -9,7 +9,7 @@ class VposGateway < Gateway
self.supported_countries = ['PY']
self.default_currency = 'PYG'
- self.supported_cardtypes = %i[visa master]
+ self.supported_cardtypes = %i[visa master panal]
self.homepage_url = 'https://comercios.bancard.com.py'
self.display_name = 'vPOS'
@@ -137,7 +137,7 @@ def add_card_data(post, payment)
card_number = payment.number
cvv = payment.verification_value
- payload = { card_number: card_number, 'cvv': cvv }.to_json
+ payload = { card_number: card_number, cvv: cvv }.to_json
encryption_key = @encryption_key || OpenSSL::PKey::RSA.new(one_time_public_key)
@@ -158,10 +158,10 @@ def commit(action, parameters)
url = build_request_url(action)
begin
response = parse(ssl_post(url, post_data(parameters)))
- rescue ResponseError => response
+ rescue ResponseError => e
# Errors are returned with helpful data,
# but get filtered out by `ssl_post` because of their HTTP status.
- response = parse(response.response.body)
+ response = parse(e.response.body)
end
Response.new(
diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb
index 699e37177b4..60f1aa1eca8 100644
--- a/lib/active_merchant/billing/gateways/wirecard.rb
+++ b/lib/active_merchant/billing/gateways/wirecard.rb
@@ -179,11 +179,15 @@ def commit(action, money, options)
message = response[:Message]
authorization = response[:GuWID]
- Response.new(success, message, response,
+ Response.new(
+ success,
+ message,
+ response,
test: test?,
authorization: authorization,
avs_result: { code: avs_code(response, options) },
- cvv_result: response[:CVCResponseCode])
+ cvv_result: response[:CVCResponseCode]
+ )
rescue ResponseError => e
if e.response.code == '401'
return Response.new(false, 'Invalid Login')
@@ -405,7 +409,7 @@ def errors_to_string(root)
'N' => 'I', # CSC Match
'U' => 'U', # Data Not Checked
'Y' => 'D', # All Data Matched
- 'Z' => 'P', # CSC and Postcode Matched
+ 'Z' => 'P' # CSC and Postcode Matched
}
# Amex have different AVS response codes to visa etc
diff --git a/lib/active_merchant/billing/gateways/wompi.rb b/lib/active_merchant/billing/gateways/wompi.rb
index ed0f4536039..ff145e2a22d 100644
--- a/lib/active_merchant/billing/gateways/wompi.rb
+++ b/lib/active_merchant/billing/gateways/wompi.rb
@@ -34,6 +34,7 @@ def purchase(money, payment, options = {})
public_key: public_key
}
add_invoice(post, money, options)
+ add_tip_in_cents(post, options)
add_card(post, payment, options)
commit('sale', post, '/transactions_sync')
@@ -141,6 +142,10 @@ def add_basic_card_info(post, card, options)
post[:cvc] = cvc if cvc && !cvc.empty?
end
+ def add_tip_in_cents(post, options)
+ post[:tip_in_cents] = options[:tip_in_cents].to_i if options[:tip_in_cents]
+ end
+
def parse(body)
JSON.parse(body)
end
diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb
index da5ac218014..1eac5a69b0d 100644
--- a/lib/active_merchant/billing/gateways/worldpay.rb
+++ b/lib/active_merchant/billing/gateways/worldpay.rb
@@ -28,21 +28,6 @@ class WorldpayGateway < Gateway
network_token: 'NETWORKTOKEN'
}
- CARD_CODES = {
- 'visa' => 'VISA-SSL',
- 'master' => 'ECMC-SSL',
- 'discover' => 'DISCOVER-SSL',
- 'american_express' => 'AMEX-SSL',
- 'jcb' => 'JCB-SSL',
- 'maestro' => 'MAESTRO-SSL',
- 'diners_club' => 'DINERS-SSL',
- 'elo' => 'ELO-SSL',
- 'naranja' => 'NARANJA-SSL',
- 'cabal' => 'CABAL-SSL',
- 'unionpay' => 'CHINAUNIONPAY-SSL',
- 'unknown' => 'CARD-SSL'
- }
-
AVS_CODE_MAP = {
'A' => 'M', # Match
'B' => 'P', # Postcode matches, address not verified
@@ -53,14 +38,14 @@ class WorldpayGateway < Gateway
'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
+ '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
+ 'D' => 'N' # Does not match
}
def initialize(options = {})
@@ -77,7 +62,7 @@ def purchase(money, payment_method, options = {})
def authorize(money, payment_method, options = {})
requires!(options, :order_id)
- payment_details = payment_details(payment_method)
+ payment_details = payment_details(payment_method, options)
authorize_request(money, payment_method, payment_details.merge(options))
end
@@ -123,7 +108,7 @@ def refund(money, authorization, options = {})
# and other transactions should be performed on a normal eCom-flagged
# merchant ID.
def credit(money, payment_method, options = {})
- payment_details = payment_details(payment_method)
+ payment_details = payment_details(payment_method, options)
if options[:fast_fund_credit]
fast_fund_credit_request(money, payment_method, payment_details.merge(credit: true, **options))
else
@@ -168,6 +153,14 @@ def scrub(transcript)
private
+ def eci_value(payment_method, options)
+ eci = payment_method.respond_to?(:eci) ? format(payment_method.eci, :two_digits) : ''
+
+ return eci unless eci.empty?
+
+ options[:use_default_eci] ? '07' : eci
+ end
+
def authorize_request(money, payment_method, options)
commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', 'CAPTURED', options)
end
@@ -267,9 +260,8 @@ def add_level_two_and_three_data(xml, amount, data)
xml.invoiceReferenceNumber data[:invoice_reference_number] if data.include?(:invoice_reference_number)
xml.customerReference data[:customer_reference] if data.include?(:customer_reference)
xml.cardAcceptorTaxId data[:card_acceptor_tax_id] if data.include?(:card_acceptor_tax_id)
-
{
- sales_tax: 'salesTax',
+ tax_amount: 'salesTax',
discount_amount: 'discountAmount',
shipping_amount: 'shippingAmount',
duty_amount: 'dutyAmount'
@@ -277,53 +269,37 @@ def add_level_two_and_three_data(xml, amount, data)
next unless data.include?(key)
xml.tag! tag do
- data_amount = data[key].symbolize_keys
- add_amount(xml, data_amount[:amount].to_i, data_amount)
+ add_amount(xml, data[key].to_i, data)
end
end
- xml.discountName data[:discount_name] if data.include?(:discount_name)
- xml.discountCode data[:discount_code] if data.include?(:discount_code)
-
- add_date_element(xml, 'shippingDate', data[:shipping_date]) if data.include?(:shipping_date)
-
- if data.include?(:shipping_courier)
- xml.shippingCourier(
- data[:shipping_courier][:priority],
- data[:shipping_courier][:tracking_number],
- data[:shipping_courier][:name]
- )
- end
-
add_optional_data_level_two_and_three(xml, data)
- if data.include?(:item) && data[:item].kind_of?(Array)
- data[:item].each { |item| add_items_into_level_three_data(xml, item.symbolize_keys) }
- elsif data.include?(:item)
- add_items_into_level_three_data(xml, data[:item].symbolize_keys)
- end
+ data[:line_items].each { |item| add_line_items_into_level_three_data(xml, item.symbolize_keys, data) } if data.include?(:line_items)
end
- def add_items_into_level_three_data(xml, item)
+ def add_line_items_into_level_three_data(xml, item, data)
xml.item do
xml.description item[:description] if item[:description]
xml.productCode item[:product_code] if item[:product_code]
xml.commodityCode item[:commodity_code] if item[:commodity_code]
xml.quantity item[:quantity] if item[:quantity]
-
- {
- unit_cost: 'unitCost',
- item_total: 'itemTotal',
- item_total_with_tax: 'itemTotalWithTax',
- item_discount_amount: 'itemDiscountAmount',
- tax_amount: 'taxAmount'
- }.each do |key, tag|
- next unless item.include?(key)
-
- xml.tag! tag do
- data_amount = item[key].symbolize_keys
- add_amount(xml, data_amount[:amount].to_i, data_amount)
- end
+ xml.unitCost do
+ add_amount(xml, item[:unit_cost], data)
+ end
+ xml.unitOfMeasure item[:unit_of_measure] || 'each'
+ xml.itemTotal do
+ sub_total_amount = item[:quantity].to_i * (item[:unit_cost].to_i - item[:discount_amount].to_i)
+ add_amount(xml, sub_total_amount, data)
+ end
+ xml.itemTotalWithTax do
+ add_amount(xml, item[:total_amount], data)
+ end
+ xml.itemDiscountAmount do
+ add_amount(xml, item[:discount_amount], data)
+ end
+ xml.taxAmount do
+ add_amount(xml, item[:tax_amount], data)
end
end
end
@@ -333,7 +309,7 @@ def add_optional_data_level_two_and_three(xml, data)
xml.destinationPostalCode data[:destination_postal_code] if data.include?(:destination_postal_code)
xml.destinationCountryCode data[:destination_country_code] if data.include?(:destination_country_code)
add_date_element(xml, 'orderDate', data[:order_date].symbolize_keys) if data.include?(:order_date)
- xml.taxExempt data[:tax_exempt] if data.include?(:tax_exempt)
+ xml.taxExempt data[:tax_amount].to_i > 0 ? 'false' : 'true'
end
def order_tag_attributes(options)
@@ -458,7 +434,7 @@ def add_token_for_ff_credit(xml, payment_method, options)
end
def add_additional_3ds_data(xml, options)
- additional_data = { 'dfReferenceId' => options[:session_id] }
+ additional_data = { 'dfReferenceId' => options[:df_reference_id] }
additional_data['challengeWindowSize'] = options[:browser_size] if options[:browser_size]
xml.additional3DSData additional_data
@@ -569,10 +545,10 @@ def add_date_element(xml, name, date)
end
def add_amount(xml, money, options)
- currency = options[:currency] || currency(money)
+ currency = options[:currency] || currency(money.to_i)
amount_hash = {
- :value => localized_amount(money, currency),
+ :value => localized_amount(money.to_i, currency),
'currencyCode' => currency,
'exponent' => currency_exponent(currency)
}
@@ -587,7 +563,7 @@ def add_payment_method(xml, amount, payment_method, options)
when :pay_as_order
add_amount_for_pay_as_order(xml, amount, payment_method, options)
when :network_token
- add_network_tokenization_card(xml, payment_method)
+ add_network_tokenization_card(xml, payment_method, options)
else
add_card_or_token(xml, payment_method, options)
end
@@ -605,8 +581,9 @@ def add_amount_for_pay_as_order(xml, amount, payment_method, options)
end
end
- def add_network_tokenization_card(xml, payment_method)
- token_type = NETWORK_TOKEN_TYPE.fetch(payment_method.source, 'NETWORKTOKEN')
+ def add_network_tokenization_card(xml, payment_method, options)
+ source = payment_method.respond_to?(:source) ? payment_method.source : options[:wallet_type]
+ token_type = NETWORK_TOKEN_TYPE.fetch(source, 'NETWORKTOKEN')
xml.paymentDetails do
xml.tag! 'EMVCO_TOKEN-SSL', 'type' => token_type do
@@ -618,11 +595,12 @@ def add_network_tokenization_card(xml, payment_method)
)
end
name = card_holder_name(payment_method, options)
- eci = format(payment_method.eci, :two_digits)
xml.cardHolderName name if name.present?
- xml.cryptogram payment_method.payment_cryptogram
- xml.eciIndicator eci.empty? ? '07' : eci
+ xml.cryptogram payment_method.payment_cryptogram unless options[:wallet_type] == :google_pay
+ eci = eci_value(payment_method, options)
+ xml.eciIndicator eci if eci.present?
end
+ add_stored_credential_options(xml, options)
end
end
@@ -646,7 +624,7 @@ def add_token_details(xml, options)
end
def add_card_details(xml, payment_method, options)
- xml.tag! card_code_for(payment_method) do
+ xml.tag! 'CARD-SSL' do
add_card(xml, payment_method, options)
end
end
@@ -683,7 +661,8 @@ def add_card(xml, payment_method, options)
'year' => format(payment_method.year, :four_digits_year)
)
end
- xml.cardHolderName card_holder_name(payment_method, options)
+ name = card_holder_name(payment_method, options)
+ xml.cardHolderName name if name.present?
xml.cvc payment_method.verification_value
add_address(xml, (options[:billing_address] || options[:address]), options)
@@ -698,30 +677,27 @@ def add_stored_credential_options(xml, options = {})
end
def add_stored_credential_using_normalized_fields(xml, options)
- if options[:stored_credential][:initial_transaction]
- xml.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.storedCredentials 'usage' => 'USED', 'merchantInitiatedReason' => reason do
- xml.schemeTransactionIdentifier options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
- end
+ reason = case options[:stored_credential][:reason_type]
+ when 'installment' then 'INSTALMENT'
+ when 'recurring' then 'RECURRING'
+ when 'unscheduled' then 'UNSCHEDULED'
+ end
+ is_initial_transaction = options[:stored_credential][:initial_transaction]
+ stored_credential_params = generate_stored_credential_params(is_initial_transaction, reason)
+
+ xml.storedCredentials stored_credential_params do
+ xml.schemeTransactionIdentifier network_transaction_id(options) if network_transaction_id(options) && !is_initial_transaction
end
end
def add_stored_credential_using_gateway_specific_fields(xml, options)
return unless options[:stored_credential_usage]
- if options[:stored_credential_initiated_reason]
- xml.storedCredentials 'usage' => options[:stored_credential_usage], 'merchantInitiatedReason' => options[:stored_credential_initiated_reason] do
- xml.schemeTransactionIdentifier options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id]
- end
- else
- xml.storedCredentials 'usage' => options[:stored_credential_usage]
+ is_initial_transaction = options[:stored_credential_usage] == 'FIRST'
+ stored_credential_params = generate_stored_credential_params(is_initial_transaction, options[:stored_credential_initiated_reason])
+
+ xml.storedCredentials stored_credential_params do
+ xml.schemeTransactionIdentifier options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id] && !is_initial_transaction
end
end
@@ -846,7 +822,8 @@ def headers(options)
# ensure cookie included on follow-up '3ds' and 'capture_request' calls, using the cookie saved from the preceding response
# cookie should be present in options on the 3ds and capture calls, but also still saved in the instance var in case
- cookie = options[:cookie] || @cookie || nil
+ cookie = defined?(@cookie) ? @cookie : nil
+ cookie = options[:cookie] || cookie
headers['Cookie'] = cookie if cookie
headers['Idempotency-Key'] = idempotency_key if idempotency_key
@@ -991,17 +968,23 @@ def token_details_from_authorization(authorization)
token_details
end
- def payment_details(payment_method)
+ def payment_details(payment_method, options = {})
case payment_method
when String
token_type_and_details(payment_method)
- when NetworkTokenizationCreditCard
- { payment_type: :network_token }
else
- { payment_type: :credit }
+ type = network_token?(payment_method) || options[:wallet_type] == :google_pay ? :network_token : :credit
+
+ { payment_type: type }
end
end
+ def network_token?(payment_method)
+ payment_method.respond_to?(:source) &&
+ payment_method.respond_to?(:payment_cryptogram) &&
+ payment_method.respond_to?(:eci)
+ end
+
def token_type_and_details(token)
token_details = token_details_from_authorization(token)
token_details[:payment_type] = token_details.has_key?(:token_id) ? :token : :pay_as_order
@@ -1027,10 +1010,6 @@ def currency_exponent(currency)
return 2
end
- def card_code_for(payment_method)
- CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown']
- end
-
def eligible_for_0_auth?(payment_method, options = {})
payment_method.is_a?(CreditCard) && %w(visa master).include?(payment_method.brand) && options[:zero_dollar_auth]
end
@@ -1038,6 +1017,15 @@ def eligible_for_0_auth?(payment_method, options = {})
def card_holder_name(payment_method, options)
test? && options[:execute_threed] && !options[:three_ds_version]&.start_with?('2') ? '3D' : payment_method.name
end
+
+ def generate_stored_credential_params(is_initial_transaction, reason = nil)
+ customer_or_merchant = reason == 'RECURRING' && is_initial_transaction ? 'customerInitiatedReason' : 'merchantInitiatedReason'
+
+ stored_credential_params = {}
+ stored_credential_params['usage'] = is_initial_transaction ? 'FIRST' : 'USED'
+ stored_credential_params[customer_or_merchant] = reason if reason
+ stored_credential_params
+ end
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
index 2481dce0805..260839af916 100644
--- a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
+++ b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
@@ -34,14 +34,16 @@ def capture(money, authorization, options = {})
if authorization
commit(:post, "orders/#{CGI.escape(authorization)}/capture", { 'captureAmount' => money }, options, 'capture')
else
- Response.new(false,
+ Response.new(
+ false,
'FAILED',
'FAILED',
test: test?,
authorization: false,
avs_result: {},
cvv_result: {},
- error_code: false)
+ error_code: false
+ )
end
end
@@ -84,8 +86,7 @@ def create_token(reusable, name, exp_month, exp_year, number, cvc)
},
'clientKey' => @client_key
}
- token_response = commit(:post, 'tokens', obj, { 'Authorization' => @service_key }, 'token')
- token_response
+ commit(:post, 'tokens', obj, { 'Authorization' => @service_key }, 'token')
end
def create_post_for_auth_or_purchase(token, money, options)
@@ -134,7 +135,10 @@ def commit(method, url, parameters = nil, options = {}, type = false)
raw_response = ssl_request(method, self.live_url + url, json, headers(options))
- if raw_response != ''
+ if raw_response == ''
+ success = true
+ response = {}
+ else
response = parse(raw_response)
if type == 'token'
success = response.key?('token')
@@ -151,9 +155,6 @@ def commit(method, url, parameters = nil, options = {}, type = false)
end
end
end
- else
- success = true
- response = {}
end
rescue ResponseError => e
raw_response = e.response.body
@@ -170,14 +171,16 @@ def commit(method, url, parameters = nil, options = {}, type = false)
authorization = response['message']
end
- Response.new(success,
+ Response.new(
+ success,
success ? 'SUCCESS' : response['message'],
response,
test: test?,
authorization: authorization,
avs_result: {},
cvv_result: {},
- error_code: success ? nil : response['customCode'])
+ error_code: success ? nil : response['customCode']
+ )
end
def test?
diff --git a/lib/active_merchant/billing/gateways/xpay.rb b/lib/active_merchant/billing/gateways/xpay.rb
new file mode 100644
index 00000000000..66725fdc2b5
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/xpay.rb
@@ -0,0 +1,242 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class XpayGateway < Gateway
+ self.display_name = 'XPay Gateway'
+ self.homepage_url = 'https://developer.nexi.it/en'
+
+ self.test_url = 'https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1/'
+ self.live_url = 'https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1/'
+
+ self.supported_countries = %w(AT BE CY EE FI FR DE GR IE IT LV LT LU MT PT SK SI ES BG HR DK NO PL RO RO SE CH HU)
+ self.default_currency = 'EUR'
+ self.currencies_without_fractions = %w(BGN HRK DKK NOK GBP PLN CZK RON SEK CHF HUF)
+ self.money_format = :cents
+ self.supported_cardtypes = %i[visa master maestro american_express jcb]
+
+ ENDPOINTS_MAPPING = {
+ validation: 'orders/3steps/validation',
+ purchase: 'orders/3steps/payment',
+ authorize: 'orders/3steps/payment',
+ preauth: 'orders/3steps/init',
+ capture: 'operations/%s/captures',
+ verify: 'orders/card_verification',
+ refund: 'operations/%s/refunds'
+ }
+
+ SUCCESS_MESSAGES = %w(PENDING AUTHORIZED THREEDS_VALIDATED EXECUTED).freeze
+
+ def initialize(options = {})
+ requires!(options, :api_key)
+ @api_key = options[:api_key]
+ super
+ end
+
+ def preauth(amount, credit_card, options = {})
+ order_request(:preauth, amount, {}, credit_card, options)
+ end
+
+ def purchase(amount, credit_card, options = {})
+ complete_order_request(:purchase, amount, credit_card, options)
+ end
+
+ def authorize(amount, credit_card, options = {})
+ complete_order_request(:authorize, amount, credit_card, options)
+ end
+
+ def capture(amount, authorization, options = {})
+ operation_request(:capture, amount, authorization, options)
+ end
+
+ def refund(amount, authorization, options = {})
+ operation_request(:refund, amount, authorization, options)
+ end
+
+ def verify(credit_card, options = {})
+ post = {}
+ add_invoice(post, 0, options)
+ add_customer_data(post, credit_card, options)
+ add_credit_card(post, credit_card)
+ commit(:verify, post, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((X-Api-Key: )(\w|-)+), '\1[FILTERED]').
+ gsub(%r(("pan\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def validation(options = {})
+ post = {}
+ add_3ds_validation_params(post, options)
+ commit(:validation, post, options)
+ end
+
+ def complete_order_request(action, amount, credit_card, options = {})
+ MultiResponse.run do |r|
+ r.process { validation(options) }
+ r.process { order_request(action, amount, { captureType: (action == :authorize ? 'EXPLICIT' : 'IMPLICIT') }, credit_card, options.merge!(validation: r.params)) }
+ end
+ end
+
+ def order_request(action, amount, post, credit_card, options = {})
+ add_invoice(post, amount, options)
+ add_credit_card(post, credit_card)
+ add_customer_data(post, credit_card, options)
+ add_address(post, options)
+ add_recurrence(post, options) unless options[:operation_id]
+ add_exemptions(post, options)
+ add_3ds_params(post, options[:validation]) if options[:validation]
+
+ commit(action, post, options)
+ end
+
+ def operation_request(action, amount, authorization, options)
+ options[:correlation_id], options[:reference] = authorization.split('#')
+ commit(action, { amount: amount, currency: options[:currency] }, options)
+ end
+
+ def add_invoice(post, amount, options)
+ currency = options[:currency] || currency(amount)
+ post[:order] = {
+ orderId: options[:order_id],
+ amount: localized_amount(amount, currency),
+ currency: currency
+ }.compact
+ end
+
+ def add_credit_card(post, credit_card)
+ post[:card] = {
+ pan: credit_card.number,
+ expiryDate: expdate(credit_card),
+ cvv: credit_card.verification_value
+ }
+ end
+
+ def add_customer_data(post, credit_card, options)
+ post[:order][:customerInfo] = {
+ cardHolderName: credit_card.name,
+ cardHolderEmail: options[:email]
+ }.compact
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:order][:customerInfo][:billingAddress] = {
+ name: address[:name],
+ street: address[:address1],
+ additionalInfo: address[:address2],
+ city: address[:city],
+ postCode: address[:zip],
+ country: address[:country]
+ }.compact
+ end
+
+ if address = options[:shipping_address]
+ post[:order][:customerInfo][:shippingAddress] = {
+ name: address[:name],
+ street: address[:address1],
+ additionalInfo: address[:address2],
+ city: address[:city],
+ postCode: address[:zip],
+ country: address[:country]
+ }.compact
+ end
+ end
+
+ def add_recurrence(post, options)
+ post[:recurrence] = { action: options[:recurrence] || 'NO_RECURRING' }
+ end
+
+ def add_exemptions(post, options)
+ post[:exemptions] = options[:exemptions] || 'NO_PREFERENCE'
+ end
+
+ def add_3ds_params(post, validation)
+ post[:threeDSAuthData] = {
+ authenticationValue: validation['threeDSAuthResult']['authenticationValue'],
+ eci: validation['threeDSAuthResult']['eci'],
+ xid: validation['threeDSAuthResult']['xid']
+ }
+ post[:operationId] = validation['operation']['operationId']
+ end
+
+ def add_3ds_validation_params(post, options)
+ post[:operationId] = options[:operation_id]
+ post[:threeDSAuthResponse] = options[:three_ds_auth_response]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, params, options)
+ options[:correlation_id] ||= SecureRandom.uuid
+ transaction_id = transaction_id_from(params, options, action)
+ raw_response =
+ begin
+ url = build_request_url(action, transaction_id)
+ ssl_post(url, params.to_json, request_headers(options, action))
+ rescue ResponseError => e
+ { errors: [code: e.response.code, description: e.response.body] }.to_json
+ end
+ response = parse(raw_response)
+
+ Response.new(
+ success_from(action, response),
+ message_from(response),
+ response,
+ authorization: authorization_from(options[:correlation_id], response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def request_headers(options, action = nil)
+ headers = { 'X-Api-Key' => @api_key, 'Content-Type' => 'application/json', 'Correlation-Id' => options[:correlation_id] }
+ headers.merge!('Idempotency-Key' => options[:idempotency_key] || SecureRandom.uuid) if %i[capture refund].include?(action)
+ headers
+ end
+
+ def transaction_id_from(params, options, action = nil)
+ case action
+ when :refund, :capture
+ return options[:reference]
+ else
+ return params[:operation_id]
+ end
+ end
+
+ def build_request_url(action, id = nil)
+ "#{test? ? test_url : live_url}#{ENDPOINTS_MAPPING[action.to_sym] % id}"
+ end
+
+ def success_from(action, response)
+ case action
+ when :capture, :refund
+ response.include?('operationId') && response.include?('operationTime')
+ else
+ SUCCESS_MESSAGES.include?(response.dig('operation', 'operationResult'))
+ end
+ end
+
+ def message_from(response)
+ response['operationId'] || response.dig('operation', 'operationResult') || response.dig('errors', 0, 'description')
+ end
+
+ def authorization_from(correlation_id, response = {})
+ [correlation_id, (response['operationId'] || response.dig('operation', 'operationId'))].join('#')
+ end
+
+ def error_code_from(response)
+ response.dig('errors', 0, 'code')
+ 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
index e4108977f80..51798547d1e 100644
--- a/lib/active_merchant/billing/network_tokenization_credit_card.rb
+++ b/lib/active_merchant/billing/network_tokenization_credit_card.rb
@@ -14,7 +14,7 @@ class NetworkTokenizationCreditCard < CreditCard
self.require_verification_value = false
self.require_name = false
- attr_accessor :payment_cryptogram, :eci, :transaction_id, :metadata
+ attr_accessor :payment_cryptogram, :eci, :transaction_id, :metadata, :payment_data
attr_writer :source
SOURCES = %i(apple_pay android_pay google_pay network_token)
diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb
index fb1c502d7ee..754f8f5d4a0 100644
--- a/lib/active_merchant/billing/response.rb
+++ b/lib/active_merchant/billing/response.rb
@@ -100,11 +100,11 @@ def cvv_result
end
%w(params message test authorization error_code emv_authorization test? fraud_review?).each do |m|
- class_eval %(
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{m}
(@responses.empty? ? nil : primary_response.#{m})
end
- )
+ RUBY
end
end
end
diff --git a/lib/active_merchant/connection.rb b/lib/active_merchant/connection.rb
index c15ac5bb803..c2669cd4a2e 100644
--- a/lib/active_merchant/connection.rb
+++ b/lib/active_merchant/connection.rb
@@ -18,27 +18,13 @@ class Connection
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
+ attr_accessor :endpoint, :open_timeout, :read_timeout, :verify_peer, :ssl_version, :ca_file, :ca_path, :pem, :pem_password, :logger, :tag, :ignore_http_status, :max_retries, :proxy_address, :proxy_port
+
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
+ attr_reader :ssl_connection, :wiredump_device
def initialize(endpoint)
@endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint)
@@ -85,8 +71,6 @@ def request(method, body, headers = {})
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
diff --git a/lib/active_merchant/country.rb b/lib/active_merchant/country.rb
index 6fee9d6a874..11e53082993 100644
--- a/lib/active_merchant/country.rb
+++ b/lib/active_merchant/country.rb
@@ -9,6 +9,7 @@ class CountryCodeFormatError < StandardError
class CountryCode
attr_reader :value, :format
+
def initialize(value)
@value = value.to_s.upcase
detect_format
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
index af4bcb8b1be..562629b395e 100644
--- a/lib/active_merchant/errors.rb
+++ b/lib/active_merchant/errors.rb
@@ -23,10 +23,23 @@ def initialize(response, message = nil)
end
def to_s
- "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
+ if response.kind_of?(String)
+ if response.start_with?('Failed')
+ return response
+ else
+ return "Failed with #{response}"
+ end
+ end
+
+ return response.message if response.respond_to?(:message) && response.message.start_with?('Failed')
+
+ "Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
+ class OAuthResponseError < ResponseError # :nodoc:
+ end
+
class ClientCertificateError < ActiveMerchantError # :nodoc
end
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index a36602fe521..2a2845e4371 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = '1.127.0'
+ VERSION = '1.136.0'
end
diff --git a/lib/support/gateway_support.rb b/lib/support/gateway_support.rb
index c1e358db323..6d77898cafc 100644
--- a/lib/support/gateway_support.rb
+++ b/lib/support/gateway_support.rb
@@ -23,8 +23,8 @@ def initialize
@gateways.delete(ActiveMerchant::Billing::BogusGateway)
end
- def each_gateway
- @gateways.each { |g| yield g }
+ def each_gateway(&block)
+ @gateways.each(&block)
end
def features
diff --git a/lib/support/ssl_verify.rb b/lib/support/ssl_verify.rb
index 9f431a8da48..eb5a9c61157 100644
--- a/lib/support/ssl_verify.rb
+++ b/lib/support/ssl_verify.rb
@@ -80,9 +80,9 @@ def ssl_verify_peer?(uri)
end
return :success
- rescue OpenSSL::SSL::SSLError => ex
- return :fail, ex.inspect
- rescue Net::HTTPBadResponse, Errno::ETIMEDOUT, EOFError, SocketError, Errno::ECONNREFUSED, Timeout::Error => ex
- return :error, ex.inspect
+ rescue OpenSSL::SSL::SSLError => e
+ return :fail, e.inspect
+ rescue Net::HTTPBadResponse, Errno::ETIMEDOUT, EOFError, SocketError, Errno::ECONNREFUSED, Timeout::Error => e
+ return :error, e.inspect
end
end
diff --git a/lib/support/ssl_version.rb b/lib/support/ssl_version.rb
index 3050d09f438..7a6def3a5d5 100644
--- a/lib/support/ssl_version.rb
+++ b/lib/support/ssl_version.rb
@@ -75,12 +75,12 @@ def test_min_version(uri, min_version)
return :success
rescue Net::HTTPBadResponse
return :success # version negotiation succeeded
- rescue OpenSSL::SSL::SSLError => ex
- return :fail, ex.inspect
- rescue Interrupt => ex
+ rescue OpenSSL::SSL::SSLError => e
+ return :fail, e.inspect
+ rescue Interrupt => e
print_summary
- raise ex
- rescue StandardError => ex
- return :error, ex.inspect
+ raise e
+ rescue StandardError => e
+ return :error, e.inspect
end
end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 66606cf5e6d..c4bb3de97ab 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -178,7 +178,8 @@ cecabank:
merchant_id: MERCHANTID
acquirer_bin: ACQUIRERBIN
terminal_id: TERMINALID
- key: KEY
+ signature_key: KEY
+ encryption_key: KEY
cenpos:
merchant_id: SOMECREDENTIAL
@@ -198,6 +199,10 @@ checkout_v2:
client_id: CLIENT_ID_FOR_OAUTH_TRANSACTIONS
client_secret: CLIENT_SECRET_FOR_OAUTH_TRANSACTIONS
+checkout_v2_token:
+ secret_key: sk_sbox_xxxxxxxxxxxxxxxxx
+ public_key: pk_sbox_xxxxxxxxxxxxxxxxx
+
citrus_pay:
userid: CPF00001
password: 7c70414732de7e0ba3a04db5f24fcec8
@@ -272,6 +277,12 @@ cyber_source_latam_pe:
login: merchant_id
password: soap_key
+# Working credentials, no need to replace
+cybersource_rest:
+ merchant_id: "testrest"
+ public_key: "08c94330-f618-42a3-b09d-e1e43be5efda"
+ private_key: "yBJxy6LjM2TmcPGu+GaJrHtkke25fPpUX+UY6/L/1tE="
+
# Working credentials, no need to replace
d_local:
login: aeaf9bbfa1
@@ -282,6 +293,10 @@ data_cash:
login: X
password: Y
+datatrans:
+ merchant_id: MERCHANT_ID_WEB
+ password: MERCHANT_PASSWORD_WEB
+
# Working credentials, no need to replace
decidir_authorize:
api_key: 5a15fbc227224edabdb6f2e8219e8b28
@@ -298,6 +313,11 @@ decidir_plus_preauth:
decidir_purchase:
api_key: 5df6b5764c3f4822aecdc82d56f26b9d
+deepstack:
+ publishable_api_key: pk_test_7H5GkZJ4ktV38eZxKDItVMZZvluUhORE
+ app_id: sk_test_8fe27907-c359-4fe4-ad9b-eaaa
+ shared_secret: JC6zgUX3oZ9vRshFsM98lXzH4tu6j4ZfB4cSOqOX/xQ=
+
# No working test credentials
dibs:
merchant_id: SOMECREDENTIAL
@@ -395,6 +415,11 @@ first_pay:
transaction_center_id: 1264
gateway_id: "a91c38c3-7d7f-4d29-acc7-927b4dca0dbe"
+first_pay_rest_json:
+ mode: "rest_json"
+ merchant_key: "a91c38c3-7d7f-4d29-acc7-927b4dca0dbe"
+ processor_id: "15417"
+
firstdata_e4:
login: SD8821-67
password: T6bxSywbcccbJ19eDXNIGaCDOBg1W7T8
@@ -405,6 +430,12 @@ firstdata_e4_v27:
key_id: ANINTEGER
hmac_key: AMAGICALKEY
+flex_charge:
+ app_key: 'your app key'
+ app_secret: 'app secret'
+ site_id: 'site id'
+ mid: 'merchant id'
+
flo2cash:
username: SOMECREDENTIAL
password: ANOTHERCREDENTIAL
@@ -429,8 +460,13 @@ garanti:
global_collect:
merchant_id: 2196
- api_key_id: c91d6752cbbf9cf1
- secret_api_key: xHjQr5gL9Wcihkqoj4w/UQugdSCNXM2oUQHG5C82jy4=
+ api_key_id: b2311c2c832dd238
+ secret_api_key: Av5wKihoVlLN8SnGm6669hBHyG4Y4aS4KwaZUCvEIbY=
+
+global_collect_direct:
+ merchant_id: "NamastayTest"
+ api_key_id: "CF4CDF3F45F13C5CCBD0"
+ secret_api_key: "mvcEXR7Rem+KJE/atKsQ3Luqv37VEvTe2VOH5/Ibqd90VDzQ71Ht41RBVVyJuebzGnFu30dYpptgdrCcNvAu5A=="
global_transport:
global_user_name: "USERNAME"
@@ -441,6 +477,10 @@ hdfc:
login: LOGIN
password: PASSWORD
+hi_pay:
+ username: "USERNAME"
+ password: "PASSWORD"
+
# Working credentials, no need to replace
hps:
secret_api_key: "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ"
@@ -461,7 +501,6 @@ instapay:
login: TEST0
password:
-# Working credentials, no need to replace
ipg:
store_id: "YOUR STORE ID"
user_id: "YOUR USER ID"
@@ -469,6 +508,13 @@ ipg:
pem_password: "CERTIFICATE PASSWORD"
pem: "YOUR CERTIFICATE WITH PRIVATE KEY"
+ipg_ma:
+ store_id: "ONE OF YOUR STORE IDs"
+ user_id: "YOUR USER ID"
+ password: "YOUR PASSWORD"
+ pem_password: "CERTIFICATE PASSWORD"
+ pem: "YOUR CERTIFICATE WITH PRIVATE KEY"
+
# Working credentials, no need to replace
ipp:
username: nmi.api
@@ -706,6 +752,11 @@ orbital_tandem_gateway:
password: PASSWORD
merchant_id: MERCHANTID
+orbital_tpv_gateway:
+ login: LOGIN
+ password: PASSWORD
+ merchant_id: MERCHANTID
+
# Working credentials, no need to replace
pagarme:
api_key: 'ak_test_e1QGU2gL98MDCHZxHLJ9sofPUFJ7tH'
@@ -1216,6 +1267,12 @@ redsys:
login: MERCHANT CODE
secret_key: SECRET KEY
+# https://pagosonline.redsys.es/entornosPruebas.html
+redsys_rest:
+ login: 999008881
+ secret_key: sq7HjrUOBfKmC576ILgskD5srU870gJ7
+ terminal: 001
+
redsys_sha256:
login: MERCHANT CODE
secret_key: SECRET KEY
@@ -1271,6 +1328,9 @@ shift4:
client_guid: YOUR_CLIENT_ID
auth_token: YOUR_AUTH_TOKEN
+shift4_v2:
+ secret_key: pr_test_xxxxxxxxx
+
# Working credentials, no need to replace
simetrik:
client_id: 'wNhJBdrKDk3vTmkQMAWi5zWN7y21adO3'
@@ -1304,6 +1364,14 @@ stripe_verified_bank_account:
customer_id: "cus_7s22nNueP2Hjj6"
bank_account_id: "ba_17cHxeAWOtgoysogv3NM8CJ1"
+sum_up:
+ access_token: SOMECREDENTIAL
+ pay_to_email: SOMECREDENTIAL
+
+sum_up_3ds:
+ access_token: SOMECREDENTIAL
+ pay_to_email: SOMECREDENTIAL
+
# Working credentials, no need to replace
swipe_checkout:
login: 2077103073D8B5
@@ -1451,3 +1519,6 @@ worldpay_us:
acctid: MPNAB
subid: SPREE
merchantpin: "1234567890"
+
+xpay:
+ api_key: 5d952446-9004-4023-9eae-a527a152846b
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
index b888898b8ed..70fea7c9c1a 100644
--- a/test/remote/gateways/remote_adyen_test.rb
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -12,67 +12,83 @@ def setup
@general_bank_account = check(name: 'A. Klaassen', account_number: '123456789', routing_number: 'NL13TEST0123456789')
- @credit_card = credit_card('4111111111111111',
+ @credit_card = credit_card(
+ '4111111111111111',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @avs_credit_card = credit_card('4400000000000008',
+ @avs_credit_card = credit_card(
+ '4400000000000008',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @elo_credit_card = credit_card('5066 9911 1111 1118',
+ @elo_credit_card = credit_card(
+ '5066 9911 1111 1118',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
- @three_ds_enrolled_card = credit_card('4917610000000000',
+ @three_ds_enrolled_card = credit_card(
+ '4917610000000000',
month: 3,
year: 2030,
verification_value: '737',
- brand: :visa)
+ brand: :visa
+ )
- @cabal_credit_card = credit_card('6035 2277 1642 7021',
+ @cabal_credit_card = credit_card(
+ '6035 2277 1642 7021',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @invalid_cabal_credit_card = credit_card('6035 2200 0000 0006',
+ @invalid_cabal_credit_card = credit_card(
+ '6035 2200 0000 0006',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @unionpay_credit_card = credit_card('8171 9999 0000 0000 021',
+ @unionpay_credit_card = credit_card(
+ '8171 9999 0000 0000 021',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
- @invalid_unionpay_credit_card = credit_card('8171 9999 1234 0000 921',
+ @invalid_unionpay_credit_card = credit_card(
+ '8171 9999 1234 0000 921',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
@declined_card = credit_card('4000300011112220')
@@ -112,13 +128,33 @@ def setup
verification_value: '737',
brand: 'visa'
)
+ @us_address = {
+ address1: 'Brannan Street',
+ address2: '121',
+ country: 'US',
+ city: 'Beverly Hills',
+ state: 'CA',
+ zip: '90210'
+ }
+
+ @payout_options = {
+ reference: 'P9999999999999999',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: @us_address,
+ nationality: 'NL',
+ order_id: 'P9999999999999999',
+ date_of_birth: '1990-01-01',
+ payout: true
+ }
@options = {
reference: '345123',
email: 'john.smith@test.com',
ip: '77.110.174.153',
shopper_reference: 'John Smith',
- billing_address: address(country: 'US', state: 'CA'),
+ billing_address: @us_address,
order_id: '123',
stored_credential: { reason_type: 'unscheduled' }
}
@@ -136,7 +172,7 @@ def setup
notification_url: 'https://example.com/notification',
browser_info: {
accept_header: 'unknown',
- depth: 100,
+ depth: 48,
java: false,
language: 'US',
height: 1000,
@@ -148,44 +184,6 @@ def setup
}
@long_order_id = 'asdfjkl;asdfjkl;asdfj;aiwyutinvpoaieryutnmv;203987528752098375j3q-p489756ijmfpvbijpq348nmdf;vbjp3845'
-
- @sub_seller_options = {
- "subMerchant.numberOfSubSellers": '2',
- "subMerchant.subSeller1.id": '111111111',
- "subMerchant.subSeller1.name": 'testSub1',
- "subMerchant.subSeller1.street": 'Street1',
- "subMerchant.subSeller1.postalCode": '12242840',
- "subMerchant.subSeller1.city": 'Sao jose dos campos',
- "subMerchant.subSeller1.state": 'SP',
- "subMerchant.subSeller1.country": 'BRA',
- "subMerchant.subSeller1.taxId": '12312312340',
- "subMerchant.subSeller1.mcc": '5691',
- "subMerchant.subSeller1.debitSettlementBank": '1',
- "subMerchant.subSeller1.debitSettlementAgency": '1',
- "subMerchant.subSeller1.debitSettlementAccountType": '1',
- "subMerchant.subSeller1.debitSettlementAccount": '1',
- "subMerchant.subSeller1.creditSettlementBank": '1',
- "subMerchant.subSeller1.creditSettlementAgency": '1',
- "subMerchant.subSeller1.creditSettlementAccountType": '1',
- "subMerchant.subSeller1.creditSettlementAccount": '1',
- "subMerchant.subSeller2.id": '22222222',
- "subMerchant.subSeller2.name": 'testSub2',
- "subMerchant.subSeller2.street": 'Street2',
- "subMerchant.subSeller2.postalCode": '12300000',
- "subMerchant.subSeller2.city": 'Jacarei',
- "subMerchant.subSeller2.state": 'SP',
- "subMerchant.subSeller2.country": 'BRA',
- "subMerchant.subSeller2.taxId": '12312312340',
- "subMerchant.subSeller2.mcc": '5691',
- "subMerchant.subSeller2.debitSettlementBank": '1',
- "subMerchant.subSeller2.debitSettlementAgency": '1',
- "subMerchant.subSeller2.debitSettlementAccountType": '1',
- "subMerchant.subSeller2.debitSettlementAccount": '1',
- "subMerchant.subSeller2.creditSettlementBank": '1',
- "subMerchant.subSeller2.creditSettlementAgency": '1',
- "subMerchant.subSeller2.creditSettlementAccountType": '1',
- "subMerchant.subSeller2.creditSettlementAccount": '1'
- }
end
def test_successful_authorize
@@ -343,13 +341,15 @@ def test_successful_authorize_with_3ds2_app_based_request
# 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',
+ mastercard_threed = credit_card(
+ '5212345678901234',
month: 3,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'mastercard')
+ brand: 'mastercard'
+ )
assert response = @gateway.authorize(@amount, mastercard_threed, @options.merge(threed_dynamic: true))
assert response.test?
refute response.authorization.blank?
@@ -456,6 +456,7 @@ def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
assert_equal 'Refused', response.message
+ assert_equal 'Refused', response.error_code
end
def test_failed_authorize_with_bank_account
@@ -530,6 +531,36 @@ def test_successful_purchase_with_idempotency_key
assert_equal response.authorization, first_auth
end
+ def test_successful_purchase_with_billing_default_country_code
+ options = @options.dup.update({
+ billing_address: {
+ address1: 'Infinite Loop',
+ address2: 1,
+ country: '',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014'
+ }
+ })
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_shipping_default_country_code
+ options = @options.dup.update({
+ shipping_address: {
+ address1: 'Infinite Loop',
+ address2: 1,
+ country: '',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014'
+ }
+ })
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
def test_successful_purchase_with_apple_pay
response = @gateway.purchase(@amount, @apple_pay_card, @options)
assert_success response
@@ -775,6 +806,36 @@ def test_failed_credit
assert_equal "Required field 'reference' is not provided.", response.message
end
+ def test_successful_payout_with_credit_card
+ response = @gateway.credit(2500, @credit_card, @payout_options)
+
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_successful_payout_with_fund_source
+ fund_source = {
+ fund_source: {
+ additional_data: { fundingSource: 'Debit' },
+ first_name: 'Payer',
+ last_name: 'Name',
+ billing_address: @us_address
+ }
+ }
+
+ response = @gateway.credit(2500, @credit_card, @payout_options.merge!(fund_source))
+
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_failed_payout_with_credit_card
+ response = @gateway.credit(2500, @credit_card, @payout_options.except(:date_of_birth))
+
+ assert_failure response
+ assert_equal 'Payout has been refused due to regulatory reasons', response.message
+ end
+
def test_successful_void
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -1015,11 +1076,9 @@ def test_successful_store_with_elo_card
assert_equal 'Authorised', response.message
end
- # Adyen does not currently support recurring transactions with Cabal cards
- def test_failed_store_with_cabal_card
+ def test_successful_store_with_cabal_card
assert response = @gateway.store(@cabal_credit_card, @options)
- assert_failure response
- assert_equal 'Recurring transactions are not supported for this card type.', response.message
+ assert_success response
end
def test_successful_store_with_unionpay_card
@@ -1161,7 +1220,7 @@ 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 'The provided Expiry Date is not valid.: Expiry month should be between 1 and 12 inclusive', response.message
+ assert_equal 'The provided Expiry Date is not valid.: Expiry month should be between 1 and 12 inclusive: 16', response.message
end
def test_invalid_expiry_year_for_purchase
@@ -1206,8 +1265,7 @@ def test_missing_state_for_purchase
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
+ assert_success response
end
def test_nil_state_for_purchase
@@ -1216,6 +1274,12 @@ def test_nil_state_for_purchase
assert_success response
end
+ def test_nil_country_for_purchase
+ @options[:billing_address][:country] = 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)
@@ -1343,6 +1407,47 @@ def test_auth_capture_refund_with_network_txn_id
assert_success refund
end
+ def test_successful_capture_with_shopper_statement
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(shopper_statement: 'test1234'))
+ assert_success capture
+ end
+
+ def test_purchase_with_skip_mpi_data
+ options = {
+ reference: '345123',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'shopper 123',
+ billing_address: address(country: 'US', state: 'CA')
+ }
+ first_options = options.merge(
+ order_id: generate_unique_id,
+ shopper_interaction: 'Ecommerce',
+ recurring_processing_model: 'Subscription'
+ )
+ assert auth = @gateway.authorize(@amount, @apple_pay_card, first_options)
+ assert_success auth
+
+ assert_equal 'Subscription', auth.params['additionalData']['recurringProcessingModel']
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+
+ used_options = options.merge(
+ order_id: generate_unique_id,
+ skip_mpi_data: 'Y',
+ shopper_interaction: 'ContAuth',
+ recurring_processing_model: 'Subscription',
+ network_transaction_id: auth.network_transaction_id
+ )
+
+ assert purchase = @gateway.purchase(@amount, @apple_pay_card, used_options)
+ assert_success purchase
+ end
+
def test_successful_authorize_with_sub_merchant_data
sub_merchant_data = {
sub_merchant_id: '123451234512345',
@@ -1373,12 +1478,63 @@ def test_successful_authorize_with_sub_merchant_data
end
def test_successful_authorize_with_sub_merchant_sub_seller_data
+ @sub_seller_options = {
+ "subMerchant.numberOfSubSellers": '2',
+ "subMerchant.subSeller1.id": '111111111',
+ "subMerchant.subSeller1.name": 'testSub1',
+ "subMerchant.subSeller1.street": 'Street1',
+ "subMerchant.subSeller1.postalCode": '12242840',
+ "subMerchant.subSeller1.city": 'Sao jose dos campos',
+ "subMerchant.subSeller1.state": 'SP',
+ "subMerchant.subSeller1.country": 'BRA',
+ "subMerchant.subSeller1.taxId": '12312312340',
+ "subMerchant.subSeller1.mcc": '5691',
+ "subMerchant.subSeller1.debitSettlementBank": '1',
+ "subMerchant.subSeller1.debitSettlementAgency": '1',
+ "subMerchant.subSeller1.debitSettlementAccountType": '1',
+ "subMerchant.subSeller1.debitSettlementAccount": '1',
+ "subMerchant.subSeller1.creditSettlementBank": '1',
+ "subMerchant.subSeller1.creditSettlementAgency": '1',
+ "subMerchant.subSeller1.creditSettlementAccountType": '1',
+ "subMerchant.subSeller1.creditSettlementAccount": '1',
+ "subMerchant.subSeller2.id": '22222222',
+ "subMerchant.subSeller2.name": 'testSub2',
+ "subMerchant.subSeller2.street": 'Street2',
+ "subMerchant.subSeller2.postalCode": '12300000',
+ "subMerchant.subSeller2.city": 'Jacarei',
+ "subMerchant.subSeller2.state": 'SP',
+ "subMerchant.subSeller2.country": 'BRA',
+ "subMerchant.subSeller2.taxId": '12312312340',
+ "subMerchant.subSeller2.mcc": '5691',
+ "subMerchant.subSeller2.debitSettlementBank": '1',
+ "subMerchant.subSeller2.debitSettlementAgency": '1',
+ "subMerchant.subSeller2.debitSettlementAccountType": '1',
+ "subMerchant.subSeller2.debitSettlementAccount": '1',
+ "subMerchant.subSeller2.creditSettlementBank": '1',
+ "subMerchant.subSeller2.creditSettlementAgency": '1',
+ "subMerchant.subSeller2.creditSettlementAccountType": '1',
+ "subMerchant.subSeller2.creditSettlementAccount": '1'
+ }
assert response = @gateway.authorize(@amount, @avs_credit_card, @options.merge(sub_merchant_data: @sub_seller_options))
assert response.test?
refute response.authorization.blank?
assert_success response
end
+ def test_sending_mcc_on_authorize
+ options = {
+ reference: '345123',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ order_id: '123',
+ mcc: '5411'
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Could not find an acquirer account for the provided currency (USD).', response.message
+ end
+
def test_successful_authorize_with_level_2_data
level_2_data = {
total_tax_amount: '160',
@@ -1458,6 +1614,63 @@ def test_successful_purchase_with_level_3_data
assert_equal '[capture-received]', response.message
end
+ def test_succesful_purchase_with_airline_data
+ airline_data = {
+ agency_invoice_number: 'BAC123',
+ agency_plan_name: 'plan name',
+ airline_code: '434234',
+ airline_designator_code: '1234',
+ boarding_fee: '100',
+ computerized_reservation_system: 'abcd',
+ customer_reference_number: 'asdf1234',
+ document_type: 'cc',
+ flight_date: '2023-09-08',
+ ticket_issue_address: 'abcqwer',
+ ticket_number: 'ABCASDF',
+ travel_agency_code: 'ASDF',
+ travel_agency_name: 'hopper',
+ passenger_name: 'Joe Doe',
+ leg: {
+ carrier_code: 'KL',
+ class_of_travel: 'F'
+ },
+ passenger: {
+ first_name: 'Joe',
+ last_name: 'Doe',
+ telephone_number: '432211111'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_data_airline: airline_data))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_succesful_purchase_with_lodging_data
+ lodging_data = {
+ check_in_date: '20230822',
+ check_out_date: '20230830',
+ customer_service_toll_free_number: '234234',
+ fire_safety_act_indicator: 'abc123',
+ folio_cash_advances: '1234667',
+ folio_number: '32343',
+ food_beverage_charges: '1234',
+ no_show_indicator: 'Y',
+ prepaid_expenses: '100',
+ property_phone_number: '54545454',
+ number_of_nights: '5',
+ rate: '100',
+ total_room_tax: '1000',
+ total_tax: '100',
+ duration: '2',
+ market: 'H'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_data_lodging: lodging_data))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
def test_successful_cancel_or_refund
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -1512,6 +1725,33 @@ def test_successful_authorize_with_alternate_kosovo_code
assert_success response
end
+ def test_successful_authorize_with_address_override
+ address = {
+ address1: 'Bag End',
+ address2: '123 Hobbiton Way',
+ city: 'Hobbiton',
+ state: 'Derbyshire',
+ country: 'GB',
+ zip: 'DE45 1PP'
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address, address_override: true))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_metadata
+ metadata = {
+ field_one: 'A',
+ field_two: 'B',
+ field_three: 'C',
+ field_four: 'EASY AS ONE TWO THREE'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(metadata: metadata))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
private
def stored_credential_options(*args, ntid: nil)
diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb
index 5cbc4053c7d..24aa9fe3b61 100644
--- a/test/remote/gateways/remote_airwallex_test.rb
+++ b/test/remote/gateways/remote_airwallex_test.rb
@@ -14,6 +14,13 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
+ gateway.send :setup_access_token
+ end
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb
index be4d9ae9059..8a4fef24e7b 100644
--- a/test/remote/gateways/remote_alelo_test.rb
+++ b/test/remote/gateways/remote_alelo_test.rb
@@ -26,7 +26,7 @@ def test_access_token_success
end
def test_failure_access_token_with_invalid_keys
- error = assert_raises(ActiveMerchant::ResponseError) do
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
@@ -145,9 +145,11 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')
- response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match %r{invalid_client}, response.message
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_match(/401/, error.message)
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
index 0cd7a16fe8f..836af912cb3 100644
--- a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
+++ b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
@@ -82,9 +82,11 @@ def apple_pay_payment_token(options = {})
transaction_identifier: 'uniqueidentifier123'
}.update(options)
- ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(
+ defaults[:payment_data],
payment_instrument_name: defaults[:payment_instrument_name],
payment_network: defaults[:payment_network],
- transaction_identifier: defaults[:transaction_identifier])
+ transaction_identifier: defaults[:transaction_identifier]
+ )
end
end
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index 2dd8f4ef594..ea504fb3171 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -175,6 +175,16 @@ def test_successful_purchase_with_level_2_and_3_data
assert_equal 'This transaction has been approved', response.message
end
+ def test_successful_purchase_with_surcharge
+ options = @options.merge(surcharge: {
+ amount: 20,
+ description: 'test description'
+ })
+ response = @gateway.purchase(@amount, @credit_card, 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
@@ -414,6 +424,16 @@ def test_successful_authorization_with_moto_retail_type
assert response.authorization
end
+ def test_successful_purchase_with_phone_number
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '5554443210'
+ 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_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
@@ -448,8 +468,7 @@ def test_successful_verify_after_store_with_custom_verify_amount
end
def test_successful_verify_with_apple_pay
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = @gateway.verify(credit_card, @options)
assert_success response
assert_equal 'This transaction has been approved', response.message
@@ -825,6 +844,18 @@ def test_successful_echeck_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_successful_echeck_refund_truncates_long_account_name
+ check_with_long_name = check(name: 'Michelangelo Donatello-Raphael')
+ purchase = @gateway.purchase(@amount, check_with_long_name, @options)
+ assert_success purchase
+
+ @options.update(first_name: check_with_long_name.first_name, last_name: check_with_long_name.last_name, routing_number: check_with_long_name.routing_number,
+ account_number: check_with_long_name.account_number, account_type: check_with_long_name.account_type)
+ refund = @gateway.refund(@amount, purchase.authorization, @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
@@ -850,9 +881,11 @@ def test_dump_transcript
end
def test_successful_authorize_and_capture_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
+ credit_card = network_tokenization_credit_card(
+ '4000100011112224',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ verification_value: nil
+ )
auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
assert_equal 'This transaction has been approved', auth.message
@@ -862,9 +895,11 @@ def test_successful_authorize_and_capture_with_network_tokenization
end
def test_successful_refund_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
+ credit_card = network_tokenization_credit_card(
+ '4000100011112224',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ verification_value: nil
+ )
purchase = @gateway.purchase(@amount, credit_card, @options)
assert_success purchase
@@ -877,9 +912,11 @@ def test_successful_refund_with_network_tokenization
end
def test_successful_credit_with_network_tokenization
- credit_card = network_tokenization_credit_card('4000100011112224',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- verification_value: nil)
+ credit_card = network_tokenization_credit_card(
+ '5424000000000015',
+ payment_cryptogram: 'EjRWeJASNFZ4kBI0VniQEjRWeJA=',
+ verification_value: nil
+ )
response = @gateway.credit(@amount, credit_card, @options)
assert_success response
@@ -888,10 +925,12 @@ def test_successful_credit_with_network_tokenization
end
def test_network_tokenization_transcript_scrubbing
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
transcript = capture_transcript(@gateway) do
@gateway.authorize(@amount, credit_card, @options)
diff --git a/test/remote/gateways/remote_barclaycard_smartpay_test.rb b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
index ac59135ddba..1839dfe8ea9 100644
--- a/test/remote/gateways/remote_barclaycard_smartpay_test.rb
+++ b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
@@ -100,10 +100,12 @@ def setup
}
}
- @avs_credit_card = credit_card('4400000000000008',
+ @avs_credit_card = credit_card(
+ '4400000000000008',
month: 8,
year: 2018,
- verification_value: 737)
+ verification_value: 737
+ )
@avs_address = @options.clone
@avs_address.update(billing_address: {
@@ -158,25 +160,31 @@ def test_failed_purchase
end
def test_successful_purchase_with_unusual_address
- response = @gateway.purchase(@amount,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options_with_alternate_address)
+ @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,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options.merge(street: 'Top Level Drive', house_number: '100'))
+ @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,
+ response = @gateway.purchase(
+ @amount,
@credit_card,
- @options_with_no_address)
+ @options_with_no_address
+ )
assert_success response
assert_equal '[capture-received]', response.message
end
diff --git a/test/remote/gateways/remote_blue_snap_test.rb b/test/remote/gateways/remote_blue_snap_test.rb
index 3eb143e6986..c5099c4aa04 100644
--- a/test/remote/gateways/remote_blue_snap_test.rb
+++ b/test/remote/gateways/remote_blue_snap_test.rb
@@ -6,13 +6,13 @@ def setup
@amount = 100
@credit_card = credit_card('4263982640269299')
- @cabal_card = credit_card('6271701225979642', month: 3, year: 2024)
- @naranja_card = credit_card('5895626746595650', month: 11, year: 2024)
- @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)
+ @cabal_card = credit_card('6271701225979642')
+ @naranja_card = credit_card('5895626746595650')
+ @declined_card = credit_card('4917484589897107')
+ @invalid_card = credit_card('4917484589897106')
+ @three_ds_visa_card = credit_card('4000000000001091')
+ @three_ds_master_card = credit_card('5200000000001096')
+ @invalid_cabal_card = credit_card('5896 5700 0000 0000')
# BlueSnap may require support contact to activate fraud checking on sandbox accounts.
# Specific merchant-configurable thresholds can be set as follows:
@@ -292,7 +292,7 @@ def test_successful_purchase_with_currency
end
def test_successful_purchase_with_level3_data
- l_three_visa = credit_card('4111111111111111', month: 2, year: 2023)
+ l_three_visa = credit_card('4111111111111111')
options = @options.merge({
customer_reference_number: '1234A',
sales_tax_amount: 0.6,
@@ -447,6 +447,13 @@ def test_successful_authorize_and_partial_capture
assert_equal 'Success', capture.message
end
+ def test_successful_authorize_with_descriptor_phone_number
+ response = @gateway.authorize(@amount, @credit_card, @options.merge({ descriptor_phone_number: '321-321-4321' }))
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
def test_successful_authorize_and_capture_with_3ds2_auth
auth = @gateway.authorize(@amount, @three_ds_master_card, @options_3ds2)
assert_success auth
diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb
index 4b6771917dd..1108632f95f 100644
--- a/test/remote/gateways/remote_borgun_test.rb
+++ b/test/remote/gateways/remote_borgun_test.rb
@@ -30,14 +30,14 @@ def test_successful_purchase
end
def test_successful_preauth_3ds
- response = @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
assert_success response
assert_equal 'Succeeded', response.message
assert_not_nil response.params['redirecttoacsform']
end
def test_successful_preauth_frictionless_3ds
- response = @gateway.purchase(@amount, @frictionless_3ds_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
+ response = @gateway.purchase(@amount, @frictionless_3ds_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1' }))
assert_success response
assert_equal 'Succeeded', response.message
assert_nil response.params['redirecttoacsform']
@@ -187,19 +187,19 @@ def test_failed_void
# This test does not consistently pass. When run multiple times within 1 minute,
# an ActiveMerchant::ConnectionError()
# 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_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
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 842218143e1..859dacf1288 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -31,6 +31,12 @@ def setup
},
ach_mandate: ach_mandate
}
+
+ @nt_credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :network_token,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
end
def test_credit_card_details_on_store
@@ -54,6 +60,13 @@ def test_successful_authorize
assert_equal 'authorized', response.params['braintree_transaction']['status']
end
+ def test_successful_authorize_with_nt
+ assert response = @gateway.authorize(@amount, @nt_credit_card, @options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ 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 = {
@@ -90,6 +103,14 @@ def test_successful_setup_purchase
assert_not_nil response.params['client_token']
end
+ def test_successful_setup_purchase_with_merchant_account_id
+ assert response = @gateway.setup_purchase(merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id])
+ assert_success response
+ assert_equal 'Client token created', response.message
+
+ assert_not_nil response.params['client_token']
+ end
+
def test_successful_authorize_with_order_id
assert response = @gateway.authorize(@amount, @credit_card, order_id: '123')
assert_success response
@@ -201,6 +222,15 @@ def test_successful_purchase_sending_risk_data
assert_success response
end
+ def test_successful_purchase_with_paypal_options
+ options = @options.merge(
+ paypal_custom_field: 'abc',
+ paypal_description: 'shoes'
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
# Follow instructions found at https://developer.paypal.com/braintree/articles/guides/payment-methods/venmo#multiple-profiles
# for sandbox control panel https://sandbox.braintreegateway.com/login to create a venmo profile.
# Insert your Profile Id into fixtures.
@@ -236,8 +266,9 @@ def test_failed_verify
def test_successful_credit_card_verification
card = credit_card('4111111111111111')
- assert response = @gateway.verify(card, @options.merge({ allow_card_verification: true }))
+ assert response = @gateway.verify(card, @options.merge({ allow_card_verification: true, merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id] }))
assert_success response
+
assert_match 'OK', response.message
assert_equal 'M', response.cvv_result['code']
assert_equal 'P', response.avs_result['code']
@@ -461,8 +492,7 @@ def test_cvv_no_match
end
def test_successful_purchase_with_email
- assert response = @gateway.purchase(@amount, @credit_card,
- email: 'customer@example.com')
+ assert response = @gateway.purchase(@amount, @credit_card, email: 'customer@example.com')
assert_success response
transaction = response.params['braintree_transaction']
assert_equal 'customer@example.com', transaction['customer_details']['email']
@@ -482,6 +512,15 @@ def test_successful_purchase_with_phone_from_address
assert_equal '(555)555-5555', transaction['customer_details']['phone']
end
+ def test_successful_purchase_with_phone_number_from_address
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '9191231234'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ transaction = response.params['braintree_transaction']
+ assert_equal '9191231234', 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
@@ -512,7 +551,7 @@ def test_successful_purchase_with_device_data
assert transaction = response.params['braintree_transaction']
assert transaction['risk_data']
assert transaction['risk_data']['id']
- assert_equal 'Approve', transaction['risk_data']['decision']
+ assert_equal true, ['Not Evaluated', 'Approve'].include?(transaction['risk_data']['decision'])
assert_equal false, transaction['risk_data']['device_data_captured']
assert_equal 'fraud_protection', transaction['risk_data']['fraud_service_provider']
end
@@ -552,9 +591,7 @@ def test_purchase_with_transaction_source
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),
+ credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012),
email: 'old@example.com',
phone: '321-654-0987'
)
@@ -586,9 +623,12 @@ def test_successful_purchase_with_addresses
zip: '60103',
country_name: 'Mexico'
}
- assert response = @gateway.purchase(@amount, @credit_card,
+ assert response = @gateway.purchase(
+ @amount,
+ @credit_card,
billing_address: billing_address,
- shipping_address: shipping_address)
+ shipping_address: shipping_address
+ )
assert_success response
transaction = response.params['braintree_transaction']
assert_equal '1 E Main St', transaction['billing_details']['street_address']
@@ -607,17 +647,18 @@ def test_successful_purchase_with_addresses
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)
+ def test_successful_purchase_with_three_d_secure_pass_thru_and_sca_exemption
+ options = {
+ three_ds_exemption_type: 'low_value',
+ three_d_secure: { 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, options)
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)
+ response = @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_params)
assert_success response
end
@@ -651,27 +692,12 @@ def test_authorize_and_capture
end
def test_authorize_and_capture_with_apple_pay_card
- credit_card = network_tokenization_credit_card('4111111111111111',
+ 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')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
@@ -682,13 +708,15 @@ def test_authorize_and_capture_with_android_pay_card
end
def test_authorize_and_capture_with_google_pay_card
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: '2024',
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
@@ -799,9 +827,7 @@ def test_unstore_with_delete_method
def test_successful_update
assert response = @gateway.store(
- credit_card('4111111111111111',
- first_name: 'Old First', last_name: 'Old Last',
- month: 9, year: 2012),
+ credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012),
email: 'old@example.com',
phone: '321-654-0987'
)
@@ -820,9 +846,7 @@ def test_successful_update
assert response = @gateway.update(
customer_vault_id,
- credit_card('5105105105105100',
- first_name: 'New First', last_name: 'New Last',
- month: 10, year: 2014),
+ credit_card('5105105105105100', first_name: 'New First', last_name: 'New Last', month: 10, year: 2014),
email: 'new@example.com',
phone: '987-765-5432'
)
@@ -915,6 +939,25 @@ def test_successful_credit_with_merchant_account_id
assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
end
+ def test_failed_credit_with_merchant_account_id
+ assert response = @gateway.credit(@declined_amount, credit_card('4000111111111115'), merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id])
+ assert_failure response
+ assert_equal '2000 Do Not Honor', response.message
+ assert_equal '2000 : Do Not Honor', response.params['braintree_transaction']['additional_processor_response']
+ end
+
+ def test_successful_credit_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 response = @gateway.credit(@amount, credit_card_token, { merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id], payment_method_token: true })
+ 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.'
@@ -928,25 +971,31 @@ def test_authorize_with_descriptor
end
def test_authorize_with_travel_data
- assert auth = @gateway.authorize(@amount, @credit_card,
+ assert auth = @gateway.authorize(
+ @amount,
+ @credit_card,
travel_data: {
travel_package: 'flight',
departure_date: '2050-07-22',
lodging_check_in_date: '2050-07-22',
lodging_check_out_date: '2050-07-25',
lodging_name: 'Best Hotel Ever'
- })
+ }
+ )
assert_success auth
end
def test_authorize_with_lodging_data
- assert auth = @gateway.authorize(@amount, @credit_card,
+ assert auth = @gateway.authorize(
+ @amount,
+ @credit_card,
lodging_data: {
folio_number: 'ABC123',
check_in_date: '2050-12-22',
check_out_date: '2050-12-25',
room_rate: '80.00'
- })
+ }
+ )
assert_success auth
end
@@ -974,6 +1023,43 @@ def test_verify_credentials
assert !gateway.verify_credentials
end
+ def test_successful_recurring_first_stored_credential_v2
+ creds_options = stored_credential_options(:cardholder, :recurring, :initial)
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ 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_follow_on_recurring_first_cit_stored_credential_v2
+ creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ 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_follow_on_recurring_first_mit_stored_credential_v2
+ creds_options = stored_credential_options(:merchant, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+ 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_one_time_mit_stored_credential_v2
+ creds_options = stored_credential_options(:merchant, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options, stored_credentials_v2: true))
+
+ 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_merchant_purchase_initial
creds_options = stored_credential_options(:merchant, :recurring, :initial)
response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
@@ -1182,6 +1268,144 @@ def test_successful_purchase_with_the_same_bank_account_several_times
assert_equal '4002 Settlement Pending', response.message
end
+ def test_successful_purchase_with_processor_authorization_code
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['processor_authorization_code']
+ end
+
+ def test_successful_purchase_and_return_paypal_details_object
+ @non_payal_link_gateway = BraintreeGateway.new(fixtures(:braintree_blue_non_linked_paypal))
+ assert response = @non_payal_link_gateway.purchase(400000, 'fake-paypal-one-time-nonce', @options.merge(payment_method_nonce: 'fake-paypal-one-time-nonce'))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'paypal_payer_id', response.params['braintree_transaction']['paypal_details']['payer_id']
+ assert_equal 'payer@example.com', response.params['braintree_transaction']['paypal_details']['payer_email']
+ end
+
+ def test_successful_credit_card_purchase_with_prepaid_debit_issuing_bank
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'credit_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['issuing_bank']
+ end
+
+ def test_unsuccessful_credit_card_purchase_and_return_payment_details
+ assert response = @gateway.purchase(204700, @credit_card)
+ assert_failure response
+ assert_equal('2047 : Call Issuer. Pick Up Card.', response.params['braintree_transaction']['additional_processor_response'])
+ assert_equal 'credit_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['debit']
+ assert_equal 'M', response.params.dig('braintree_transaction', 'cvv_response_code')
+ assert_equal 'I', response.params.dig('braintree_transaction', 'avs_response_code')
+ assert_equal 'Call Issuer. Pick Up Card.', response.params.dig('braintree_transaction', 'gateway_message')
+ assert_equal 'Unknown', response.params.dig('braintree_transaction', 'credit_card_details', 'country_of_issuance')
+ assert_equal 'Unknown', response.params['braintree_transaction']['credit_card_details']['issuing_bank']
+ end
+
+ def test_successful_network_token_purchase_with_prepaid_debit_issuing_bank
+ assert response = @gateway.purchase(@amount, @nt_credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'network_token', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['issuing_bank']
+ end
+
+ def test_unsuccessful_network_token_purchase_and_return_payment_details
+ assert response = @gateway.purchase(204700, @nt_credit_card)
+ assert_failure response
+ assert_equal('2047 : Call Issuer. Pick Up Card.', response.params['braintree_transaction']['additional_processor_response'])
+ assert_equal 'network_token', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['network_token_details']['issuing_bank']
+ end
+
+ def test_successful_google_pay_purchase_with_prepaid_debit
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ month: '01',
+ year: '2024',
+ source: :google_pay,
+ transaction_id: '123456789',
+ eci: '05'
+ )
+
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'android_pay_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['google_pay_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['google_pay_details']['debit']
+ end
+
+ def test_unsuccessful_google_pay_purchase_and_return_payment_details
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ month: '01',
+ year: '2024',
+ source: :google_pay,
+ transaction_id: '123456789',
+ eci: '05'
+ )
+ assert response = @gateway.purchase(204700, credit_card, @options)
+ assert_failure response
+ assert_equal('2047 : Call Issuer. Pick Up Card.', response.params['braintree_transaction']['additional_processor_response'])
+ assert_equal 'android_pay_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['google_pay_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['google_pay_details']['debit']
+ end
+
+ def test_successful_apple_pay_purchase_with_prepaid_debit_issuing_bank
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'apple_pay_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['issuing_bank']
+ end
+
+ def test_unsuccessful_apple_pay_purchase_and_return_payment_details
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ assert response = @gateway.purchase(204700, credit_card, @options)
+ assert_failure response
+ assert_equal('2047 : Call Issuer. Pick Up Card.', response.params['braintree_transaction']['additional_processor_response'])
+ assert_equal 'apple_pay_card', response.params['braintree_transaction']['payment_instrument_type']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['prepaid']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['debit']
+ assert_equal 'Unknown', response.params['braintree_transaction']['apple_pay_details']['issuing_bank']
+ end
+
+ def test_successful_purchase_with_global_id
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['payment_receipt']['global_id']
+ end
+
def test_unsucessful_purchase_using_a_bank_account_token_not_verified
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
response = @gateway.store(bank_account, @options.merge(@check_required_options))
diff --git a/test/remote/gateways/remote_braintree_token_nonce_test.rb b/test/remote/gateways/remote_braintree_token_nonce_test.rb
index 312af9361b6..cbc8dbc3c24 100644
--- a/test/remote/gateways/remote_braintree_token_nonce_test.rb
+++ b/test/remote/gateways/remote_braintree_token_nonce_test.rb
@@ -47,7 +47,7 @@ def test_unsucesfull_create_token_with_invalid_state
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'state' of variable 'input' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'state' has coerced Null value for NonNull type 'UsStateCode!'", err_messages
end
def test_unsucesfull_create_token_with_invalid_zip_code
@@ -57,7 +57,7 @@ def test_unsucesfull_create_token_with_invalid_zip_code
tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account)
assert_nil tokenized_bank_account
- assert_equal "Field 'zipCode' of variable 'input' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
+ assert_equal "Variable 'input' has an invalid value: Field 'zipCode' has coerced Null value for NonNull type 'UsZipCode!'", err_messages
end
def test_url_generation
@@ -80,4 +80,13 @@ def test_url_generation
assert_equal 'https://payments.braintree-api.com/graphql', generator.url
end
+
+ def test_successfully_create_token_nonce_for_credit_card
+ generator = TokenNonce.new(@braintree_backend, @options)
+ credit_card = credit_card('4111111111111111')
+ tokenized_credit_card, err_messages = generator.create_token_nonce_for_payment_method(credit_card)
+ assert_not_nil tokenized_credit_card
+ assert_match %r(^tokencc_), tokenized_credit_card
+ assert_nil err_messages
+ end
end
diff --git a/test/remote/gateways/remote_card_connect_test.rb b/test/remote/gateways/remote_card_connect_test.rb
index 68301145fd6..4717bb2f2b8 100644
--- a/test/remote/gateways/remote_card_connect_test.rb
+++ b/test/remote/gateways/remote_card_connect_test.rb
@@ -123,9 +123,9 @@ def test_successful_purchase_with_user_fields
order_date: '20170507',
ship_from_date: '20877',
user_fields: [
- { 'udf0': 'value0' },
- { 'udf1': 'value1' },
- { 'udf2': 'value2' }
+ { udf0: 'value0' },
+ { udf1: 'value1' },
+ { udf2: 'value2' }
]
}
diff --git a/test/remote/gateways/remote_card_stream_test.rb b/test/remote/gateways/remote_card_stream_test.rb
index 046ee4e4d3a..48fe71952ec 100644
--- a/test/remote/gateways/remote_card_stream_test.rb
+++ b/test/remote/gateways/remote_card_stream_test.rb
@@ -6,33 +6,43 @@ def setup
@gateway = CardStreamGateway.new(fixtures(:card_stream))
- @amex = credit_card('374245455400001',
+ @amex = credit_card(
+ '374245455400001',
month: '12',
year: Time.now.year + 1,
verification_value: '4887',
- brand: :american_express)
+ brand: :american_express
+ )
- @mastercard = credit_card('5301250070000191',
+ @mastercard = credit_card(
+ '5301250070000191',
month: '12',
year: Time.now.year + 1,
verification_value: '419',
- brand: :master)
+ brand: :master
+ )
- @visacreditcard = credit_card('4929421234600821',
+ @visacreditcard = credit_card(
+ '4929421234600821',
month: '12',
year: Time.now.year + 1,
verification_value: '356',
- brand: :visa)
+ brand: :visa
+ )
- @visadebitcard = credit_card('4539791001730106',
+ @visadebitcard = credit_card(
+ '4539791001730106',
month: '12',
year: Time.now.year + 1,
verification_value: '289',
- brand: :visa)
+ brand: :visa
+ )
- @declined_card = credit_card('4000300011112220',
+ @declined_card = credit_card(
+ '4000300011112220',
month: '9',
- year: Time.now.year + 1)
+ year: Time.now.year + 1
+ )
@amex_options = {
billing_address: {
@@ -109,10 +119,12 @@ def setup
ip: '1.1.1.1'
}
- @three_ds_enrolled_card = credit_card('4012001037141112',
+ @three_ds_enrolled_card = credit_card(
+ '4012001037141112',
month: '12',
year: '2020',
- brand: :visa)
+ brand: :visa
+ )
@visacredit_three_ds_options = {
threeds_required: true,
diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb
new file mode 100644
index 00000000000..f0c23f98f7c
--- /dev/null
+++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb
@@ -0,0 +1,181 @@
+require 'test_helper'
+
+class RemoteCecabankTest < Test::Unit::TestCase
+ def setup
+ @gateway = CecabankJsonGateway.new(fixtures(:cecabank))
+
+ @amount = 100
+ @credit_card = credit_card('4507670001000009', { 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,
+ three_d_secure: three_d_secure
+ }
+
+ @cit_options = @options.merge({
+ recurring_end_date: "#{Time.now.year}1231",
+ recurring_frequency: '1',
+ stored_credential: {
+ reason_type: 'unscheduled',
+ initiator: 'cardholder'
+ }
+ })
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match @gateway.options[:merchant_id], response.message
+ assert_match '190', response.error_code
+ end
+
+ def test_successful_capture
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert response = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_capture
+ assert response = @gateway.capture(@amount, 'abc123', @options)
+ assert_failure response
+ assert_match @gateway.options[:merchant_id], response.message
+ assert_match '807', response.error_code
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, order_id: generate_unique_id)
+ assert_success response
+ assert_equal %i[codAut numAut referencia], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match @gateway.options[:merchant_id], response.message
+ assert_match '190', response.error_code
+ 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 %i[acquirerBIN codAut importe merchantID numAut numOperacion pais referencia terminalID tipoOperacion], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_failure response
+ assert_match @gateway.options[:merchant_id], response.message
+ assert_match '15', response.error_code
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ assert response = @gateway.void(authorize.authorization, order_id: @options[:order_id])
+ assert_success response
+ assert_equal %i[acquirerBIN codAut importe merchantID numAut numOperacion pais referencia terminalID tipoOperacion], JSON.parse(response.message).symbolize_keys.keys.sort
+ end
+
+ def test_unsuccessful_void
+ assert response = @gateway.void('reference', { order_id: generate_unique_id })
+ assert_failure response
+ assert_match @gateway.options[:merchant_id], response.message
+ assert_match '15', response.error_code
+ end
+
+ def test_invalid_login
+ gateway = CecabankGateway.new(fixtures(:cecabank).merge(cypher_key: 'invalid'))
+
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'ERROR AL CALCULAR FIRMA', response.message
+ end
+
+ def test_purchase_using_stored_credential_cit
+ assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
+ assert_success purchase
+ end
+
+ def test_purchase_stored_credential_with_network_transaction_id
+ @cit_options.merge!({ network_transaction_id: '999999999999999' })
+ assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_auth_capture_and_stored_credential_cit
+ assert authorize = @gateway.authorize(@amount, @credit_card, @cit_options)
+ assert_success authorize
+ assert_equal authorize.network_transaction_id, '999999999999999'
+
+ assert capture = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success capture
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit
+ @cit_options[:stored_credential][:reason_type] = 'installment'
+ assert purchase = @gateway.purchase(@amount, @credit_card, @cit_options)
+ assert_success purchase
+
+ options = @cit_options.except(:three_d_secure, :extra_options_for_three_d_secure)
+ options[:stored_credential][:reason_type] = 'recurring'
+ options[:stored_credential][:initiator] = 'merchant'
+ options[:stored_credential][:network_transaction_id] = purchase.network_transaction_id
+
+ assert purchase2 = @gateway.purchase(@amount, @credit_card, options)
+ assert_success purchase2
+ end
+
+ def test_failure_stored_credential_invalid_cit_transaction_id
+ options = @cit_options
+ options[:stored_credential][:reason_type] = 'recurring'
+ options[:stored_credential][:initiator] = 'merchant'
+ options[:stored_credential][:network_transaction_id] = 'bad_reference'
+
+ assert purchase = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure purchase
+ assert_match @gateway.options[:merchant_id], purchase.message
+ assert_match '810', purchase.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)
+ end
+
+ private
+
+ def get_response_params(transcript)
+ response = JSON.parse(transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"'))
+ JSON.parse(Base64.decode64(response['parametros']))
+ end
+
+ def three_d_secure
+ {
+ version: '2.2.0',
+ eci: '02',
+ cavv: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
+ ds_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
+ acs_transaction_id: '18c353b0-76e3-4a4c-8033-f14fe9ce39dc',
+ authentication_response_status: 'Y',
+ three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5'
+ }
+ end
+end
diff --git a/test/remote/gateways/remote_cecabank_test.rb b/test/remote/gateways/remote_cecabank_test.rb
index d0389ba26fc..217ed8cc501 100644
--- a/test/remote/gateways/remote_cecabank_test.rb
+++ b/test/remote/gateways/remote_cecabank_test.rb
@@ -41,11 +41,11 @@ def test_unsuccessful_refund
assert response = @gateway.refund(@amount, purchase.authorization, @options.merge(currency: 'USD'))
assert_failure response
- assert_match 'ERROR', response.message
+ assert_match 'Error', response.message
end
def test_invalid_login
- gateway = CecabankGateway.new(fixtures(:cecabank).merge(key: 'invalid'))
+ gateway = CecabankGateway.new(fixtures(:cecabank).merge(signature_key: 'invalid'))
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
index 4a212d8790c..4347b82842c 100644
--- a/test/remote/gateways/remote_checkout_v2_test.rb
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -1,68 +1,85 @@
+require 'timecop'
require 'test_helper'
class RemoteCheckoutV2Test < Test::Unit::TestCase
def setup
gateway_fixtures = fixtures(:checkout_v2)
+ gateway_token_fixtures = fixtures(:checkout_v2_token)
@gateway = CheckoutV2Gateway.new(secret_key: gateway_fixtures[:secret_key])
@gateway_oauth = CheckoutV2Gateway.new({ client_id: gateway_fixtures[:client_id], client_secret: gateway_fixtures[:client_secret] })
+ @gateway_token = CheckoutV2Gateway.new(secret_key: gateway_token_fixtures[:secret_key], public_key: gateway_token_fixtures[:public_key])
@amount = 200
- @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025')
+ @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: Time.now.year + 1)
+ @credit_card_dnh = credit_card('4024007181869214', verification_value: '100', month: '6', year: Time.now.year + 1)
@expired_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2010')
- @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: '2025')
- @threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: '2020')
+ @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: Time.now.year + 1)
+ @threeds_card = credit_card('4485040371536584', verification_value: '100', month: '12', year: Time.now.year + 1)
@mada_card = credit_card('5043000000000000', brand: 'mada')
- @vts_network_token = network_tokenization_credit_card('4242424242424242',
+ @vts_network_token = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: '2025',
- source: :network_token,
- brand: 'visa',
- verification_value: nil)
-
- @mdes_network_token = network_tokenization_credit_card('5436031030606378',
- eci: '02',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :network_token,
+ brand: 'visa',
+ verification_value: nil
+ )
+
+ @mdes_network_token = network_tokenization_credit_card(
+ '5436031030606378',
+ eci: '02',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: '2025',
- source: :network_token,
- brand: 'master',
- verification_value: nil)
-
- @google_pay_visa_cryptogram_3ds_network_token = network_tokenization_credit_card('4242424242424242',
- eci: '05',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :network_token,
+ brand: 'master',
+ verification_value: nil
+ )
+
+ @google_pay_visa_cryptogram_3ds_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: '2025',
- source: :google_pay,
- verification_value: nil)
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: nil
+ )
- @google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card('5436031030606378',
+ @google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card(
+ '5436031030606378',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: '2025',
- source: :google_pay,
- brand: 'master',
- verification_value: nil)
-
- @google_pay_pan_only_network_token = network_tokenization_credit_card('4242424242424242',
- month: '10',
- year: '2025',
- source: :google_pay,
- verification_value: nil)
-
- @apple_pay_network_token = network_tokenization_credit_card('4242424242424242',
- eci: '05',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ brand: 'master',
+ verification_value: nil
+ )
+
+ @google_pay_pan_only_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ month: '10',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: nil
+ )
+
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4242424242424242',
+ eci: '05',
payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
- month: '10',
- year: '2025',
- source: :apple_pay,
- verification_value: nil)
+ month: '10',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: nil
+ )
@options = {
order_id: '1',
billing_address: address,
+ shipping_address: address,
description: 'Purchase',
email: 'longbob.longsen@example.com',
processing_channel_id: 'pc_lxgl7aqahkzubkundd2l546hdm'
@@ -71,10 +88,7 @@ def setup
card_on_file: true,
transaction_indicator: 2,
previous_charge_id: 'pay_123',
- processing_channel_id: 'pc_123',
- marketplace: {
- sub_entity_id: 'ent_123'
- }
+ processing_channel_id: 'pc_123'
)
@additional_options_3ds = @options.merge(
execute_threed: true,
@@ -99,6 +113,68 @@ def setup
authentication_response_status: 'Y'
}
)
+ @extra_customer_data = @options.merge(
+ phone_country_code: '1',
+ phone: '9108675309'
+ )
+ @payout_options = @options.merge(
+ source_type: 'currency_account',
+ source_id: 'ca_spwmped4qmqenai7hcghquqle4',
+ funds_transfer_type: 'FD',
+ instruction_purpose: 'leisure',
+ destination: {
+ account_holder: {
+ phone: {
+ number: '9108675309',
+ country_code: '1'
+ },
+ identification: {
+ type: 'passport',
+ number: '12345788848438'
+ }
+ }
+ },
+ currency: 'GBP',
+ sender: {
+ type: 'individual',
+ first_name: 'Jane',
+ middle_name: 'Middle',
+ last_name: 'Doe',
+ address: {
+ address1: '123 Main St',
+ address2: 'Apt G',
+ city: 'Narnia',
+ state: 'ME',
+ zip: '12345',
+ country: 'US'
+ },
+ reference: '012345',
+ reference_type: 'other',
+ source_of_funds: 'debit',
+ identification: {
+ type: 'passport',
+ number: 'ABC123',
+ issuing_country: 'US',
+ date_of_expiry: '2027-07-07'
+ }
+ }
+ )
+ end
+
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.send :setup_access_token
+ end
+ end
+
+ def test_failed_purchase_with_failed_oauth_credentials
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert_equal error.message, 'Failed with 400 Bad Request'
end
def test_transcript_scrubbing
@@ -122,6 +198,7 @@ def test_transcript_scrubbing_via_oauth
assert_scrubbed(declined_card.verification_value, transcript)
assert_scrubbed(@gateway_oauth.options[:client_id], transcript)
assert_scrubbed(@gateway_oauth.options[:client_secret], transcript)
+ assert_scrubbed(@gateway_oauth.options[:access_token], transcript)
end
def test_network_transaction_scrubbing
@@ -134,6 +211,16 @@ def test_network_transaction_scrubbing
assert_scrubbed(@gateway.options[:secret_key], transcript)
end
+ def test_store_transcript_scrubbing
+ response = nil
+ transcript = capture_transcript(@gateway) do
+ response = @gateway_token.store(@credit_card, @options)
+ end
+ token = response.responses.first.params['token']
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(token, transcript)
+ end
+
def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -146,6 +233,53 @@ def test_successful_purchase_via_oauth
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_via_oauth_with_access_token
+ assert_nil @gateway_oauth.options[:access_token]
+ assert_nil @gateway_oauth.options[:expires]
+ purchase = @gateway_oauth.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ access_token = @gateway_oauth.options[:access_token]
+ expires = @gateway_oauth.options[:expires]
+ response = @gateway_oauth.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal @gateway_oauth.options[:access_token], access_token
+ assert_equal @gateway_oauth.options[:expires], expires
+ end
+
+ def test_failure_purchase_via_oauth_with_invalid_access_token_without_expires
+ @gateway_oauth.options[:access_token] = 'ABC123'
+ @gateway_oauth.options[:expires] = DateTime.now.strftime('%Q').to_i + 3600.seconds
+
+ response = @gateway_oauth.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal '401: Unauthorized', response.message
+ assert_equal @gateway_oauth.options[:access_token], ''
+ end
+
+ def test_successful_purchase_via_oauth_with_invalid_access_token_with_correct_expires
+ @gateway_oauth.options[:access_token] = 'ABC123'
+ response = @gateway_oauth.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_not_equal 'ABC123', @gateway_oauth.options[:access_token]
+ end
+
+ def test_successful_purchase_with_an_expired_access_token
+ initial_access_token = @gateway_oauth.options[:access_token] = SecureRandom.alphanumeric(10)
+ initial_expires = @gateway_oauth.options[:expires] = DateTime.now.strftime('%Q').to_i
+
+ Timecop.freeze(DateTime.now + 1.hour) do
+ purchase = @gateway_oauth.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert_equal 2, purchase.responses.size
+ assert_not_equal initial_access_token, @gateway_oauth.options[:access_token]
+ assert_not_equal initial_expires, @gateway.options[:expires]
+
+ assert_not_nil purchase.responses.first.params['access_token']
+ assert_not_nil purchase.responses.first.params['expires']
+ end
+ end
+
def test_successful_purchase_with_vts_network_token
response = @gateway.purchase(100, @vts_network_token, @options)
assert_success response
@@ -328,6 +462,12 @@ def test_successful_purchase_includes_cvv_result
assert_equal 'Y', response.cvv_result['code']
end
+ def test_successful_purchase_with_extra_customer_data
+ response = @gateway.purchase(@amount, @credit_card, @extra_customer_data)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_authorize_includes_cvv_result
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
@@ -396,6 +536,120 @@ def test_successful_purchase_with_metadata
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_processing_data
+ options = @options.merge(
+ processing: {
+ aft: true,
+ preferred_scheme: 'cartes_bancaires',
+ app_id: 'com.iap.linker_portal',
+ airline_data: [
+ {
+ ticket: {
+ number: '045-21351455613',
+ issue_date: '2023-05-20',
+ issuing_carrier_code: 'AI',
+ travel_package_indicator: 'B',
+ travel_agency_name: 'World Tours',
+ travel_agency_code: '01'
+ },
+ passenger: [
+ {
+ first_name: 'John',
+ last_name: 'White',
+ date_of_birth: '1990-05-26',
+ address: {
+ country: 'US'
+ }
+ }
+ ],
+ flight_leg_details: [
+ {
+ flight_number: '101',
+ carrier_code: 'BA',
+ class_of_travelling: 'J',
+ departure_airport: 'LHR',
+ departure_date: '2023-06-19',
+ departure_time: '15:30',
+ arrival_airport: 'LAX',
+ stop_over_code: 'x',
+ fare_basis_code: 'SPRSVR'
+ }
+ ]
+ }
+ ],
+ partner_customer_id: '2102209000001106125F8',
+ partner_payment_id: '440644309099499894406',
+ tax_amount: '1000',
+ purchase_country: 'GB',
+ locale: 'en-US',
+ retrieval_reference_number: '909913440644',
+ partner_order_id: 'string',
+ partner_status: 'string',
+ partner_transaction_id: 'string',
+ partner_error_codes: [],
+ partner_error_message: 'string',
+ partner_authorization_code: 'string',
+ partner_authorization_response_code: 'string',
+ fraud_status: 'string'
+ }
+ )
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_recipient_data
+ options = @options.merge(
+ recipient: {
+ dob: '1985-05-15',
+ account_number: '5555554444',
+ zip: 'SW1A',
+ first_name: 'john',
+ last_name: 'johnny',
+ address: {
+ address1: '123 High St.',
+ address2: 'Flat 456',
+ city: 'London',
+ state: 'str',
+ zip: 'SW1A 1AA',
+ country: 'GB'
+ }
+ }
+ )
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_sender_data
+ options = @options.merge(
+ sender: {
+ type: 'individual',
+ dob: '1985-05-15',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ address: {
+ address_line1: '123 High St.',
+ address_line2: 'Flat 456',
+ city: 'London',
+ state: 'str',
+ zip: 'SW1A 1AA',
+ country: 'GB'
+ },
+ reference: '8285282045818',
+ identification: {
+ type: 'passport',
+ number: 'ABC123',
+ issuing_country: 'GB'
+ }
+ }
+ )
+ response = @gateway_oauth.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_purchase_with_metadata_via_oauth
options = @options.merge(
metadata: {
@@ -414,8 +668,22 @@ def test_successful_purchase_with_minimal_options
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_shipping_address
+ response = @gateway.purchase(@amount, @credit_card, shipping_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: ''))
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: nil))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_name
+ credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: Time.now.year + 1, first_name: nil, last_name: nil)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_equal response.params['source']['name'], ''
assert_success response
assert_equal 'Succeeded', response.message
end
@@ -427,9 +695,9 @@ def test_successful_purchase_with_ip
end
def test_failed_purchase
- response = @gateway.purchase(12305, @credit_card, @options)
+ response = @gateway.purchase(100, @credit_card_dnh, @options)
assert_failure response
- assert_equal 'Declined - Do Not Honour', response.message
+ assert_equal 'Invalid Card Number', response.message
end
def test_failed_purchase_via_oauth
@@ -450,6 +718,12 @@ def test_avs_failed_authorize
assert_equal 'request_invalid: card_number_invalid', response.message
end
+ def test_invalid_shipping_address
+ response = @gateway.authorize(@amount, @credit_card, shipping_address: address.update(country: 'Canada'))
+ assert_failure response
+ assert_equal 'request_invalid: country_address_invalid', response.message
+ end
+
def test_successful_authorize_and_capture
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -546,9 +820,9 @@ def test_direct_3ds_authorize
end
def test_failed_authorize
- response = @gateway.authorize(12314, @credit_card, @options)
+ response = @gateway.authorize(12314, @declined_card, @options)
assert_failure response
- assert_equal 'Invalid Card Number', response.message
+ assert_equal 'request_invalid: card_number_invalid', response.message
end
def test_failed_authorize_via_oauth
@@ -583,6 +857,122 @@ def test_successful_credit
assert_equal 'Succeeded', response.message
end
+ def test_successful_money_transfer_payout_via_credit_individual_account_holder_type
+ @credit_card.first_name = 'John'
+ @credit_card.last_name = 'Doe'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge(account_holder_type: 'individual', payout: true))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_money_transfer_payout_via_credit_corporate_account_holder_type
+ @credit_card.name = 'ACME, Inc.'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge(account_holder_type: 'corporate', payout: true))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_money_transfer_payout_reverts_to_credit_if_payout_sent_as_nil
+ @credit_card.first_name = 'John'
+ @credit_card.last_name = 'Doe'
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge({ account_holder_type: 'individual', payout: nil }))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_money_transfer_payout_handles_blank_destination_address
+ @payout_options[:billing_address] = nil
+ response = @gateway_oauth.credit(@amount, @credit_card, @payout_options.merge({ account_holder_type: 'individual', payout: true }))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_store
+ response = @gateway_token.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_unstore_after_store
+ store = @gateway_token.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Succeeded', store.message
+ source_id = store.params['id']
+ response = @gateway_token.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_unstore_after_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ assert_equal response.params['response_code'], '204'
+ end
+
+ def test_successful_purchase_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ end
+
+ def test_successful_store_apple_pay
+ response = @gateway.store(@apple_pay_network_token, @options)
+ assert_success response
+ end
+
+ def test_successful_unstore_after_purchase_with_google_pay
+ purchase = @gateway.purchase(@amount, @google_pay_master_cryptogram_3ds_network_token, @options)
+ source_id = purchase.params['source']['id']
+ response = @gateway.unstore(source_id, @options)
+ assert_success response
+ end
+
+ def test_success_store_with_google_pay_3ds
+ response = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
+ assert_success response
+ end
+
+ def test_failed_store_oauth_credit_card
+ response = @gateway_oauth.store(@credit_card, @options)
+ assert_failure response
+ assert_equal '401: Unauthorized', response.message
+ end
+
+ def test_successful_purchase_oauth_after_store_credit_card
+ store = @gateway_token.store(@credit_card, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway_oauth.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_after_store_with_google_pay
+ store = @gateway.store(@google_pay_visa_cryptogram_3ds_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_after_store_with_apple_pay
+ store = @gateway.store(@apple_pay_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway.purchase(@amount, token, @options)
+ assert_success response
+ end
+
+ def test_success_purchase_oauth_after_store_ouath_with_apple_pay
+ store = @gateway_oauth.store(@apple_pay_network_token, @options)
+ assert_success store
+ token = store.params['id']
+ response = @gateway_oauth.purchase(@amount, token, @options)
+ assert_success response
+ end
+
def test_successful_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -648,6 +1038,15 @@ def test_successful_void
assert_success void
end
+ def test_successful_purchase_store_after_verify
+ verify = @gateway.verify(@apple_pay_network_token, @options)
+ assert_success verify
+ source_id = verify.params['source']['id']
+ response = @gateway.purchase(@amount, source_id, @options.merge(source_id: source_id, source_type: 'id'))
+ assert_success response
+ assert_success verify
+ end
+
def test_successful_void_via_oauth
auth = @gateway_oauth.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -683,21 +1082,16 @@ def test_failed_void_via_oauth
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
- # this should only be a Response and not a MultiResponse
- # as we are passing in a 0 amount and there should be
- # no void call
- assert_instance_of(Response, response)
- refute_instance_of(MultiResponse, response)
assert_success response
assert_match %r{Succeeded}, response.message
end
def test_successful_verify_via_oauth
response = @gateway_oauth.verify(@credit_card, @options)
- assert_instance_of(Response, response)
- refute_instance_of(MultiResponse, response)
assert_success response
assert_match %r{Succeeded}, response.message
+ assert_not_nil response.responses.first.params['access_token']
+ assert_not_nil response.responses.first.params['expires']
end
def test_failed_verify
@@ -712,4 +1106,10 @@ def test_expired_card_returns_error_code
assert_equal 'request_invalid: card_expired', response.message
assert_equal 'request_invalid: card_expired', response.error_code
end
+
+ def test_successful_purchase_with_idempotency_key
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: 'test123'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
end
diff --git a/test/remote/gateways/remote_clearhaus_test.rb b/test/remote/gateways/remote_clearhaus_test.rb
index 844b748aee4..dfe1fd1b07d 100644
--- a/test/remote/gateways/remote_clearhaus_test.rb
+++ b/test/remote/gateways/remote_clearhaus_test.rb
@@ -44,7 +44,7 @@ def test_unsuccessful_signing_request
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
+ assert_equal 'Neither PUB key nor PRIV key: unsupported', auth.message
credentials = fixtures(:clearhaus_secure)
credentials[:signing_key] = 'foo'
diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb
index 1f8d32a453e..d52c1d93a29 100644
--- a/test/remote/gateways/remote_commerce_hub_test.rb
+++ b/test/remote/gateways/remote_commerce_hub_test.rb
@@ -2,33 +2,66 @@
class RemoteCommerceHubTest < Test::Unit::TestCase
def setup
+ # Uncomment the sleep if you want to run the entire set of remote tests without
+ # getting 'The transaction limit was exceeded. Please try again!' errors
+ # sleep 10
+
@gateway = CommerceHubGateway.new(fixtures(:commerce_hub))
@amount = 1204
- @credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '111')
- @google_pay = network_tokenization_credit_card('4005550000000019',
+ @credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123', first_name: 'John', last_name: 'Doe')
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :google_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @apple_pay = network_tokenization_credit_card('4005550000000019',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @declined_apple_pay = network_tokenization_credit_card('4000300011112220',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @declined_apple_pay = network_tokenization_credit_card(
+ '4000300011112220',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
+ @master_card = credit_card('5454545454545454', brand: 'master')
@options = {}
+ @three_d_secure = {
+ ds_transaction_id: '3543-b90d-d6dc1765c98',
+ authentication_response_status: 'A',
+ cavv: 'AAABCZIhcQAAAABZlyFxAAAAAAA',
+ eci: '05',
+ xid: '&x_MD5_Hash=abfaf1d1df004e3c27d5d2e05929b529&x_state=BC&x_reference_3=&x_auth_code=ET141870&x_fp_timestamp=1231877695',
+ version: '2.2.0'
+ }
+ @dynamic_descriptors = {
+ mcc: '1234',
+ merchant_name: 'Spreedly',
+ customer_service_number: '555444321',
+ service_entitlement: '123444555',
+ dynamic_descriptors_address: {
+ street: '123 Main Street',
+ houseNumberOrName: 'Unit B',
+ city: 'Atlanta',
+ stateOrProvince: 'GA',
+ postalCode: '30303',
+ country: 'US'
+ }
+ }
end
def test_successful_purchase
@@ -37,6 +70,59 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_3ds_purchase
+ @options.merge!(three_d_secure: @three_d_secure)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_whit_physical_goods_indicator
+ @options[:physical_goods_indicator] = true
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert response.params['transactionDetails']['physicalGoodsIndicator']
+ end
+
+ def test_successful_purchase_with_gsf_mit
+ @options[:data_entry_source] = 'ELECTRONIC_PAYMENT_TERMINAL'
+ @options[:pos_entry_mode] = 'CONTACTLESS'
+ response = @gateway.purchase(@amount, @master_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_cit_with_gsf
+ stored_credential_options = {
+ initial_transaction: true,
+ reason_type: 'cardholder',
+ initiator: 'unscheduled'
+ }
+ @options[:eci_indicator] = 'CHANNEL_ENCRYPTED'
+ @options[:stored_credential] = stored_credential_options
+ response = @gateway.purchase(@amount, @master_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_failed_avs_cvv_response_codes
+ @options[:billing_address] = {
+ address1: '112 Main St.',
+ city: 'Atlanta',
+ state: 'GA',
+ zip: '30301',
+ country: 'US'
+ }
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal 'X', response.cvv_result['code']
+ assert_equal 'CVV check not supported for card', response.cvv_result['message']
+ assert_nil response.avs_result['code']
+ end
+
def test_successful_purchase_with_billing_and_shipping
response = @gateway.purchase(@amount, @credit_card, @options.merge({ billing_address: address, shipping_address: address }))
assert_success response
@@ -63,10 +149,17 @@ def test_successful_purchase_with_stored_credential_framework
assert_success response
end
+ def test_successful_purchase_with_dynamic_descriptors
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(@dynamic_descriptors))
+ 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 'Unable to assign card to brand: Invalid.', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
+ assert_equal '104', response.error_code
end
def test_successful_authorize
@@ -75,19 +168,26 @@ def test_successful_authorize
assert_equal 'Approved', response.message
end
- # Commenting out until we are able to resolve issue with capture transactions failing at gateway
- # def test_successful_authorize_and_capture
- # authorize = @gateway.authorize(@amount, @credit_card, @options)
- # assert_success authorize
+ def test_successful_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
- # capture = @gateway.capture(@amount, authorize.authorization)
- # assert_success capture
- # end
+ capture = @gateway.capture(@amount, authorize.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_with_dynamic_descriptors
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(@dynamic_descriptors))
+ assert_success authorize
+
+ capture = @gateway.capture(@amount, authorize.authorization, @options.merge(@dynamic_descriptors))
+ assert_success capture
+ end
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
end
def test_successful_authorize_and_void
@@ -109,7 +209,28 @@ def test_failed_void
def test_successful_verify
response = @gateway.verify(@credit_card, @options)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'VERIFIED', response.message
+ end
+
+ def test_successful_verify_with_address
+ @options[:billing_address] = {
+ address1: '112 Main St.',
+ city: 'Atlanta',
+ state: 'GA',
+ zip: '30301',
+ country: 'US'
+ }
+
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'VERIFIED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+
+ assert_failure response
end
def test_successful_purchase_and_refund
@@ -133,11 +254,24 @@ def test_successful_purchase_and_partial_refund
end
def test_failed_refund
- response = @gateway.refund(nil, '123', @options)
+ response = @gateway.refund(nil, 'abc123|123', @options)
assert_failure response
assert_equal 'Referenced transaction is invalid or not found', 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
+ response = @gateway.credit(@amount, '')
+ assert_failure response
+ assert_equal 'Invalid or Missing Field Data', response.message
+ end
+
def test_successful_store
response = @gateway.store(@credit_card, @options)
assert_success response
@@ -170,7 +304,7 @@ def test_successful_purchase_with_apple_pay
def test_failed_purchase_with_declined_apple_pay
response = @gateway.purchase(@amount, @declined_apple_pay, @options)
assert_failure response
- assert_equal 'Unable to assign card to brand: Invalid.', response.message
+ assert_match 'Unable to assign card to brand: Invalid', response.message
end
def test_transcript_scrubbing
@@ -182,7 +316,6 @@ def test_transcript_scrubbing
assert_scrubbed(@credit_card.number, transcript)
assert_scrubbed(@gateway.options[:api_key], transcript)
assert_scrubbed(@gateway.options[:api_secret], transcript)
- assert_scrubbed(@credit_card.verification_value, transcript)
end
def test_transcript_scrubbing_apple_pay
@@ -196,4 +329,18 @@ def test_transcript_scrubbing_apple_pay
assert_scrubbed(@gateway.options[:api_secret], transcript)
assert_scrubbed(@apple_pay.payment_cryptogram, transcript)
end
+
+ def test_successful_purchase_with_encrypted_credit_card
+ @options[:encryption_data] = {
+ keyId: '6d0b6b63-3658-4c90-b7a4-bffb8a928288',
+ encryptionType: 'RSA',
+ encryptionBlock: 'udJ89RebrHLVxa3ofdyiQ/RrF2Y4xKC/qw4NuV1JYrTDEpNeIq9ZimVffMjgkyKL8dlnB2R73XFtWA4klHrpn6LZrRumYCgoqAkBRJCrk09+pE5km2t2LvKtf/Bj2goYQNFA9WLCCvNGwhofp8bNfm2vfGsBr2BkgL+PH/M4SqyRHz0KGKW/NdQ4Mbdh4hLccFsPjtDnNidkMep0P02PH3Se6hp1f5GLkLTbIvDLPSuLa4eNgzb5/hBBxrq5M5+5n9a1PhQnVT1vPU0WbbWe1SGdGiVCeSYmmX7n+KkVmc1lw0dD7NXBjKmD6aGFAWGU/ls+7JVydedDiuz4E7HSDQ==',
+ encryptionBlockFields: 'card.cardData:16,card.nameOnCard:10,card.expirationMonth:2,card.expirationYear:4,card.securityCode:3',
+ encryptionTarget: 'MANUAL'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
end
diff --git a/test/remote/gateways/remote_creditcall_test.rb b/test/remote/gateways/remote_creditcall_test.rb
index d7ed5a7d2fa..67669780996 100644
--- a/test/remote/gateways/remote_creditcall_test.rb
+++ b/test/remote/gateways/remote_creditcall_test.rb
@@ -147,7 +147,7 @@ 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
+ assert_match %r{PAN Must be >= 12 Digits}, response.message
end
def test_invalid_login
@@ -155,7 +155,7 @@ def test_invalid_login
response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_match %r{Invalid TerminalID - Must be 8 digit number}, response.message
+ assert_match %r{Invalid terminal details}, response.message
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
index e7973e90c10..30b2dddab3f 100644
--- a/test/remote/gateways/remote_credorax_test.rb
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -45,7 +45,8 @@ def setup
}
}
- @apple_pay_card = network_tokenization_credit_card('4176661000001015',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
month: 10,
year: Time.new.year + 2,
first_name: 'John',
@@ -54,15 +55,25 @@ def setup
payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_card = network_tokenization_credit_card('4176661000001015',
+ @google_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '07'
+ )
+
+ @nt_credit_card = network_tokenization_credit_card(
+ '4176661000001015',
+ brand: 'visa',
+ source: :network_token,
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA='
+ )
end
def test_successful_purchase_with_apple_pay
@@ -79,6 +90,13 @@ def test_successful_purchase_with_google_pay
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_network_token
+ response = @gateway.purchase(@amount, @nt_credit_card, @options)
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
def test_transcript_scrubbing_network_tokenization_card
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @apple_pay_card, @options)
@@ -533,7 +551,7 @@ def test_purchase_using_stored_credential_installment_mit
assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
assert_success purchase
assert_equal '8', purchase.params['A9']
- assert network_transaction_id = purchase.params['Z13']
+ assert network_transaction_id = purchase.params['Z50']
used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id)
assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
@@ -557,7 +575,7 @@ def test_purchase_using_stored_credential_unscheduled_mit
assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
assert_success purchase
assert_equal '8', purchase.params['A9']
- assert network_transaction_id = purchase.params['Z13']
+ assert network_transaction_id = purchase.params['Z50']
used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id)
assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
diff --git a/test/remote/gateways/remote_cyber_source_rest_test.rb b/test/remote/gateways/remote_cyber_source_rest_test.rb
new file mode 100644
index 00000000000..f21d23df587
--- /dev/null
+++ b/test/remote/gateways/remote_cyber_source_rest_test.rb
@@ -0,0 +1,619 @@
+require 'test_helper'
+
+class RemoteCyberSourceRestTest < Test::Unit::TestCase
+ def setup
+ @gateway = CyberSourceRestGateway.new(fixtures(:cybersource_rest))
+ @amount = 10221
+ @card_without_funds = credit_card('42423482938483873')
+ @bank_account = check(account_number: '4100', routing_number: '121042882')
+ @declined_bank_account = check(account_number: '550111', routing_number: '121107882')
+
+ @visa_card = credit_card('4111111111111111', verification_value: '987', month: 12, year: 2031)
+
+ @master_card = credit_card('2222420000001113', brand: 'master')
+ @discover_card = credit_card('6011111111111117', brand: 'discover')
+
+ @visa_network_token = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+ @amex_network_token = network_tokenization_credit_card(
+ '378282246310005',
+ brand: 'american_express',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+
+ @mastercard_network_token = network_tokenization_credit_card(
+ '5555555555554444',
+ brand: 'master',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+
+ @apple_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569
+ )
+
+ @google_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569
+ )
+
+ @google_pay_master = network_tokenization_credit_card(
+ '5555555555554444',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'master'
+ )
+
+ @apple_pay_jcb = network_tokenization_credit_card(
+ '3566111111111113',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'jcb'
+ )
+
+ @apple_pay_american_express = network_tokenization_credit_card(
+ '378282246310005',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'american_express'
+ )
+
+ @google_pay_discover = network_tokenization_credit_card(
+ '6011111111111117',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'discover'
+ )
+
+ @billing_address = {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ }
+
+ @options = {
+ order_id: generate_unique_id,
+ currency: 'USD',
+ email: 'test@cybs.com',
+ billing_address: {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ }
+ }
+ end
+
+ def test_handle_credentials_error
+ gateway = CyberSourceRestGateway.new({ merchant_id: 'abc123', public_key: 'abc456', private_key: 'def789' })
+ response = gateway.authorize(@amount, @visa_card, @options)
+
+ assert_equal('Authentication Failed', response.message)
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_billing_address
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_failure_authorize_with_declined_credit_card
+ response = @gateway.authorize(@amount, @card_without_funds, @options)
+
+ assert_failure response
+ assert_match %r{Invalid account}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
+ def test_successful_capture
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.capture(@amount, authorize.authorization, @options)
+
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_capture_with_partial_amount
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.capture(@amount - 10, authorize.authorization, @options)
+
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ # def test_failure_capture_with_higher_amount
+ # authorize = @gateway.authorize(@amount, @visa_card, @options)
+ # response = @gateway.capture(@amount + 10, authorize.authorization, @options)
+
+ # assert_failure response
+ # assert_match(/exceeds/, response.params['message'])
+ # end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_credit_card_ignore_avs
+ @options[:ignore_avs] = 'true'
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_network_token_ignore_avs
+ @options[:ignore_avs] = 'true'
+ response = @gateway.purchase(@amount, @apple_pay, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_credit_card_ignore_cvv
+ @options[:ignore_cvv] = 'true'
+ response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_network_token_ignore_cvv
+ @options[:ignore_cvv] = 'true'
+ response = @gateway.purchase(@amount, @apple_pay, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert response.params['_links']['void'].present?
+ end
+
+ def test_failure_refund
+ purchase = @gateway.purchase(@amount, @card_without_funds, @options)
+ response = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_failure response
+ assert response.test?
+ assert_match %r{Declined - One or more fields in the request contains invalid data}, response.params['message']
+ assert_equal 'INVALID_DATA', response.params['reason']
+ end
+
+ def test_successful_partial_refund
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response = @gateway.refund(@amount / 2, purchase.authorization, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert response.params['_links']['void'].present?
+ end
+
+ def test_successful_repeat_refund_transaction
+ purchase = @gateway.purchase(@amount, @visa_card, @options)
+ response1 = @gateway.refund(@amount, purchase.authorization, @options)
+
+ assert_success response1
+ assert response1.test?
+ assert_equal 'PENDING', response1.message
+ assert response1.params['id'].present?
+ assert response1.params['_links']['void']
+
+ response2 = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success response2
+ assert response2.test?
+ assert_equal 'PENDING', response2.message
+ assert response2.params['id'].present?
+ assert response2.params['_links']['void']
+
+ assert_not_equal response1.params['_links']['void'], response2.params['_links']['void']
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @visa_card, @options)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.params['id'].present?
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_failure_credit
+ response = @gateway.credit(@amount, @card_without_funds, @options)
+
+ assert_failure response
+ assert response.test?
+ assert_match %r{Decline - Invalid account number}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @visa_card, @options)
+ response = @gateway.void(authorize.authorization, @options)
+ assert_success response
+ assert response.params['id'].present?
+ assert_equal 'REVERSED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_failure_void_using_card_without_funds
+ authorize = @gateway.authorize(@amount, @card_without_funds, @options)
+ response = @gateway.void(authorize.authorization, @options)
+ assert_failure response
+ assert_match %r{Declined - The request is missing one or more fields}, response.params['message']
+ assert_equal 'INVALID_REQUEST', response.params['status']
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@visa_card, @options)
+ assert_success response
+ assert response.params['id'].present?
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_failure_verify
+ response = @gateway.verify(@card_without_funds, @options)
+ assert_failure response
+ assert_match %r{Decline - Invalid account number}, response.message
+ assert_equal 'INVALID_ACCOUNT', response.error_code
+ end
+
+ def test_successful_authorize_with_visa_network_token
+ response = @gateway.authorize(@amount, @visa_network_token, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_mastercard_network_token
+ response = @gateway.authorize(@amount, @mastercard_network_token, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_amex_network_token
+ response = @gateway.authorize(@amount, @amex_network_token, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_apple_pay
+ response = @gateway.authorize(@amount, @apple_pay, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_authorize_with_google_pay
+ response = @gateway.authorize(@amount, @apple_pay, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ refute_empty response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_apple_pay_jcb
+ response = @gateway.purchase(@amount, @apple_pay_jcb, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay_american_express
+ response = @gateway.purchase(@amount, @apple_pay_american_express, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_purchase_with_google_pay_master
+ response = @gateway.purchase(@amount, @google_pay_master, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_authorize_with_google_pay_discover
+ response = @gateway.purchase(@amount, @google_pay_discover, @options)
+
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.authorize(@amount, @visa_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(@visa_card.number, transcript)
+ assert_scrubbed(@visa_card.verification_value, transcript)
+ end
+
+ def test_transcript_scrubbing_bank
+ @options[:billing_address] = @billing_address
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @bank_account, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@bank_account.account_number, transcript)
+ assert_scrubbed(@bank_account.routing_number, transcript)
+ end
+
+ def test_successful_authorize_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @bank_account, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_purchase_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.purchase(@amount, @bank_account, @options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_failed_authorize_with_bank_account
+ @options[:billing_address] = @billing_address
+ response = @gateway.authorize(@amount, @declined_bank_account, @options)
+ assert_failure response
+ assert_equal 'Decline - General decline by the processor.', response.message
+ end
+
+ def test_failed_authorize_with_bank_account_missing_country_code
+ response = @gateway.authorize(@amount, @bank_account, @options.except(:billing_address))
+ assert_failure response
+ assert_equal 'Declined - The request is missing one or more fields', response.params['message']
+ end
+
+ def stored_credential_options(*args, ntid: nil)
+ @options.merge(stored_credential: stored_credential(*args, network_transaction_id: ntid))
+ end
+
+ def test_purchase_using_stored_credential_initial_mit
+ options = stored_credential_options(:merchant, :internet, :initial)
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ assert purchase = @gateway.purchase(@amount, @visa_card, options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_with_discover
+ options = stored_credential_options(:cardholder, :recurring, :initial)
+ assert auth = @gateway.authorize(@amount, @discover_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:cardholder, :recurring, ntid: auth.network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @discover_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_non_us
+ options = stored_credential_options(:cardholder, :recurring, :initial)
+ options[:billing_address][:country] = 'CA'
+ options[:billing_address][:state] = 'ON'
+ options[:billing_address][:city] = 'Ottawa'
+ options[:billing_address][:zip] = 'K1C2N6'
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:merchant, :recurring, ntid: auth.network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_cit
+ options = stored_credential_options(:cardholder, :recurring, :initial)
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:cardholder, :recurring, ntid: auth.network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit
+ options = stored_credential_options(:merchant, :recurring, :initial)
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:merchant, :recurring, ntid: auth.network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_installment
+ options = stored_credential_options(:cardholder, :installment, :initial)
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ used_store_credentials = stored_credential_options(:merchant, :installment, ntid: auth.network_transaction_id)
+ assert purchase = @gateway.authorize(@amount, @visa_card, options.merge(used_store_credentials))
+ assert_success purchase
+ end
+
+ def test_auth_and_purchase_with_network_txn_id
+ options = stored_credential_options(:merchant, :recurring, :initial)
+ assert auth = @gateway.authorize(@amount, @visa_card, options)
+ assert_success auth
+ assert purchase = @gateway.purchase(@amount, @visa_card, options.merge(network_transaction_id: auth.network_transaction_id))
+ assert_success purchase
+ end
+
+ def test_successful_purchase_with_reconciliation_id
+ options = @options.merge(reconciliation_id: '1936831')
+ assert response = @gateway.purchase(@amount, @visa_card, options)
+ assert_success response
+ end
+
+ def test_successful_authorization_with_reconciliation_id
+ options = @options.merge(reconciliation_id: '1936831')
+ assert response = @gateway.authorize(@amount, @visa_card, options)
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_verify_zero_amount
+ @options[:zero_amount_auth] = true
+ response = @gateway.verify(@visa_card, @options)
+ assert_success response
+ assert_match '0.00', response.params['orderInformation']['amountDetails']['authorizedAmount']
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_successful_bank_account_purchase_with_sec_code
+ options = @options.merge(sec_code: 'WEB')
+ response = @gateway.purchase(@amount, @bank_account, options)
+ assert_success response
+ assert_equal 'PENDING', response.message
+ end
+
+ def test_successful_purchase_with_solution_id
+ ActiveMerchant::Billing::CyberSourceRestGateway.application_id = 'A1000000'
+ assert response = @gateway.purchase(@amount, @visa_card, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ ensure
+ ActiveMerchant::Billing::CyberSourceGateway.application_id = nil
+ end
+
+ def test_successful_authorize_with_3ds2_visa
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+ auth = @gateway.authorize(@amount, @visa_card, @options)
+ assert_success auth
+ end
+
+ def test_successful_authorize_with_3ds2_mastercard
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+ auth = @gateway.authorize(@amount, @master_card, @options)
+ assert_success auth
+ end
+
+ def test_successful_purchase_with_level_2_data
+ response = @gateway.purchase(@amount, @visa_card, @options.merge({ purchase_order_number: '13829012412' }))
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+
+ def test_successful_purchase_with_level_2_and_3_data
+ options = {
+ purchase_order_number: '6789',
+ discount_amount: '150',
+ ships_from_postal_code: '90210',
+ line_items: [
+ {
+ productName: 'Product Name',
+ kind: 'debit',
+ quantity: 10,
+ unitPrice: '9.5000',
+ totalAmount: '95.00',
+ taxAmount: '5.00',
+ discountAmount: '0.00',
+ productCode: '54321',
+ commodityCode: '98765'
+ },
+ {
+ productName: 'Other Product Name',
+ kind: 'debit',
+ quantity: 1,
+ unitPrice: '2.5000',
+ totalAmount: '90.00',
+ taxAmount: '2.00',
+ discountAmount: '1.00',
+ productCode: '54322',
+ commodityCode: '98766'
+ }
+ ]
+ }
+ assert response = @gateway.purchase(@amount, @visa_card, @options.merge(options))
+ assert_success response
+ assert response.test?
+ assert_equal 'AUTHORIZED', response.message
+ assert_nil response.params['_links']['capture']
+ end
+end
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 2ab0c970f54..1ff2469ca47 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -10,37 +10,71 @@ def setup
@credit_card = credit_card('4111111111111111', verification_value: '987')
@declined_card = credit_card('801111111111111')
- @master_credit_card = credit_card('5555555555554444',
+ @master_credit_card = credit_card(
+ '5555555555554444',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :master)
+ brand: :master
+ )
@pinless_debit_card = credit_card('4002269999999999')
- @elo_credit_card = credit_card('5067310000000010',
+ @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',
+ 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',
+ 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',
+ brand: :visa
+ )
+ @three_ds_invalid_card = credit_card(
+ '4000000000000010',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :visa)
- @three_ds_enrolled_mastercard = credit_card('5200000000001005',
+ brand: :visa
+ )
+ @three_ds_enrolled_mastercard = credit_card(
+ '5200000000001005',
verification_value: '321',
month: '12',
year: (Time.now.year + 2).to_s,
- brand: :master)
+ brand: :master
+ )
+ @visa_network_token = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+ @amex_network_token = network_tokenization_credit_card(
+ '378282246310005',
+ brand: 'american_express',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+
+ @mastercard_network_token = network_tokenization_credit_card(
+ '5555555555554444',
+ brand: 'master',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
@amount = 100
@@ -70,6 +104,7 @@ def setup
original_amount: '4',
reference_data_code: 'ABC123',
invoice_number: '123',
+ first_recurring_payment: true,
mobile_remote_payment_type: 'A1',
vat_tax_rate: '1'
}
@@ -106,18 +141,13 @@ def test_transcript_scrubbing
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)
+ @gateway.authorize(@amount, @visa_network_token, @options)
end
transcript = @gateway.scrub(transcript)
- assert_scrubbed(credit_card.number, transcript)
- assert_scrubbed(credit_card.payment_cryptogram, transcript)
+ assert_scrubbed(@visa_network_token.number, transcript)
+ assert_scrubbed(@visa_network_token.payment_cryptogram, transcript)
assert_scrubbed(@gateway.options[:password], transcript)
end
@@ -134,6 +164,13 @@ def test_successful_authorization_with_reconciliation_id
assert !response.authorization.blank?
end
+ def test_successful_authorization_with_aggregator_id
+ options = @options.merge(aggregator_id: 'ABCDE')
+ assert response = @gateway.authorize(@amount, @credit_card, options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
def test_successful_authorize_with_solution_id
ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000'
assert response = @gateway.authorize(@amount, @credit_card, @options)
@@ -249,6 +286,38 @@ def test_successful_authorization_with_merchant_tax_id
assert_successful_response(response)
end
+ def test_successful_auth_with_single_element_from_other_tax
+ options = @options.merge(vat_tax_rate: '1')
+
+ assert response = @gateway.authorize(@amount, @master_credit_card, options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_single_element_from_other_tax
+ options = @options.merge(national_tax_amount: '0.05')
+
+ assert response = @gateway.purchase(@amount, @master_credit_card, options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_auth_with_gratuity_amount
+ options = @options.merge(gratuity_amount: '7.50')
+
+ assert response = @gateway.authorize(@amount, @master_credit_card, options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_gratuity_amount
+ options = @options.merge(gratuity_amount: '7.50')
+
+ assert response = @gateway.purchase(@amount, @master_credit_card, options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
def test_successful_authorization_with_sales_slip_number
options = @options.merge(sales_slip_number: '456')
assert response = @gateway.authorize(@amount, @credit_card, options)
@@ -372,6 +441,22 @@ def test_successful_purchase_with_bank_account
assert_successful_response(response)
end
+ # To properly run this test couple of test your account needs to be enabled to
+ # handle canadian bank accounts.
+ def test_successful_purchase_with_a_canadian_bank_account_full_number
+ bank_account = check({ account_number: '4100', routing_number: '011000015' })
+ @options[:currency] = 'CAD'
+ assert response = @gateway.purchase(10000, bank_account, @options)
+ assert_successful_response(response)
+ end
+
+ def test_successful_purchase_with_a_canadian_bank_account_8_digit_number
+ bank_account = check({ account_number: '4100', routing_number: '11000015' })
+ @options[:currency] = 'CAD'
+ assert response = @gateway.purchase(10000, bank_account, @options)
+ assert_successful_response(response)
+ end
+
def test_successful_purchase_with_bank_account_savings_account
bank_account = check({ account_number: '4100', routing_number: '011000015', account_type: 'savings' })
assert response = @gateway.purchase(10000, bank_account, @options)
@@ -458,6 +543,12 @@ def test_successful_purchase_with_reconciliation_id
assert_successful_response(response)
end
+ def test_successful_purchase_with_reconciliation_id_2
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ assert response.params['reconciliationID2']
+ end
+
def test_successful_authorize_with_customer_id
options = @options.merge(customer_id: '7500BB199B4270EFE05348D0AFCAD')
assert response = @gateway.authorize(@amount, @credit_card, options)
@@ -651,12 +742,7 @@ def test_successful_refund_with_bank_account_follow_on
end
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 auth = @gateway.authorize(@amount, @visa_network_token, @options)
assert_successful_response(auth)
assert capture = @gateway.capture(@amount, auth.authorization)
@@ -664,12 +750,15 @@ def test_network_tokenization_authorize_and_capture
end
def test_network_tokenization_with_amex_cc_and_basic_cryptogram
- credit_card = network_tokenization_credit_card('378282246310005',
- brand: 'american_express',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ assert auth = @gateway.authorize(@amount, @amex_network_token, @options)
+ assert_successful_response(auth)
- assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_successful_response(capture)
+ end
+
+ def test_network_tokenization_with_mastercard
+ assert auth = @gateway.authorize(@amount, @mastercard_network_token, @options)
assert_successful_response(auth)
assert capture = @gateway.capture(@amount, auth.authorization)
@@ -680,10 +769,12 @@ def test_network_tokenization_with_amex_cc_longer_cryptogram
# Generate a random 40 bytes binary amex cryptogram => Base64.encode64(Random.bytes(40))
long_cryptogram = "NZwc40C4eTDWHVDXPekFaKkNYGk26w+GYDZmU50cATbjqOpNxR/eYA==\n"
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
eci: '05',
- payment_cryptogram: long_cryptogram)
+ payment_cryptogram: long_cryptogram
+ )
assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_successful_response(auth)
@@ -693,15 +784,69 @@ def test_network_tokenization_with_amex_cc_longer_cryptogram
end
def test_purchase_with_network_tokenization_with_amex_cc
- credit_card = network_tokenization_credit_card('378282246310005',
- brand: 'american_express',
- eci: '05',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ assert auth = @gateway.purchase(@amount, @amex_network_token, @options)
+ assert_successful_response(auth)
+ end
+
+ def test_purchase_with_apple_pay_network_tokenization_visa_subsequent_auth
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+
+ assert auth = @gateway.purchase(@amount, credit_card, @options)
+ assert_successful_response(auth)
+ end
+
+ def test_purchase_with_apple_pay_network_tokenization_mastercard_subsequent_auth
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ eci: '05',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '0602MCC603474'
+ }
assert auth = @gateway.purchase(@amount, credit_card, @options)
assert_successful_response(auth)
end
+ def test_successful_auth_and_capture_nt_mastercard_with_tax_options_and_no_xml_parsing_errors
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+
+ options = { ignore_avs: true, order_id: generate_unique_id, vat_tax_rate: 1.01 }
+
+ assert auth = @gateway.authorize(@amount, credit_card, options)
+ assert_successful_response(auth)
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_successful_response(capture)
+ end
+
+ def test_successful_purchase_nt_mastercard_with_tax_options_and_no_xml_parsing_errors
+ credit_card = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+
+ options = { ignore_avs: true, order_id: generate_unique_id, vat_tax_rate: 1.01 }
+
+ assert response = @gateway.purchase(@amount, credit_card, options)
+ assert_successful_response(response)
+ end
+
def test_successful_authorize_with_mdd_fields
(1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" }
@@ -894,8 +1039,11 @@ def test_successful_update_subscription_billing_address
assert response = @gateway.store(@credit_card, @subscription_options)
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 response = @gateway.update(
+ response.authorization,
+ nil,
+ { order_id: generate_unique_id, setup_fee: 100, billing_address: address, email: 'someguy1232@fakeemail.net' }
+ )
assert_successful_response(response)
end
@@ -1133,6 +1281,72 @@ def test_successful_subsequent_unscheduled_cof_purchase
assert_successful_response(response)
end
+ def test_successful_authorize_with_3ds_exemption
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: 'moto'))
+ assert_successful_response(response)
+ end
+
+ def test_successful_purchase_with_3ds_exemption
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.purchase(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: 'moto'))
+ assert_successful_response(response)
+ end
+
+ def test_successful_recurring_cof_authorize_with_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: ''
+ }
+
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential]))
+ assert_successful_response(response)
+ end
+
+ def test_successful_recurring_cof_purchase_with_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: ''
+ }
+
+ @options[:three_d_secure] = {
+ version: '2.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+
+ assert response = @gateway.purchase(@amount, @three_ds_enrolled_card, @options.merge(three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential]))
+ assert_successful_response(response)
+ end
+
def test_invalid_field
@options = @options.merge({
address: {
diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb
index 001817efef5..c46b6aeae6c 100644
--- a/test/remote/gateways/remote_d_local_test.rb
+++ b/test/remote/gateways/remote_d_local_test.rb
@@ -4,7 +4,7 @@ class RemoteDLocalTest < Test::Unit::TestCase
def setup
@gateway = DLocalGateway.new(fixtures(:d_local))
- @amount = 200
+ @amount = 1000
@credit_card = credit_card('4111111111111111')
@credit_card_naranja = credit_card('5895627823453005')
@cabal_credit_card = credit_card('5896 5700 0000 0004')
@@ -50,27 +50,34 @@ def test_successful_purchase
assert_match 'The payment was paid', response.message
end
+ def test_successful_purchase_with_save_option
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(save: true))
+ assert_success response
+ assert_equal true, response.params['card']['save']
+ assert_equal 'CREDIT', response.params['card']['type']
+ assert_not_empty response.params['card']['card_id']
+ assert_match 'The payment was paid', response.message
+ end
+
def test_successful_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_match 'The payment was paid', response.message
end
def test_successful_purchase_with_network_tokens_and_store_credential_type
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
- response = @gateway.purchase(@amount, credit_card, @options.merge!(stored_credential_type: 'SUBSCRIPTION'))
+ options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ response = @gateway.purchase(@amount, credit_card, options)
assert_success response
assert_match 'SUBSCRIPTION', response.params['card']['stored_credential_type']
assert_match 'The payment was paid', response.message
end
def test_successful_purchase_with_network_tokens_and_store_credential_usage
- options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
response = @gateway.purchase(@amount, credit_card, options)
assert_success response
assert_match 'USED', response.params['card']['stored_credential_usage']
@@ -78,13 +85,13 @@ def test_successful_purchase_with_network_tokens_and_store_credential_usage
end
def test_successful_purchase_with_installments
- response = @gateway.purchase(@amount, @credit_card, @options_argentina_installments)
+ response = @gateway.purchase(@amount * 50, @credit_card, @options_argentina_installments)
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)
+ response = @gateway.purchase(@amount * 50, @credit_card_naranja, @options_argentina)
assert_success response
assert_match 'The payment was paid', response.message
end
@@ -155,9 +162,9 @@ def test_successful_purchase_with_additional_data
end
def test_successful_purchase_with_force_type_debit
- options = @options.merge(force_type: 'DEBIT')
+ options = @options_argentina.merge(force_type: 'DEBIT')
- response = @gateway.purchase(@amount, @credit_card, options)
+ response = @gateway.purchase(@amount * 50, @credit_card, options)
assert_success response
assert_match 'The payment was paid', response.message
end
@@ -170,13 +177,13 @@ def test_successful_purchase_colombia
end
def test_successful_purchase_argentina
- response = @gateway.purchase(@amount, @credit_card, @options_argentina)
+ response = @gateway.purchase(@amount * 50, @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)
+ response = @gateway.purchase(@amount, @cabal_credit_card, @options_mexico)
assert_success response
assert_match 'The payment was paid', response.message
end
@@ -200,8 +207,10 @@ def test_failed_purchase
end
def test_failed_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA='
+ )
response = @gateway.purchase(@amount, credit_card, @options.merge(description: '300'))
assert_failure response
assert_match 'The payment was rejected', response.message
diff --git a/test/remote/gateways/remote_datatrans_test.rb b/test/remote/gateways/remote_datatrans_test.rb
new file mode 100644
index 00000000000..43d74f755ed
--- /dev/null
+++ b/test/remote/gateways/remote_datatrans_test.rb
@@ -0,0 +1,251 @@
+require 'test_helper'
+
+class RemoteDatatransTest < Test::Unit::TestCase
+ def setup
+ @gateway = DatatransGateway.new(fixtures(:datatrans))
+
+ @amount = 756
+ @credit_card = credit_card('4242424242424242', verification_value: '123', first_name: 'John', last_name: 'Smith', month: 6, year: 2025)
+ @bad_amount = 100000 # anything grather than 500 EUR
+ @credit_card_frictionless = credit_card('4000001000000018', verification_value: '123', first_name: 'John', last_name: 'Smith', month: 6, year: 2025)
+
+ @options = {
+ order_id: SecureRandom.random_number(1000000000).to_s,
+ description: 'An authorize',
+ email: 'john.smith@test.com'
+ }
+
+ @three_d_secure = {
+ three_d_secure: {
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv8=',
+ cavv_algorithm: '1',
+ xid: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'Y',
+ authentication_response_status: 'Y',
+ directory_response_status: 'Y',
+ version: '2',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+ }
+
+ @billing_address = address
+
+ @google_pay_card = network_tokenization_credit_card(
+ '4900000000000094',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '06',
+ year: '2025',
+ source: :google_pay,
+ verification_value: 569
+ )
+
+ @apple_pay_card = network_tokenization_credit_card(
+ '4900000000000094',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '06',
+ year: '2025',
+ source: :apple_pay,
+ verification_value: 569
+ )
+
+ @nt_credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ eci: '07',
+ source: :network_token,
+ verification_value: '737',
+ brand: 'visa'
+ )
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_include response.params, 'transactionId'
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_include response.params, 'transactionId'
+ end
+
+ def test_failed_authorize
+ # the bad amount currently is only setle to EUR currency
+ response = @gateway.purchase(@bad_amount, @credit_card, @options.merge({ currency: 'EUR' }))
+ assert_failure response
+ assert_equal response.error_code, 'BLOCKED_CARD'
+ assert_equal response.message, 'card blocked'
+ end
+
+ def test_failed_authorize_invalid_currency
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ currency: 'DKK' }))
+ assert_failure response
+ assert_equal response.error_code, 'INVALID_PROPERTY'
+ assert_equal response.message, 'authorize.currency'
+ end
+
+ def test_successful_capture
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.capture(@amount, authorize_response.authorization, @options)
+ assert_success response
+ assert_equal response.authorization, nil
+ end
+
+ def test_successful_refund
+ purchase_response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase_response
+
+ response = @gateway.refund(@amount, purchase_response.authorization, @options)
+ assert_success response
+ assert_include response.params, 'transactionId'
+ end
+
+ def test_successful_capture_with_less_authorized_amount_and_refund
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ capture_response = @gateway.capture(@amount - 100, authorize_response.authorization, @options)
+ assert_success capture_response
+
+ response = @gateway.refund(@amount - 200, authorize_response.authorization, @options)
+ assert_success response
+ end
+
+ def test_failed_partial_capture_already_captured
+ authorize_response = @gateway.authorize(2500, @credit_card, @options)
+ assert_success authorize_response
+
+ capture_response = @gateway.capture(100, authorize_response.authorization, @options)
+ assert_success capture_response
+
+ response = @gateway.capture(100, authorize_response.authorization, @options)
+ assert_failure response
+ assert_equal response.error_code, 'INVALID_TRANSACTION_STATUS'
+ assert_equal response.message, 'already settled'
+ end
+
+ def test_failed_partial_capture_refund_refund_exceed_captured
+ authorize_response = @gateway.authorize(200, @credit_card, @options)
+ assert_success authorize_response
+
+ capture_response = @gateway.capture(100, authorize_response.authorization, @options)
+ assert_success capture_response
+
+ response = @gateway.refund(200, authorize_response.authorization, @options)
+ assert_failure response
+ assert_equal response.error_code, 'INVALID_PROPERTY'
+ assert_equal response.message, 'credit.amount'
+ end
+
+ def test_failed_consecutive_partial_refund_when_total_exceed_amount
+ purchase_response = @gateway.purchase(700, @credit_card, @options)
+
+ assert_success purchase_response
+
+ refund_response_1 = @gateway.refund(200, purchase_response.authorization, @options)
+ assert_success refund_response_1
+
+ refund_response_2 = @gateway.refund(200, purchase_response.authorization, @options)
+ assert_success refund_response_2
+
+ refund_response_3 = @gateway.refund(200, purchase_response.authorization, @options)
+ assert_success refund_response_3
+
+ refund_response_4 = @gateway.refund(200, purchase_response.authorization, @options)
+ assert_failure refund_response_4
+ assert_equal refund_response_4.error_code, 'INVALID_PROPERTY'
+ assert_equal refund_response_4.message, 'credit.amount'
+ end
+
+ def test_failed_refund_not_settle_transaction
+ purchase_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success purchase_response
+
+ response = @gateway.refund(@amount, purchase_response.authorization, @options)
+ assert_failure response
+ assert_equal response.error_code, 'INVALID_TRANSACTION_STATUS'
+ assert_equal response.message, 'the transaction cannot be credited'
+ end
+
+ def test_successful_void
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.void(authorize_response.authorization, @options)
+ assert_success response
+
+ assert_equal response.authorization, nil
+ end
+
+ def test_failed_void_because_captured_transaction
+ omit("the transaction could take about 20 minutes to
+ pass from settle to transmited, use a previos
+ transaction acutually transmited and comment this
+ omition")
+
+ # this is a previos transmited transaction, if the test fail use another, check dashboard to confirm it.
+ previous_authorization = '240417191339383491|339523493'
+ response = @gateway.void(previous_authorization, @options)
+ assert_failure response
+ assert_equal 'Action denied : Wrong transaction status', 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_successful_purchase_with_billing_address
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ billing_address: @billing_address }))
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_network_token
+ response = @gateway.purchase(@amount, @nt_credit_card, @options)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = @gateway.purchase(@amount, @apple_pay_card, @options)
+
+ assert_success response
+ end
+
+ def test_successful_authorize_with_google_pay
+ response = @gateway.authorize(@amount, @google_pay_card, @options)
+ assert_success response
+ end
+
+ def test_successful_void_with_google_pay
+ authorize_response = @gateway.authorize(@amount, @google_pay_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.void(authorize_response.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_3ds
+ response = @gateway.purchase(@amount, @credit_card_frictionless, @options.merge(@three_d_secure))
+ assert_success response
+ end
+
+ def test_failed_purchase_with_3ds
+ @three_d_secure[:three_d_secure][:cavv] = '\/\/\/\/8='
+ response = @gateway.purchase(@amount, @credit_card_frictionless, @options.merge(@three_d_secure))
+ assert_failure response
+ assert_equal response.error_code, 'INVALID_PROPERTY'
+ assert_equal response.message, 'cavv format is invalid. make sure that the value is base64 encoded and has a proper length.'
+ end
+end
diff --git a/test/remote/gateways/remote_decidir_plus_test.rb b/test/remote/gateways/remote_decidir_plus_test.rb
index 0f36584dab5..5a27ae05fc8 100644
--- a/test/remote/gateways/remote_decidir_plus_test.rb
+++ b/test/remote/gateways/remote_decidir_plus_test.rb
@@ -160,7 +160,7 @@ def test_successful_verify
def test_failed_verify
assert response = @gateway_auth.verify(@declined_card, @options)
assert_failure response
- assert_equal 'missing: fraud_detection', response.message
+ assert_equal '10734: Fraud Detection Data is required', response.message
end
def test_successful_store
@@ -217,7 +217,7 @@ def test_successful_purchase_with_fraud_detection
response = @gateway_purchase.purchase(@amount, payment_reference, options)
assert_success response
- assert_equal({ 'status' => nil }, response.params['fraud_detection'])
+ assert_equal({ 'send_to_cs' => false, 'status' => nil }, response.params['fraud_detection'])
end
def test_successful_purchase_with_card_brand
diff --git a/test/remote/gateways/remote_decidir_test.rb b/test/remote/gateways/remote_decidir_test.rb
index 7e590f2fd00..6f91f22778c 100644
--- a/test/remote/gateways/remote_decidir_test.rb
+++ b/test/remote/gateways/remote_decidir_test.rb
@@ -30,6 +30,16 @@ def setup
amount: 1500
}
]
+ @network_token = network_tokenization_credit_card(
+ '4012001037141112',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: '000203016912340000000FA08400317500000000',
+ name: 'Tesest payway'
+ )
+
+ @failed_message = ['PEDIR AUTORIZACION | request_authorization_card', 'COMERCIO INVALIDO | invalid_card']
+ @failed_code = ['1, call_issuer', '3, config_error']
end
def test_successful_purchase
@@ -53,6 +63,22 @@ def test_successful_purchase_with_amex
assert response.authorization
end
+ def test_successful_purchase_with_network_token
+ options = {
+ card_holder_door_number: 1234,
+ card_holder_birthday: '200988',
+ card_holder_identification_type: 'DNI',
+ card_holder_identification_number: '44444444',
+ order_id: SecureRandom.uuid,
+ last_4: @credit_card.last_digits
+ }
+ response = @gateway_for_purchase.purchase(500, @network_token, options)
+
+ assert_success response
+ assert_equal 'approved', response.message
+ assert response.authorization
+ end
+
# This test is currently failing.
# Decidir hasn't been able to provide a valid Diners Club test card number.
#
@@ -145,6 +171,18 @@ def test_successful_purchase_with_sub_payments
assert_equal 'approved', response.message
end
+ def test_successful_purchase_with_customer_object
+ customer_options = {
+ customer_id: 'John',
+ customer_email: 'decidir@decidir.com'
+ }
+
+ assert response = @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(customer_options))
+ assert_success response
+
+ assert_equal 'approved', response.message
+ end
+
def test_failed_purchase_with_bad_csmdds
options = {
fraud_detection: {
@@ -169,9 +207,14 @@ def test_failed_purchase_with_bad_csmdds
def test_failed_purchase
response = @gateway_for_purchase.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'COMERCIO INVALIDO | invalid_card', response.message
- assert_equal '3, config_error', response.error_code
- assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ assert_equal @failed_message.include?(response.message), true
+ assert_equal @failed_code.include?(response.error_code), true
+
+ if response.error_code.start_with?('1')
+ assert_match Gateway::STANDARD_ERROR_CODE[:call_issuer], response.error_code
+ else
+ assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
end
def test_failed_purchase_with_invalid_field
@@ -196,8 +239,13 @@ def test_successful_authorize_and_capture
def test_failed_authorize
response = @gateway_for_auth.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'PEDIR AUTORIZACION | request_authorization_card', response.message
- assert_match '1, call_issuer', response.error_code
+ assert_equal @failed_message.include?(response.message), true
+ assert_equal @failed_code.include?(response.error_code), true
+ if response.error_code.start_with?('1')
+ assert_match Gateway::STANDARD_ERROR_CODE[:call_issuer], response.error_code
+ else
+ assert_match Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
end
def test_failed_partial_capture
diff --git a/test/remote/gateways/remote_deepstack_test.rb b/test/remote/gateways/remote_deepstack_test.rb
new file mode 100644
index 00000000000..f34a26960c2
--- /dev/null
+++ b/test/remote/gateways/remote_deepstack_test.rb
@@ -0,0 +1,230 @@
+require 'test_helper'
+
+class RemoteDeepstackTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+ @gateway = DeepstackGateway.new(fixtures(:deepstack))
+
+ @credit_card = credit_card
+ @amount = 100
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4111111111111111',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Bob',
+ last_name: 'Bobby'
+ )
+
+ @invalid_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '5146315000000051',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Failure',
+ last_name: 'Fail'
+ )
+
+ address = {
+ address1: '123 Some st',
+ address2: '',
+ first_name: 'Bob',
+ last_name: 'Bobberson',
+ city: 'Some City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ shipping_address = {
+ address1: '321 Some st',
+ address2: '#9',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ city: 'Other City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ shipping_address: shipping_address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_token
+ response = @gateway.get_token(@credit_card, @options)
+ assert_success response
+
+ sale = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success sale
+ assert_equal 'Approved', sale.message
+ end
+
+ def test_failed_token
+ response = @gateway.get_token(@invalid_card, @options)
+ assert_failure response
+ assert_equal 'InvalidRequestException: Card number is invalid.', response.message
+ end
+
+ # Feature currently gated. Will be released in future version
+ # def test_successful_vault
+
+ # response = @gateway.gettoken(@credit_card, @options)
+ # assert_success response
+
+ # vault = @gateway.store(response.authorization, @options)
+ # assert_success vault
+
+ # sale = @gateway.purchase(@amount, vault.authorization, @options)
+ # assert_success sale
+
+ # end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_true response.params['captured']
+ end
+
+ def test_successful_purchase_with_more_options
+ additional_options = {
+ ip: '127.0.0.1',
+ email: 'joe@example.com'
+ }
+
+ sent_options = @options.merge(additional_options)
+
+ response = @gateway.purchase(@amount, @credit_card, sent_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_not_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.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
+ response = @gateway.authorize(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_not_equal 'Approved', 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 'Current transaction does not exist or is in an invalid state.', response.message
+ end
+
+ # This test will always void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ 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
+
+ # This test will always void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.params['id'])
+ assert_success refund
+ assert_equal @amount - 1, refund.params['amount']
+ end
+
+ # This test always be a void because we determine void/refund based on settlement status of the charge request (i.e can't refund a transaction that was just created)
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Specified transaction does not exist.', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(0, auth.params['id'])
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void(0, '')
+ assert_failure response
+ assert_equal 'Specified transaction does not exist.', 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(@invalid_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Request: Card number is invalid.}, response.message
+ end
+
+ def test_invalid_login
+ gateway = DeepstackGateway.new(publishable_api_key: '', app_id: '', shared_secret: '', sandbox: true)
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Specified transaction does not exist', 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)
+ expiration = '%02d%02d' % [@credit_card.month, @credit_card.year % 100]
+ assert_scrubbed(expiration, transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.get_token(@credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed('pk_test_XQS71KYAW9HW7XQOGAJIY4ENHZYZEO0C', transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
index 3c9624cb0a7..266c7b4e2ed 100644
--- a/test/remote/gateways/remote_ebanx_test.rb
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -24,8 +24,12 @@ def setup
metadata_2: 'test2'
},
tags: EbanxGateway::TAGS,
- soft_descriptor: 'ActiveMerchant'
+ soft_descriptor: 'ActiveMerchant',
+ email: 'neymar@test.com'
}
+
+ @hiper_card = credit_card('6062825624254001')
+ @elo_card = credit_card('6362970000457013')
end
def test_successful_purchase
@@ -34,6 +38,24 @@ def test_successful_purchase
assert_equal 'Accepted', response.message
end
+ def test_successful_purchase_hipercard
+ response = @gateway.purchase(@amount, @hiper_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_purchase_elocard
+ response = @gateway.purchase(@amount, @elo_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_store_elocard
+ response = @gateway.purchase(@amount, @elo_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,
@@ -112,6 +134,13 @@ def test_failed_authorize
assert_equal 'NOK', response.error_code
end
+ def test_failed_authorize_no_email
+ response = @gateway.authorize(@amount, @declined_card, @options.except(:email))
+ assert_failure response
+ assert_equal 'Field payment.email is required', response.message
+ assert_equal 'BP-DR-15', response.error_code
+ end
+
def test_successful_partial_capture_when_include_capture_amount_is_not_passed
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -194,6 +223,23 @@ def test_successful_store_and_purchase_as_brazil_business
store = @gateway.store(@credit_card, options)
assert_success store
+ assert_equal store.authorization.split('|')[1], 'visa'
+
+ 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_with_hipercard
+ options = @options.update(document: '32593371000110',
+ person_type: 'business',
+ responsible_name: 'Business Person',
+ responsible_document: '32593371000111',
+ responsible_birth_date: '1/11/1975')
+
+ store = @gateway.store(@hiper_card, options)
+ assert_success store
+ assert_equal store.authorization.split('|')[1], 'hipercard'
assert purchase = @gateway.purchase(@amount, store.authorization, options)
assert_success purchase
@@ -256,9 +302,20 @@ def test_successful_verify_for_mexico
end
def test_failed_verify
- response = @gateway.verify(@declined_card, @options)
+ declined_card = credit_card('6011088896715918')
+ response = @gateway.verify(declined_card, @options)
assert_failure response
- assert_match %r{Invalid card or card type}, response.message
+ assert_match %r{Not accepted}, response.message
+ end
+
+ def test_successful_inquire
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ inquire = @gateway.inquire(purchase.authorization)
+ assert_success inquire
+
+ assert_equal 'Accepted', purchase.message
end
def test_invalid_login
diff --git a/test/remote/gateways/remote_elavon_test.rb b/test/remote/gateways/remote_elavon_test.rb
index f4c4356b404..6ad3e3c6097 100644
--- a/test/remote/gateways/remote_elavon_test.rb
+++ b/test/remote/gateways/remote_elavon_test.rb
@@ -401,11 +401,11 @@ def test_successful_purchase_with_custom_fields
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 response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'ZAR', 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_equal 'The transaction currency sent is not supported', response.message
assert response.authorization
end
@@ -429,7 +429,7 @@ def test_successful_purchase_with_multi_currency_transaction_setting
end
def test_successful_purchase_with_level_3_fields
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(level_3_data: @level_3_data))
+ assert response = @gateway.purchase(500, @credit_card, @options.merge(level_3_data: @level_3_data))
assert_success response
assert_equal 'APPROVAL', response.message
@@ -445,7 +445,7 @@ def test_successful_purchase_with_shipping_address
end
def test_successful_purchase_with_shipping_address_and_l3
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: @shipping_address).merge(level_3_data: @level_3_data))
+ assert response = @gateway.purchase(500, @credit_card, @options.merge(shipping_address: @shipping_address).merge(level_3_data: @level_3_data))
assert_success response
assert_equal 'APPROVAL', response.message
diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb
index 7c90ff55646..3a9f2dbc42f 100644
--- a/test/remote/gateways/remote_element_test.rb
+++ b/test/remote/gateways/remote_element_test.rb
@@ -8,12 +8,14 @@ def setup
@credit_card = credit_card('4000100011112224')
@check = check
@options = {
- order_id: '1',
- billing_address: address,
- description: 'Store Purchase'
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true'
}
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4000100011112224',
month: '01',
year: Time.new.year + 2,
first_name: 'Jane',
@@ -22,9 +24,11 @@ def setup
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
eci: '05',
transaction_id: '123456789',
- source: :google_pay)
+ source: :google_pay
+ )
- @apple_pay_network_token = network_tokenization_credit_card('4895370015293175',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
month: '10',
year: Time.new.year + 2,
first_name: 'John',
@@ -33,19 +37,20 @@ def setup
payment_cryptogram: 'CeABBJQ1AgAAAAAgJDUCAAAAAAA=',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
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{Street address and 5-digit postal code match.}, response.avs_result['message']
assert_match %r{CVV matches}, response.cvv_result['message']
end
def test_failed_purchase
- @amount = 20
+ @amount = 51
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Declined', response.message
@@ -115,19 +120,65 @@ def test_successful_purchase_with_duplicate_check_disable_flag
end
def test_successful_purchase_with_duplicate_override_flag
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: true))
- assert_success response
- assert_equal 'Approved', response.message
+ options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase'
+ }
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: false))
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: true))
assert_success response
assert_equal 'Approved', response.message
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_overrride_flag: 'true'))
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: 'true'))
assert_success response
assert_equal 'Approved', response.message
- response = @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx'))
+ # Due to the way these new creds are configured, they fail on duplicate transactions.
+ # We expect failures if duplicate_override_flag: false
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(@amount, @credit_card, options.merge(duplicate_override_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_lodging_and_all_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'Sale',
+ charge_type: 'Restaurant'
+ },
+ card_holder_present_code: 'ECommerce',
+ card_input_code: 'ManualKeyed',
+ card_present_code: 'NotPresent',
+ cvv_presence_code: 'NotProvided',
+ market_code: 'HotelLodging',
+ terminal_capability_code: 'KeyEntered',
+ terminal_environment_code: 'ECommerce',
+ terminal_type: 'ECommerce',
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
assert_success response
assert_equal 'Approved', response.message
end
@@ -162,11 +213,11 @@ def test_successful_authorize_and_capture
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
- assert_equal 'Success', capture.message
+ assert_equal 'Approved', capture.message
end
def test_failed_authorize
- @amount = 20
+ @amount = 51
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Declined', response.message
diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb
index 2778f1d5c4e..3113dec205d 100644
--- a/test/remote/gateways/remote_eway_rapid_test.rb
+++ b/test/remote/gateways/remote_eway_rapid_test.rb
@@ -112,7 +112,9 @@ def test_successful_purchase_with_shipping_address
end
def test_fully_loaded_purchase
- response = @gateway.purchase(@amount, @credit_card,
+ response = @gateway.purchase(
+ @amount,
+ @credit_card,
redirect_url: 'http://awesomesauce.com',
ip: '0.0.0.0',
application_id: 'Woohoo',
@@ -148,7 +150,8 @@ def test_fully_loaded_purchase
country: 'US',
phone: '1115555555',
fax: '1115556666'
- })
+ }
+ )
assert_success response
end
diff --git a/test/remote/gateways/remote_eway_test.rb b/test/remote/gateways/remote_eway_test.rb
index 64f536d176f..4c306709656 100644
--- a/test/remote/gateways/remote_eway_test.rb
+++ b/test/remote/gateways/remote_eway_test.rb
@@ -4,9 +4,7 @@ class EwayTest < Test::Unit::TestCase
def setup
@gateway = EwayGateway.new(fixtures(:eway))
@credit_card_success = credit_card('4444333322221111')
- @credit_card_fail = credit_card('1234567812345678',
- month: Time.now.month,
- year: Time.now.year - 1)
+ @credit_card_fail = credit_card('1234567812345678', month: Time.now.month, year: Time.now.year - 1)
@params = {
order_id: '1230123',
diff --git a/test/remote/gateways/remote_fat_zebra_test.rb b/test/remote/gateways/remote_fat_zebra_test.rb
index ff8afe7c584..17da0b1c94d 100644
--- a/test/remote/gateways/remote_fat_zebra_test.rb
+++ b/test/remote/gateways/remote_fat_zebra_test.rb
@@ -227,4 +227,34 @@ def test_transcript_scrubbing
assert_scrubbed(@credit_card.number, transcript)
assert_scrubbed(@credit_card.verification_value, transcript)
end
+
+ def test_successful_purchase_with_3DS
+ @options[:three_d_secure] = {
+ version: '2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase_with_3DS
+ @options[:three_d_secure] = {
+ version: '3.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match(/version is not valid/, response.message)
+ end
end
diff --git a/test/remote/gateways/remote_first_pay_json_test.rb b/test/remote/gateways/remote_first_pay_json_test.rb
new file mode 100644
index 00000000000..0ca84b502e7
--- /dev/null
+++ b/test/remote/gateways/remote_first_pay_json_test.rb
@@ -0,0 +1,162 @@
+require 'test_helper'
+
+class RemoteFirstPayJsonTest < Test::Unit::TestCase
+ def setup
+ @gateway = FirstPayGateway.new(fixtures(:first_pay_rest_json))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('5130405452262903')
+
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
+ brand: 'visa',
+ eci: '05',
+ month: '02',
+ year: '2035',
+ source: :google_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
+ brand: 'visa',
+ eci: '05',
+ month: '02',
+ year: '2035',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ @options = {
+ order_id: SecureRandom.hex(24),
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match 'APPROVED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(99999999999, @credit_card, @options)
+ assert_failure response
+ assert_equal 'validationHasFailed', response.error_code
+ assert_match 'Amount exceed numeric limit of 9999999.99', response.message
+ end
+
+ def test_successful_purchase_with_google_pay
+ response = @gateway.purchase(@amount, @google_pay, @options)
+ assert_success response
+ assert_match 'APPROVED', response.message
+ assert_equal 'Visa-GooglePay', response.params['data']['cardType']
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = @gateway.purchase(@amount, @apple_pay, @options)
+ assert_success response
+ assert_match 'APPROVED', response.message
+ assert_equal 'Visa-ApplePay', response.params['data']['cardType']
+ end
+
+ def test_failed_purchase_with_no_address
+ @options.delete(:billing_address)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_equal 'validationHasFailed', response.error_code
+ assert_equal 'Name on credit card is required; Street is required.; City is required.; State is required.; Postal Code is required.', 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(99999999999, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '1234')
+ assert_failure response
+ end
+
+ def test_successful_refund_for_authorize_capture
+ 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, capture.authorization)
+ assert_success refund
+ end
+
+ def test_successful_refund_for_purchase
+ 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
+ response = @gateway.refund(@amount, '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('1')
+ assert_failure response
+ end
+
+ def test_recurring_payment
+ @options.merge!({
+ recurring: 'monthly',
+ recurring_start_date: (DateTime.now + 1.day).strftime('%m/%d/%Y'),
+ recurring_end_date: (DateTime.now + 1.month).strftime('%m/%d/%Y')
+ })
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match 'APPROVED', response.message
+ end
+
+ def test_invalid_login
+ gateway = FirstPayGateway.new(
+ processor_id: '1234',
+ merchant_key: 'abcd'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal('isError', response.error_code)
+ end
+
+ def test_transcript_scrubbing
+ @google_pay.verification_value = 789
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @google_pay, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@google_pay.number, transcript)
+ assert_scrubbed(@google_pay.verification_value, transcript)
+ assert_scrubbed(@google_pay.payment_cryptogram, transcript)
+ assert_scrubbed(@gateway.options[:processor_id], transcript)
+ assert_scrubbed(@gateway.options[:merchant_key], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_firstdata_e4_test.rb b/test/remote/gateways/remote_firstdata_e4_test.rb
index 9666a8637d7..e8a84ac81da 100755
--- a/test/remote/gateways/remote_firstdata_e4_test.rb
+++ b/test/remote/gateways/remote_firstdata_e4_test.rb
@@ -26,9 +26,11 @@ def test_successful_purchase
end
def test_successful_purchase_with_network_tokenization
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction Normal - Approved', response.message
diff --git a/test/remote/gateways/remote_firstdata_e4_v27_test.rb b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
index 8b41dc9013a..ef3f1ddb6ea 100644
--- a/test/remote/gateways/remote_firstdata_e4_v27_test.rb
+++ b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
@@ -27,9 +27,11 @@ def test_successful_purchase
end
def test_successful_purchase_with_network_tokenization
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction Normal - Approved', response.message
diff --git a/test/remote/gateways/remote_flex_charge_test.rb b/test/remote/gateways/remote_flex_charge_test.rb
new file mode 100644
index 00000000000..fd2ce646c94
--- /dev/null
+++ b/test/remote/gateways/remote_flex_charge_test.rb
@@ -0,0 +1,224 @@
+require 'timecop'
+require 'test_helper'
+
+class RemoteFlexChargeTest < Test::Unit::TestCase
+ def setup
+ @gateway = FlexChargeGateway.new(fixtures(:flex_charge))
+
+ @amount = 100
+ @credit_card_cit = credit_card('4111111111111111', verification_value: '999', first_name: 'Cure', last_name: 'Tester')
+ @credit_card_mit = credit_card('4000002760003184')
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ is_mit: true,
+ is_recurring: false,
+ mit_expiry_date_utc: (Time.now + 1.day).getutc.iso8601,
+ description: 'MyShoesStore',
+ is_declined: true,
+ order_id: SecureRandom.uuid,
+ idempotency_key: SecureRandom.uuid,
+ card_not_present: false,
+ email: 'test@gmail.com',
+ response_code: '100',
+ response_code_source: 'nmi',
+ avs_result_code: '200',
+ cvv_result_code: '111',
+ cavv_result_code: '111',
+ timezone_utc_offset: '-5',
+ billing_address: address.merge(name: 'Cure Tester')
+ }
+
+ @cit_options = @options.merge(
+ is_mit: false,
+ phone: '+99.2001a/+99.2001b'
+ )
+ end
+
+ def test_successful_purchase_with_three_ds_global
+ @options[:three_d_secure] = {
+ version: '2.1.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ xid: 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=',
+ cavv_algorithm: 'AAABCSIIAAAAAAACcwgAEMCoNh=',
+ enrolled: 'Y',
+ authentication_response_status: 'Y'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card_cit, @options)
+ assert_success response
+ assert_match 'SUBMITTED', response.message
+ end
+
+ def test_setting_access_token_when_no_present
+ assert_nil @gateway.options[:access_token]
+
+ @gateway.send(:fetch_access_token)
+
+ assert_not_nil @gateway.options[:access_token]
+ assert_not_nil @gateway.options[:token_expires]
+ end
+
+ def test_successful_access_token_generation_and_use
+ @gateway.send(:fetch_access_token)
+
+ second_purchase = @gateway.purchase(@amount, @credit_card_cit, @cit_options)
+
+ assert_success second_purchase
+ assert_kind_of MultiResponse, second_purchase
+ assert_equal 1, second_purchase.responses.size
+ assert_equal @gateway.options[:access_token], second_purchase.params[:access_token]
+ end
+
+ def test_successful_purchase_with_an_expired_access_token
+ initial_access_token = @gateway.options[:access_token] = SecureRandom.alphanumeric(10)
+ initial_expires = @gateway.options[:token_expires] = DateTime.now.strftime('%Q').to_i
+
+ Timecop.freeze(DateTime.now + 10.minutes) do
+ second_purchase = @gateway.purchase(@amount, @credit_card_cit, @cit_options)
+ assert_success second_purchase
+
+ assert_equal 2, second_purchase.responses.size
+ assert_not_equal initial_access_token, @gateway.options[:access_token]
+ assert_not_equal initial_expires, @gateway.options[:token_expires]
+
+ assert_not_nil second_purchase.params[:access_token]
+ assert_not_nil second_purchase.params[:token_expires]
+
+ assert_nil second_purchase.responses.first.params[:access_token]
+ end
+ end
+
+ def test_should_reset_access_token_when_401_error
+ @gateway.options[:access_token] = SecureRandom.alphanumeric(10)
+ @gateway.options[:token_expires] = DateTime.now.strftime('%Q').to_i + 15000
+
+ response = @gateway.purchase(@amount, @credit_card_cit, @cit_options)
+
+ assert_equal '', response.params['access_token']
+ end
+
+ def test_successful_purchase_cit_challenge_purchase
+ set_credentials!
+ response = @gateway.purchase(@amount, @credit_card_cit, @cit_options)
+ assert_success response
+ assert_equal 'CHALLENGE', response.message
+ end
+
+ def test_successful_purchase_mit
+ set_credentials!
+ response = @gateway.purchase(@amount, @credit_card_mit, @options)
+ assert_success response
+ assert_equal 'SUBMITTED', response.message
+ end
+
+ def test_failed_purchase
+ set_credentials!
+ response = @gateway.purchase(@amount, @credit_card_cit, billing_address: address)
+ assert_failure response
+ assert_equal nil, response.error_code
+ assert_not_nil response.params['TraceId']
+ end
+
+ def test_failed_cit_declined_purchase
+ set_credentials!
+ response = @gateway.purchase(@amount, @credit_card_cit, @cit_options.except(:phone))
+ assert_failure response
+ assert_equal 'DECLINED', response.error_code
+ end
+
+ def test_successful_refund
+ set_credentials!
+ purchase = @gateway.purchase(@amount, @credit_card_mit, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'DECLINED', refund.message
+ end
+
+ def test_partial_refund
+ omit('Partial refunds requires to raise some limits on merchant account')
+ set_credentials!
+ purchase = @gateway.purchase(100, @credit_card_cit, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(90, purchase.authorization)
+ assert_success refund
+ assert_equal 'DECLINED', refund.message
+ end
+
+ def test_failed_fetch_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = FlexChargeGateway.new(
+ app_key: 'SOMECREDENTIAL',
+ app_secret: 'SOMECREDENTIAL',
+ site_id: 'SOMECREDENTIAL',
+ mid: 'SOMECREDENTIAL'
+ )
+ gateway.send :fetch_access_token
+ end
+
+ assert_match(/400/, error.message)
+ end
+
+ def test_successful_purchase_with_token
+ set_credentials!
+ store = @gateway.store(@credit_card_cit, {})
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_inquire_request
+ set_credentials!
+ response = @gateway.inquire('abe573e3-7567-4cc6-a7a4-02766dbd881a', {})
+ assert_success response
+ end
+
+ def test_unsuccessful_inquire_request
+ set_credentials!
+ response = @gateway.inquire(SecureRandom.uuid, {})
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card_cit, @cit_options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card_cit.number, transcript)
+ assert_scrubbed(@credit_card_cit.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:access_token], transcript)
+ assert_scrubbed(@gateway.options[:app_key], transcript)
+ assert_scrubbed(@gateway.options[:app_secret], transcript)
+ assert_scrubbed(@gateway.options[:site_id], transcript)
+ assert_scrubbed(@gateway.options[:mid], transcript)
+ end
+
+ private
+
+ def set_credentials!
+ if FlexChargeCredentials.instance.access_token.nil?
+ @gateway.send :fetch_access_token
+ FlexChargeCredentials.instance.access_token = @gateway.options[:access_token]
+ FlexChargeCredentials.instance.token_expires = @gateway.options[:token_expires]
+ end
+
+ @gateway.options[:access_token] = FlexChargeCredentials.instance.access_token
+ @gateway.options[:token_expires] = FlexChargeCredentials.instance.token_expires
+ end
+end
+
+# A simple singleton so access-token and expires can
+# be shared among several tests
+class FlexChargeCredentials
+ include Singleton
+
+ attr_accessor :access_token, :token_expires
+end
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
index 9c2553700bf..e325c35e5df 100644
--- a/test/remote/gateways/remote_global_collect_test.rb
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -6,32 +6,31 @@ def setup
@gateway_preprod = GlobalCollectGateway.new(fixtures(:global_collect_preprod))
@gateway_preprod.options[:url_override] = 'preproduction'
+ @gateway_direct = GlobalCollectGateway.new(fixtures(:global_collect_direct))
+ @gateway_direct.options[:url_override] = 'ogone_direct'
+
@amount = 100
@credit_card = credit_card('4567350000427977')
+ @credit_card_challenge_3ds2 = credit_card('4874970686672022')
@naranja_card = credit_card('5895620033330020', brand: 'naranja')
@cabal_card = credit_card('6271701225979642', brand: 'cabal')
@declined_card = credit_card('5424180279791732')
@preprod_card = credit_card('4111111111111111')
- @apple_pay = network_tokenization_credit_card('4567350000427977',
+ @apple_pay = network_tokenization_credit_card(
+ '4567350000427977',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
first_name: 'John',
last_name: 'Smith',
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay = network_tokenization_credit_card('4567350000427977',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- month: '01',
- year: Time.new.year + 2,
+ @google_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
source: :google_pay,
- transaction_id: '123456789',
- eci: '05')
-
- @google_pay_pan_only = credit_card('4567350000427977',
- month: '01',
- year: Time.new.year + 2)
+ payment_data: "{ 'version': 'EC_v1', 'data': 'QlzLxRFnNP9/GTaMhBwgmZ2ywntbr9'}"
+ })
@accepted_amount = 4005
@rejected_amount = 2997
@@ -63,6 +62,14 @@ def test_successful_purchase
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
+ def test_successful_purchase_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'EUR')
+ response = @gateway_direct.purchase(@accepted_amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'PENDING_CAPTURE', response.params['payment']['status']
+ end
+
def test_successful_purchase_with_naranja
options = @preprod_options.merge(requires_approval: false, currency: 'ARS')
response = @gateway_preprod.purchase(1000, @naranja_card, options)
@@ -87,29 +94,36 @@ def test_successful_purchase_with_apple_pay
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
- def test_successful_purchase_with_google_pay
- options = @preprod_options.merge(requires_approval: false)
- response = @gateway_preprod.purchase(4500, @google_pay, options)
+ def test_successful_authorize_with_apple_pay
+ options = @preprod_options.merge(requires_approval: false, currency: 'USD')
+ response = @gateway_preprod.authorize(4500, @apple_pay, options)
assert_success response
assert_equal 'Succeeded', response.message
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
- def test_successful_purchase_with_google_pay_pan_only
- options = @preprod_options.merge(requires_approval: false, customer: 'GP1234ID', google_pay_pan_only: true)
- response = @gateway_preprod.purchase(4500, @google_pay_pan_only, options)
-
+ def test_successful_purchase_with_apple_pay_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'EUR')
+ response = @gateway_direct.purchase(100, @apple_pay, options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
+ assert_equal 'PENDING_CAPTURE', response.params['payment']['status']
end
- def test_unsuccessful_purchase_with_google_pay_pan_only
- options = @preprod_options.merge(requires_approval: false, google_pay_pan_only: true, customer: '')
- response = @gateway_preprod.purchase(4500, @google_pay_pan_only, options)
+ def test_successful_authorize_and_capture_with_apple_pay_ogone_direct
+ options = @preprod_options.merge(requires_approval: false, currency: 'EUR')
+ auth = @gateway_direct.authorize(100, @apple_pay, options)
+ assert_success auth
+
+ assert capture = @gateway_direct.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+ def test_failed_purchase_with_google_pay
+ options = @preprod_options.merge(requires_approval: false)
+ response = @gateway_direct.purchase(4500, @google_pay, options)
assert_failure response
- assert_equal 'order.customer.merchantCustomerId is missing for UCOF', response.message
end
def test_successful_purchase_with_fraud_fields
@@ -145,8 +159,8 @@ def test_successful_purchase_with_more_options
end
def test_successful_purchase_with_installments
- options = @options.merge(number_of_installments: 2)
- response = @gateway.purchase(@amount, @credit_card, options)
+ options = @preprod_options.merge(number_of_installments: 2, currency: 'EUR')
+ response = @gateway_direct.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
end
@@ -159,20 +173,32 @@ def test_successful_purchase_with_requires_approval_true
response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
- assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
# When requires_approval is false, `purchase` will only make an `auth` call
# to request capture (and no subsequent `capture` call).
- def test_successful_purchase_with_requires_approval_false
+ def test_successful_purchase_with_requires_approval_false_ogone_direct
options = @options.merge(requires_approval: false)
+ response = @gateway_direct.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+ def test_successful_purchase_with_requires_approval_false
+ options = @options.merge(requires_approval: false)
response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Succeeded', response.message
assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status']
end
+ def test_successful_authorize_with_moto_exemption
+ options = @options.merge(three_ds_exemption_type: 'moto')
+
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ end
+
def test_successful_authorize_via_normalized_3ds2_fields
options = @options.merge(
three_d_secure: {
@@ -193,6 +219,56 @@ def test_successful_authorize_via_normalized_3ds2_fields
assert_equal 'Succeeded', response.message
end
+ def test_successful_authorize_via_3ds2_fields_direct_api
+ options = @options.merge(
+ currency: 'EUR',
+ phone: '5555555555',
+ three_d_secure: {
+ version: '2.1.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
+ acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
+ cavv_algorithm: 1,
+ authentication_response_status: 'Y',
+ challenge_indicator: 'no-challenge-requested',
+ flow: 'frictionless'
+ }
+ )
+
+ response = @gateway_direct.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_match 'PENDING_CAPTURE', response.params['payment']['status']
+ assert_match 'jJ81HADVRtXfCBATEp01CJUAAAA=', response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['threeDSecureResults']['cavv']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_via_3ds2_fields_direct_api_challenge
+ options = @options.merge(
+ currency: 'EUR',
+ phone: '5555555555',
+ is_recurring: true,
+ skip_authentication: false,
+ three_d_secure: {
+ version: '2.1.0',
+ eci: '05',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
+ acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
+ cavv_algorithm: 1,
+ authentication_response_status: 'Y',
+ challenge_indicator: 'challenge-required'
+ }
+ )
+
+ response = @gateway_direct.purchase(@amount, @credit_card_challenge_3ds2, options)
+ assert_success response
+ assert_match 'CAPTURE_REQUESTED', response.params['status']
+ assert_equal 'Succeeded', response.message
+ end
+
def test_successful_purchase_with_airline_data
options = @options.merge(
airline_data: {
@@ -205,6 +281,7 @@ def test_successful_purchase_with_airline_data
is_third_party: 'true',
issue_date: 'tday',
merchant_customer_id: 'MIDs',
+ agent_numeric_code: '12345',
passengers: [
{ first_name: 'Randi',
surname: 'Smith',
@@ -321,6 +398,14 @@ def test_successful_purchase_with_blank_name
assert_equal 'Succeeded', response.message
end
+ def test_unsuccessful_purchase_with_blank_name_ogone_direct
+ credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil })
+
+ response = @gateway_direct.purchase(@amount, credit_card, @options)
+ assert_failure response
+ assert_equal 'PARAMETER_NOT_FOUND_IN_REQUEST', response.message
+ end
+
def test_successful_purchase_with_pre_authorization_flag
response = @gateway.purchase(@accepted_amount, @credit_card, @options.merge(pre_authorization: true))
assert_success response
@@ -335,7 +420,7 @@ def test_successful_purchase_with_payment_product_id
assert_equal 135, response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['paymentProductId']
end
- def test_successful_purchase_with_truncated_address
+ def test_successful_purchase_with_truncated_split_address
response = @gateway.purchase(@amount, @credit_card, @long_address)
assert_success response
assert_equal 'Succeeded', response.message
@@ -347,6 +432,12 @@ def test_failed_purchase
assert_equal 'Not authorised', response.message
end
+ def test_failed_purchase_ogone_direct
+ response = @gateway_direct.purchase(@rejected_amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cardPaymentMethodSpecificInput.card.cardNumber does not match with cardPaymentMethodSpecificInput.paymentProductId.', response.message
+ end
+
def test_successful_authorize_and_capture
auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -368,13 +459,18 @@ def test_failed_authorize
assert_equal 'Not authorised', response.message
end
+ def test_failed_authorize_ogone_direct
+ response = @gateway_direct.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cardPaymentMethodSpecificInput.card.cardNumber does not match with cardPaymentMethodSpecificInput.paymentProductId.', 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
@@ -416,6 +512,15 @@ def test_successful_void
assert_equal 'Succeeded', void.message
end
+ def test_successful_void_ogone_direct
+ auth = @gateway_direct.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway_direct.void(auth.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
def test_failed_void
response = @gateway.void('123')
assert_failure response
@@ -450,12 +555,24 @@ def test_successful_verify
assert_equal 'Succeeded', response.message
end
+ def test_successful_verify_ogone_direct
+ response = @gateway_direct.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_failed_verify_ogone_direct
+ response = @gateway_direct.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal false, response.params['paymentResult']['payment']['statusOutput']['isAuthorized']
+ end
+
def test_invalid_login
gateway = GlobalCollectGateway.new(merchant_id: '', api_key_id: '', secret_api_key: '')
response = gateway.purchase(@amount, @credit_card, @options)
@@ -473,16 +590,6 @@ def test_transcript_scrubbing
assert_scrubbed(@gateway.options[:secret_api_key], transcript)
end
- def test_scrub_google_payment
- options = @preprod_options.merge(requires_approval: false)
- transcript = capture_transcript(@gateway) do
- @gateway_preprod.purchase(@amount, @google_pay, options)
- end
- transcript = @gateway.scrub(transcript)
- assert_scrubbed(@google_pay.payment_cryptogram, transcript)
- assert_scrubbed(@google_pay.number, transcript)
- end
-
def test_scrub_apple_payment
options = @preprod_options.merge(requires_approval: false)
transcript = capture_transcript(@gateway) do
diff --git a/test/remote/gateways/remote_hi_pay_test.rb b/test/remote/gateways/remote_hi_pay_test.rb
new file mode 100644
index 00000000000..0d0af9025e0
--- /dev/null
+++ b/test/remote/gateways/remote_hi_pay_test.rb
@@ -0,0 +1,241 @@
+require 'test_helper'
+
+class RemoteHiPayTest < Test::Unit::TestCase
+ def setup
+ @gateway = HiPayGateway.new(fixtures(:hi_pay))
+ @bad_gateway = HiPayGateway.new(username: 'bad', password: 'password')
+
+ @amount = 500
+ @credit_card = credit_card('4111111111111111', verification_value: '514', first_name: 'John', last_name: 'Smith', month: 12, year: 2025)
+ @bad_credit_card = credit_card('4150551403657424')
+ @master_credit_card = credit_card('5399999999999999')
+ @challenge_credit_card = credit_card('4242424242424242')
+
+ @options = {
+ order_id: "Sp_ORDER_#{SecureRandom.random_number(1000000000)}",
+ description: 'An authorize',
+ email: 'john.smith@test.com'
+ }
+
+ @billing_address = address
+
+ @execute_threed = {
+ execute_threed: true,
+ redirect_url: 'http://www.example.com/redirect',
+ callback_url: 'http://www.example.com/callback',
+ three_ds_2: {
+ browser_info: {
+ width: 390,
+ height: 400,
+ depth: 24,
+ timezone: 300,
+ user_agent: 'Spreedly Agent',
+ java: false,
+ javascript: true,
+ language: 'en-US',
+ browser_size: '05',
+ accept_header: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
+ }
+ }
+ }
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal response.message, 'Authorized'
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Captured', response.message
+
+ assert_kind_of MultiResponse, response
+ assert_equal 2, response.responses.size
+ end
+
+ def test_successful_purchase_with_3ds
+ response = @gateway.purchase(@amount, @challenge_credit_card, @options.merge(@billing_address).merge(@execute_threed))
+ assert_success response
+ assert_equal 'Authentication requested', response.message
+ assert_match %r{stage-secure-gateway.hipay-tpp.com\/gateway\/forward\/\w+}, response.params['forwardUrl']
+
+ assert_kind_of MultiResponse, response
+ assert_equal 2, response.responses.size
+ end
+
+ def test_successful_purchase_with_mastercard
+ response = @gateway.purchase(@amount, @master_credit_card, @options)
+ assert_success response
+ assert_equal 'Captured', response.message
+
+ assert_kind_of MultiResponse, response
+ assert_equal 2, response.responses.size
+ end
+
+ def test_failed_purchase_due_failed_tokenization
+ response = @bad_gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Incorrect Credentials _ Username and/or password is incorrect', response.message
+ assert_equal '1000001', response.error_code
+
+ assert_kind_of MultiResponse, response
+ # Failed in tokenization step
+ assert_equal 1, response.responses.size
+ end
+
+ def test_failed_purchase_due_authorization_refused
+ response = @gateway.purchase(@amount, @bad_credit_card, @options)
+ assert_failure response
+ assert_equal 'Authorization Refused', response.message
+ assert_equal '1010201', response.error_code
+ assert_equal 'Invalid Parameter', response.params['reason']['message']
+
+ assert_kind_of MultiResponse, response
+ # Complete tokenization, failed in the purhcase step
+ assert_equal 2, response.responses.size
+ end
+
+ def test_successful_purchase_with_billing_address
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ billing_address: @billing_address }))
+
+ assert_success response
+ end
+
+ def test_successful_capture
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.capture(@amount, authorize_response.authorization, @options)
+ assert_success response
+ assert_equal 'Captured', response.message
+ assert_equal authorize_response.authorization, response.authorization
+ end
+
+ def test_successful_authorize_with_store
+ store_response = @gateway.store(@credit_card, @options)
+ assert_nil store_response.message
+ assert_success store_response
+ assert_not_empty store_response.authorization
+
+ response = @gateway.authorize(@amount, store_response.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_multiple_purchases_with_single_store
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response1 = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response1
+
+ @options[:order_id] = "Sp_ORDER_2_#{SecureRandom.random_number(1000000000)}"
+
+ response2 = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response2
+ end
+
+ def test_successful_unstore
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.unstore(store_response.authorization, @options)
+ assert_success response
+ end
+
+ def test_failed_purchase_after_unstore_payment_method
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ purchase_response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success purchase_response
+
+ unstore_response = @gateway.unstore(store_response.authorization, @options)
+ assert_success unstore_response
+
+ response = @gateway.purchase(
+ @amount,
+ store_response.authorization,
+ @options.merge(
+ {
+ order_id: "Sp_UNSTORE_#{SecureRandom.random_number(1000000000)}"
+ }
+ )
+ )
+ assert_failure response
+ assert_equal 'Unknown Token', response.message
+ assert_equal '3040001', response.error_code
+ end
+
+ def test_successful_refund
+ purchase_response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase_response
+
+ response = @gateway.refund(@amount, purchase_response.authorization, @options)
+ assert_success response
+ assert_equal 'Refund Requested', response.message
+ assert_include response.params['authorizedAmount'], '5.00'
+ assert_include response.params['capturedAmount'], '5.00'
+ assert_include response.params['refundedAmount'], '5.00'
+ end
+
+ def test_successful_partial_capture_refund
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+ assert_include authorize_response.params['authorizedAmount'], '5.00'
+ assert_include authorize_response.params['capturedAmount'], '0.00'
+ assert_equal authorize_response.params['refundedAmount'], '0.00'
+
+ capture_response = @gateway.capture(@amount - 100, authorize_response.authorization, @options)
+ assert_success capture_response
+ assert_equal authorize_response.authorization, capture_response.authorization
+ assert_include capture_response.params['authorizedAmount'], '5.00'
+ assert_include capture_response.params['capturedAmount'], '4.00'
+ assert_equal capture_response.params['refundedAmount'], '0.00'
+
+ response = @gateway.refund(@amount - 200, capture_response.authorization, @options)
+ assert_success response
+ assert_include response.params['authorizedAmount'], '5.00'
+ assert_include response.params['capturedAmount'], '4.00'
+ assert_include response.params['refundedAmount'], '3.00'
+ end
+
+ def test_failed_refund_because_auth_no_captured
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.refund(@amount, authorize_response.authorization, @options)
+ assert_failure response
+ assert_equal 'Operation Not Permitted : transaction not captured', response.message
+ end
+
+ def test_successful_void
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize_response
+
+ response = @gateway.void(authorize_response.authorization, @options)
+ assert_success response
+ assert_equal 'Authorization Cancellation requested', response.message
+ end
+
+ def test_failed_void_because_captured_transaction
+ purchase_response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase_response
+
+ response = @gateway.void(purchase_response.authorization, @options)
+ assert_failure response
+ assert_equal 'Action denied : Wrong transaction status', 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_hps_test.rb b/test/remote/gateways/remote_hps_test.rb
index 4b8f7229bfc..9f7a0e08c24 100644
--- a/test/remote/gateways/remote_hps_test.rb
+++ b/test/remote/gateways/remote_hps_test.rb
@@ -359,11 +359,13 @@ def test_transcript_scrubbing
end
def test_transcript_scrubbing_with_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, credit_card, @options)
end
@@ -384,126 +386,150 @@ def test_account_number_scrubbing
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ 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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ 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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ 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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
end
def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
diff --git a/test/remote/gateways/remote_ipg_test.rb b/test/remote/gateways/remote_ipg_test.rb
index 8c797606e97..95ca04930e8 100644
--- a/test/remote/gateways/remote_ipg_test.rb
+++ b/test/remote/gateways/remote_ipg_test.rb
@@ -3,11 +3,11 @@
class RemoteIpgTest < Test::Unit::TestCase
def setup
@gateway = IpgGateway.new(fixtures(:ipg))
-
+ @gateway_ma = IpgGateway.new(fixtures(:ipg_ma).merge({ store_id: nil }))
@amount = 100
- @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '530', month: '12', year: '2022')
+ @credit_card = credit_card('5165850000000008', brand: 'mastercard', month: '12', year: '2029')
@declined_card = credit_card('4000300011112220', brand: 'mastercard', verification_value: '652', month: '12', year: '2022')
- @visa_card = credit_card('4704550000000005', brand: 'visa', verification_value: '123', month: '12', year: '2022')
+ @visa_card = credit_card('4704550000000005', brand: 'visa', verification_value: '123', month: '12', year: '2029')
@options = {
currency: 'ARS'
}
@@ -96,15 +96,15 @@ def test_successful_purchase_with_3ds2_options
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
assert_equal 'SGS-050005', response.error_code
end
def test_failed_purchase_with_passed_in_store_id
- # passing in a bad store id results in a 401 unauthorized error
- assert_raises(ActiveMerchant::ResponseError) do
- @gateway.purchase(@amount, @declined_card, @options.merge({ store_id: '1234' }))
- end
+ response = @gateway.purchase(@amount, @visa_card, @options.merge({ store_id: '1234' }))
+
+ assert_failure response
+ assert 'MerchantException', response.params['faultstring']
end
def test_successful_authorize_and_capture
@@ -121,14 +121,14 @@ def test_successful_authorize_and_capture
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_equal 'DECLINED, Do not honour', response.message
assert_equal 'SGS-050005', response.error_code
end
def test_failed_capture
response = @gateway.capture(@amount, '', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
assert_equal 'SGS-005001', response.error_code
end
@@ -159,7 +159,7 @@ def test_successful_refund
def test_failed_refund
response = @gateway.refund(@amount, '', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
assert_equal 'SGS-005001', response.error_code
end
@@ -172,7 +172,7 @@ def test_successful_verify
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
assert_equal 'SGS-050005', response.error_code
end
@@ -184,4 +184,16 @@ def test_transcript_scrubbing
assert_scrubbed(@credit_card.number, transcript)
assert_scrubbed(@credit_card.verification_value, transcript)
end
+
+ def test_successful_purchase_with_ma_credentials
+ response = @gateway_ma.purchase(@amount, @credit_card, @options.merge({ store_id: fixtures(:ipg_ma)[:store_id] }))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_purchase_without_store_id
+ assert_raises(ArgumentError) do
+ @gateway_ma.purchase(@amount, @credit_card, @options)
+ end
+ end
end
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
index 1fb1ad41a98..d1c71647e15 100644
--- a/test/remote/gateways/remote_kushki_test.rb
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -3,6 +3,7 @@
class RemoteKushkiTest < Test::Unit::TestCase
def setup
@gateway = KushkiGateway.new(fixtures(:kushki))
+ @gateway_partial_refund = KushkiGateway.new(fixtures(:kushki_partial))
@amount = 100
@credit_card = credit_card('4000100011112224', verification_value: '777')
@declined_card = credit_card('4000300011112220')
@@ -15,6 +16,13 @@ def test_successful_purchase
assert_match %r(^\d+$), response.authorization
end
+ def test_successful_purchase_brazil
+ response = @gateway.purchase(@amount, @credit_card, { currency: 'BRL' })
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
def test_successful_purchase_with_options
options = {
currency: 'USD',
@@ -36,7 +44,27 @@ def test_successful_purchase_with_options
metadata: {
productos: 'bananas',
nombre_apellido: 'Kirk'
- }
+ },
+ months: 2,
+ deferred_grace_months: '05',
+ deferred_credit_type: '01',
+ deferred_months: 3,
+ product_details: [
+ {
+ id: 'test1',
+ title: 'tester1',
+ price: 10,
+ sku: 'abcde',
+ quantity: 1
+ },
+ {
+ id: 'test2',
+ title: 'tester2',
+ price: 5,
+ sku: 'edcba',
+ quantity: 2
+ }
+ ]
}
amount = 100 * (
@@ -133,8 +161,14 @@ def test_failed_purchase
end
def test_successful_authorize
- # Kushki only allows preauthorization for PEN, CLP, and UF.
- response = @gateway.authorize(@amount, @credit_card, { currency: 'PEN' })
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, { currency: 'PEN' })
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_authorize_brazil
+ response = @gateway.authorize(@amount, @credit_card, { currency: 'BRL' })
assert_success response
assert_equal 'Succeeded', response.message
assert_match %r(^\d+$), response.authorization
@@ -162,6 +196,101 @@ def test_failed_authorize
assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message
end
+ def test_successful_3ds2_authorize_with_visa_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE=',
+ eci: '07'
+ }
+ }
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_3ds2_authorize_with_visa_card_with_optional_xid
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ eci: '07'
+ }
+ }
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_3ds2_authorize_with_master_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ eci: '00',
+ ds_transaction_id: 'b23e0264-1209-41L6-Jca4-b82143c1a782'
+ }
+ }
+
+ credit_card = credit_card('5223450000000007', brand: 'master', verification_value: '777')
+ response = @gateway_partial_refund.authorize(@amount, credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_3ds2_purchase
+ options = {
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE=',
+ eci: '07'
+ }
+ }
+
+ 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_3ds2_authorize
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ authentication_response_status: 'Y',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE='
+ }
+ }
+ response = @gateway_partial_refund.authorize(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'K001', response.responses.last.error_code
+ end
+
+ def test_failed_3ds2_authorize_with_different_card
+ options = {
+ currency: 'PEN',
+ three_d_secure: {
+ version: '2.2.0',
+ cavv: 'AAABBoVBaZKAR3BkdkFpELpWIiE=',
+ xid: 'NEpab1F1MEdtaWJ2bEY3ckYxQzE='
+ }
+ }
+ credit_card = credit_card('6011111111111117', brand: 'discover', verification_value: '777')
+ assert_raise ArgumentError do
+ @gateway_partial_refund.authorize(@amount, credit_card, options)
+ end
+ end
+
def test_successful_capture
auth = @gateway.authorize(@amount, @credit_card)
assert_success auth
@@ -204,6 +333,26 @@ def test_failed_refund
assert_equal 'Missing Authentication Token', refund.message
end
+ # partial refunds are only available in Colombia, Chile, Mexico and Peru
+ def test_partial_refund
+ options = {
+ currency: 'PEN',
+ full_response: 'v2'
+ }
+ purchase = @gateway_partial_refund.purchase(500, @credit_card, options)
+ assert_success purchase
+
+ refund_options = {
+ currency: 'PEN',
+ partial_refund: true,
+ full_response: 'v2'
+ }
+
+ assert refund = @gateway_partial_refund.refund(250, purchase.authorization, refund_options)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
def test_successful_void
purchase = @gateway.purchase(@amount, @credit_card)
assert_success purchase
diff --git a/test/remote/gateways/remote_linkpoint_test.rb b/test/remote/gateways/remote_linkpoint_test.rb
index bb6db34f34f..efcffa07dec 100644
--- a/test/remote/gateways/remote_linkpoint_test.rb
+++ b/test/remote/gateways/remote_linkpoint_test.rb
@@ -100,12 +100,15 @@ def test_successfull_purchase_with_item_entity
end
def test_successful_recurring_payment
- assert response = @gateway.recurring(2400, @credit_card,
+ assert response = @gateway.recurring(
+ 2400,
+ @credit_card,
order_id: generate_unique_id,
installments: 12,
startdate: 'immediate',
periodicity: :monthly,
- billing_address: address)
+ billing_address: address
+ )
assert_success response
assert_equal 'APPROVED', response.params['approved']
diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb
index 465f9b1cc4b..c16c628ee2f 100644
--- a/test/remote/gateways/remote_litle_test.rb
+++ b/test/remote/gateways/remote_litle_test.rb
@@ -86,12 +86,15 @@ def setup
name: 'John Smith',
routing_number: '011075150',
account_number: '1099999999',
- account_type: 'checking'
+ account_type: nil,
+ account_holder_type: 'checking'
)
@store_check = check(
routing_number: '011100012',
account_number: '1099999998'
)
+
+ @declined_card = credit_card('4488282659650110', first_name: nil, last_name: 'REFUSED')
end
def test_successful_authorization
@@ -143,14 +146,24 @@ def test_successful_authorization_with_echeck
assert_equal 'Approved', response.message
end
- def test_avs_and_cvv_result
+ def test_avs_result
+ @credit_card1.number = '4200410886320101'
+ assert response = @gateway.authorize(10010, @credit_card1, @options)
+
+ assert_equal 'Z', response.avs_result['code']
+ end
+
+ def test__cvv_result
+ @credit_card1.number = '4100521234567000'
assert response = @gateway.authorize(10010, @credit_card1, @options)
- assert_equal 'X', response.avs_result['code']
- assert_equal 'M', response.cvv_result['code']
+
+ assert_equal 'P', response.cvv_result['code']
end
def test_unsuccessful_authorization
- assert response = @gateway.authorize(60060, @credit_card2,
+ assert response = @gateway.authorize(
+ 60060,
+ @declined_card,
{
order_id: '6',
billing_address: {
@@ -161,7 +174,8 @@ def test_unsuccessful_authorization
zip: '03038',
country: 'US'
}
- })
+ }
+ )
assert_failure response
assert_equal 'Insufficient Funds', response.message
end
@@ -231,7 +245,7 @@ def test_successful_purchase_with_3ds_fields
def test_successful_purchase_with_apple_pay
assert response = @gateway.purchase(10010, @decrypted_apple_pay)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_successful_purchase_with_android_pay
@@ -295,7 +309,7 @@ def test_successful_purchase_with_level_three_data_visa
card_acceptor_tax_id: '361531321',
line_items: [{
item_sequence_number: 1,
- item_commodity_code: 300,
+ commodity_code: '041235',
item_description: 'ramdom-object',
product_code: 'TB123',
quantity: 2,
@@ -333,6 +347,7 @@ def test_successful_purchase_with_level_three_data_master
customer_code: 'PO12345',
card_acceptor_tax_id: '011234567',
tax_amount: 50,
+ tax_included_in_total: true,
line_items: [{
item_description: 'ramdom-object',
product_code: 'TB123',
@@ -370,7 +385,7 @@ def test_successful_purchase_with_echeck
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(60060, @credit_card2, {
+ assert response = @gateway.purchase(60060, @declined_card, {
order_id: '6',
billing_address: {
name: 'Joe Green',
@@ -398,6 +413,8 @@ def test_authorize_capture_refund_void
assert_success refund
assert_equal 'Approved', refund.message
+ sleep 40.seconds
+
assert void = @gateway.void(refund.authorization)
assert_success void
assert_equal 'Approved', void.message
@@ -422,7 +439,7 @@ def test_authorize_and_capture_with_stored_credential_recurring
)
assert auth = @gateway.authorize(4999, credit_card, initial_options)
assert_success auth
- assert_equal 'Approved', auth.message
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', auth.message
assert network_transaction_id = auth.params['networkTransactionId']
assert capture = @gateway.capture(4999, auth.authorization)
@@ -438,9 +455,10 @@ def test_authorize_and_capture_with_stored_credential_recurring
network_transaction_id: network_transaction_id
}
)
+
assert auth = @gateway.authorize(4999, credit_card, used_options)
assert_success auth
- assert_equal 'Approved', auth.message
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', auth.message
assert capture = @gateway.capture(4999, auth.authorization)
assert_success capture
@@ -617,6 +635,7 @@ def test_purchase_with_stored_credential_cit_card_on_file_non_ecommerce
}
)
assert auth = @gateway.purchase(4000, credit_card, used_options)
+
assert_success auth
assert_equal 'Approved', auth.message
end
@@ -642,9 +661,9 @@ def test_void_authorization
end
def test_unsuccessful_void
- assert void = @gateway.void('123456789012345360;authorization;100')
+ assert void = @gateway.void('1234567890r2345360;authorization;100')
assert_failure void
- assert_equal 'No transaction found with specified Transaction Id', void.message
+ assert_match(/^Error validating xml data against the schema/, void.message)
end
def test_successful_credit
@@ -710,15 +729,15 @@ def test_nil_amount_capture
end
def test_capture_unsuccessful
- assert capture_response = @gateway.capture(10010, '123456789012345360')
+ assert capture_response = @gateway.capture(10010, '123456789w123')
assert_failure capture_response
- assert_equal 'No transaction found with specified Transaction Id', capture_response.message
+ assert_match(/^Error validating xml data against the schema/, capture_response.message)
end
def test_refund_unsuccessful
- assert credit_response = @gateway.refund(10010, '123456789012345360')
+ assert credit_response = @gateway.refund(10010, '123456789w123')
assert_failure credit_response
- assert_equal 'No transaction found with specified Transaction Id', credit_response.message
+ assert_match(/^Error validating xml data against the schema/, credit_response.message)
end
def test_void_unsuccessful
@@ -733,10 +752,8 @@ def test_store_successful
assert_success store_response
assert_equal 'Account number was successfully registered', store_response.message
- 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']
+ assert_equal '1111222233334444', store_response.params['litleToken']
end
def test_store_with_paypage_registration_id_successful
@@ -750,11 +767,11 @@ def test_store_with_paypage_registration_id_successful
end
def test_store_unsuccessful
- credit_card = CreditCard.new(@credit_card_hash.merge(number: '4457119999999999'))
+ credit_card = CreditCard.new(@credit_card_hash.merge(number: '4100282090123000'))
assert store_response = @gateway.store(credit_card, order_id: '51')
assert_failure store_response
- assert_equal 'Credit card number was invalid', store_response.message
+ assert_equal 'Credit card Number was invalid', store_response.message
assert_equal '820', store_response.params['response']
end
@@ -768,7 +785,7 @@ def test_store_and_purchase_with_token_successful
assert response = @gateway.purchase(10010, token)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_purchase_with_token_and_date_successful
@@ -780,7 +797,7 @@ def test_purchase_with_token_and_date_successful
assert response = @gateway.purchase(10010, token, { basis_expiration_month: '01', basis_expiration_year: '2024' })
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_echeck_store_and_purchase
@@ -793,7 +810,7 @@ def test_echeck_store_and_purchase
assert response = @gateway.purchase(10010, token)
assert_success response
- assert_equal 'Approved', response.message
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
end
def test_successful_verify
diff --git a/test/remote/gateways/remote_mercado_pago_test.rb b/test/remote/gateways/remote_mercado_pago_test.rb
index b9a9c0d5f3e..9aab14911f3 100644
--- a/test/remote/gateways/remote_mercado_pago_test.rb
+++ b/test/remote/gateways/remote_mercado_pago_test.rb
@@ -10,26 +10,31 @@ def setup
@amount = 500
@credit_card = credit_card('5031433215406351')
@colombian_card = credit_card('4013540682746260')
- @elo_credit_card = credit_card('5067268650517446',
+ @elo_credit_card = credit_card(
+ '5067268650517446',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @cabal_credit_card = credit_card('6035227716427021',
+ verification_value: '737'
+ )
+ @cabal_credit_card = credit_card(
+ '6035227716427021',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @naranja_credit_card = credit_card('5895627823453005',
+ verification_value: '737'
+ )
+ @naranja_credit_card = credit_card(
+ '5895627823453005',
month: 10,
year: exp_year,
first_name: 'John',
last_name: 'Smith',
- verification_value: '123')
- @declined_card = credit_card('5031433215406351',
- first_name: 'OTHE')
+ verification_value: '123'
+ )
+ @declined_card = credit_card('5031433215406351', first_name: 'OTHE')
@options = {
billing_address: address,
shipping_address: address,
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 2d18cbeb868..b553611b7e3 100644
--- a/test/remote/gateways/remote_merchant_ware_version_four_test.rb
+++ b/test/remote/gateways/remote_merchant_ware_version_four_test.rb
@@ -69,9 +69,11 @@ def test_purchase_and_reference_purchase
assert_success purchase
assert purchase.authorization
- assert reference_purchase = @gateway.purchase(@amount,
+ assert reference_purchase = @gateway.purchase(
+ @amount,
purchase.authorization,
- @reference_purchase_options)
+ @reference_purchase_options
+ )
assert_success reference_purchase
assert_not_nil reference_purchase.authorization
end
diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb
index 5cd61daff72..852e79ce380 100644
--- a/test/remote/gateways/remote_merchant_warrior_test.rb
+++ b/test/remote/gateways/remote_merchant_warrior_test.rb
@@ -60,7 +60,7 @@ def test_successful_purchase
def test_failed_purchase
assert purchase = @gateway.purchase(@success_amount, @expired_card, @options)
- assert_match 'Card has expired', purchase.message
+ assert_match 'Transaction declined', purchase.message
assert_failure purchase
assert_not_nil purchase.params['transaction_id']
assert_equal purchase.params['transaction_id'], purchase.authorization
@@ -192,4 +192,34 @@ def test_transcript_scrubbing_store
assert_scrubbed(@gateway.options[:api_passphrase], transcript)
assert_scrubbed(@gateway.options[:api_key], transcript)
end
+
+ def test_successful_purchase_with_three_ds
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: 'e1E3SN0xF1lDp9js723iASu3wrA=',
+ eci: '05',
+ xid: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+
+ assert response = @gateway.purchase(@success_amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_purchase_with_three_ds_transaction_id
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: 'e1E3SN0xF1lDp9js723iASu3wrA=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
+
+ assert response = @gateway.purchase(@success_amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
end
diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb
index 11f2d8af78a..4a6b6a2c842 100644
--- a/test/remote/gateways/remote_moneris_test.rb
+++ b/test/remote/gateways/remote_moneris_test.rb
@@ -15,6 +15,15 @@ def setup
@no_liability_shift_eci = 7
@credit_card = credit_card('4242424242424242', verification_value: '012')
+ @network_tokenization_credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ @apple_pay_credit_card = @network_tokenization_credit_card
+ @apple_pay_credit_card.source = :apple_pay
+ @google_pay_credit_card = @network_tokenization_credit_card
+ @google_pay_credit_card.source = :google_pay
@visa_credit_card_3ds = credit_card('4606633870436092', verification_value: '012')
@options = {
order_id: generate_unique_id,
@@ -32,7 +41,9 @@ def test_successful_purchase
def test_successful_cavv_purchase
# See https://developer.moneris.com/livedemo/3ds2/cavv_purchase/tool/php
- assert response = @gateway.purchase(@amount, @visa_credit_card_3ds,
+ assert response = @gateway.purchase(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -41,7 +52,8 @@ def test_successful_cavv_purchase
three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -56,6 +68,14 @@ def test_successful_first_purchase_with_credential_on_file
assert_not_empty response.params['issuer_id']
end
+ def test_successful_first_purchase_with_cust_id
+ gateway = MonerisGateway.new(fixtures(:moneris))
+ assert response = gateway.purchase(@amount, @credit_card, @options.merge(cust_id: 'test1234'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ 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)
@@ -104,38 +124,74 @@ def test_successful_subsequent_purchase_with_credential_on_file
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 response = @gateway.purchase(@amount, @network_tokenization_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 response = @gateway.purchase(@amount, @apple_pay_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_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.purchase(@amount, @apple_pay_credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
end
def test_successful_purchase_with_network_tokenization_google_pay_source
- @credit_card = network_tokenization_credit_card(
- '4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil,
- source: :google_pay
- )
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_network_tokenization_google_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.purchase(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization
+ assert response = @gateway.authorize(@amount, @network_tokenization_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_apple_pay_source
+ assert response = @gateway.authorize(@amount, @apple_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_apple_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.authorize(@amount, @apple_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_google_pay_source
+ assert response = @gateway.authorize(@amount, @google_pay_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorize_with_network_tokenization_google_pay_source_with_nil_order_id
+ @options[:order_id] = nil
+ assert response = @gateway.authorize(@amount, @google_pay_credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -185,7 +241,9 @@ def test_successful_authorization_and_void
def test_successful_cavv_authorization
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -194,7 +252,8 @@ def test_successful_cavv_authorization
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -203,7 +262,9 @@ def test_successful_cavv_authorization
def test_successful_cavv_authorization_and_capture
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -212,7 +273,8 @@ def test_successful_cavv_authorization_and_capture
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
@@ -225,7 +287,9 @@ def test_failed_cavv_authorization
omit('There is no way to currently create a failed cavv authorization scenario')
# see https://developer.moneris.com/livedemo/3ds2/cavv_preauth/tool/php
# also see https://github.com/Moneris/eCommerce-Unified-API-PHP/blob/3cd3f0bd5a92432c1b4f9727d1ca6334786d9066/Examples/CA/TestCavvPreAuth.php
- response = @gateway.authorize(@fail_amount, @visa_credit_card_3ds,
+ response = @gateway.authorize(
+ @fail_amount,
+ @visa_credit_card_3ds,
@options.merge(
three_d_secure: {
version: '2',
@@ -234,7 +298,8 @@ def test_failed_cavv_authorization
three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f',
ds_transaction_id: '12345'
}
- ))
+ )
+ )
assert_failure response
end
@@ -327,8 +392,8 @@ def test_successful_store_and_purchase_with_avs
assert_false response.authorization.blank?
assert_equal(response.avs_result, {
- 'code' => 'M',
- 'message' => 'Street address and postal code match.',
+ 'code' => 'Y',
+ 'message' => 'Street address and 5-digit postal code match.',
'street_match' => 'Y',
'postal_match' => 'Y'
})
diff --git a/test/remote/gateways/remote_mundipagg_test.rb b/test/remote/gateways/remote_mundipagg_test.rb
index 2eacfc42fa1..a3b5606fef1 100644
--- a/test/remote/gateways/remote_mundipagg_test.rb
+++ b/test/remote/gateways/remote_mundipagg_test.rb
@@ -26,26 +26,26 @@ def setup
@submerchant_options = {
submerchant: {
- "merchant_category_code": '44444',
- "payment_facilitator_code": '5555555',
- "code": 'code2',
- "name": 'Sub Tony Stark',
- "document": '123456789',
- "type": 'individual',
- "phone": {
- "country_code": '55',
- "number": '000000000',
- "area_code": '21'
+ merchant_category_code: '44444',
+ payment_facilitator_code: '5555555',
+ code: 'code2',
+ name: 'Sub Tony Stark',
+ document: '123456789',
+ type: 'individual',
+ phone: {
+ country_code: '55',
+ number: '000000000',
+ area_code: '21'
},
- "address": {
- "street": 'Malibu Point',
- "number": '10880',
- "complement": 'A',
- "neighborhood": 'Central Malibu',
- "city": 'Malibu',
- "state": 'CA',
- "country": 'US',
- "zip_code": '24210-460'
+ address: {
+ street: 'Malibu Point',
+ number: '10880',
+ complement: 'A',
+ neighborhood: 'Central Malibu',
+ city: 'Malibu',
+ state: 'CA',
+ country: 'US',
+ zip_code: '24210-460'
}
}
}
diff --git a/test/remote/gateways/remote_net_registry_test.rb b/test/remote/gateways/remote_net_registry_test.rb
index 2b983e8b680..dfbb80c9ac1 100644
--- a/test/remote/gateways/remote_net_registry_test.rb
+++ b/test/remote/gateways/remote_net_registry_test.rb
@@ -56,9 +56,7 @@ def test_successful_authorization_and_capture
assert_equal 'approved', response.params['status']
assert_match(/\A\d{6}\z/, response.authorization)
- response = @gateway.capture(@amount,
- response.authorization,
- credit_card: @valid_creditcard)
+ response = @gateway.capture(@amount, response.authorization, credit_card: @valid_creditcard)
assert_success response
assert_equal 'approved', response.params['status']
end
diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb
index ad01c314b92..bd77940790b 100644
--- a/test/remote/gateways/remote_nmi_test.rb
+++ b/test/remote/gateways/remote_nmi_test.rb
@@ -10,13 +10,15 @@ def setup
routing_number: '123123123',
account_number: '123123123'
)
- @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: '2024',
source: :apple_pay,
eci: '5',
- transaction_id: '123456789')
+ transaction_id: '123456789'
+ )
@options = {
order_id: generate_unique_id,
billing_address: address,
@@ -193,6 +195,26 @@ def test_successful_purchase_with_descriptors
assert response.authorization
end
+ def test_successful_purchase_with_shipping_fields
+ options = @options.merge({ shipping_address: shipping_address, shipping_email: 'test@example.com' })
+
+ 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_with_surcharge
+ options = @options.merge({ surcharge: '1.00' })
+
+ 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_failed_authorization
assert response = @gateway.authorize(99, @credit_card, @options)
assert_failure response
@@ -366,6 +388,18 @@ def test_purchase_using_stored_credential_recurring_mit
assert_success purchase
end
+ def test_purchase_using_ntid_override_mit
+ 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']
+
+ @options[:network_transaction_id] = network_transaction_id
+ used_options = stored_credential_options(:merchant, :recurring)
+ 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)
diff --git a/test/remote/gateways/remote_ogone_test.rb b/test/remote/gateways/remote_ogone_test.rb
index 800e635563a..202c17002f7 100644
--- a/test/remote/gateways/remote_ogone_test.rb
+++ b/test/remote/gateways/remote_ogone_test.rb
@@ -5,12 +5,16 @@
class RemoteOgoneTest < Test::Unit::TestCase
def setup
@gateway = OgoneGateway.new(fixtures(:ogone))
+
+ # this change is according the new PSD2 guideline
+ # https://support.legacy.worldline-solutions.com/en/direct/faq/i-have-noticed-i-have-more-declined-transactions-status-2-than-usual-what-can-i-do
+ @gateway_3ds = OgoneGateway.new(fixtures(:ogone).merge(signature_encryptor: 'sha512'))
@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')
- @credit_card_d3d_2_challenge = credit_card('4874970686672022', verification_value: '123')
+ @credit_card_d3d_2_challenge = credit_card('5130257474533310', verification_value: '123')
@credit_card_d3d_2_frictionless = credit_card('4186455175836497', verification_value: '123')
@options = {
order_id: generate_unique_id[0...30],
@@ -22,36 +26,36 @@ def setup
@options_browser_info = {
three_ds_2: {
browser_info: {
- "width": 390,
- "height": 400,
- "depth": 24,
- "timezone": 300,
- "user_agent": 'Spreedly Agent',
- "java": false,
- "javascript": true,
- "language": 'en-US',
- "browser_size": '05',
- "accept_header": 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
+ width: 390,
+ height: 400,
+ depth: 24,
+ timezone: 300,
+ user_agent: 'Spreedly Agent',
+ java: false,
+ javascript: true,
+ language: 'en-US',
+ browser_size: '05',
+ accept_header: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
}
}
}
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
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_3ds.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_3ds.purchase(@amount, credit_card('4000100011112224', first_name: 'ワタシ', last_name: 'ёжзийклмнопрсуфхцч'), @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -87,25 +91,35 @@ def test_successful_purchase_with_signature_encryptor_to_sha512
# NOTE: You have to contact Ogone to make sure your test account allow 3D Secure transactions before running this test
def test_successful_purchase_with_3d_secure_v1
- assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d, @options.merge(@options_browser_info, d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v1', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert_success response
+ assert_equal '46', response.params['STATUS']
+ assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
+ assert response.params['HTML_ANSWER']
+ assert Base64.decode64(response.params['HTML_ANSWER'])
+ end
+
+ def test_successful_purchase_with_3d_secure_v2_flag_updated
+ options = @options_browser_info.merge(three_d_secure: { required: true })
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d, options)
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2_frictionless
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_frictionless, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_frictionless, @options_browser_info.merge(d3d: true))
assert_success response
assert_includes response.params, 'PAYID'
assert_equal '0', response.params['NCERROR']
@@ -115,27 +129,27 @@ def test_successful_purchase_with_3d_secure_v2_frictionless
def test_successful_purchase_with_3d_secure_v2_recomended_parameters
options = @options.merge(@options_browser_info)
- assert response = @gateway.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
+ assert response = @gateway_3ds.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_successful_purchase_with_3d_secure_v2_optional_parameters
options = @options.merge(@options_browser_info).merge(mpi: { threeDSRequestorChallengeIndicator: '04' })
- assert response = @gateway.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
+ assert response = @gateway_3ds.authorize(@amount, @credit_card_d3d_2_challenge, options.merge(d3d: true))
assert_success response
assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
assert response.params['HTML_ANSWER']
- assert_match 'mpi-v2', Base64.decode64(response.params['HTML_ANSWER'])
+ assert Base64.decode64(response.params['HTML_ANSWER'])
end
def test_unsuccessful_purchase_with_3d_secure_v2
@credit_card_d3d_2_challenge.number = '4419177274955460'
- assert response = @gateway.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card_d3d_2_challenge, @options_browser_info.merge(d3d: true))
assert_failure response
assert_includes response.params, 'PAYID'
assert_equal response.params['NCERROR'], '40001134'
@@ -145,20 +159,20 @@ def test_unsuccessful_purchase_with_3d_secure_v2
def test_successful_with_non_numeric_order_id
@options[:order_id] = "##{@options[:order_id][0...26]}.12"
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_without_explicit_order_id
@options.delete(:order_id)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_with_custom_eci
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(eci: 4))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options.merge(eci: 4))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -166,8 +180,7 @@ def test_successful_purchase_with_custom_eci
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
def test_successful_purchase_with_custom_currency_at_the_gateway_level
- gateway = OgoneGateway.new(fixtures(:ogone).merge(currency: 'USD'))
- assert response = gateway.purchase(@amount, @credit_card)
+ assert response = @gateway_3ds.purchase(@amount, @credit_card)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
@@ -175,92 +188,90 @@ def test_successful_purchase_with_custom_currency_at_the_gateway_level
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
def test_successful_purchase_with_custom_currency
- gateway = OgoneGateway.new(fixtures(:ogone).merge(currency: 'EUR'))
- assert response = gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert response = @gateway_3ds.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert response = @gateway_3ds.purchase(@amount, @declined_card, @options)
assert_failure response
assert_equal 'No brand or invalid card number', response.message
end
def test_successful_authorize_with_mastercard
- assert auth = @gateway.authorize(@amount, @mastercard, @options)
+ assert auth = @gateway_3ds.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 auth = @gateway_3ds.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
+ assert capture = @gateway_3ds.capture(@amount, auth.authorization)
assert_success capture
end
def test_authorize_and_capture_with_custom_eci
- assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(eci: 4))
+ assert auth = @gateway_3ds.authorize(@amount, @credit_card, @options.merge(eci: 4))
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert capture = @gateway_3ds.capture(@amount, auth.authorization, @options)
assert_success capture
end
def test_unsuccessful_capture
- assert response = @gateway.capture(@amount, '')
+ assert response = @gateway_3ds.capture(@amount, '')
assert_failure response
assert_equal 'No card no, no exp date, no brand or invalid card number', response.message
end
def test_successful_void
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert auth = @gateway_3ds.authorize(@amount, @credit_card, @options)
assert_success auth
assert auth.authorization
- assert void = @gateway.void(auth.authorization)
+ assert void = @gateway_3ds.void(auth.authorization)
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
assert_success void
end
def test_successful_store
- assert response = @gateway.store(@credit_card, billing_id: 'test_alias')
+ assert response = @gateway_3ds.store(@credit_card, billing_id: 'test_alias')
assert_success response
- assert purchase = @gateway.purchase(@amount, 'test_alias')
+ assert purchase = @gateway_3ds.purchase(@amount, 'test_alias')
assert_success purchase
end
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 response = @gateway_3ds.store(@credit_card, billing_id: 'test_alias')
assert_success response
- assert purchase = gateway.purchase(@amount, 'test_alias')
+ assert purchase = @gateway_3ds.purchase(@amount, 'test_alias')
assert_success purchase
end
def test_successful_store_generated_alias
- assert response = @gateway.store(@credit_card)
+ assert response = @gateway_3ds.store(@credit_card)
assert_success response
- assert purchase = @gateway.purchase(@amount, response.billing_id)
+ assert purchase = @gateway_3ds.purchase(@amount, response.billing_id)
assert_success purchase
end
def test_successful_refund
- assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert refund = @gateway_3ds.refund(@amount, purchase.authorization, @options)
assert_success refund
assert refund.authorization
assert_equal OgoneGateway::SUCCESS_MESSAGE, refund.message
end
def test_unsuccessful_refund
- assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway_3ds.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert refund = @gateway.refund(@amount + 1, purchase.authorization, @options) # too much refund requested
+ assert refund = @gateway_3ds.refund(@amount + 1, purchase.authorization, @options) # too much refund requested
assert_failure refund
assert refund.authorization
assert_equal 'Overflow in refunds requests', refund.message
@@ -274,26 +285,26 @@ def test_successful_credit
end
def test_successful_verify
- response = @gateway.verify(@credit_card, @options)
+ response = @gateway_3ds.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)
+ response = @gateway_3ds.verify(@declined_card, @options)
assert_failure response
assert_equal 'No brand or invalid card number', 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_3ds.purchase(@amount, credit_card('4000100011112224'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '1'))
assert_success response
# 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_3ds.purchase(@amount, credit_card('4111111111111111'), @options.merge(billing_id: 'awesomeman', order_id: Time.now.to_i.to_s + '2'))
assert_success response
# 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_3ds.purchase(@amount, 'awesomeman', @options.merge(order_id: Time.now.to_i.to_s + '3'))
assert_success response
end
diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb
index 5152ab4c338..b923774c987 100644
--- a/test/remote/gateways/remote_orbital_test.rb
+++ b/test/remote/gateways/remote_orbital_test.rb
@@ -1,4 +1,4 @@
-require 'test_helper.rb'
+require 'test_helper'
class RemoteOrbitalGatewayTest < Test::Unit::TestCase
def setup
@@ -6,10 +6,11 @@ def setup
@gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_gateway))
@echeck_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_asv_aoa_gateway))
@three_ds_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_3ds_gateway))
-
+ @tpv_orbital_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_tpv_gateway))
@amount = 100
@google_pay_amount = 10000
@credit_card = credit_card('4556761029983886')
+ @mastercard_card_tpv = credit_card('2521000000000006')
@declined_card = credit_card('4000300011112220')
# Electronic Check object with test credentials of saving account
@echeck = check(account_number: '072403004', account_type: 'savings', routing_number: '072403004')
@@ -50,7 +51,15 @@ def setup
address2: address[:address2],
city: address[:city],
state: address[:state],
- zip: address[:zip]
+ zip: address[:zip],
+ requestor_name: 'ArtVandelay123',
+ total_tax_amount: '75',
+ national_tax: '625',
+ pst_tax_reg_number: '8675309',
+ customer_vat_reg_number: '1234567890',
+ merchant_vat_reg_number: '987654321',
+ commodity_code: 'SUMM',
+ local_tax_rate: '6250'
}
@level_3_options_visa = {
@@ -60,7 +69,11 @@ def setup
dest_country: 'USA',
discount_amount: 1,
vat_tax: 1,
- vat_rate: 25
+ vat_rate: 25,
+ invoice_discount_treatment: 1,
+ tax_treatment: 1,
+ ship_vat_rate: 10,
+ unique_vat_invoice_ref: 'ABC123'
}
@level_2_options_master = {
@@ -195,11 +208,13 @@ def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci
end
def test_successful_purchase_with_master_card_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'master')
+ brand: 'master'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -249,11 +264,13 @@ def test_successful_purchase_with_sca_merchant_initiated_master_card
end
def test_successful_purchase_with_american_express_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'american_express')
+ brand: 'american_express'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -261,11 +278,13 @@ def test_successful_purchase_with_american_express_network_tokenization_credit_c
end
def test_successful_purchase_with_discover_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'discover')
+ brand: 'discover'
+ )
assert response = @gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -823,6 +842,98 @@ def test_successful_verify
assert_equal 'No reason to decline', response.message
end
+ def test_successful_store
+ response = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success response
+ assert_false response.params['safetech_token'].blank?
+ end
+
+ def test_successful_purchase_stored_token
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ response = @tpv_orbital_gateway.purchase(@amount, store.authorization, @options.merge(card_brand: 'VI'))
+ assert_success response
+ assert_equal response.params['card_brand'], 'VI'
+ end
+
+ def test_successful_authorize_stored_token
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(29, store.authorization, @options.merge(card_brand: 'VI'))
+ assert_success auth
+ end
+
+ def test_successful_authorize_stored_token_mastercard
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ response = @tpv_orbital_gateway.authorize(29, store.authorization, @options.merge(card_brand: 'MC'))
+ assert_success response
+ assert_equal response.params['card_brand'], 'MC'
+ end
+
+ def test_failed_authorize_and_capture
+ store = @tpv_orbital_gateway.store(@credit_card, @options)
+ assert_success store
+ authorization = store.authorization.split(';').values_at(2).first
+ response = @tpv_orbital_gateway.capture(39, authorization, @options.merge(card_brand: 'VI'))
+ assert_failure response
+ assert_equal response.params['status_msg'], "The LIDM you supplied (#{authorization}) does not match with any existing transaction"
+ end
+
+ def test_successful_authorize_and_capture_with_stored_token
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(28, store.authorization, @options.merge(card_brand: 'MC'))
+ assert_success auth
+ assert_equal auth.params['card_brand'], 'MC'
+ response = @tpv_orbital_gateway.capture(28, auth.authorization, @options.merge(card_brand: 'MC'))
+ assert_success response
+ end
+
+ def test_successful_authorize_with_stored_token_and_refund
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(38, store.authorization, @options.merge(card_brand: 'MC'))
+ assert_success auth
+ response = @tpv_orbital_gateway.refund(38, auth.authorization, @options.merge(card_brand: 'MC'))
+ assert_success response
+ end
+
+ def test_failed_refund_wrong_token
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ auth = @tpv_orbital_gateway.authorize(38, store.authorization, @options.merge(card_brand: 'MC'))
+ assert_success auth
+ authorization = store.authorization.split(';').values_at(2).first
+ response = @tpv_orbital_gateway.refund(38, authorization, @options.merge(card_brand: 'MC'))
+ assert_failure response
+ assert_equal response.params['status_msg'], "The LIDM you supplied (#{authorization}) does not match with any existing transaction"
+ end
+
+ def test_successful_purchase_with_stored_token_and_refund
+ store = @tpv_orbital_gateway.store(@mastercard_card_tpv, @options)
+ assert_success store
+ purchase = @tpv_orbital_gateway.purchase(38, store.authorization, @options.merge(card_brand: 'MC'))
+ assert_success purchase
+ response = @tpv_orbital_gateway.refund(38, purchase.authorization, @options.merge(card_brand: 'MC'))
+ assert_success response
+ end
+
+ def test_successful_purchase_without_store
+ response = @tpv_orbital_gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.params['safetech_token'], nil
+ end
+
+ def test_failed_purchase_with_stored_token
+ auth = @tpv_orbital_gateway.authorize(@amount, @credit_card, @options.merge(store: true))
+ assert_success auth
+ options = @options.merge!(card_brand: 'VI')
+ response = @tpv_orbital_gateway.purchase(@amount, nil, options)
+ assert_failure response
+ assert_equal response.params['status_msg'], 'Error validating card/account number range'
+ end
+
def test_successful_different_cards
@credit_card.brand = 'master'
response = @gateway.verify(@credit_card, @options)
@@ -1133,7 +1244,13 @@ def setup
tax_indicator: '1',
tax: '75',
purchase_order: '123abc',
- zip: address[:zip]
+ zip: address[:zip],
+ requestor_name: 'ArtVandelay123',
+ total_tax_amount: '75',
+ pst_tax_reg_number: '8675309',
+ customer_vat_reg_number: '1234567890',
+ commodity_code: 'SUMM',
+ local_tax_rate: '6250'
}
@level_3_options = {
@@ -1143,7 +1260,11 @@ def setup
dest_country: 'USA',
discount_amount: 1,
vat_tax: 1,
- vat_rate: 25
+ vat_rate: 25,
+ invoice_discount_treatment: 1,
+ tax_treatment: 1,
+ ship_vat_rate: 10,
+ unique_vat_invoice_ref: 'ABC123'
}
@line_items = [
@@ -1202,6 +1323,13 @@ def test_successful_purchase_with_level_2_data
assert_equal 'Approved', response.message
end
+ def test_successful_purchase_with_level_2_data_canadian_currency
+ response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD', merchant_vat_reg_number: '987654321', national_tax: '625', level_2_data: @level_2_options))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
def test_successful_purchase_with_level_3_data
response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options, line_items: @line_items))
@@ -1226,11 +1354,13 @@ def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci
end
def test_successful_purchase_with_master_card_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'master')
+ brand: 'master'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -1238,11 +1368,13 @@ def test_successful_purchase_with_master_card_network_tokenization_credit_card
end
def test_successful_purchase_with_american_express_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'american_express')
+ brand: 'american_express'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
@@ -1250,11 +1382,13 @@ def test_successful_purchase_with_american_express_network_tokenization_credit_c
end
def test_successful_purchase_with_discover_network_tokenization_credit_card
- network_card = network_tokenization_credit_card('4788250000028291',
+ network_card = network_tokenization_credit_card(
+ '4788250000028291',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
verification_value: '111',
- brand: 'discover')
+ brand: 'discover'
+ )
assert response = @tandem_gateway.purchase(3000, network_card, @options)
assert_success response
assert_equal 'Approved', response.message
diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb
index b5a0292cd25..280e89e3883 100644
--- a/test/remote/gateways/remote_pay_junction_test.rb
+++ b/test/remote/gateways/remote_pay_junction_test.rb
@@ -71,8 +71,7 @@ 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 auth.authorization, response.authorization,
- 'Should maintain transaction ID across request'
+ assert_equal auth.authorization, response.authorization, 'Should maintain transaction ID across request'
end
def test_successful_credit
@@ -93,8 +92,7 @@ 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 purchase.authorization, response.authorization,
- 'Should maintain transaction ID across request'
+ assert_equal purchase.authorization, response.authorization, 'Should maintain transaction ID across request'
end
def test_successful_instant_purchase
@@ -110,17 +108,19 @@ def test_successful_instant_purchase
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_not_equal purchase.authorization, response.authorization,
- 'Should have recieved new transaction ID'
+ assert_not_equal purchase.authorization, response.authorization, 'Should have recieved new transaction ID'
assert_success response
end
def test_successful_recurring
- assert response = @gateway.recurring(AMOUNT, @credit_card,
+ assert response = @gateway.recurring(
+ AMOUNT,
+ @credit_card,
periodicity: :monthly,
payments: 12,
- order_id: generate_unique_id[0..15])
+ order_id: generate_unique_id[0..15]
+ )
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
assert_equal 'charge', response.params['transaction_action']
diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb
index b10e5119e3a..611fec465a3 100644
--- a/test/remote/gateways/remote_pay_trace_test.rb
+++ b/test/remote/gateways/remote_pay_trace_test.rb
@@ -17,11 +17,11 @@ def setup
@gateway = PayTraceGateway.new(fixtures(:pay_trace))
@amount = 100
- @credit_card = credit_card('4012000098765439')
- @mastercard = credit_card('5499740000000057')
+ @credit_card = credit_card('4012000098765439', verification_value: '999')
+ @mastercard = credit_card('5499740000000057', verification_value: '998')
@invalid_card = credit_card('54545454545454', month: '14', year: '1999')
- @discover = credit_card('6011000993026909')
- @amex = credit_card('371449635392376')
+ @discover = credit_card('6011000993026909', verification_value: '996')
+ @amex = credit_card('371449635392376', verification_value: '9997')
@echeck = check(account_number: '123456', routing_number: '325070760')
@options = {
billing_address: {
@@ -39,6 +39,15 @@ def test_acquire_token
assert_not_nil response['access_token']
end
+ def test_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ gateway.send :acquire_access_token
+ end
+
+ assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ end
+
def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
@@ -390,13 +399,6 @@ def test_duplicate_customer_creation
# gateway is with a specific dollar amount. Since verify is auth and void combined,
# having separate tests for auth and void should suffice.
- def test_invalid_login
- gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'integrator_id')
-
- response = gateway.acquire_access_token
- assert_match 'invalid_grant', response
- end
-
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @amex, @options)
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
index ebe148841a8..da2fd0fae93 100644
--- a/test/remote/gateways/remote_payeezy_test.rb
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -50,6 +50,16 @@ def setup
source: :apple_pay,
verification_value: 569
)
+ @apple_pay_card_amex = network_tokenization_credit_card(
+ '373953192351004',
+ brand: 'american_express',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ eci: 5,
+ source: :apple_pay,
+ verification_value: 569
+ )
end
def test_successful_store
@@ -86,6 +96,19 @@ def test_successful_purchase_with_apple_pay
assert_success response
end
+ def test_successful_purchase_with_apple_pay_amex
+ assert response = @gateway.purchase(@amount, @apple_pay_card_amex, @options)
+ assert_success response
+ end
+
+ def test_successful_authorize_and_capture_with_apple_pay
+ assert auth = @gateway.authorize(@amount, @apple_pay_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ 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)
@@ -99,6 +122,26 @@ def test_successful_purchase_with_soft_descriptors
assert_success response
end
+ def test_successful_purchase_and_authorize_with_reference_3
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(reference_3: '123345'))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(reference_3: '123345'))
+ assert_match(/Transaction Normal/, auth.message)
+ assert_success auth
+ end
+
+ def test_successful_purchase_and_authorize_with_customer_ref_top_level
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(customer_ref: 'abcde'))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(customer_ref: 'abcde'))
+ assert_match(/Transaction Normal/, auth.message)
+ assert_success auth
+ end
+
def test_successful_purchase_with_customer_ref
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(level2: { customer_ref: 'An important customer' }))
assert_match(/Transaction Normal/, response.message)
@@ -148,6 +191,48 @@ def test_failed_purchase_with_insufficient_funds
assert_match(/Insufficient Funds/, response.message)
end
+ def test_successful_purchase_with_three_ds_data
+ @options[:three_d_secure] = {
+ version: '1',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_equal '100', response.params['bank_resp_code']
+ assert_equal nil, response.error_code
+ assert_success response
+ end
+
+ def test_authorize_and_capture_three_ds_data
+ @options[:three_d_secure] = {
+ version: '1',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ 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_purchase_with_three_ds_version_data
+ @options[:three_d_secure] = {
+ version: '1.0.2',
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv////8=',
+ acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg'
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_equal '100', response.params['bank_resp_code']
+ assert_equal nil, response.error_code
+ assert_success response
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -376,7 +461,7 @@ def test_trans_error
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
+ assert_equal '500 INTERNAL_SERVER_ERROR', response.error_code
end
def test_transcript_scrubbing_store
diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb
index 71823d5d617..ed12025cd79 100644
--- a/test/remote/gateways/remote_payflow_test.rb
+++ b/test/remote/gateways/remote_payflow_test.rb
@@ -445,12 +445,15 @@ def test_reference_purchase
def test_recurring_with_initial_authorization
response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(1000, @credit_card,
+ @gateway.recurring(
+ 1000,
+ @credit_card,
periodicity: :monthly,
initial_transaction: {
type: :purchase,
amount: 500
- })
+ }
+ )
end
assert_success response
diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb
index 37d3fced73c..e2a504648be 100644
--- a/test/remote/gateways/remote_paymentez_test.rb
+++ b/test/remote/gateways/remote_paymentez_test.rb
@@ -8,13 +8,15 @@ def setup
@amount = 100
@credit_card = credit_card('4111111111111111', verification_value: '666')
@otp_card = credit_card('36417002140808', verification_value: '666')
- @elo_credit_card = credit_card('6362970000457013',
+ @elo_credit_card = credit_card(
+ '6362970000457013',
month: 10,
- year: 2022,
+ year: Time.now.year + 1,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
@declined_card = credit_card('4242424242424242', verification_value: '666')
@options = {
billing_address: address,
@@ -30,7 +32,7 @@ def setup
@eci = '01'
@three_ds_v1_version = '1.0.2'
@three_ds_v2_version = '2.1.0'
- @three_ds_server_trans_id = 'three-ds-v2-trans-id'
+ @ds_server_trans_id = 'ffffffff-9002-51a3-8000-0000000345a2'
@authentication_response_status = 'Y'
@three_ds_v1_mpi = {
@@ -44,7 +46,7 @@ def setup
cavv: @cavv,
eci: @eci,
version: @three_ds_v2_version,
- three_ds_server_trans_id: @three_ds_server_trans_id,
+ ds_transaction_id: @ds_server_trans_id,
authentication_response_status: @authentication_response_status
}
end
@@ -303,6 +305,15 @@ def test_unstore_with_elo
assert_success response
end
+ def test_successful_inquire_with_transaction_id
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ gateway_transaction_id = response.authorization
+ response = @gateway.inquire(gateway_transaction_id, @options)
+ assert_success response
+ end
+
def test_invalid_login
gateway = PaymentezGateway.new(application_code: '9z8y7w6x', app_key: '1a2b3c4d')
diff --git a/test/remote/gateways/remote_paypal_test.rb b/test/remote/gateways/remote_paypal_test.rb
index d73c21528c8..eee5d7aba1a 100644
--- a/test/remote/gateways/remote_paypal_test.rb
+++ b/test/remote/gateways/remote_paypal_test.rb
@@ -232,9 +232,11 @@ def test_successful_multiple_transfer
response = @gateway.purchase(900, @credit_card, @params)
assert_success response
- response = @gateway.transfer([@amount, 'joe@example.com'],
+ response = @gateway.transfer(
+ [@amount, 'joe@example.com'],
[600, 'jane@example.com', { note: 'Thanks for taking care of that' }],
- subject: 'Your money')
+ subject: 'Your money'
+ )
assert_success response
end
diff --git a/test/remote/gateways/remote_paysafe_test.rb b/test/remote/gateways/remote_paysafe_test.rb
index c7943d6801a..72f5c0a3aae 100644
--- a/test/remote/gateways/remote_paysafe_test.rb
+++ b/test/remote/gateways/remote_paysafe_test.rb
@@ -148,6 +148,20 @@ def test_successful_purchase_with_airline_details
assert_equal 'F', response.params['airlineTravelDetails']['tripLegs']['leg2']['serviceClass']
end
+ def test_successful_purchase_with_truncated_address
+ options = {
+ billing_address: {
+ address1: "This is an extremely long address, it is unreasonably long and we can't allow it.",
+ address2: "This is an extremely long address2, it is unreasonably long and we can't allow it.",
+ city: 'Lake Chargoggagoggmanchauggagoggchaubunagungamaugg',
+ state: 'NC',
+ zip: '27701'
+ }
+ }
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
def test_successful_purchase_with_token
response = @gateway.purchase(200, @pm_token, @options)
assert_success response
diff --git a/test/remote/gateways/remote_payway_test.rb b/test/remote/gateways/remote_payway_test.rb
index 5276e60e38e..643411f520a 100644
--- a/test/remote/gateways/remote_payway_test.rb
+++ b/test/remote/gateways/remote_payway_test.rb
@@ -8,37 +8,49 @@ 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')
+ 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')
+ brand: 'master'
+ )
- @invalid = credit_card('4564720000000037',
+ @invalid = credit_card(
+ '4564720000000037',
month: 9,
year: 2019,
- verification_value: '030')
+ verification_value: '030'
+ )
end
def test_successful_visa
diff --git a/test/remote/gateways/remote_pin_test.rb b/test/remote/gateways/remote_pin_test.rb
index 003b2729601..eaae9661ffa 100644
--- a/test/remote/gateways/remote_pin_test.rb
+++ b/test/remote/gateways/remote_pin_test.rb
@@ -47,6 +47,20 @@ def test_successful_purchase_with_metadata
assert_equal options_with_metadata[:metadata][:purchase_number], response.params['response']['metadata']['purchase_number']
end
+ def test_successful_purchase_with_platform_adjustment
+ options_with_platform_adjustment = {
+ platform_adjustment: {
+ amount: 30,
+ currency: 'AUD'
+ }
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options_with_platform_adjustment))
+ assert_success response
+ assert_equal true, response.params['response']['captured']
+ assert_equal options_with_platform_adjustment[:platform_adjustment][:amount], response.params['response']['platform_adjustment']['amount']
+ assert_equal options_with_platform_adjustment[:platform_adjustment][:currency], response.params['response']['platform_adjustment']['currency']
+ end
+
def test_successful_purchase_with_reference
response = @gateway.purchase(@amount, @credit_card, @options.merge(reference: 'statement descriptor'))
assert_success response
diff --git a/test/remote/gateways/remote_plexo_test.rb b/test/remote/gateways/remote_plexo_test.rb
index 433a8a72245..88f70b20de6 100644
--- a/test/remote/gateways/remote_plexo_test.rb
+++ b/test/remote/gateways/remote_plexo_test.rb
@@ -31,6 +31,22 @@ def setup
description: 'Test desc',
reason: 'requested by client'
}
+
+ @network_token_credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
+ first_name: 'Santiago', last_name: 'Navatta',
+ brand: 'Mastercard',
+ payment_cryptogram: 'UnVBR0RlYm42S2UzYWJKeWJBdWQ=',
+ number: '5555555555554444',
+ source: :network_token,
+ month: '12',
+ year: Time.now.year
+ })
+ end
+
+ def test_successful_purchase_with_network_token
+ response = @gateway.purchase(@amount, @network_token_credit_card, @options.merge({ invoice_number: '12345abcde' }))
+ assert_success response
+ assert_equal 'You have been mocked.', response.message
end
def test_successful_purchase
@@ -43,6 +59,24 @@ def test_successful_purchase_with_finger_print
assert_success response
end
+ def test_successful_purchase_with_invoice_number
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ invoice_number: '12345abcde' }))
+ assert_success response
+ assert_equal '12345abcde', response.params['invoiceNumber']
+ end
+
+ def test_successfully_send_merchant_id
+ # ensures that we can set and send the merchant_id and get a successful response
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_id: 3243 }))
+ assert_success response
+ assert_equal 3243, response.params['merchant']['id']
+
+ # ensures that we can set and send the merchant_id and expect a failed response for invalid merchant_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_id: 1234 }))
+ assert_failure response
+ assert_equal 'The requested Merchant was not found.', response.message
+ end
+
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
@@ -85,9 +119,12 @@ def test_partial_capture
end
def test_failed_capture
- response = @gateway.capture(@amount, '123')
+ auth = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure auth
+
+ response = @gateway.capture(@amount, auth.authorization)
assert_failure response
- assert_equal 'An internal error occurred. Contact support.', response.message
+ assert_equal 'The selected payment state is not valid.', response.message
end
def test_successful_refund
@@ -107,9 +144,12 @@ def test_partial_refund
end
def test_failed_refund
- response = @gateway.refund(@amount, '123', @cancel_options)
+ auth = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure auth
+
+ response = @gateway.refund(@amount, auth.authorization, @cancel_options)
assert_failure response
- assert_equal 'An internal error occurred. Contact support.', response.message
+ assert_equal 'The selected payment state is not valid.', response.message
end
def test_successful_void
@@ -121,9 +161,12 @@ def test_successful_void
end
def test_failed_void
- response = @gateway.void('123', @cancel_options)
+ auth = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure auth
+
+ response = @gateway.void(auth.authorization, @cancel_options)
assert_failure response
- assert_equal 'An internal error occurred. Contact support.', response.message
+ assert_equal 'The selected payment state is not valid.', response.message
end
def test_successful_verify
@@ -136,6 +179,11 @@ def test_successful_verify_with_custom_amount
assert_success response
end
+ def test_successful_verify_with_invoice_number
+ response = @gateway.verify(@credit_card, @options.merge({ invoice_number: '12345abcde' }))
+ assert_success response
+ end
+
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
@@ -261,6 +309,6 @@ def test_successful_purchase_and_declined_cancellation_sodexo
assert_success purchase
assert void = @gateway.void(purchase.authorization, @cancel_options)
- assert_success void
+ assert_failure void
end
end
diff --git a/test/remote/gateways/remote_priority_test.rb b/test/remote/gateways/remote_priority_test.rb
index ef3849fb283..d70fe153b42 100644
--- a/test/remote/gateways/remote_priority_test.rb
+++ b/test/remote/gateways/remote_priority_test.rb
@@ -70,6 +70,51 @@ def setup
}
]
}
+
+ @all_gateway_fields = {
+ is_auth: true,
+ invoice: '123',
+ source: 'test',
+ replay_id: @replay_id,
+ ship_amount: 1,
+ ship_to_country: 'US',
+ ship_to_zip: '12345',
+ payment_type: '',
+ tender_type: '',
+ tax_exempt: true,
+ pos_data: {
+ cardholder_presence: 'NotPresent',
+ device_attendance: 'Unknown',
+ device_input_capability: 'KeyedOnly',
+ device_location: 'Unknown',
+ pan_capture_method: 'Manual',
+ partial_approval_support: 'Supported',
+ pin_capture_capability: 'Twelve'
+ },
+ purchases: [
+ {
+ line_item_id: 79402,
+ name: 'Book',
+ description: 'The Elements of Style',
+ quantity: 1,
+ unit_price: 1.23,
+ discount_amount: 0,
+ extended_amount: '1.23',
+ discount_rate: 0,
+ tax_amount: 1
+ },
+ {
+ line_item_id: 79403,
+ name: 'Cat Poster',
+ description: 'A sleeping cat',
+ quantity: 1,
+ unit_price: '2.34',
+ discount_amount: 0,
+ extended_amount: '2.34',
+ discount_rate: 0
+ }
+ ]
+ }
end
def test_successful_authorize
@@ -162,6 +207,15 @@ def test_successful_purchase_with_additional_options
assert_equal 'Approved or completed successfully', response.message
end
+ def test_successful_authorize_and_capture_with_auth_purchase_params
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(@amount, auth.authorization, @all_gateway_fields)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
def test_successful_credit
options = @options.merge(@additional_creditoptions)
response = @gateway.credit(@credit_amount, @credit_card, options)
@@ -267,7 +321,7 @@ def test_failed_get_payment_status
def test_successful_verify
response = @gateway.verify(credit_card('411111111111111'))
assert_success response
- assert_match 'JPMORGAN CHASE BANK, N.A.', response.params['bank']['name']
+ assert_match 'JPMORGAN CHASE BANK N.A.', response.params['bank']['name']
end
def test_failed_verify
diff --git a/test/remote/gateways/remote_psl_card_test.rb b/test/remote/gateways/remote_psl_card_test.rb
index 44630800f89..18e911faea2 100644
--- a/test/remote/gateways/remote_psl_card_test.rb
+++ b/test/remote/gateways/remote_psl_card_test.rb
@@ -24,65 +24,55 @@ def setup
end
def test_successful_visa_purchase
- response = @gateway.purchase(@accept_amount, @visa,
- billing_address: @visa_address)
+ response = @gateway.purchase(@accept_amount, @visa, billing_address: @visa_address)
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)
+ 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'
- response = @gateway.purchase(@accept_amount, @visa_debit,
- billing_address: @visa_debit_address)
+ 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,
- currency: 'GBP')
+ response = @gateway.purchase(@accept_amount, @visa, billing_address: @visa_address, currency: 'GBP')
assert_success response
assert response.test?
end
def test_successful_solo_purchase
- response = @gateway.purchase(@accept_amount, @solo,
- billing_address: @solo_address)
+ 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,
- billing_address: @uk_maestro_address)
+ 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,
- billing_address: @uk_maestro_address)
+ 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,
- billing_address: @uk_maestro_address)
+ 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,
- billing_address: @visa_address)
+ response = @gateway.authorize(@accept_amount, @visa, billing_address: @visa_address)
assert_success response
assert response.test?
end
@@ -91,15 +81,13 @@ def test_no_login
@gateway = PslCardGateway.new(
login: ''
)
- response = @gateway.authorize(@accept_amount, @uk_maestro,
- billing_address: @uk_maestro_address)
+ 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)
+ authorization = @gateway.authorize(@accept_amount, @visa, billing_address: @visa_address)
assert_success authorization
assert authorization.test?
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
index f3457706af5..a319954a7ce 100644
--- a/test/remote/gateways/remote_quickbooks_test.rb
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -16,6 +16,12 @@ def setup
state: 'CA' }),
description: 'Store Purchase'
}
+
+ @amex = credit_card(
+ '378282246310005',
+ verification_value: '1234',
+ brand: 'american_express'
+ )
end
def test_successful_purchase
@@ -128,6 +134,18 @@ def test_transcript_scrubbing
assert_scrubbed(@gateway.options[:refresh_token], transcript)
end
+ def test_transcript_scrubbing_for_amex
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @amex, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@amex.number, transcript)
+ assert_scrubbed(@amex.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:access_token], transcript)
+ assert_scrubbed(@gateway.options[:refresh_token], transcript)
+ end
+
def test_failed_purchase_with_expired_token
@gateway.options[:access_token] = 'not_a_valid_token'
response = @gateway.purchase(@amount, @credit_card, @options)
diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb
index 6aead2a6dc4..40e9564cc17 100644
--- a/test/remote/gateways/remote_rapyd_test.rb
+++ b/test/remote/gateways/remote_rapyd_test.rb
@@ -3,7 +3,7 @@
class RemoteRapydTest < Test::Unit::TestCase
def setup
@gateway = RapydGateway.new(fixtures(:rapyd))
-
+ @gateway_payment_redirect = RapydGateway.new(fixtures(:rapyd).merge(url_override: 'payment_redirect'))
@amount = 100
@credit_card = credit_card('4111111111111111', first_name: 'Ryan', last_name: 'Reynolds', month: '12', year: '2035', verification_value: '345')
@declined_card = credit_card('4111111111111105')
@@ -16,29 +16,43 @@ def setup
description: 'Describe this transaction',
statement_descriptor: 'Statement Descriptor',
email: 'test@example.com',
- billing_address: address(name: 'Jim Reynolds')
+ billing_address: address(name: 'Jim Reynolds'),
+ order_id: '987654321'
+ }
+ @stored_credential_options = {
+ pm_type: 'gb_visa_card',
+ currency: 'GBP',
+ complete_payment_url: 'https://www.rapyd.net/platform/collect/online/',
+ error_payment_url: 'https://www.rapyd.net/platform/collect/online/',
+ description: 'Describe this transaction',
+ statement_descriptor: 'Statement Descriptor',
+ email: 'test@example.com',
+ billing_address: address(name: 'Jim Reynolds'),
+ order_id: '987654321'
}
@ach_options = {
pm_type: 'us_ach_bank',
currency: 'USD',
proof_of_authorization: false,
- payment_purpose: 'Testing Purpose'
+ payment_purpose: 'Testing Purpose',
+ email: 'test@example.com',
+ billing_address: address(name: 'Jim Reynolds')
}
@metadata = {
- 'array_of_objects': [
- { 'name': 'John Doe' },
- { 'type': 'customer' }
+ array_of_objects: [
+ { name: 'John Doe' },
+ { type: 'customer' }
],
- 'array_of_strings': %w[
+ array_of_strings: %w[
color
size
],
- 'number': 1234567890,
- 'object': {
- 'string': 'person'
+ number: 1234567890,
+ object: {
+ string: 'person'
},
- 'string': 'preferred',
- 'Boolean': true
+ string: 'preferred',
+ Boolean: true
}
@three_d_secure = {
version: '2.1.0',
@@ -46,6 +60,8 @@ def setup
xid: '00000000000000000501',
eci: '02'
}
+
+ @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', zip: '12345', name: 'john doe', phone_number: '12125559999')
end
def test_successful_purchase
@@ -54,13 +70,77 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
+ def test_successful_purchase_for_idempotent_requests
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: '1234567890'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ original_operation_id = response.params['status']['operation_id']
+ original_data_id = response.params['data']['id']
+ idempotent_request = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: '1234567890'))
+ assert_success idempotent_request
+ assert_equal 'SUCCESS', idempotent_request.message
+ assert_equal original_operation_id, idempotent_request.params['status']['operation_id']
+ assert_equal original_data_id, idempotent_request.params['data']['id']
+ end
+
+ def test_successful_purchase_for_non_idempotent_requests
+ # is not a idemptent request due the amount is different
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: '1234567890'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ original_operation_id = response.params['status']['operation_id']
+ idempotent_request = @gateway.purchase(25, @credit_card, @options.merge(idempotency_key: '1234567890'))
+ assert_success idempotent_request
+ assert_equal 'SUCCESS', idempotent_request.message
+ assert_not_equal original_operation_id, idempotent_request.params['status']['operation_id']
+ end
+
+ def test_successful_authorize_with_mastercard
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_mastercard
+ @options[:pm_type] = 'us_debit_mastercard_card'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_success_purchase_without_address_object_customer
+ @options[:pm_type] = 'us_debit_discover_card'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_successful_subsequent_purchase_with_stored_credential
- @options[:currency] = 'EUR'
- @options[:pm_type] = 'gi_visa_card'
- @options[:complete_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
- @options[:error_payment_url] = 'https://www.rapyd.net/platform/collect/online/'
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: rand.to_s[2..11], initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields_along_with_stored_credentials
+ # Rapyd requires a random int between 10 and 15 digits for NTID
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }, network_transaction_id: rand.to_s[2..11], initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ assert_equal 'customer_present', response.params['data']['initiation_type']
+ end
- response = @gateway.purchase(15000, @credit_card, @options.merge({ stored_credential: { network_transaction_id: '123456', reason_type: 'recurring' } }))
+ def test_successful_purchase_with_reccurence_type
+ @options[:pm_type] = 'gb_visa_mo_card'
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(recurrence_type: 'recurring'))
assert_success response
assert_equal 'SUCCESS', response.message
end
@@ -73,6 +153,18 @@ def test_successful_purchase_with_address
assert_equal 'SUCCESS', response.message
end
+ def test_successful_purchase_with_no_address
+ credit_card = credit_card('4111111111111111', month: '12', year: '2035', verification_value: '345')
+
+ options = @options.dup
+ options[:billing_address] = nil
+ options[:pm_type] = 'gb_mastercard_card'
+
+ response = @gateway.purchase(@amount, credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_successful_purchase_using_ach
response = @gateway.purchase(100000, @check, @ach_options)
assert_success response
@@ -81,7 +173,7 @@ def test_successful_purchase_using_ach
end
def test_successful_purchase_with_options
- options = @options.merge(metadata: @metadata, ewallet_id: 'ewallet_1a867a32b47158b30a8c17d42f12f3f1')
+ options = @options.merge(metadata: @metadata, ewallet_id: 'ewallet_897aca846f002686e14677541f78a0f4')
response = @gateway.purchase(100000, @credit_card, options)
assert_success response
assert_equal 'SUCCESS', response.message
@@ -163,37 +255,39 @@ def test_failed_void_with_payment_method_error
assert void = @gateway.void(auth.authorization)
assert_failure void
- assert_equal 'ERROR_PAYMENT_METHOD_TYPE_DOES_NOT_SUPPORT_PAYMENT_CANCELLATION', void.error_code
+ assert_equal 'ERROR_PAYMENT_METHOD_TYPE_DOES_NOT_SUPPORT_PAYMENT_CANCELLATION', void.params['status']['response_code']
+ end
+
+ def test_failed_authorize_with_payment_method_type_error
+ auth = @gateway_payment_redirect.authorize(@amount, @credit_card, @options.merge(pm_type: 'worng_type'))
+ assert_failure auth
+ assert_equal 'ERROR', auth.params['status']['status']
+ assert_equal 'ERROR_GET_PAYMENT_METHOD_TYPE', auth.params['status']['response_code']
+ end
+
+ def test_failed_purchase_with_zero_amount
+ response = @gateway_payment_redirect.purchase(0, @credit_card, @options)
+ assert_failure response
+ assert_equal 'ERROR', response.params['status']['status']
+ assert_equal 'ERROR_CARD_VALIDATION_CAPTURE_TRUE', response.params['status']['response_code']
end
def test_failed_void
response = @gateway.void('')
assert_failure response
- assert_equal 'UNAUTHORIZED_API_CALL', response.message
+ assert_equal 'NOT_FOUND', response.message
end
def test_successful_verify
- response = @gateway.verify(@credit_card, @options.except(:billing_address))
+ response = @gateway.verify(@credit_card, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
def test_successful_verify_with_peso
- options = {
- pm_type: 'mx_visa_card',
- currency: 'MXN'
- }
- response = @gateway.verify(@credit_card, options)
- assert_success response
- assert_equal 'SUCCESS', response.message
- end
-
- def test_successful_verify_with_yen
- options = {
- pm_type: 'jp_visa_card',
- currency: 'JPY'
- }
- response = @gateway.verify(@credit_card, options)
+ @options[:pm_type] = 'mx_visa_card'
+ @options[:currency] = 'MXN'
+ response = @gateway.verify(@credit_card, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
@@ -211,8 +305,9 @@ def test_successful_store_and_purchase
assert store.params.dig('data', 'default_payment_method')
# 3DS authorization is required on storing a payment method for future transactions
- # purchase = @gateway.purchase(100, store.authorization, @options.merge(customer_id: customer_id))
- # assert_sucess purchase
+ # This test verifies that the card id and customer id are sent with the purchase
+ purchase = @gateway.purchase(100, store.authorization, @options)
+ assert_match(/The request tried to use a card ID, but the cardholder has not completed the 3DS verification process./, purchase.message)
end
def test_successful_store_and_unstore
@@ -239,6 +334,7 @@ def test_failed_unstore
unstore = @gateway.unstore('')
assert_failure unstore
+ assert_equal 'NOT_FOUND', unstore.message
end
def test_invalid_login
@@ -256,7 +352,7 @@ def test_transcript_scrubbing
transcript = @gateway.scrub(transcript)
assert_scrubbed(@credit_card.number, transcript)
- assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(/"#{@credit_card.verification_value}"/, transcript)
assert_scrubbed(@gateway.options[:secret_key], transcript)
assert_scrubbed(@gateway.options[:access_key], transcript)
end
@@ -295,4 +391,127 @@ def test_successful_authorize_with_3ds_v2_options
assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
assert response.params['data']['redirect_url']
end
+
+ def test_successful_purchase_with_3ds_v2_gateway_specific
+ options = @options.merge(three_d_secure: { required: true })
+ options[:pm_type] = 'gb_visa_card'
+
+ response = @gateway.purchase(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ assert response.params['data']['redirect_url']
+ assert_match 'https://sandboxcheckout.rapyd.net/3ds-payment?token=payment_', response.params['data']['redirect_url']
+ end
+
+ def test_successful_purchase_without_3ds_v2_gateway_specific
+ options = @options.merge(three_d_secure: { required: false })
+ options[:pm_type] = 'gb_visa_card'
+ response = @gateway.purchase(1000, @credit_card, options)
+ assert_success response
+ assert_equal 'CLO', response.params['data']['status']
+ assert_equal 'not_applicable', response.params['data']['payment_method_data']['next_action']
+ assert_equal '', response.params['data']['redirect_url']
+ end
+
+ def test_successful_authorize_with_execute_threed
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = true
+ @options[:complete_payment_url] = 'http://www.google.com?param1=1¶m2=2'
+ options = @options.merge(pm_type: 'gb_visa_card', execute_threed: true)
+ response = @gateway.authorize(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ assert response.params['data']['redirect_url']
+ ensure
+ ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false
+ end
+
+ def test_successful_purchase_without_cvv
+ options = @options.merge({ pm_type: 'gb_visa_card', network_transaction_id: rand.to_s[2..11] })
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(100, @credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_recurring_transaction_without_cvv
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_empty_network_transaction_id
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: '', initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_nil_network_transaction_id
+ response = @gateway.purchase(15000, @credit_card, @stored_credential_options.merge(network_transaction_id: nil, initiation_type: 'customer_present'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_payment_redirect_url
+ response = @gateway_payment_redirect.purchase(@amount, @credit_card, @options.merge(pm_type: 'gb_visa_mo_card'))
+
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_3ds_v2_gateway_specific_payment_redirect_url
+ options = @options.merge(three_d_secure: { required: true })
+ options[:pm_type] = 'gb_visa_card'
+
+ response = @gateway_payment_redirect.purchase(105000, @credit_card, options)
+ assert_success response
+ assert_equal 'ACT', response.params['data']['status']
+ assert_equal '3d_verification', response.params['data']['payment_method_data']['next_action']
+ end
+
+ def test_successful_purchase_without_cvv_payment_redirect_url
+ options = @options.merge({ pm_type: 'gb_visa_card', network_transaction_id: rand.to_s[2..11] })
+ @credit_card.verification_value = nil
+ response = @gateway_payment_redirect.purchase(100, @credit_card, options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_refund_payment_redirect_url
+ purchase = @gateway_payment_redirect.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_successful_subsequent_purchase_stored_credential_payment_redirect_url
+ response = @gateway_payment_redirect.purchase(15000, @credit_card, @stored_credential_options.merge(stored_credential: { network_transaction_id: rand.to_s[2..11], reason_type: 'recurring' }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_fx_fields_with_currency_exchange
+ @options[:pm_type] = 'gb_visa_card'
+ @options[:currency] = 'GBP'
+ @options[:requested_currency] = 'USD'
+ @options[:fixed_side] = 'buy'
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_fx_fields_us_debit_card
+ @options[:currency] = 'EUR'
+ @options[:requested_currency] = 'USD'
+ @options[:fixed_side] = 'buy'
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
diff --git a/test/remote/gateways/remote_reach_test.rb b/test/remote/gateways/remote_reach_test.rb
index b72af06f159..9aaf8b59fda 100644
--- a/test/remote/gateways/remote_reach_test.rb
+++ b/test/remote/gateways/remote_reach_test.rb
@@ -320,7 +320,6 @@ def test_transcript_scrubbing
def fingerprint
raw_response = @gateway.ssl_get @gateway.send(:url, "fingerprint?MerchantId=#{@gateway.options[:merchant_id]}")
- fingerprint = raw_response.match(/(gip_device_fingerprint=')([\w -]+)/)[2]
- fingerprint
+ raw_response.match(/(gip_device_fingerprint=')([\w -]+)/)[2]
end
end
diff --git a/test/remote/gateways/remote_realex_test.rb b/test/remote/gateways/remote_realex_test.rb
index 39006c27d44..a45904d8e60 100644
--- a/test/remote/gateways/remote_realex_test.rb
+++ b/test/remote/gateways/remote_realex_test.rb
@@ -18,17 +18,21 @@ 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',
+ @apple_pay = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @declined_apple_pay = network_tokenization_credit_card('4000120000001154',
+ @declined_apple_pay = network_tokenization_credit_card(
+ '4000120000001154',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
@amount = 10000
end
@@ -38,13 +42,16 @@ def card_fixtures(name)
def test_realex_purchase
[@visa, @mastercard].each do |card|
- response = @gateway.purchase(@amount, 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 response.test?
@@ -58,9 +65,12 @@ def test_realex_purchase_with_invalid_login
login: 'invalid',
password: 'invalid'
)
- response = gateway.purchase(@amount, @visa,
+ response = gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Invalid login test')
+ description: 'Invalid login test'
+ )
assert_not_nil response
assert_failure response
@@ -70,9 +80,12 @@ def test_realex_purchase_with_invalid_login
end
def test_realex_purchase_with_invalid_account
- response = RealexGateway.new(fixtures(:realex_with_account).merge(account: 'invalid')).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 account')
+ description: 'Test Realex purchase with invalid account'
+ )
assert_not_nil response
assert_failure response
@@ -90,9 +103,12 @@ def test_realex_purchase_with_apple_pay
def test_realex_purchase_declined
[@visa_declined, @mastercard_declined].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex purchase declined')
+ description: 'Test Realex purchase declined'
+ )
assert_not_nil response
assert_failure response
@@ -178,9 +194,12 @@ def test_subsequent_purchase_with_stored_credential
def test_realex_purchase_referral_b
[@visa_referral_b, @mastercard_referral_b].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex Referral B')
+ description: 'Test Realex Referral B'
+ )
assert_not_nil response
assert_failure response
assert response.test?
@@ -191,9 +210,12 @@ 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,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex Rqeferral A')
+ description: 'Test Realex Rqeferral A'
+ )
assert_not_nil response
assert_failure response
assert_equal '103', response.params['result']
@@ -203,9 +225,12 @@ def test_realex_purchase_referral_a
def test_realex_purchase_coms_error
[@visa_coms_error, @mastercard_coms_error].each do |card|
- response = @gateway.purchase(@amount, card,
+ response = @gateway.purchase(
+ @amount,
+ card,
order_id: generate_unique_id,
- description: 'Test Realex coms error')
+ description: 'Test Realex coms error'
+ )
assert_not_nil response
assert_failure response
@@ -217,9 +242,12 @@ def test_realex_purchase_coms_error
def test_realex_expiry_month_error
@visa.month = 13
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex expiry month error')
+ description: 'Test Realex expiry month error'
+ )
assert_not_nil response
assert_failure response
@@ -230,9 +258,12 @@ def test_realex_expiry_month_error
def test_realex_expiry_year_error
@visa.year = 2005
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex expiry year error')
+ description: 'Test Realex expiry year error'
+ )
assert_not_nil response
assert_failure response
@@ -244,9 +275,12 @@ def test_invalid_credit_card_name
@visa.first_name = ''
@visa.last_name = ''
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
- description: 'test_chname_error')
+ description: 'test_chname_error'
+ )
assert_not_nil response
assert_failure response
@@ -257,32 +291,41 @@ def test_invalid_credit_card_name
def test_cvn
@visa_cvn = @visa.clone
@visa_cvn.verification_value = '111'
- response = @gateway.purchase(@amount, @visa_cvn,
+ response = @gateway.purchase(
+ @amount,
+ @visa_cvn,
order_id: generate_unique_id,
- description: 'test_cvn')
+ description: 'test_cvn'
+ )
assert_not_nil response
assert_success response
assert response.authorization.length > 0
end
def test_customer_number
- response = @gateway.purchase(@amount, @visa,
+ response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'test_cust_num',
- customer: 'my customer id')
+ customer: 'my customer id'
+ )
assert_not_nil response
assert_success response
assert response.authorization.length > 0
end
def test_realex_authorize
- response = @gateway.authorize(@amount, @visa,
+ response = @gateway.authorize(
+ @amount,
+ @visa,
order_id: generate_unique_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert_not_nil response
assert_success response
@@ -294,13 +337,16 @@ def test_realex_authorize
def test_realex_authorize_then_capture
order_id = generate_unique_id
- auth_response = @gateway.authorize(@amount, @visa,
+ auth_response = @gateway.authorize(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert auth_response.test?
capture_response = @gateway.capture(nil, auth_response.authorization)
@@ -315,13 +361,16 @@ def test_realex_authorize_then_capture
def test_realex_authorize_then_capture_with_extra_amount
order_id = generate_unique_id
- auth_response = @gateway.authorize(@amount * 115, @visa,
+ 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)
@@ -336,13 +385,16 @@ def test_realex_authorize_then_capture_with_extra_amount
def test_realex_purchase_then_void
order_id = generate_unique_id
- purchase_response = @gateway.purchase(@amount, @visa,
+ purchase_response = @gateway.purchase(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert purchase_response.test?
void_response = @gateway.void(purchase_response.authorization)
@@ -358,13 +410,16 @@ def test_realex_purchase_then_refund
gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(rebate_secret: 'rebate'))
- purchase_response = gateway_with_refund_password.purchase(@amount, @visa,
+ purchase_response = gateway_with_refund_password.purchase(
+ @amount,
+ @visa,
order_id: order_id,
description: 'Test Realex Purchase',
billing_address: {
zip: '90210',
country: 'US'
- })
+ }
+ )
assert purchase_response.test?
rebate_response = gateway_with_refund_password.refund(@amount, purchase_response.authorization)
@@ -376,9 +431,11 @@ def test_realex_purchase_then_refund
end
def test_realex_verify
- response = @gateway.verify(@visa,
+ response = @gateway.verify(
+ @visa,
order_id: generate_unique_id,
- description: 'Test Realex verify')
+ description: 'Test Realex verify'
+ )
assert_not_nil response
assert_success response
@@ -388,9 +445,11 @@ def test_realex_verify
end
def test_realex_verify_declined
- response = @gateway.verify(@visa_declined,
+ response = @gateway.verify(
+ @visa_declined,
order_id: generate_unique_id,
- description: 'Test Realex verify declined')
+ description: 'Test Realex verify declined'
+ )
assert_not_nil response
assert_failure response
@@ -402,13 +461,16 @@ def test_realex_verify_declined
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,
+ 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
@@ -417,13 +479,16 @@ def test_successful_credit
end
def test_failed_credit
- credit_response = @gateway.credit(@amount, @visa,
+ 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
@@ -433,13 +498,16 @@ def test_failed_credit
def test_maps_avs_and_cvv_response_codes
[@visa, @mastercard].each do |card|
- response = @gateway.purchase(@amount, 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']
@@ -449,13 +517,16 @@ def test_maps_avs_and_cvv_response_codes
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
- @gateway.purchase(@amount, @visa_declined,
+ @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)
diff --git a/test/remote/gateways/remote_redsys_rest_test.rb b/test/remote/gateways/remote_redsys_rest_test.rb
new file mode 100644
index 00000000000..6c4f2361e59
--- /dev/null
+++ b/test/remote/gateways/remote_redsys_rest_test.rb
@@ -0,0 +1,210 @@
+require 'test_helper'
+
+class RemoteRedsysRestTest < Test::Unit::TestCase
+ def setup
+ @gateway = RedsysRestGateway.new(fixtures(:redsys_rest))
+ @amount = 100
+ @credit_card = credit_card('4548812049400004')
+ @credit_card_no_cvv = credit_card('4548812049400004', verification_value: nil)
+ @declined_card = credit_card
+ @threeds2_credit_card = credit_card('4918019199883839')
+
+ @threeds2_credit_card_frictionless = credit_card('4548814479727229')
+ @threeds2_credit_card_alt = credit_card('4548817212493017')
+ @options = {
+ order_id: generate_order_id
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_purchase_with_invalid_order_id
+ response = @gateway.purchase(@amount, @credit_card, order_id: "a%4#{generate_order_id}")
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_purchase_and_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_purchase_and_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(@amount + 100, purchase.authorization, @options)
+ assert_failure refund
+ assert_equal 'SIS0057 ERROR', refund.message
+ end
+
+ def test_failed_purchase_with_unsupported_currency
+ response = @gateway.purchase(600, @credit_card, @options.merge(currency: 'PEN'))
+ assert_failure response
+ assert_equal 'SIS0027 ERROR', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success capture
+ assert_match(/Refund.*approved/, capture.message)
+ end
+
+ def test_successful_authorize_and_failed_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(2 * @amount, authorize.authorization, @options)
+ assert_failure capture
+ assert_match(/SIS0062 ERROR/, capture.message)
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', response.message
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization, @options)
+ assert_success void
+ assert_equal '100', void.params['ds_amount']
+ assert_equal 'Cancellation Accepted', void.message
+ end
+
+ def test_failed_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ authorization = "#{authorize.params[:ds_order]}|#{@amount}|203"
+ void = @gateway.void(authorization, @options)
+ assert_failure void
+ assert_equal 'SIS0027 ERROR', void.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_successful_verify_without_cvv
+ assert response = @gateway.verify(@credit_card_no_cvv, @options)
+ assert_success response
+
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Refusal with no specific reason', 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_encrypt_handles_url_safe_character_in_secret_key_without_error
+ gateway = RedsysRestGateway.new({
+ login: '091952713',
+ secret_key: 'yG78qf-PkHyRzRiZGSTCJdO2TvjWgFa8'
+ })
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert response
+ end
+
+ def test_successful_authorize_3ds_setup
+ options = @options.merge(execute_threed: true, terminal: 12)
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert response.params['ds_emv3ds']
+ assert_equal '2.2.0', response.params['ds_emv3ds']['protocolVersion']
+ assert_equal 'CardConfiguration', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_3ds
+ options = @options.merge(execute_threed: true)
+ response = @gateway.purchase(@amount, @threeds2_credit_card, options)
+ assert_success response
+ assert three_ds_data = response.params['ds_emv3ds']
+ assert_equal '2.1.0', three_ds_data['protocolVersion']
+ assert_equal 'https://sis-d.redsys.es/sis-simulador-web/threeDsMethod.jsp', three_ds_data['threeDSMethodURL']
+ assert_equal 'CardConfiguration', response.message
+ assert response.authorization
+ end
+
+ # Pending 3DS support
+ # Requires account configuration to allow setting moto flag
+ # def test_purchase_with_moto_flag
+ # response = @gateway.purchase(@amount, @credit_card, @options.merge(moto: true, metadata: { manual_entry: true }))
+ # assert_equal 'SIS0488 ERROR', response.message
+ # end
+
+ # Pending 3DS support
+ # def test_successful_3ds_authorize_with_exemption
+ # options = @options.merge(execute_threed: true, terminal: 12)
+ # response = @gateway.authorize(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
+ # assert_success response
+ # assert response.params['ds_emv3ds']
+ # assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ # assert_equal 'CardConfiguration', response.message
+ # end
+
+ # Pending 3DS support
+ # def test_successful_3ds_purchase_with_exemption
+ # options = @options.merge(execute_threed: true, terminal: 12)
+ # response = @gateway.purchase(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
+ # assert_success response
+ # assert response.params['ds_emv3ds']
+ # assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ # assert_equal 'CardConfiguration', response.message
+ # 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_sha256_test.rb b/test/remote/gateways/remote_redsys_sha256_test.rb
index 334e4cc21ca..bd36ae2bb72 100644
--- a/test/remote/gateways/remote_redsys_sha256_test.rb
+++ b/test/remote/gateways/remote_redsys_sha256_test.rb
@@ -105,7 +105,7 @@ def test_successful_authorize_3ds
response = @gateway.authorize(@amount, @credit_card, options)
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
assert response.authorization
end
@@ -132,7 +132,7 @@ def test_successful_3ds_authorize_with_exemption
response = @gateway.authorize(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
end
@@ -141,7 +141,7 @@ def test_successful_3ds_purchase_with_exemption
response = @gateway.purchase(@amount, @credit_card, options.merge(sca_exemption: 'LWV'))
assert_success response
assert response.params['ds_emv3ds']
- assert_equal 'NO_3DS_v2', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
+ assert_equal '2.2.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion']
assert_equal 'CardConfiguration', response.message
end
@@ -364,12 +364,9 @@ def test_failed_void
authorize = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorize
- void = @gateway.void(authorize.authorization)
- assert_success void
-
- another_void = @gateway.void(authorize.authorization)
+ another_void = @gateway.void(authorize.authorization << '123')
assert_failure another_void
- assert_equal 'SIS0222 ERROR', another_void.message
+ assert_equal 'SIS0007 ERROR', another_void.message
end
def test_successful_verify
@@ -377,8 +374,6 @@ def test_successful_verify
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
diff --git a/test/remote/gateways/remote_redsys_test.rb b/test/remote/gateways/remote_redsys_test.rb
index b8c790d30f9..eef1dc8a108 100644
--- a/test/remote/gateways/remote_redsys_test.rb
+++ b/test/remote/gateways/remote_redsys_test.rb
@@ -175,12 +175,9 @@ def test_failed_void
authorize = @gateway.authorize(@amount, @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
+ void = @gateway.void(authorize.authorization << '123')
+ assert_failure void
+ assert_equal 'SIS0007 ERROR', void.message
end
def test_successful_verify
@@ -188,8 +185,6 @@ def test_successful_verify
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
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
index 158e1464518..5c9d81fc6b6 100644
--- a/test/remote/gateways/remote_safe_charge_test.rb
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -63,6 +63,16 @@ def test_successful_purchase
assert_equal 'Success', response.message
end
+ def test_successful_purchase_with_token
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ subsequent_response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success subsequent_response
+ assert_equal 'Success', subsequent_response.message
+ end
+
def test_successful_purchase_with_non_fractional_currency
options = @options.merge(currency: 'CLP')
response = @gateway.purchase(127999, @credit_card, options)
@@ -256,6 +266,28 @@ def test_failed_refund
assert_equal 'Transaction must contain a Card/Token/Account', response.message
end
+ def test_successful_unreferenced_refund
+ option = {
+ unreferenced_refund: true
+ }
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, option)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_successful_unreferenced_refund_with_credit
+ option = {
+ unreferenced_refund: true
+ }
+
+ assert general_credit = @gateway.credit(@amount, @credit_card, option)
+ assert_success general_credit
+ assert_equal 'Success', general_credit.message
+ end
+
def test_successful_credit
response = @gateway.credit(@amount, credit_card('4444436501403986'), @options)
assert_success response
@@ -282,6 +314,12 @@ def test_successful_credit_with_extra_options
assert_equal 'Success', response.message
end
+ def test_successful_credit_with_customer_details
+ response = @gateway.credit(@amount, credit_card('4444436501403986'), @options.merge(email: 'test@example.com'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
def test_failed_credit
response = @gateway.credit(@amount, @declined_card, @options)
assert_failure response
diff --git a/test/remote/gateways/remote_sage_pay_test.rb b/test/remote/gateways/remote_sage_pay_test.rb
index e8de154a2aa..6015b5b3645 100644
--- a/test/remote/gateways/remote_sage_pay_test.rb
+++ b/test/remote/gateways/remote_sage_pay_test.rb
@@ -44,7 +44,7 @@ def setup
)
@mastercard = CreditCard.new(
- number: '5404000000000001',
+ number: '5186150660000009',
month: 12,
year: next_year,
verification_value: 419,
@@ -53,6 +53,16 @@ def setup
brand: 'master'
)
+ @frictionless = CreditCard.new(
+ number: '5186150660000009',
+ month: 12,
+ year: next_year,
+ verification_value: 419,
+ first_name: 'SUCCESSFUL',
+ last_name: '',
+ brand: 'master'
+ )
+
@electron = CreditCard.new(
number: '4917300000000008',
month: 12,
@@ -64,9 +74,10 @@ def setup
)
@declined_card = CreditCard.new(
- number: '4111111111111111',
+ number: '4000000000000001',
month: 9,
year: next_year,
+ verification_value: 123,
first_name: 'Tekin',
last_name: 'Suleyman',
brand: 'visa'
@@ -97,9 +108,111 @@ def setup
phone: '0161 123 4567'
}
+ @options_v4 = {
+ billing_address: {
+ name: 'Tekin Suleyman',
+ address1: 'Flat 10 Lapwing Court',
+ address2: 'West Didsbury',
+ city: 'Manchester',
+ county: 'Greater Manchester',
+ country: 'GB',
+ zip: 'M20 2PS'
+ },
+ shipping_address: {
+ name: 'Tekin Suleyman',
+ address1: '120 Grosvenor St',
+ city: 'Manchester',
+ county: 'Greater Manchester',
+ country: 'GB',
+ zip: 'M1 7QW'
+ },
+ order_id: generate_unique_id,
+ description: 'Store purchase',
+ ip: '86.150.65.37',
+ email: 'tekin@tekin.co.uk',
+ phone: '0161 123 4567',
+ protocol_version: '4.00',
+ three_ds_2: {
+ channel: 'browser',
+ browser_info: {
+ accept_header: 'unknown',
+ depth: 48,
+ java: true,
+ language: 'US',
+ height: 1000,
+ width: 500,
+ timezone: '-120',
+ user_agent: 'unknown',
+ browser_size: '05'
+ },
+ notification_url: 'https://example.com/notification'
+ }
+ }
+
@amount = 100
end
+ # Protocol 4
+ def test_successful_purchase_v4
+ assert response = @gateway.purchase(@amount, @mastercard, @options_v4)
+ assert_success response
+
+ assert response.test?
+ assert !response.authorization.blank?
+ end
+
+ def test_three_ds_challenge_purchase_v4
+ assert response = @gateway.purchase(@amount, @mastercard, @options_v4.merge(apply_3d_secure: 1))
+
+ assert_equal '3DAUTH', response.params['Status']
+ assert response.params.include?('ACSURL')
+ assert response.params.include?('CReq')
+ end
+
+ def test_frictionless_purchase_v4
+ assert response = @gateway.purchase(@amount, @frictionless, @options_v4.merge(apply_3d_secure: 1))
+ assert_success response
+
+ assert_equal 'OK', response.params['3DSecureStatus']
+ end
+
+ def test_successful_purchase_v4_cit
+ cit_options = @options_v4.merge!({
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'cardholder',
+ reason_type: 'installment'
+ },
+ recurring_frequency: '30',
+ recurring_expiry: "#{Time.now.year + 1}-04-21",
+ installment_data: 5,
+ order_id: generate_unique_id
+ })
+ assert response = @gateway.purchase(@amount, @mastercard, cit_options)
+ assert_success response
+ assert response.test?
+ assert !response.authorization.blank?
+
+ network_transaction_id = response.params['SchemeTraceID']
+ cit_options = @options_v4.merge!({
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'installment',
+ network_transaction_id: network_transaction_id
+ },
+ recurring_frequency: '30',
+ recurring_expiry: "#{Time.now.year + 1}-04-21",
+ installment_data: 5,
+ order_id: generate_unique_id
+ })
+ assert response = @gateway.purchase(@amount, @mastercard, cit_options)
+ assert_success response
+ assert response.test?
+ assert !response.authorization.blank?
+ end
+
+ # Protocol 3
def test_successful_mastercard_purchase
assert response = @gateway.purchase(@amount, @mastercard, @options)
assert_success response
@@ -108,6 +221,14 @@ def test_successful_mastercard_purchase
assert !response.authorization.blank?
end
+ def test_protocol_version_v4_purchase
+ assert response = @gateway.purchase(@amount, @mastercard, @options.merge(protocol_version: '4.00'))
+ assert_failure response
+
+ assert_equal 'MALFORMED', response.params['Status']
+ assert_equal '3227 : The ThreeDSNotificationURL field is required.', response.message
+ end
+
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
@@ -142,10 +263,7 @@ def test_successful_authorization_and_capture_and_refund
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 refund = @gateway.refund(@amount, capture.authorization, description: 'Crediting trx', order_id: generate_unique_id)
assert_success refund
end
@@ -168,11 +286,7 @@ def test_successful_purchase_and_void
def test_successful_purchase_and_refund
assert purchase = @gateway.purchase(@amount, @mastercard, @options)
assert_success purchase
-
- assert refund = @gateway.refund(@amount, purchase.authorization,
- description: 'Crediting trx',
- order_id: generate_unique_id)
-
+ assert refund = @gateway.refund(@amount, purchase.authorization, description: 'Crediting trx', order_id: generate_unique_id)
assert_success refund
end
@@ -272,15 +386,16 @@ def test_successful_purchase_with_gift_aid_payment
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
+ # Test failing on master and feature branch
+ # 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'
@@ -402,7 +517,7 @@ def test_successful_verify
def test_failed_verify
response = @gateway.verify(@declined_card, @options)
assert_failure response
- assert_match(/Card Range not supported/, response.message)
+ assert_match(/5011 : Your card number has failed our validity checks and appears to be incorrect. Please check and re-enter./, response.message)
end
def test_transcript_scrubbing
diff --git a/test/remote/gateways/remote_sage_test.rb b/test/remote/gateways/remote_sage_test.rb
index 8ec91e95810..1bbca7cbad1 100644
--- a/test/remote/gateways/remote_sage_test.rb
+++ b/test/remote/gateways/remote_sage_test.rb
@@ -164,8 +164,7 @@ def test_partial_refund
def test_store_visa
assert response = @gateway.store(@visa, @options)
assert_success response
- assert response.authorization,
- 'Store card authorization should not be nil'
+ assert response.authorization, 'Store card authorization should not be nil'
assert_not_nil response.message
end
@@ -176,15 +175,13 @@ def test_failed_store
end
def test_unstore_visa
- assert auth = @gateway.store(@visa, @options).authorization,
- 'Unstore card authorization should not be nil'
+ 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 auth = @gateway.store(@visa, @options).authorization, 'Unstore card authorization should not be nil'
assert response = @gateway.unstore(auth, @options)
assert_success response
end
diff --git a/test/remote/gateways/remote_secure_pay_test.rb b/test/remote/gateways/remote_secure_pay_test.rb
index dec1ff43d41..77d41451b3b 100644
--- a/test/remote/gateways/remote_secure_pay_test.rb
+++ b/test/remote/gateways/remote_secure_pay_test.rb
@@ -4,9 +4,7 @@ class RemoteSecurePayTest < Test::Unit::TestCase
def setup
@gateway = SecurePayGateway.new(fixtures(:secure_pay))
- @credit_card = credit_card('4111111111111111',
- month: '7',
- year: '2014')
+ @credit_card = credit_card('4111111111111111', month: '7', year: '2014')
@options = {
order_id: generate_unique_id,
diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb
index b2ffead799f..6e6e8a82494 100644
--- a/test/remote/gateways/remote_securion_pay_test.rb
+++ b/test/remote/gateways/remote_securion_pay_test.rb
@@ -20,15 +20,28 @@ def setup
}
end
+ def test_successful_store_and_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ 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.params['cards'][0]['customerId']
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ 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
+ @options[:customer_id] = response.params['cards'][0]['customerId']
response = @gateway.store(@new_credit_card, @options)
assert_success response
assert_match %r(^card_\w+$), response.params['card']['id']
@@ -43,11 +56,6 @@ def test_successful_store
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)
@@ -81,7 +89,7 @@ def test_successful_purchase_with_three_ds_data
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 %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
end
@@ -99,12 +107,15 @@ def test_authorization_and_capture
def test_failed_authorize
response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
+ assert_match CHARGE_ID_REGEX, response.authorization
+ assert_equal response.authorization, response.params['error']['chargeId']
+ assert_equal response.message, 'The card was declined.'
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
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_successful_full_refund
@@ -116,7 +127,7 @@ def test_successful_full_refund
assert_success refund
assert refund.params['refunded']
- assert_equal 0, refund.params['amount']
+ assert_equal 2000, refund.params['refunds'].first['amount']
assert_equal 1, refund.params['refunds'].size
assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum
@@ -130,6 +141,7 @@ def test_successful_partially_refund
first_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success first_refund
+ assert_equal @refund_amount, first_refund.params['refunds'].first['amount']
second_refund = @gateway.refund(@refund_amount, purchase.authorization)
assert_success second_refund
@@ -143,7 +155,7 @@ def test_successful_partially_refund
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
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_unsuccessful_refund
@@ -173,7 +185,7 @@ def test_successful_void
def test_failed_void
response = @gateway.void('invalid_authorization_token', @options)
assert_failure response
- assert_match %r{Requested Charge does not exist}, response.message
+ assert_match %r{Charge 'invalid_authorization_token' does not exist}, response.message
end
def test_successful_verify
@@ -185,7 +197,7 @@ def test_successful_verify
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 %r{The card was declined}, response.message
assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.primary_response.error_code
end
diff --git a/test/remote/gateways/remote_shift4_test.rb b/test/remote/gateways/remote_shift4_test.rb
index 5c13379c92f..013b89982ab 100644
--- a/test/remote/gateways/remote_shift4_test.rb
+++ b/test/remote/gateways/remote_shift4_test.rb
@@ -18,7 +18,8 @@ def setup
tax: '2',
customer_reference: 'D019D09309F2',
destination_postal_code: '94719',
- product_descriptors: %w(Hamburger Fries Soda Cookie)
+ product_descriptors: %w(Hamburger Fries Soda Cookie),
+ order_id: '123456'
}
@customer_address = {
address1: '65 Easy St',
@@ -78,6 +79,12 @@ def test_successful_purchase_with_extra_options
assert_success response
end
+ def test_successful_purchase_passes_vendor_reference
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options))
+ assert_success response
+ assert_equal response_result(response)['transaction']['vendorReference'], @extra_options[:order_id]
+ end
+
def test_successful_purchase_with_stored_credential_framework
stored_credential_options = {
initial_transaction: true,
@@ -173,13 +180,13 @@ def test_transcript_scrubbing
end
def test_failed_purchase
- response = @gateway.purchase(@amount, @declined_card, @options)
+ response = @gateway.purchase(1500000000, @credit_card, @options)
assert_failure response
- assert_include response.message, 'Card for Merchant Id 0008628968 not found'
+ assert_include response.message, 'Transaction declined'
end
def test_failure_on_referral_transactions
- response = @gateway.purchase(67800, @credit_card, @options)
+ response = @gateway.purchase(99999899, @credit_card, @options)
assert_failure response
assert_include 'Transaction declined', response.message
end
@@ -190,10 +197,18 @@ def test_failed_authorize
assert_include response.message, 'Card for Merchant Id 0008628968 not found'
end
+ def test_failed_authorize_with_failure_amount
+ # this amount triggers failure according to Shift4 docs
+ response = @gateway.authorize(1500000000, @credit_card, @options)
+ assert_failure response
+ assert_equal response.message, 'Transaction declined'
+ end
+
def test_failed_authorize_with_error_message
- response = @gateway.authorize(@amount, @unsupported_card, @options)
+ # this amount triggers failure according to Shift4 docs
+ response = @gateway.authorize(1500000000, @credit_card, @options)
assert_failure response
- assert_equal response.message, 'Format \'UTF8: An unexpected continuatio\' invalid or incompatible with argument'
+ assert_equal response.message, 'Transaction declined'
end
def test_failed_capture
@@ -203,9 +218,21 @@ def test_failed_capture
end
def test_failed_refund
- response = @gateway.refund(@amount, 'YC', @options)
+ response = @gateway.refund(1919, @credit_card, @options)
+ assert_failure response
+ assert_include response.message, 'Transaction declined'
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.message, 'Transaction successful'
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(1919, @credit_card, @options)
assert_failure response
- assert_include response.message, 'record not posted'
+ assert_include response.message, 'Transaction declined'
end
def test_successful_refund
@@ -236,6 +263,13 @@ def test_failed_void
assert_include response.message, 'Invoice Not Found'
end
+ def test_failed_access_token
+ gateway = Shift4Gateway.new({ client_guid: 'YOUR_CLIENT_ID', auth_token: 'YOUR_AUTH_TOKEN' })
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway.setup_access_token
+ end
+ end
+
private
def response_result(response)
diff --git a/test/remote/gateways/remote_shift4_v2_test.rb b/test/remote/gateways/remote_shift4_v2_test.rb
new file mode 100644
index 00000000000..f30bf15fb5a
--- /dev/null
+++ b/test/remote/gateways/remote_shift4_v2_test.rb
@@ -0,0 +1,217 @@
+require 'test_helper'
+require_relative 'remote_securion_pay_test'
+
+class RemoteShift4V2Test < RemoteSecurionPayTest
+ def setup
+ super
+ @gateway = Shift4V2Gateway.new(fixtures(:shift4_v2))
+
+ @options[:ip] = '127.0.0.1'
+ @bank_account = check(
+ routing_number: '021000021',
+ account_number: '4242424242424242',
+ account_type: 'savings'
+ )
+ end
+
+ def test_successful_purchase_third_party_token
+ auth = @gateway.store(@credit_card, @options)
+ token = auth.params['defaultCardId']
+ customer_id = auth.params['id']
+ response = @gateway.purchase(@amount, token, @options.merge!(customer_id: customer_id))
+ 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_third_party_token
+ auth = @gateway.store(@credit_card, @options)
+ customer_id = auth.params['id']
+ response = @gateway.purchase(@amount, @invalid_token, @options.merge!(customer_id: customer_id))
+ assert_failure response
+ assert_equal "Token 'tok_invalid' does not exist", response.message
+ end
+
+ def test_successful_stored_credentials_first_recurring
+ stored_credentials = {
+ initiator: 'cardholder',
+ reason_type: 'recurring'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'first_recurring', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_subsequent_recurring
+ stored_credentials = {
+ initiator: 'merchant',
+ reason_type: 'recurring'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'subsequent_recurring', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_customer_initiated
+ stored_credentials = {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'customer_initiated', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_stored_credentials_merchant_initiated
+ stored_credentials = {
+ initiator: 'merchant',
+ reason_type: 'installment'
+ }
+ @options.merge!(stored_credential: stored_credentials)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'merchant_initiated', response.params['type']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match CHARGE_ID_REGEX, response.authorization
+ assert_equal response.authorization, response.params['error']['chargeId']
+ assert_equal response.message, 'The card was declined.'
+ end
+
+ def test_successful_store_and_unstore
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert card_id = store.params['defaultCardId']
+ assert customer_id = store.params['cards'][0]['customerId']
+ unstore = @gateway.unstore(card_id, customer_id: customer_id)
+ assert_success unstore
+ assert_equal unstore.params['id'], card_id
+ end
+
+ def test_failed_unstore
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert customer_id = store.params['cards'][0]['customerId']
+ unstore = @gateway.unstore(nil, customer_id: customer_id)
+ assert_failure unstore
+ assert_equal unstore.params['error']['type'], 'invalid_request'
+ end
+
+ def test_successful_purchase_with_a_savings_bank_account
+ @options[:billing_address] = address(country: 'US')
+ response = @gateway.purchase(@amount, @bank_account, @options)
+
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_purchase_with_a_checking_bank_account
+ @options[:billing_address] = address(country: 'US')
+ @bank_account.account_type = 'checking'
+
+ response = @gateway.purchase(@amount, @bank_account, @options)
+
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_bank_account_store
+ @options[:billing_address] = address(country: 'US')
+ @bank_account.account_type = 'checking'
+
+ response = @gateway.store(@bank_account, @options)
+
+ assert_success response
+ assert_match(/^pm_/, response.authorization)
+ end
+
+ def test_successful_credit_card_store_with_existent_customer_id
+ @options[:customer_id] = 'cust_gHrIXDZqIq9Jp2t78A1Wp8CT'
+ response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert_match(/^card_/, response.authorization)
+ assert_match(/^card_/, response.params['id'])
+ end
+
+ def test_successful_credit_card_store_without_customer_id
+ response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'foo@example.com', response.params['email']
+ assert_match(/^card_/, response.authorization)
+ assert_match(/^cust_/, response.params['id'])
+ end
+
+ def test_successful_purchase_with_an_stored_credit_card
+ @options[:customer_id] = 'cust_gHrIXDZqIq9Jp2t78A1Wp8CT'
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_purchase_with_an_stored_bank_account
+ @options[:billing_address] = address(country: 'US')
+ @bank_account.account_type = 'checking'
+
+ response = @gateway.store(@bank_account, @options)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_store_raises_error_on_invalid_payment_method
+ assert_raises(ArgumentError) do
+ @gateway.store('abc123', @options)
+ end
+ end
+
+ def test_successful_purchase_with_a_corporate_savings_bank_account
+ @options[:billing_address] = address(country: 'US')
+ @bank_account.account_type = 'checking'
+ @bank_account.account_holder_type = 'business'
+
+ response = @gateway.purchase(@amount, @bank_account, @options)
+
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_full_refund_with_a_savings_bank_account
+ @options[:billing_address] = address(country: 'US')
+ purchase = @gateway.purchase(@amount, @bank_account, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+
+ assert_equal 2000, refund.params['refunds'].first['amount']
+ assert_equal 1, refund.params['refunds'].size
+ assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum
+
+ assert refund.authorization
+ end
+end
diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb
index b1e2eb24daf..90f66e2f844 100644
--- a/test/remote/gateways/remote_simetrik_test.rb
+++ b/test/remote/gateways/remote_simetrik_test.rb
@@ -78,6 +78,22 @@ def setup
}
end
+ def test_failed_access_token
+ assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.send :fetch_access_token
+ end
+ end
+
+ def test_failed_authorize_with_failed_access_token
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
+ gateway.authorize(@amount, @credit_card, @authorize_options_success)
+ end
+
+ assert_equal error.message, 'Failed with 401 Unauthorized'
+ end
+
def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
diff --git a/test/remote/gateways/remote_skipjack_test.rb b/test/remote/gateways/remote_skipjack_test.rb
index 096aaee9602..1c53076c8ff 100644
--- a/test/remote/gateways/remote_skipjack_test.rb
+++ b/test/remote/gateways/remote_skipjack_test.rb
@@ -6,8 +6,7 @@ def setup
@gateway = SkipJackGateway.new(fixtures(:skip_jack))
- @credit_card = credit_card('4445999922225',
- verification_value: '999')
+ @credit_card = credit_card('4445999922225', verification_value: '999')
@amount = 100
diff --git a/test/remote/gateways/remote_stripe_android_pay_test.rb b/test/remote/gateways/remote_stripe_android_pay_test.rb
index 6daee95f62b..7adda284d40 100644
--- a/test/remote/gateways/remote_stripe_android_pay_test.rb
+++ b/test/remote/gateways/remote_stripe_android_pay_test.rb
@@ -15,11 +15,13 @@ def setup
end
def test_successful_purchase_with_android_pay_raw_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -30,11 +32,13 @@ def test_successful_purchase_with_android_pay_raw_cryptogram
end
def test_successful_auth_with_android_pay_raw_cryptogram
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
diff --git a/test/remote/gateways/remote_stripe_apple_pay_test.rb b/test/remote/gateways/remote_stripe_apple_pay_test.rb
index f6d844feaba..b1a2f8c92aa 100644
--- a/test/remote/gateways/remote_stripe_apple_pay_test.rb
+++ b/test/remote/gateways/remote_stripe_apple_pay_test.rb
@@ -101,11 +101,13 @@ def test_purchase_with_unsuccessful_apple_pay_token_exchange
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -116,10 +118,12 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
end
def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -130,11 +134,13 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
end
def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
@@ -145,10 +151,12 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
end
def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'charge', response.params['object']
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
index 932ed7bc85b..fe2769e5115 100644
--- a/test/remote/gateways/remote_stripe_payment_intents_test.rb
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -3,42 +3,68 @@
class RemoteStripeIntentsTest < Test::Unit::TestCase
def setup
@gateway = StripePaymentIntentsGateway.new(fixtures(:stripe))
- @customer = fixtures(:stripe_verified_bank_account)[:customer_id]
+ @customer = @gateway.create_test_customer
@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'
+ @cvc_check_fails_credit_card = 'pm_card_cvcCheckFail'
+ @avs_fail_card = 'pm_card_avsFail'
@three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession'
- @three_ds_off_session_credit_card = credit_card('4000002500003155',
+ @three_ds_off_session_credit_card = credit_card(
+ '4000002500003155',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_1_credit_card = credit_card('4000000000003063',
+ year: 2028
+ )
+
+ @three_ds_1_credit_card = credit_card(
+ '4000000000003063',
+ verification_value: '737',
+ month: 10,
+ year: 2028
+ )
+
+ @three_ds_credit_card = credit_card(
+ '4000000000003220',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_credit_card = credit_card('4000000000003220',
+ year: 2028
+ )
+
+ @three_ds_not_required_card = credit_card(
+ '4000000000003055',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_not_required_card = credit_card('4000000000003055',
+ year: 2028
+ )
+
+ @three_ds_external_data_card = credit_card(
+ '4000002760003184',
verification_value: '737',
month: 10,
- year: 2028)
- @three_ds_external_data_card = credit_card('4000002760003184',
+ year: 2031
+ )
+
+ @visa_card = credit_card(
+ '4242424242424242',
verification_value: '737',
month: 10,
- year: 2031)
- @visa_card = credit_card('4242424242424242',
+ year: 2028
+ )
+
+ @visa_card_brand_choice = credit_card(
+ '4000002500001001',
verification_value: '737',
month: 10,
- year: 2028)
+ year: 2028
+ )
@google_pay = network_tokenization_credit_card(
'4242424242424242',
- payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
+ payment_cryptogram: 'AgAAAAAABk4DWZ4C28yUQAAAAAA=',
source: :google_pay,
brand: 'visa',
eci: '05',
@@ -50,7 +76,7 @@ def setup
@apple_pay = network_tokenization_credit_card(
'4242424242424242',
- payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
+ payment_cryptogram: 'AMwBRjPWDnAgAA7Rls7mAoABFA==',
source: :apple_pay,
brand: 'visa',
eci: '05',
@@ -60,6 +86,17 @@ def setup
last_name: 'Longsen'
)
+ @network_token_credit_card = network_tokenization_credit_card(
+ '4000056655665556',
+ payment_cryptogram: 'AAEBAwQjSQAAXXXXXXXJYe0BbQA=',
+ source: :network_token,
+ brand: 'visa',
+ month: '09',
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
+ )
+
@destination_account = fixtures(:stripe_destination)[:stripe_user_id]
end
@@ -83,9 +120,24 @@ def test_successful_purchase
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']
+ assert purchase.params.dig('charges', 'data')[0]['balance_transaction']
+ end
+ def test_successful_purchase_with_card_brand
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ card_brand: 'cartes_bancaires'
+ }
+ assert purchase = @gateway.purchase(@amount, @visa_card_brand_choice, options)
assert_equal 'succeeded', purchase.params['status']
+
assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['balance_transaction']
+ assert_equal purchase.params['payment_method_options']['card']['network'], 'cartes_bancaires'
end
def test_successful_purchase_with_shipping_address
@@ -100,12 +152,15 @@ def test_successful_purchase_with_shipping_address
address1: 'block C',
address2: 'street 48',
zip: '22400',
- state: 'California'
+ state: 'California',
+ email: 'test@email.com'
}
}
+
assert response = @gateway.purchase(@amount, @visa_payment_method, options)
assert_success response
assert_equal 'succeeded', response.params['status']
+ assert_nil response.params['shipping']['email']
end
def test_successful_purchase_with_level3_data
@@ -143,72 +198,111 @@ def test_successful_purchase_with_level3_data
def test_unsuccessful_purchase_google_pay_with_invalid_card_number
options = {
- currency: 'GBP'
+ currency: 'GBP',
+ new_ap_gp_route: true
}
@google_pay.number = '378282246310000'
purchase = @gateway.purchase(@amount, @google_pay, options)
- assert_equal 'The tokenization process fails. Your card number is incorrect.', purchase.message
+ assert_equal 'Your card number is incorrect.', purchase.message
assert_false purchase.success?
end
def test_unsuccessful_purchase_google_pay_without_cryptogram
options = {
- currency: 'GBP'
+ currency: 'GBP',
+ new_ap_gp_route: true
}
@google_pay.payment_cryptogram = ''
purchase = @gateway.purchase(@amount, @google_pay, options)
- assert_equal "The tokenization process fails. Cards using 'tokenization_method=android_pay' require the 'cryptogram' field to be set.", purchase.message
+ assert_equal 'Missing required param: payment_method_options[card][network_token][cryptogram].', purchase.message
assert_false purchase.success?
end
def test_unsuccessful_purchase_google_pay_without_month
options = {
- currency: 'GBP'
+ currency: 'GBP',
+ new_ap_gp_route: true
}
@google_pay.month = ''
purchase = @gateway.purchase(@amount, @google_pay, options)
- assert_equal 'The tokenization process fails. Missing required param: card[exp_month].', purchase.message
+ assert_equal 'Missing required param: payment_method_data[card][exp_month].', purchase.message
assert_false purchase.success?
end
def test_successful_authorize_with_google_pay
options = {
- currency: 'GBP'
+ currency: 'GBP',
+ new_ap_gp_route: true
}
+ @google_pay.eci = '5'
+ assert_match('5', @google_pay.eci)
auth = @gateway.authorize(@amount, @google_pay, options)
-
- assert_match('android_pay', auth.responses.first.params.dig('token', 'card', 'tokenization_method'))
assert auth.success?
- assert_match('google_pay', auth.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ assert_match('google_pay', auth.params.dig('charges', 'data')[0].dig('payment_method_details', 'card', 'wallet', 'type'))
end
- def test_successful_purchase_with_apple_pay
+ def test_successful_purchase_with_google_pay
options = {
- currency: 'GBP'
+ currency: 'GBP',
+ new_ap_gp_route: true
}
- purchase = @gateway.purchase(@amount, @apple_pay, options)
- assert_match('apple_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ purchase = @gateway.purchase(@amount, @google_pay, options)
assert purchase.success?
- assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
end
- def test_succesful_purchase_with_connect_for_apple_pay
+ def test_successful_purchase_with_tokenized_visa
options = {
- stripe_account: @destination_account
+ currency: 'USD',
+ last_4: '4242'
}
- assert response = @gateway.purchase(@amount, @apple_pay, options)
- assert_success response
+
+ purchase = @gateway.purchase(@amount, @network_token_credit_card, options)
+ assert_equal(nil, purchase.responses.first.params.dig('token', 'card', 'tokenization_method'))
+ assert purchase.success?
+ assert_not_nil(purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_token'])
end
- def test_succesful_application_with_connect_for_google_pay
+ def test_successful_purchase_with_google_pay_when_sending_the_billing_address
options = {
- stripe_account: @destination_account
+ currency: 'GBP',
+ billing_address: address,
+ new_ap_gp_route: true
}
- assert response = @gateway.purchase(@amount, @google_pay, options)
- assert_success response
+
+ purchase = @gateway.purchase(@amount, @google_pay, options)
+ assert purchase.success?
+ billing_address_line1 = purchase.params.dig('charges', 'data')[0]['billing_details']['address']['line1']
+ assert_equal '456 My Street', billing_address_line1
+ assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ end
+
+ def test_successful_purchase_with_apple_pay
+ options = {
+ currency: 'GBP',
+ new_ap_gp_route: true
+ }
+
+ purchase = @gateway.purchase(@amount, @apple_pay, options)
+ assert purchase.success?
+ assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
+ end
+
+ def test_successful_purchase_with_apple_pay_when_sending_the_billing_address
+ options = {
+ currency: 'GBP',
+ billing_address: address,
+ new_ap_gp_route: true
+ }
+
+ purchase = @gateway.purchase(@amount, @apple_pay, options)
+ assert purchase.success?
+ billing_address_line1 = purchase.params.dig('charges', 'data')[0]['billing_details']['address']['line1']
+ assert_equal '456 My Street', billing_address_line1
+ assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type'])
end
def test_purchases_with_same_idempotency_key
@@ -549,6 +643,33 @@ def test_create_setup_intent_with_setup_future_usage
end
end
+ def test_create_setup_intent_with_setup_future_usage_and_card_brand
+ response = @gateway.create_setup_intent(@visa_card_brand_choice, {
+ address: {
+ email: 'test@example.com',
+ name: 'John Doe',
+ line1: '1 Test Ln',
+ city: 'Durham',
+ tracking_number: '123456789'
+ },
+ currency: 'USD',
+ card_brand: 'cartes_bancaires',
+ confirm: true,
+ execute_threed: true,
+ return_url: 'https://example.com'
+ })
+
+ assert_equal 'succeeded', response.params['status']
+ assert_equal response.params['payment_method_options']['card']['network'], 'cartes_bancaires'
+ # since we cannot "click" the stripe hooks URL to confirm the authorization
+ # we will at least confirm we can retrieve the created setup_intent and it contains the structure we expect
+ setup_intent_id = response.params['id']
+ assert si_response = @gateway.retrieve_setup_intent(setup_intent_id)
+
+ assert_equal 'succeeded', si_response.params['status']
+ assert_not_empty si_response.params.dig('latest_attempt', 'payment_method_details', 'card')
+ end
+
def test_create_setup_intent_with_connected_account
[@three_ds_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
assert authorize_response = @gateway.create_setup_intent(card_to_use, {
@@ -700,7 +821,7 @@ def test_purchase_works_with_stored_credentials_without_optional_ds_transaction_
confirm: true,
off_session: true,
stored_credential: {
- network_transaction_id: '1098510912210968', # TEST env seems happy with any value :/
+ network_transaction_id: '1098510912210968' # TEST env seems happy with any value :/
}
})
@@ -730,6 +851,77 @@ def test_succeeds_with_ntid_in_stored_credentials_and_separately
end
end
+ def test_succeeds_with_initial_cit
+ assert purchase = @gateway.purchase(@amount, @visa_card, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: true
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
+ def test_succeeds_with_initial_cit_3ds_required
+ assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required_setup_for_off_session, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: true
+ }
+ })
+ assert_success purchase
+ assert_equal 'requires_action', purchase.params['status']
+ end
+
+ def test_succeeds_with_mit
+ assert purchase = @gateway.purchase(@amount, @visa_card, {
+ currency: 'USD',
+ execute_threed: true,
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '1098510912210968'
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
+ def test_succeeds_with_mit_3ds_required
+ assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required_setup_for_off_session, {
+ currency: 'USD',
+ confirm: true,
+ stored_credential_transaction_type: true,
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ initial_transaction: false,
+ network_transaction_id: '1098510912210968'
+ }
+ })
+ assert_success purchase
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ assert purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id']
+ end
+
def test_successful_off_session_purchase_when_claim_without_transaction_id_present
[@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use|
assert response = @gateway.purchase(@amount, card_to_use, {
@@ -785,6 +977,7 @@ def test_purchase_fails_on_unexpected_3ds_initiation
assert response = @gateway.purchase(100, @three_ds_credit_card, options)
assert_failure response
assert_match 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.', response.message
+ assert_equal response.authorization, response.params['id']
end
def test_create_payment_intent_with_shipping_address
@@ -901,6 +1094,24 @@ def test_create_a_payment_intent_and_manually_capture
assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
end
+ def test_create_a_payment_intent_and_manually_capture_with_network_token
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ confirm: true,
+ last_4: '4242'
+ }
+ assert create_response = @gateway.create_intent(@amount, @network_token_credit_card, 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_failed_create_a_payment_intent_with_set_error_on_requires_action
options = {
currency: 'GBP',
@@ -1167,6 +1378,21 @@ def test_successful_store_with_idempotency_key
assert_equal store1.params['id'], store2.params['id']
end
+ def test_successful_customer_creating
+ options = {
+ currency: 'GBP',
+ billing_address: address,
+ shipping_address: address.merge!(email: 'test@email.com')
+ }
+ assert customer = @gateway.customer(@visa_card, options)
+
+ assert_equal customer.params['name'], 'Jim Smith'
+ assert_equal customer.params['phone'], '(555)555-5555'
+ assert_nil customer.params['shipping']['email']
+ assert_not_empty customer.params['shipping']
+ assert_not_empty customer.params['address']
+ end
+
def test_successful_store_with_false_validate_option
options = {
currency: 'GBP',
@@ -1189,10 +1415,13 @@ def test_successful_store_with_true_validate_option
def test_successful_verify
options = {
- customer: @customer
+ customer: @customer,
+ billing_address: address
}
- assert verify = @gateway.verify(@visa_payment_method, options)
+ assert verify = @gateway.verify(@visa_card, options)
+ assert_equal 'US', verify.params.dig('latest_attempt', 'payment_method_details', 'card', 'country')
assert_equal 'succeeded', verify.params['status']
+ assert_equal 'M', verify.cvv_result['code']
end
def test_failed_verify
@@ -1202,6 +1431,16 @@ def test_failed_verify
assert verify = @gateway.verify(@declined_payment_method, options)
assert_equal 'Your card was declined.', verify.message
+
+ assert_not_nil verify.authorization
+ assert_equal verify.params.dig('error', 'setup_intent', 'id'), verify.authorization
+ end
+
+ def test_verify_stores_response_for_payment_method_creation
+ assert verify = @gateway.verify(@visa_card)
+
+ assert_equal 2, verify.responses.count
+ assert_match 'pm_', verify.responses.first.params['id']
end
def test_moto_enabled_card_requires_action_when_not_marked
@@ -1295,4 +1534,32 @@ def test_transcript_scrubbing
assert_scrubbed(@three_ds_credit_card.verification_value, transcript)
assert_scrubbed(@gateway.options[:login], transcript)
end
+
+ def test_succeeded_cvc_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @visa_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'M', purchase.cvv_result.dig('code')
+ assert_equal 'CVV matches', purchase.cvv_result.dig('message')
+ end
+
+ def test_failed_cvc_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @cvc_check_fails_credit_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'N', purchase.cvv_result.dig('code')
+ assert_equal 'CVV does not match', purchase.cvv_result.dig('message')
+ end
+
+ def test_failed_avs_check
+ options = {}
+ assert purchase = @gateway.purchase(@amount, @avs_fail_card, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert_equal 'N', purchase.avs_result['code']
+ assert_equal 'N', purchase.avs_result['postal_match']
+ assert_equal 'N', purchase.avs_result['street_match']
+ end
end
diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb
new file mode 100644
index 00000000000..b49448208f5
--- /dev/null
+++ b/test/remote/gateways/remote_sum_up_test.rb
@@ -0,0 +1,126 @@
+require 'test_helper'
+
+class RemoteSumUpTest < Test::Unit::TestCase
+ def setup
+ @gateway = SumUpGateway.new(fixtures(:sum_up))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('55555555555555555')
+ @options = {
+ payment_type: 'card',
+ billing_address: address,
+ description: 'Store Purchase',
+ order_id: SecureRandom.uuid
+ }
+ end
+
+ def test_handle_pay_to_email_credential_error
+ gateway = SumUpGateway.new(fixtures(:sum_up).merge(pay_to_email: 'example@example.com'))
+ response = gateway.purchase(@amount, @credit_card, @options)
+
+ assert_equal('Validation error', response.message)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'PAID', response.message
+ assert_equal @options[:order_id], response.params['checkout_reference']
+ refute_empty response.params['id']
+ refute_empty response.params['transactions']
+ refute_empty response.params['transactions'].first['id']
+ assert_equal 'SUCCESSFUL', response.params['transactions'].first['status']
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ email: 'joe@example.com',
+ tax_id: '12345',
+ redirect_url: 'https://checkout.example.com',
+ return_url: 'https://checkout.example.com',
+ billing_address: address,
+ order_id: SecureRandom.uuid,
+ currency: 'USD',
+ description: 'Sample description',
+ payment_type: 'card'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'PAID', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Validation error', response.message
+ assert_equal 'The value located under the \'$.card.number\' path is not a valid card number', response.params['detail']
+ end
+
+ def test_failed_purchase_invalid_customer_id
+ options = @options.merge!(customer_id: 'customer@example.com', payment_type: 'card')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Validation error', response.message
+ assert_equal 'customer_id', response.params['param']
+ end
+
+ def test_failed_purchase_invalid_currency
+ options = @options.merge!(currency: 'EUR')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Given currency differs from merchant\'s country currency', response.message
+ end
+
+ # In Sum Up the account can only return checkout/purchase in pending or success status,
+ # to obtain a successful refund we will need an account that returns the checkout/purchase in successful status
+ #
+ # For the following refund tests configure in the fixtures => :sum_up_successful_purchase
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ transaction_id = purchase.params['transaction_id']
+ assert_not_nil transaction_id
+
+ response = @gateway.refund(@amount, transaction_id, {})
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_partial_refund
+ purchase = @gateway.purchase(@amount * 10, @credit_card, @options)
+ transaction_id = purchase.params['transaction_id']
+ assert_not_nil transaction_id
+
+ response = @gateway.refund(@amount, transaction_id, {})
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ # In Sum Up to trigger the 3DS flow (next_step object) you need to an European account
+ #
+ # For this example configure in the fixtures => :sum_up_3ds
+ def test_trigger_3ds_flow
+ gateway = SumUpGateway.new(fixtures(:sum_up_3ds))
+ options = @options.merge(
+ currency: 'EUR',
+ redirect_url: 'https://mysite.com/completed_purchase'
+ )
+ purchase = gateway.purchase(@amount, @credit_card, options)
+ assert_success purchase
+ assert_equal 'Succeeded', purchase.message
+ assert_not_nil purchase.params['next_step']
+ 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[:pay_to_email], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_tns_test.rb b/test/remote/gateways/remote_tns_test.rb
index 6155f16f5fa..f515c9854ff 100644
--- a/test/remote/gateways/remote_tns_test.rb
+++ b/test/remote/gateways/remote_tns_test.rb
@@ -6,8 +6,8 @@ def setup
@gateway = TnsGateway.new(fixtures(:tns))
@amount = 100
- @credit_card = credit_card('5123456789012346', month: 05, year: 2021)
- @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2021)
+ @credit_card = credit_card('5123456789012346', month: 05, year: 2024)
+ @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2024)
@declined_card = credit_card('5123456789012346', month: 01, year: 2028)
@options = {
@@ -67,7 +67,7 @@ def test_successful_purchase_with_region
def test_failed_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'FAILURE - DECLINED', response.message
+ assert_equal 'FAILURE - UNSPECIFIED_FAILURE', response.message
end
def test_successful_authorize_and_capture
@@ -84,7 +84,7 @@ def test_successful_authorize_and_capture
def test_failed_authorize
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'FAILURE - DECLINED', response.message
+ assert_equal 'FAILURE - UNSPECIFIED_FAILURE', response.message
end
def test_successful_refund
@@ -134,11 +134,4 @@ def test_transcript_scrubbing
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_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb
index 78587a190c6..19e1564c649 100644
--- a/test/remote/gateways/remote_trust_commerce_test.rb
+++ b/test/remote/gateways/remote_trust_commerce_test.rb
@@ -182,6 +182,13 @@ def test_failed_store
assert_bad_data_response(response)
end
+ def test_successful_unstore
+ assert store = @gateway.store(@credit_card)
+ assert_equal 'approved', store.params['status']
+ assert response = @gateway.unstore(store.params['billingid'])
+ assert_success response
+ end
+
def test_unstore_failure
assert response = @gateway.unstore('does-not-exist')
@@ -189,6 +196,28 @@ def test_unstore_failure
assert_failure response
end
+ def test_successful_purchase_after_store
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+ assert response = @gateway.purchase(@amount, store.params['billingid'], @options)
+ assert_equal 'Y', response.avs_result['code']
+ assert_match %r{The transaction was successful}, response.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card)
+ assert_equal 'approved', response.params['status']
+ assert_match %r{The transaction was successful}, response.message
+ assert_success response
+ end
+
+ def test_failed_verify_with_invalid_card
+ assert response = @gateway.verify(@declined_credit_card)
+ assert_equal 'baddata', response.params['status']
+ assert_match %r{A field was improperly formatted}, response.message
+ assert_failure response
+ end
+
def test_successful_recurring
assert response = @gateway.recurring(@amount, @credit_card, periodicity: :weekly)
diff --git a/test/remote/gateways/remote_vantiv_express_test.rb b/test/remote/gateways/remote_vantiv_express_test.rb
new file mode 100644
index 00000000000..1659e796c66
--- /dev/null
+++ b/test/remote/gateways/remote_vantiv_express_test.rb
@@ -0,0 +1,375 @@
+require 'test_helper'
+
+class RemoteVantivExpressTest < Test::Unit::TestCase
+ def setup
+ @gateway = VantivExpressGateway.new(fixtures(:element))
+
+ @amount = rand(1000..2000)
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('6060704495764400')
+ @check = check
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+
+ @google_pay_network_token = network_tokenization_credit_card(
+ '6011000400000000',
+ month: '01',
+ year: Time.new.year + 2,
+ first_name: 'Jane',
+ last_name: 'Doe',
+ verification_value: '888',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ eci: '05',
+ transaction_id: '123456789',
+ source: :google_pay
+ )
+
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
+ month: '10',
+ year: Time.new.year + 2,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ payment_cryptogram: 'CeABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ eci: '05',
+ transaction_id: 'abc123',
+ source: :apple_pay
+ )
+ end
+
+ def test_successful_purchase_and_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_failed_purchase
+ @amount = 20
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CARD INFO', 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_purchase_with_billing_email
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(email: 'test@example.com'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_card_present_code_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(card_present_code: 'Present'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_payment_type_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_submission_type_string
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(submission_type: 'NotUsed'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_duplicate_check_disable_flag
+ amount = @amount
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: true))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'true'))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_duplicate_override_flag
+ amount = @amount
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: true))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: false))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: 'true'))
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx'))
+ assert_failure response
+ assert_equal 'Duplicate', response.message
+ end
+
+ def test_successful_purchase_with_terminal_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(terminal_id: '02'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_lodging_and_all_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'AdvanceDeposit',
+ charge_type: 'Restaurant'
+ },
+ card_holder_present_code: '2',
+ card_input_code: '4',
+ card_present_code: 'NotPresent',
+ cvv_presence_code: '2',
+ market_code: 'HotelLodging',
+ terminal_capability_code: 'ChipReader',
+ terminal_environment_code: 'LocalUnattended',
+ terminal_type: 'Mobile',
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_enum_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: SecureRandom.hex(12),
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 1,
+ special_program_code: 2,
+ charge_type: 2
+ },
+ card_holder_present_code: '2',
+ card_input_code: '4',
+ card_present_code: 0,
+ cvv_presence_code: 2,
+ market_code: 5,
+ terminal_capability_code: 5,
+ terminal_environment_code: 6,
+ terminal_type: 2,
+ terminal_id: '0001',
+ ticket_number: 182726718192
+ }
+ response = @gateway.purchase(@amount, @credit_card, lodging_options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_google_pay
+ response = @gateway.purchase(@amount, @google_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay_no_eci
+ @apple_pay_network_token.eci = nil
+
+ response = @gateway.purchase(1202, @apple_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = @gateway.purchase(@amount, @apple_pay_network_token, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize_capture_and_void_with_apple_pay
+ auth = @gateway.authorize(3100, @apple_pay_network_token, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(3200, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_verify_with_apple_pay
+ response = @gateway.verify(@apple_pay_network_token, @options)
+ assert_success response
+ assert_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
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ @amount = 20
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CARD INFO', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'TransactionID required', 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 'TransactionID required', response.message
+ end
+
+ def test_successful_credit
+ credit_options = @options.merge({ ticket_number: '1', market_code: 'FoodRestaurant', merchant_supplied_transaction_id: '123' })
+ credit = @gateway.credit(@amount, @credit_card, credit_options)
+
+ assert_success credit
+ end
+
+ def test_failed_credit
+ credit = @gateway.credit(nil, @credit_card, @options)
+
+ assert_failure credit
+ assert_equal 'TransactionAmount required', credit.message
+ end
+
+ def test_successful_partial_capture_and_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(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_equal 'Success', 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: '3', account_token: '3', application_id: '3', acceptor_id: '3', application_name: '3', application_version: '3')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid AccountToken}, 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_vpos_test.rb b/test/remote/gateways/remote_vpos_test.rb
index 4a75f8d1754..523053f7fea 100644
--- a/test/remote/gateways/remote_vpos_test.rb
+++ b/test/remote/gateways/remote_vpos_test.rb
@@ -109,7 +109,7 @@ def test_transcript_scrubbing
transcript = @gateway.scrub(transcript)
# does not contain anything other than '[FILTERED]'
- assert_no_match(/token\\":\\"[^\[FILTERED\]]/, transcript)
- assert_no_match(/card_encrypted_data\\":\\"[^\[FILTERED\]]/, transcript)
+ assert_no_match(/token\\":\\"[^\[FILTERD\]]/, transcript)
+ assert_no_match(/card_encrypted_data\\":\\"[^\[FILTERD\]]/, transcript)
end
end
diff --git a/test/remote/gateways/remote_vpos_without_key_test.rb b/test/remote/gateways/remote_vpos_without_key_test.rb
index a17e98838f2..ca77727d60d 100644
--- a/test/remote/gateways/remote_vpos_without_key_test.rb
+++ b/test/remote/gateways/remote_vpos_without_key_test.rb
@@ -111,8 +111,8 @@ def test_transcript_scrubbing
transcript = @gateway.scrub(transcript)
# does not contain anything other than '[FILTERED]'
- assert_no_match(/token\\":\\"[^\[FILTERED\]]/, transcript)
- assert_no_match(/card_encrypted_data\\":\\"[^\[FILTERED\]]/, transcript)
+ assert_no_match(/token\\":\\"[^\[FILTERD\]]/, transcript)
+ assert_no_match(/card_encrypted_data\\":\\"[^\[FILTERD\]]/, transcript)
end
def test_regenerate_encryption_key
diff --git a/test/remote/gateways/remote_wompi_test.rb b/test/remote/gateways/remote_wompi_test.rb
index 65c312a1153..dc7a8576a22 100644
--- a/test/remote/gateways/remote_wompi_test.rb
+++ b/test/remote/gateways/remote_wompi_test.rb
@@ -34,6 +34,11 @@ def test_successful_purchase_without_cvv
assert_success response
end
+ def test_successful_purchase_with_tip_in_cents
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(tip_in_cents: 300))
+ assert_success response
+ end
+
def test_failed_purchase
response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index dfa2fe11b68..07540d478e7 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -6,18 +6,23 @@ def setup
@cftgateway = WorldpayGateway.new(fixtures(:world_pay_gateway_cft))
@amount = 100
+ @year = (Time.now.year + 2).to_s[-2..-1].to_i
@credit_card = credit_card('4111111111111111')
@amex_card = credit_card('3714 496353 98431')
- @elo_credit_card = credit_card('4514 1600 0000 0008',
+ @elo_credit_card = credit_card(
+ '4514 1600 0000 0008',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
- @credit_card_with_two_digits_year = credit_card('4111111111111111',
+ brand: 'elo'
+ )
+ @credit_card_with_two_digits_year = credit_card(
+ '4111111111111111',
month: 10,
- year: 22)
+ year: @year
+ )
@cabal_card = credit_card('6035220000000006')
@naranja_card = credit_card('5895620000000002')
@sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
@@ -26,14 +31,23 @@ def setup
@threeDS2_card = credit_card('4111111111111111', first_name: nil, last_name: '3DS_V2_FRICTIONLESS_IDENTIFIED')
@threeDS2_challenge_card = credit_card('4000000000001091', first_name: nil, last_name: 'challenge-me-plz')
@threeDS_card_external_MPI = credit_card('4444333322221111', first_name: 'AA', last_name: 'BD')
- @nt_credit_card = network_tokenization_credit_card('4895370015293175',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4895370015293175',
brand: 'visa',
eci: '07',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
- @nt_credit_card_without_eci = network_tokenization_credit_card('4895370015293175',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @visa_nt_credit_card_without_eci = network_tokenization_credit_card(
+ '4895370015293175',
+ source: :network_token,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+ @mastercard_nt_credit_card_without_eci = network_tokenization_credit_card(
+ '5555555555554444',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@options = {
order_id: generate_unique_id,
@@ -45,11 +59,8 @@ def setup
invoice_reference_number: 'INV12233565',
customer_reference: 'CUST00000101',
card_acceptor_tax_id: 'VAT1999292',
- sales_tax: {
- amount: '20',
- exponent: '2',
- currency: 'USD'
- }
+ tax_amount: '20',
+ ship_from_postal_code: '43245'
}
}
@@ -57,58 +68,32 @@ def setup
level_3_data: {
customer_reference: 'CUST00000102',
card_acceptor_tax_id: 'VAT1999285',
- sales_tax: {
- amount: '20',
- exponent: '2',
- currency: 'USD'
- },
- discount_amount: {
- amount: '1',
- exponent: '2',
- currency: 'USD'
- },
- shipping_amount: {
- amount: '50',
- exponent: '2',
- currency: 'USD'
- },
- duty_amount: {
- amount: '20',
- exponent: '2',
- currency: 'USD'
- },
- item: {
+ tax_amount: '20',
+ discount_amount: '1',
+ shipping_amount: '50',
+ duty_amount: '20',
+ line_items: [{
description: 'Laptop 14',
product_code: 'LP00125',
commodity_code: 'COM00125',
quantity: '2',
- unit_cost: {
- amount: '1500',
- exponent: '2',
- currency: 'USD'
- },
+ unit_cost: '1500',
unit_of_measure: 'each',
- item_total: {
- amount: '3000',
- exponent: '2',
- currency: 'USD'
- },
- item_total_with_tax: {
- amount: '3500',
- exponent: '2',
- currency: 'USD'
- },
- item_discount_amount: {
- amount: '200',
- exponent: '2',
- currency: 'USD'
- },
- tax_amount: {
- amount: '500',
- exponent: '2',
- currency: 'USD'
- }
- }
+ discount_amount: '200',
+ tax_amount: '500',
+ total_amount: '3300'
+ },
+ {
+ description: 'Laptop 15',
+ product_code: 'LP00125',
+ commodity_code: 'COM00125',
+ quantity: '2',
+ unit_cost: '1500',
+ unit_of_measure: 'each',
+ discount_amount: '200',
+ tax_amount: '500',
+ total_amount: '3300'
+ }]
}
}
@@ -130,7 +115,8 @@ def setup
sub_tax_id: '987-65-4321'
}
}
- @apple_pay_network_token = network_tokenization_credit_card('4895370015293175',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4895370015293175',
month: 10,
year: Time.new.year + 2,
first_name: 'John',
@@ -139,15 +125,28 @@ def setup
payment_cryptogram: 'abc1234567890',
eci: '07',
transaction_id: 'abc123',
- source: :apple_pay)
+ source: :apple_pay
+ )
+
+ @google_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ month: '01',
+ year: Time.new.year + 2,
+ source: :google_pay,
+ transaction_id: '123456789',
+ eci: '05'
+ )
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @google_pay_network_token_without_eci = network_tokenization_credit_card(
+ '4444333322221111',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
month: '01',
year: Time.new.year + 2,
source: :google_pay,
transaction_id: '123456789',
- eci: '05')
+ eci: '05'
+ )
end
def test_successful_purchase
@@ -162,8 +161,22 @@ def test_successful_purchase_with_network_token
assert_equal 'SUCCESS', response.message
end
- def test_successful_purchase_with_network_token_without_eci
- assert response = @gateway.purchase(@amount, @nt_credit_card_without_eci, @options)
+ def test_successful_purchase_with_network_token_and_stored_credentials
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
+
+ assert response = @gateway.purchase(@amount, @nt_credit_card, @options.merge({ stored_credential: stored_credential_params }))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_network_token_without_eci_visa
+ assert response = @gateway.purchase(@amount, @visa_nt_credit_card_without_eci, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_network_token_without_eci_mastercard
+ assert response = @gateway.purchase(@amount, @mastercard_nt_credit_card_without_eci, @options)
assert_success response
assert_equal 'SUCCESS', response.message
end
@@ -184,6 +197,46 @@ def test_successful_authorize_with_card_holder_name_google_pay
assert_equal 'SUCCESS', response.message
end
+ def test_successful_authorize_without_eci_google_pay
+ response = @gateway.authorize(@amount, @google_pay_network_token_without_eci, @options)
+ assert_success response
+ assert_equal @amount, response.params['amount_value'].to_i
+ assert_equal 'GBP', response.params['amount_currency_code']
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_authorize_with_default_eci_google_pay
+ response = @gateway.authorize(@amount, @google_pay_network_token_without_eci, @options.merge({ use_default_eci: true }))
+ assert_success response
+ assert_equal @amount, response.params['amount_value'].to_i
+ assert_equal 'GBP', response.params['amount_currency_code']
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_authorize_with_google_pay_pan_only
+ response = @gateway.authorize(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_purchase_with_google_pay_pan_only
+ assert auth = @gateway.purchase(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+ assert auth.authorization
+ end
+
+ def test_successful_authorize_with_void_google_pay_pan_only
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge!(wallet_type: :google_pay))
+ assert_success auth
+ assert_equal 'authorize', auth.params['action']
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(authorization_validated: true))
+ assert_success capture
+ assert void = @gateway.void(auth.authorization, @options.merge(authorization_validated: true))
+ assert_success void
+ end
+
def test_successful_authorize_without_card_holder_name_apple_pay
@apple_pay_network_token.first_name = ''
@apple_pay_network_token.last_name = ''
@@ -442,9 +495,8 @@ def test_successful_authorize_with_3ds
)
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
+ assert first_message.success?
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
@@ -470,19 +522,13 @@ def test_successful_authorize_with_3ds2_challenge
assert response = @gateway.authorize(@amount, @threeDS2_challenge_card, options)
assert response.test?
refute response.authorization.blank?
- refute response.params['issuer_url'].blank?
- refute response.params['pa_request'].blank?
+ assert response.success?
refute response.params['cookie'].blank?
refute response.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
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params }))
assert_success auth
@@ -494,12 +540,31 @@ def test_successful_auth_and_capture_with_normalized_stored_credential
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']
- }
+ @options[:stored_credential] = stored_credential(:used, :installment, :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_normalized_recurring_stored_credential
+ stored_credential_params = stored_credential(:initial, :recurring, :merchant)
+
+ 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] = stored_credential(:used, :recurring, :merchant, network_transaction_id: auth.params['transaction_identifier'])
assert next_auth = @gateway.authorize(@amount, @credit_card, @options)
assert next_auth.authorization
@@ -535,14 +600,34 @@ def test_successful_auth_and_capture_with_gateway_specific_stored_credentials
assert_success capture
end
+ def test_successful_auth_and_capture_with_gateway_specific_recurring_stored_credentials
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST', stored_credential_initiated_reason: 'RECURRING'))
+ 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: 'RECURRING',
+ 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
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
options = @options.merge(
{
execute_threed: true,
@@ -557,8 +642,7 @@ def test_successful_authorize_with_3ds_with_normalized_stored_credentials
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
refute first_message.authorization.blank?
- refute first_message.params['issuer_url'].blank?
- refute first_message.params['pa_request'].blank?
+ assert first_message.success?
refute first_message.params['cookie'].blank?
refute first_message.params['session_id'].blank?
end
@@ -579,8 +663,7 @@ def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials
assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
assert first_message.test?
refute first_message.authorization.blank?
- refute first_message.params['issuer_url'].blank?
- refute first_message.params['pa_request'].blank?
+ assert first_message.success?
refute first_message.params['cookie'].blank?
refute first_message.params['session_id'].blank?
end
@@ -593,7 +676,7 @@ def test_successful_purchase_with_level_two_fields
end
def test_successful_purchase_with_level_two_fields_and_sales_tax_zero
- @level_two_data[:level_2_data][:sales_tax][:amount] = 0
+ @level_two_data[:level_2_data][:tax_amount] = 0
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_two_data))
assert_success response
assert_equal true, response.params['ok']
@@ -608,12 +691,13 @@ def test_successful_purchase_with_level_three_fields
end
def test_unsuccessful_purchase_level_three_data_without_item_mastercard
- @level_three_data[:level_3_data][:item] = {}
+ @level_three_data[:level_3_data][:line_items] = [{
+ }]
@credit_card.brand = 'master'
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_three_data))
assert_failure response
assert_equal response.error_code, '2'
- assert_equal response.params['error'].gsub(/\"+/, ''), 'The content of element type item is incomplete, it must match (description,productCode?,commodityCode?,quantity?,unitCost?,unitOfMeasure?,itemTotal?,itemTotalWithTax?,itemDiscountAmount?,taxAmount?,categories?,pageURL?,imageURL?).'
+ assert_equal response.params['error'].gsub(/\"+/, ''), 'The content of element type item must match (description,productCode?,commodityCode?,quantity?,unitCost?,unitOfMeasure?,itemTotal?,itemTotalWithTax?,itemDiscountAmount?,itemTaxRate?,lineDiscountIndicator?,itemLocalTaxRate?,itemLocalTaxAmount?,taxAmount?,categories?,pageURL?,imageURL?).'
end
def test_successful_purchase_with_level_two_and_three_fields
@@ -837,32 +921,35 @@ def test_successful_mastercard_credit_on_cft_gateway
assert_equal 'SUCCESS', credit.message
end
- def test_successful_fast_fund_credit_on_cft_gateway
- options = @options.merge({ fast_fund_credit: true })
+ # These three fast_fund_credit tests are currently failing with the message: Disbursement transaction not supported
+ # It seems that the current sandbox setup does not support testing this.
- credit = @cftgateway.credit(@amount, @credit_card, options)
- assert_success credit
- assert_equal 'SUCCESS', credit.message
- end
+ # def test_successful_fast_fund_credit_on_cft_gateway
+ # options = @options.merge({ fast_fund_credit: true })
- def test_successful_fast_fund_credit_with_token_on_cft_gateway
- assert store = @gateway.store(@credit_card, @store_options)
- assert_success store
+ # credit = @cftgateway.credit(@amount, @credit_card, options)
+ # assert_success credit
+ # assert_equal 'SUCCESS', credit.message
+ # end
- options = @options.merge({ fast_fund_credit: true })
- assert credit = @cftgateway.credit(@amount, store.authorization, options)
- assert_success credit
- end
+ # def test_successful_fast_fund_credit_with_token_on_cft_gateway
+ # assert store = @gateway.store(@credit_card, @store_options)
+ # assert_success store
- def test_failed_fast_fund_credit_on_cft_gateway
- options = @options.merge({ fast_fund_credit: true })
- refused_card = credit_card('4917300800000000', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
+ # options = @options.merge({ fast_fund_credit: true })
+ # assert credit = @cftgateway.credit(@amount, store.authorization, options)
+ # assert_success credit
+ # end
- credit = @cftgateway.credit(@amount, refused_card, options)
- assert_failure credit
- assert_equal '01', credit.params['action_code']
- assert_equal "A transaction status of 'ok' or 'PUSH_APPROVED' is required.", credit.message
- end
+ # def test_failed_fast_fund_credit_on_cft_gateway
+ # options = @options.merge({ fast_fund_credit: true })
+ # refused_card = credit_card('4444333322221111', name: 'REFUSED') # 'magic' value for testing failures, provided by Worldpay
+
+ # credit = @cftgateway.credit(@amount, refused_card, options)
+ # assert_failure credit
+ # assert_equal '01', credit.params['action_code']
+ # assert_equal "A transaction status of 'ok' or 'PUSH_APPROVED' is required.", credit.message
+ # end
def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@@ -1144,12 +1231,7 @@ def test_failed_refund_synchronous_response
def test_successful_purchase_with_options_synchronous_response
options = @options
- stored_credential_params = {
- initial_transaction: true,
- reason_type: 'unscheduled',
- initiator: 'merchant',
- network_transaction_id: nil
- }
+ stored_credential_params = stored_credential(:initial, :unscheduled, :merchant)
options.merge(stored_credential: stored_credential_params)
assert purchase = @cftgateway.purchase(@amount, @credit_card, options.merge(instalments: 3, skip_capture: true, authorization_validated: true))
diff --git a/test/remote/gateways/remote_xpay_test.rb b/test/remote/gateways/remote_xpay_test.rb
new file mode 100644
index 00000000000..99999e14243
--- /dev/null
+++ b/test/remote/gateways/remote_xpay_test.rb
@@ -0,0 +1,46 @@
+require 'test_helper'
+
+class RemoteXpayTest < Test::Unit::TestCase
+ def setup
+ @gateway = XpayGateway.new(fixtures(:xpay))
+ @amount = 100
+ @credit_card = credit_card(
+ '5186151650005008',
+ month: 12,
+ year: 2026,
+ verification_value: '123',
+ brand: 'master'
+ )
+
+ @options = {
+ order_id: SecureRandom.alphanumeric(10),
+ email: 'example@example.com',
+ billing_address: address,
+ order: {
+ currency: 'EUR',
+ amount: @amount
+ }
+ }
+ end
+
+ ## Test for authorization, capture, purchase and refund requires set up through 3ds
+ ## The only test that does not depend on a 3ds flow is verify
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'EXECUTED', response.message
+ end
+
+ def test_successful_preauth
+ response = @gateway.preauth(@amount, @credit_card, @options)
+ assert_success response
+ assert_match 'PENDING', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match '400', response.error_code
+ assert_match 'An internal error occurred', response.message
+ end
+end
diff --git a/test/schema/orbital/Request_PTI95.xsd b/test/schema/orbital/Request_PTI95.xsd
new file mode 100644
index 00000000000..53cfb98d203
--- /dev/null
+++ b/test/schema/orbital/Request_PTI95.xsd
@@ -0,0 +1,1396 @@
+
+
+
+
+ Top level element for all XML request transaction types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New order Transaction Types
+
+
+
+
+
+ Auth Only No Capture
+
+
+
+
+ Auth and Capture
+
+
+
+
+ Force Auth No Capture and no online authorization
+
+
+
+
+ Force Auth No Capture and no online authorization
+
+
+
+
+ Force Auth and Capture no online authorization
+
+
+
+
+ Refund and Capture no online authorization
+
+
+
+
+
+
+ New order Industry Types
+
+
+
+
+
+ Ecommerce transaction
+
+
+
+
+ Recurring Payment transaction
+
+
+
+
+ Mail Order Telephone Order transaction
+
+
+
+
+ Interactive Voice Response
+
+
+
+
+ Interactive Voice Response
+
+
+
+
+
+
+
+
+
+
+
+ Tax not provided
+
+
+
+
+ Tax included
+
+
+
+
+ Non-taxable transaction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stratus
+
+
+
+
+ Tandam
+
+
+
+
+
+
+
+
+
+
+
+ No mapping to order data
+
+
+
+
+ Use customer reference for OrderID
+
+
+
+
+ Use customer reference for both Order Id and Order Description
+
+
+
+
+ Use customer reference for Order Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto Generate the CustomerRefNum
+
+
+
+
+ Use OrderID as the CustomerRefNum
+
+
+
+
+ Use CustomerRefNum Element
+
+
+
+
+ Use the description as the CustomerRefNum
+
+
+
+
+ Ignore. We will Ignore this entry if it's passed in the XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ American Express
+
+
+
+
+ Carte Blanche
+
+
+
+
+ Diners Club
+
+
+
+
+ Discover
+
+
+
+
+ GE Twinpay Credit
+
+
+
+
+ GECC Private Label Credit
+
+
+
+
+ JCB
+
+
+
+
+ Mastercard
+
+
+
+
+ Visa
+
+
+
+
+ GE Twinpay Debit
+
+
+
+
+ Switch / Solo
+
+
+
+
+ Electronic Check
+
+
+
+
+ Flex Cache
+
+
+
+
+ European Direct Debit
+
+
+
+
+ Bill Me Later
+
+
+
+
+ PINLess Debit
+
+
+
+
+ International Maestro
+
+
+
+
+ ChaseNet Credit
+
+
+
+
+ ChaseNet Signature Debit
+
+
+
+
+ Gap CoBrand for Visa
+
+
+
+
+ Interac InApp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Credit Card
+
+
+
+
+ Swith/Solo
+
+
+
+
+ Electronic Check
+
+
+
+
+ PINLess Debit
+
+
+
+
+ European Direct Debit
+
+
+
+
+ International Maestro
+
+
+
+
+ Chasenet Credit
+
+
+
+
+ Chasenet Signature Debit
+
+
+
+
+ Auto Assign
+
+
+
+
+ Use Token as Account Number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ United States
+
+
+
+
+ Canada
+
+
+
+
+ Germany
+
+
+
+
+ Great Britain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Yes
+
+
+
+
+ Yes
+
+
+
+
+ No
+
+
+
+
+ No
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ First Recurring Transaction
+
+
+
+
+ Subsequent Recurring Transactions
+
+
+
+
+ First Installment Transaction
+
+
+
+
+ Subsequent Installment Transactions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ab8a5b251ba..8c562336cea 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -151,6 +151,7 @@ def formatted_expiration_date(credit_card)
end
def credit_card(number = '4242424242424242', options = {})
+ number = number.is_a?(Integer) ? number.to_s : number
defaults = {
number: number,
month: default_expiration_date.month,
@@ -213,10 +214,12 @@ def apple_pay_payment_token(options = {})
transaction_identifier: 'uniqueidentifier123'
}.update(options)
- ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(
+ defaults[:payment_data],
payment_instrument_name: defaults[:payment_instrument_name],
payment_network: defaults[:payment_network],
- transaction_identifier: defaults[:transaction_identifier])
+ transaction_identifier: defaults[:transaction_identifier]
+ )
end
def address(options = {})
@@ -270,6 +273,7 @@ def stored_credential(*args, **options)
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[:reason_type] = 'internet' if args.include?(:internet)
stored_credential[:initiator] = 'cardholder' if args.include?(:cardholder)
stored_credential[:initiator] = 'merchant' if args.include?(:merchant)
@@ -294,7 +298,7 @@ def fixtures(key)
def load_fixtures
[DEFAULT_CREDENTIALS, LOCAL_CREDENTIALS].inject({}) do |credentials, file_name|
if File.exist?(file_name)
- yaml_data = YAML.safe_load(File.read(file_name), [], [], true)
+ yaml_data = YAML.safe_load(File.read(file_name), aliases: true)
credentials.merge!(symbolize_keys(yaml_data))
end
credentials
diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb
index 3ef0b493486..338718a99b4 100644
--- a/test/unit/connection_test.rb
+++ b/test/unit/connection_test.rb
@@ -87,13 +87,6 @@ def test_successful_delete_with_body_request
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)
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index eb4dfcf3a9c..0b0690bf248 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -13,7 +13,7 @@ def maestro_card_numbers
6390000000000000 6390700000000000 6390990000000000
6761999999999999 6763000000000000 6799999999999999
5000330000000000 5811499999999999 5010410000000000
- 5010630000000000 5892440000000000
+ 5010630000000000 5892440000000000 5016230000000000
]
end
@@ -28,7 +28,7 @@ def non_maestro_card_numbers
def maestro_bins
%w[500032 500057 501015 501016 501018 501020 501021 501023 501024 501025 501026 501027 501028 501029
501038 501039 501040 501041 501043 501045 501047 501049 501051 501053 501054 501055 501056 501057
- 501058 501060 501061 501062 501063 501066 501067 501072 501075 501083 501087
+ 501058 501060 501061 501062 501063 501066 501067 501072 501075 501083 501087 501623
501800 501089 501091 501092 501095 501104 501105 501107 501108 501500 501879
502000 502113 502301 503175 503645 503800
503670 504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910
@@ -172,8 +172,24 @@ def test_should_detect_forbrugsforeningen
assert_equal 'forbrugsforeningen', CreditCard.brand?('6007221000000000')
end
- def test_should_detect_sodexo_card
+ def test_should_detect_sodexo_card_with_six_digits
assert_equal 'sodexo', CreditCard.brand?('6060694495764400')
+ assert_equal 'sodexo', CreditCard.brand?('6060714495764400')
+ assert_equal 'sodexo', CreditCard.brand?('6033894495764400')
+ assert_equal 'sodexo', CreditCard.brand?('6060704495764400')
+ assert_equal 'sodexo', CreditCard.brand?('6060684495764400')
+ assert_equal 'sodexo', CreditCard.brand?('6008184495764400')
+ assert_equal 'sodexo', CreditCard.brand?('5058644495764400')
+ assert_equal 'sodexo', CreditCard.brand?('5058654495764400')
+ end
+
+ def test_should_detect_sodexo_card_with_eight_digits
+ assert_equal 'sodexo', CreditCard.brand?('6060760195764400')
+ assert_equal 'sodexo', CreditCard.brand?('6060760795764400')
+ assert_equal 'sodexo', CreditCard.brand?('6089440095764400')
+ assert_equal 'sodexo', CreditCard.brand?('6089441095764400')
+ assert_equal 'sodexo', CreditCard.brand?('6089442095764400')
+ assert_equal 'sodexo', CreditCard.brand?('6060760695764400')
end
def test_should_detect_alia_card
@@ -363,6 +379,7 @@ def test_should_detect_cabal_card
assert_equal 'cabal', CreditCard.brand?('6035224400000000')
assert_equal 'cabal', CreditCard.brand?('6502723300000000')
assert_equal 'cabal', CreditCard.brand?('6500870000000000')
+ assert_equal 'cabal', CreditCard.brand?('6509000000000000')
end
def test_should_detect_unionpay_card
@@ -374,6 +391,7 @@ def test_should_detect_unionpay_card
assert_equal 'unionpay', CreditCard.brand?('8171999927660000')
assert_equal 'unionpay', CreditCard.brand?('8171999900000000021')
assert_equal 'unionpay', CreditCard.brand?('6200000000000005')
+ assert_equal 'unionpay', CreditCard.brand?('6217857000000000')
end
def test_should_detect_synchrony_card
@@ -385,6 +403,7 @@ def test_should_detect_routex_card
assert_equal 'routex', CreditCard.brand?(number)
assert CreditCard.valid_number?(number)
assert_equal 'routex', CreditCard.brand?('7006789224703725591')
+ assert_equal 'routex', CreditCard.brand?('7006740000000000013')
end
def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand
@@ -502,6 +521,74 @@ def test_electron_cards
assert_false electron_test.call('42496200000000000')
end
+ def test_should_detect_panal_card
+ assert_equal 'panal', CreditCard.brand?('6020490000000000')
+ end
+
+ def test_detecting_full_range_of_verve_card_numbers
+ verve = '506099000000000'
+
+ assert_equal 15, verve.length
+ assert_not_equal 'verve', CreditCard.brand?(verve)
+
+ 4.times do
+ verve << '0'
+ assert_equal 'verve', CreditCard.brand?(verve), "Failed for bin #{verve}"
+ end
+
+ assert_equal 19, verve.length
+
+ verve << '0'
+ assert_not_equal 'verve', CreditCard.brand?(verve)
+ end
+
+ def test_should_detect_verve
+ credit_cards = %w[5060990000000000
+ 506112100000000000
+ 5061351000000000000
+ 5061591000000000
+ 506175100000000000
+ 5078801000000000000
+ 5079381000000000
+ 637058100000000000
+ 5079400000000000000
+ 507879000000000000
+ 5061930000000000
+ 506136000000000000]
+ credit_cards.all? { |cc| CreditCard.brand?(cc) == 'verve' }
+ end
+
+ def test_should_detect_tuya_card
+ assert_equal 'tuya', CreditCard.brand?('5888000000000000')
+ end
+
+ def test_should_validate_tuya_card
+ assert_true CreditCard.valid_number?('5888001211111111')
+ # numbers with invalid formats
+ assert_false CreditCard.valid_number?('5888_0000_0000_0030')
+ end
+
+ def test_should_detect_uatp_card_brand
+ assert_equal 'uatp', CreditCard.brand?('117500000000000')
+ assert_equal 'uatp', CreditCard.brand?('117515279008103')
+ assert_equal 'uatp', CreditCard.brand?('129001000000000')
+ end
+
+ def test_should_validate_uatp_card
+ assert_true CreditCard.valid_number?('117515279008103')
+ assert_true CreditCard.valid_number?('116901000000000')
+ assert_true CreditCard.valid_number?('195724000000000')
+ assert_true CreditCard.valid_number?('192004000000000')
+ assert_true CreditCard.valid_number?('135410014004955')
+ end
+
+ def test_should_detect_invalid_uatp_card
+ assert_false CreditCard.valid_number?('117515279008104')
+ assert_false CreditCard.valid_number?('116901000000001')
+ assert_false CreditCard.valid_number?('195724000000001')
+ assert_false CreditCard.valid_number?('192004000000001')
+ end
+
def test_credit_card?
assert credit_card.credit_card?
end
diff --git a/test/unit/credit_card_test.rb b/test/unit/credit_card_test.rb
index e592bef4af6..595b3698bfa 100644
--- a/test/unit/credit_card_test.rb
+++ b/test/unit/credit_card_test.rb
@@ -174,7 +174,7 @@ def test_expired_card_should_have_one_error_on_year
end
def test_should_identify_wrong_card_brand
- c = credit_card(brand: 'master')
+ c = credit_card('4779139500118580', brand: 'master')
assert_not_valid c
end
diff --git a/test/unit/fixtures_test.rb b/test/unit/fixtures_test.rb
index b72720d928c..1b99051a5dd 100644
--- a/test/unit/fixtures_test.rb
+++ b/test/unit/fixtures_test.rb
@@ -2,7 +2,7 @@
class FixturesTest < Test::Unit::TestCase
def test_sort
- keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), [], [], true).keys
+ keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), aliases: true).keys
assert_equal(
keys,
keys.sort
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
index 388ef6830e0..28a766f6ca6 100644
--- a/test/unit/gateways/adyen_test.rb
+++ b/test/unit/gateways/adyen_test.rb
@@ -12,52 +12,64 @@ def setup
@bank_account = check()
- @credit_card = credit_card('4111111111111111',
+ @credit_card = credit_card(
+ '4111111111111111',
month: 8,
year: 2018,
first_name: 'Test',
last_name: 'Card',
verification_value: '737',
- brand: 'visa')
+ brand: 'visa'
+ )
- @elo_credit_card = credit_card('5066 9911 1111 1118',
+ @elo_credit_card = credit_card(
+ '5066 9911 1111 1118',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
- @cabal_credit_card = credit_card('6035 2277 1642 7021',
+ @cabal_credit_card = credit_card(
+ '6035 2277 1642 7021',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'cabal')
+ brand: 'cabal'
+ )
- @unionpay_credit_card = credit_card('8171 9999 0000 0000 021',
+ @unionpay_credit_card = credit_card(
+ '8171 9999 0000 0000 021',
month: 10,
year: 2030,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'unionpay')
+ brand: 'unionpay'
+ )
@three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa)
- @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ @apple_pay_card = network_tokenization_credit_card(
+ '4111111111111111',
payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
month: '08',
year: '2018',
source: :apple_pay,
- verification_value: nil)
+ verification_value: nil
+ )
- @nt_credit_card = network_tokenization_credit_card('4895370015293175',
+ @nt_credit_card = network_tokenization_credit_card(
+ '4895370015293175',
brand: 'visa',
eci: '07',
source: :network_token,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@amount = 100
@@ -168,10 +180,18 @@ def test_failed_authorize_with_unexpected_3ds
assert_match 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.', response.message
end
+ def test_failed_authorize_with_unexpected_3ds_with_flag_ignore_threed_dynamic
+ @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response)
+ response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge!(threed_dynamic: true, ignore_threed_dynamic: true))
+ assert_failure response
+ assert_match 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.', response.message
+ end
+
def test_successful_authorize_with_recurring_contract_type
stub_comms do
@gateway.authorize(100, @credit_card, @options.merge({ recurring_contract_type: 'ONECLICK' }))
end.check_request do |_endpoint, data, _headers|
+ assert_equal 'john.smith@test.com', JSON.parse(data)['shopperEmail']
assert_equal 'ONECLICK', JSON.parse(data)['recurring']['contract']
end.respond_with(successful_authorize_response)
end
@@ -324,6 +344,33 @@ def test_failed_authorise3ds2
assert_failure response
end
+ def test_failed_authorise_visa
+ @gateway.expects(:ssl_post).returns(failed_authorize_visa_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, {})
+
+ assert_equal 'Refused | 01: Refer to card issuer', response.message
+ assert_failure response
+ end
+
+ def test_failed_authorise_mastercard
+ @gateway.expects(:ssl_post).returns(failed_authorize_mastercard_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, {})
+
+ assert_equal 'Refused | 01 : New account information available', response.message
+ assert_failure response
+ end
+
+ def test_failed_authorise_mastercard_raw_error_message
+ @gateway.expects(:ssl_post).returns(failed_authorize_mastercard_response)
+
+ response = @gateway.send(:commit, 'authorise', {}, { raw_error_message: true })
+
+ assert_equal 'Refused | 01: Refer to card issuer', response.message
+ assert_failure response
+ end
+
def test_successful_capture
@gateway.expects(:ssl_post).returns(successful_capture_response)
response = @gateway.capture(@amount, '7914775043909934')
@@ -340,6 +387,14 @@ def test_failed_capture
assert_failure response
end
+ def test_successful_capture_with_shopper_statement
+ stub_comms do
+ @gateway.capture(@amount, '7914775043909934', @options.merge(shopper_statement: 'test1234'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal 'test1234', JSON.parse(data)['additionalData']['shopperStatement']
+ end.respond_with(successful_capture_response)
+ end
+
def test_successful_purchase_with_credit_card
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options)
@@ -662,6 +717,30 @@ def test_stored_credential_unscheduled_mit_used
assert_success response
end
+ def test_skip_mpi_data_field_omits_mpi_hash
+ options = {
+ billing_address: address(),
+ shipping_address: address(),
+ shopper_reference: 'John Smith',
+ order_id: '1001',
+ description: 'AM test',
+ currency: 'GBP',
+ customer: '123',
+ skip_mpi_data: 'Y',
+ shopper_interaction: 'ContAuth',
+ recurring_processing_model: 'Subscription',
+ network_transaction_id: '123ABC'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"shopperInteraction":"ContAuth"/, data)
+ assert_match(/"recurringProcessingModel":"Subscription"/, data)
+ refute_includes data, 'mpiData'
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_nonfractional_currency_handling
stub_comms do
@gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY'))
@@ -725,6 +804,29 @@ def test_successful_credit
assert_success response
end
+ def test_successful_payout_with_credit_card
+ payout_options = {
+ reference: 'P9999999999999999',
+ email: 'john.smith@test.com',
+ ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: @us_address,
+ nationality: 'NL',
+ order_id: 'P9999999999999999',
+ date_of_birth: '1990-01-01',
+ payout: true
+ }
+
+ stub_comms do
+ @gateway.credit(2500, @credit_card, payout_options)
+ end.check_request do |endpoint, data, _headers|
+ assert_match(/payout/, endpoint)
+ assert_match(/"dateOfBirth\":\"1990-01-01\"/, data)
+ assert_match(/"nationality\":\"NL\"/, data)
+ assert_match(/"shopperName\":{\"firstName\":\"Test\",\"lastName\":\"Card\"}/, data)
+ end.respond_with(successful_payout_response)
+ end
+
def test_successful_void
@gateway.expects(:ssl_post).returns(successful_void_response)
response = @gateway.void('7914775043909934')
@@ -910,6 +1012,23 @@ def test_failed_avs_check_returns_refusal_reason_raw
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Refused | 05 : Do not honor', response.message
+ assert_equal '05', response.error_code
+ end
+
+ def test_failed_without_refusal_reason_raw
+ @gateway.expects(:ssl_post).returns(failed_without_raw_refusal_reason)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Your money is no good here', response.error_code
+ end
+
+ def test_failed_without_refusal_reason
+ @gateway.expects(:ssl_post).returns(failed_without_refusal_reason)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_nil response.error_code
end
def test_scrub
@@ -929,14 +1048,16 @@ def test_scrub_network_tokenization_card
def test_shopper_data
post = { card: { billingAddress: {} } }
- @gateway.send(:add_shopper_data, post, @options)
+ @gateway.send(:add_shopper_data, post, @credit_card, @options)
+ @gateway.send(:add_extra_data, post, @credit_card, @options)
assert_equal 'john.smith@test.com', post[:shopperEmail]
assert_equal '77.110.174.153', post[:shopperIP]
end
def test_shopper_data_backwards_compatibility
post = { card: { billingAddress: {} } }
- @gateway.send(:add_shopper_data, post, @options_shopper_data)
+ @gateway.send(:add_shopper_data, post, @credit_card, @options_shopper_data)
+ @gateway.send(:add_extra_data, post, @credit_card, @options_shopper_data)
assert_equal 'john2.smith@test.com', post[:shopperEmail]
assert_equal '192.168.100.100', post[:shopperIP]
end
@@ -964,6 +1085,52 @@ def test_add_address
assert_equal @options[:shipping_address][:country], post[:deliveryAddress][:country]
end
+ def test_default_billing_address_country
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge({
+ billing_address: {
+ address1: 'Infinite Loop',
+ address2: 1,
+ country: '',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014'
+ }
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"country":"ZZ"/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_default_shipping_address_country
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge({
+ shipping_address: {
+ address1: 'Infinite Loop',
+ address2: 1,
+ country: '',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014'
+ }
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"country":"ZZ"/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_address_override_that_will_swap_housenumberorname_and_street
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(address_override: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"houseNumberOrName":"456 My Street"/, data)
+ assert_match(/"street":"Apt 1"/, data)
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_successful_auth_phone
options = @options.merge(billing_address: { phone: 1234567890 })
response = stub_comms do
@@ -1282,6 +1449,98 @@ def test_level_3_data
assert_success response
end
+ def test_succesful_additional_airline_data
+ airline_data = {
+ agency_invoice_number: 'BAC123',
+ agency_plan_name: 'plan name',
+ airline_code: '434234',
+ airline_designator_code: '1234',
+ boarding_fee: '100',
+ computerized_reservation_system: 'abcd',
+ customer_reference_number: 'asdf1234',
+ document_type: 'cc',
+ leg: {
+ carrier_code: 'KL'
+ },
+ passenger: {
+ first_name: 'Joe',
+ last_name: 'Doe'
+ }
+ }
+
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(additional_data_airline: airline_data))
+ end.check_request do |_endpoint, data, _headers|
+ parsed = JSON.parse(data)
+ additional_data = parsed['additionalData']
+ assert_equal additional_data['airline.agency_invoice_number'], airline_data[:agency_invoice_number]
+ assert_equal additional_data['airline.agency_plan_name'], airline_data[:agency_plan_name]
+ assert_equal additional_data['airline.airline_code'], airline_data[:airline_code]
+ assert_equal additional_data['airline.airline_designator_code'], airline_data[:airline_designator_code]
+ assert_equal additional_data['airline.boarding_fee'], airline_data[:boarding_fee]
+ assert_equal additional_data['airline.computerized_reservation_system'], airline_data[:computerized_reservation_system]
+ assert_equal additional_data['airline.customer_reference_number'], airline_data[:customer_reference_number]
+ assert_equal additional_data['airline.document_type'], airline_data[:document_type]
+ assert_equal additional_data['airline.flight_date'], airline_data[:flight_date]
+ assert_equal additional_data['airline.ticket_issue_address'], airline_data[:abcqwer]
+ assert_equal additional_data['airline.ticket_number'], airline_data[:ticket_number]
+ assert_equal additional_data['airline.travel_agency_code'], airline_data[:travel_agency_code]
+ assert_equal additional_data['airline.travel_agency_name'], airline_data[:travel_agency_name]
+ assert_equal additional_data['airline.passenger_name'], airline_data[:passenger_name]
+ assert_equal additional_data['airline.leg.carrier_code'], airline_data[:leg][:carrier_code]
+ assert_equal additional_data['airline.leg.class_of_travel'], airline_data[:leg][:class_of_travel]
+ assert_equal additional_data['airline.passenger.first_name'], airline_data[:passenger][:first_name]
+ assert_equal additional_data['airline.passenger.last_name'], airline_data[:passenger][:last_name]
+ assert_equal additional_data['airline.passenger.telephone_number'], airline_data[:passenger][:telephone_number]
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_additional_data_lodging
+ lodging_data = {
+ check_in_date: '20230822',
+ check_out_date: '20230830',
+ customer_service_toll_free_number: '234234',
+ fire_safety_act_indicator: 'abc123',
+ folio_cash_advances: '1234667',
+ folio_number: '32343',
+ food_beverage_charges: '1234',
+ no_show_indicator: 'Y',
+ prepaid_expenses: '100',
+ property_phone_number: '54545454',
+ number_of_nights: '5'
+ }
+
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(additional_data_lodging: lodging_data))
+ end.check_request do |_endpoint, data, _headers|
+ parsed = JSON.parse(data)
+ additional_data = parsed['additionalData']
+ assert_equal additional_data['lodging.checkInDate'], lodging_data[:check_in_date]
+ assert_equal additional_data['lodging.checkOutDate'], lodging_data[:check_out_date]
+ assert_equal additional_data['lodging.customerServiceTollFreeNumber'], lodging_data[:customer_service_toll_free_number]
+ assert_equal additional_data['lodging.fireSafetyActIndicator'], lodging_data[:fire_safety_act_indicator]
+ assert_equal additional_data['lodging.folioCashAdvances'], lodging_data[:folio_cash_advances]
+ assert_equal additional_data['lodging.folioNumber'], lodging_data[:folio_number]
+ assert_equal additional_data['lodging.foodBeverageCharges'], lodging_data[:food_beverage_charges]
+ assert_equal additional_data['lodging.noShowIndicator'], lodging_data[:no_show_indicator]
+ assert_equal additional_data['lodging.prepaidExpenses'], lodging_data[:prepaid_expenses]
+ assert_equal additional_data['lodging.propertyPhoneNumber'], lodging_data[:property_phone_number]
+ assert_equal additional_data['lodging.room1.numberOfNights'], lodging_data[:number_of_nights]
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_additional_extra_data
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(store: 'test store', mcc: '1234'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal JSON.parse(data)['store'], 'test store'
+ assert_equal JSON.parse(data)['mcc'], '1234'
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
def test_extended_avs_response
response = stub_comms do
@gateway.verify(@credit_card, @options)
@@ -1307,6 +1566,26 @@ def test_three_decimal_places_currency_handling
end
end
+ def test_metadata_sent_through_in_authorize
+ metadata = {
+ field_one: 'A',
+ field_two: 'B',
+ field_three: 'C',
+ field_four: 'EASY AS ONE TWO THREE'
+ }
+
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge(metadata: metadata))
+ end.check_request do |_endpoint, data, _headers|
+ parsed = JSON.parse(data)
+ assert_equal parsed['metadata']['field_one'], metadata[:field_one]
+ assert_equal parsed['metadata']['field_two'], metadata[:field_two]
+ assert_equal parsed['metadata']['field_three'], metadata[:field_three]
+ assert_equal parsed['metadata']['field_four'], metadata[:field_four]
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
private
def stored_credential_options(*args, ntid: nil)
@@ -1623,6 +1902,63 @@ def failed_authorize_3ds2_response
RESPONSE
end
+ def failed_authorize_visa_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": "01: Refer to card issuer"
+ },
+ "refusalReason": "Refused",
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
+ def failed_without_raw_refusal_reason
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": null
+ },
+ "refusalReason": "Your money is no good here",
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
+ def failed_without_refusal_reason
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": null
+ },
+ "refusalReason": null,
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_mastercard_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "refusalReasonRaw": "01: Refer to card issuer",
+ "merchantAdviceCode": "01 : New account information available"
+ },
+ "refusalReason": "Refused",
+ "pspReference":"8514775559925128",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
def successful_capture_response
<<-RESPONSE
{
@@ -1672,6 +2008,31 @@ def successful_credit_response
RESPONSE
end
+ def successful_payout_response
+ <<-RESPONSE
+ {
+ "additionalData":
+ {
+ "liabilityShift": "false",
+ "authCode": "081439",
+ "avsResult": "0 Unknown",
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
+ "threeDOffered": "false",
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
+ "authorisationMid": "50",
+ "acquirerAccountCode": "TestPmmAcquirerAccount",
+ "cvcResult": "0 Unknown",
+ "retry.attempt1.responseCode": "Approved",
+ "threeDAuthenticated": "false",
+ "retry.attempt1.rawResponse": "AUTHORISED"
+ },
+ "pspReference": "GMTN2VTQGJHKGK82",
+ "resultCode": "Authorised",
+ "authCode": "081439"
+ }
+ RESPONSE
+ end
+
def failed_credit_response
<<-RESPONSE
{
diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb
index ade541ce88e..6ad38180082 100644
--- a/test/unit/gateways/airwallex_test.rb
+++ b/test/unit/gateways/airwallex_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class AirwallexGateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class AirwallexTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password')
+ @gateway = AirwallexGateway.new(client_id: 'login', client_api_key: 'password', access_token: '12345678')
@credit_card = credit_card
@declined_card = credit_card('2223 0000 1018 1375')
@amount = 100
@@ -28,6 +18,15 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end
+ def test_setup_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:ssl_post).returns({ code: 'invalid_argument', message: "Failed to convert 'YOUR_CLIENT_ID' to UUID", source: '' }.to_json)
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed to convert 'YOUR_CLIENT_ID' to UUID/, error.message)
+ end
+
def test_gateway_has_access_token
assert @gateway.instance_variable_defined?(:@access_token)
end
diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb
index 3e6c9f0c1c9..a900ecda9d5 100644
--- a/test/unit/gateways/alelo_test.rb
+++ b/test/unit/gateways/alelo_test.rb
@@ -19,6 +19,15 @@ def setup
}
end
+ def test_fetch_access_token_should_rise_an_exception_under_unauthorized
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 401, 'Unauthorized'))
+ @gateway.send(:fetch_access_token)
+ end
+
+ assert_match(/Failed with 401 Unauthorized/, error.message)
+ end
+
def test_required_client_id_and_client_secret
error = assert_raises ArgumentError do
AleloGateway.new
diff --git a/test/unit/gateways/authorize_net_arb_test.rb b/test/unit/gateways/authorize_net_arb_test.rb
index 2b32c503d60..dfcc2d4c9ae 100644
--- a/test/unit/gateways/authorize_net_arb_test.rb
+++ b/test/unit/gateways/authorize_net_arb_test.rb
@@ -18,7 +18,9 @@ def setup
def test_successful_recurring
@gateway.expects(:ssl_post).returns(successful_recurring_response)
- response = @gateway.recurring(@amount, @credit_card,
+ response = @gateway.recurring(
+ @amount,
+ @credit_card,
billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'),
interval: {
length: 10,
@@ -27,7 +29,8 @@ def test_successful_recurring
duration: {
start_date: Time.now.strftime('%Y-%m-%d'),
occurrences: 30
- })
+ }
+ )
assert_instance_of Response, response
assert response.success?
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index 16aa5f4a4a6..bfbada91eb2 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -16,11 +16,15 @@ def setup
@amount = 100
@credit_card = credit_card
@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'
+ @payment_token = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==',
+ brand: 'visa',
+ eci: '05',
+ month: '09',
+ year: '2030',
+ first_name: 'Longbob',
+ last_name: 'Longsen'
)
@options = {
@@ -153,7 +157,7 @@ def test_device_type_used_from_options_if_included_with_valid_track_data
end
def test_market_type_not_included_for_apple_pay_or_echeck
- [@check, @apple_pay_payment_token].each do |payment|
+ [@check, @payment_token].each do |payment|
stub_comms do
@gateway.purchase(@amount, payment)
end.check_request do |_endpoint, data, _headers|
@@ -265,12 +269,10 @@ def test_failed_echeck_authorization
def test_successful_apple_pay_authorization
response = stub_comms do
- @gateway.authorize(@amount, @apple_pay_payment_token)
+ @gateway.authorize(@amount, @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
+ assert_match(/true<\/isPaymentToken>/, data)
+ assert_no_match(//, data)
end.respond_with(successful_authorize_response)
assert response
@@ -281,12 +283,10 @@ def test_successful_apple_pay_authorization
def test_successful_apple_pay_purchase
response = stub_comms do
- @gateway.purchase(@amount, @apple_pay_payment_token)
+ @gateway.purchase(@amount, @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
+ assert_match(/true<\/isPaymentToken>/, data)
+ assert_no_match(//, data)
end.respond_with(successful_purchase_response)
assert response
@@ -367,6 +367,21 @@ def test_passes_header_email_receipt
end.respond_with(successful_purchase_response)
end
+ def test_passes_surcharge
+ options = @options.merge(surcharge: {
+ amount: 20,
+ description: 'test description'
+ })
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(//, data)
+ assert_match(/0.20<\/amount>/, data)
+ assert_match(/#{options[:surcharge][:description]}<\/description>/, data)
+ assert_match(/<\/surcharge>/, 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))
@@ -940,6 +955,20 @@ def test_address
end.respond_with(successful_authorize_response)
end
+ def test_address_with_alternate_phone_number_field
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO', phone_number: '(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
+
def test_address_with_empty_billing_address
stub_comms do
@gateway.authorize(@amount, @credit_card)
@@ -1114,6 +1143,20 @@ def test_successful_bank_refund
assert_success response
end
+ def test_successful_bank_refund_truncates_long_name
+ response = stub_comms do
+ @gateway.refund(50, '12345667', account_type: 'checking', routing_number: '123450987', account_number: '12345667', first_name: 'Louise', last_name: 'Belcher-Williamson')
+ 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-William', doc.at_xpath('//transactionRequest/payment/bankAccount/nameOnAccount').content
+ end
+ end.respond_with(successful_refund_response)
+ assert_success response
+ 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', order_id: '1', description: 'Refund for order 1')
@@ -1290,9 +1333,7 @@ def test_dont_include_cust_id_for_phone_numbers
end
def test_includes_shipping_name_when_different_from_billing_name
- card = credit_card('4242424242424242',
- first_name: 'billing',
- last_name: 'name')
+ card = credit_card('4242424242424242', first_name: 'billing', last_name: 'name')
options = {
order_id: 'a' * 21,
@@ -1313,9 +1354,7 @@ def test_includes_shipping_name_when_different_from_billing_name
end
def test_includes_shipping_name_when_passed_as_options
- card = credit_card('4242424242424242',
- first_name: 'billing',
- last_name: 'name')
+ card = credit_card('4242424242424242', first_name: 'billing', last_name: 'name')
shipping_address = address(first_name: 'shipping', last_name: 'lastname')
shipping_address.delete(:name)
@@ -1338,9 +1377,7 @@ def test_includes_shipping_name_when_passed_as_options
end
def test_truncation
- card = credit_card('4242424242424242',
- first_name: 'a' * 51,
- last_name: 'a' * 51)
+ card = credit_card('4242424242424242', first_name: 'a' * 51, last_name: 'a' * 51)
options = {
order_id: 'a' * 21,
@@ -1412,8 +1449,7 @@ def test_supports_scrubbing?
end
def test_successful_apple_pay_authorization_with_network_tokenization
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = stub_comms do
@gateway.authorize(@amount, credit_card)
@@ -1431,8 +1467,7 @@ def test_successful_apple_pay_authorization_with_network_tokenization
end
def test_failed_apple_pay_authorization_with_network_tokenization_not_supported
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: '111111111100cryptogram')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: '111111111100cryptogram')
response = stub_comms do
@gateway.authorize(@amount, credit_card)
diff --git a/test/unit/gateways/banwire_test.rb b/test/unit/gateways/banwire_test.rb
index b28c2411e75..5bbc177913a 100644
--- a/test/unit/gateways/banwire_test.rb
+++ b/test/unit/gateways/banwire_test.rb
@@ -9,10 +9,12 @@ def setup
currency: 'MXN'
)
- @credit_card = credit_card('5204164299999999',
+ @credit_card = credit_card(
+ '5204164299999999',
month: 11,
year: 2012,
- verification_value: '999')
+ verification_value: '999'
+ )
@amount = 100
@options = {
@@ -22,11 +24,13 @@ def setup
description: 'Store purchase'
}
- @amex_credit_card = credit_card('375932134599999',
+ @amex_credit_card = credit_card(
+ '375932134599999',
month: 3,
year: 2017,
first_name: 'Banwire',
- last_name: 'Test Card')
+ last_name: 'Test Card'
+ )
@amex_options = {
order_id: '2',
email: 'test@email.com',
diff --git a/test/unit/gateways/barclaycard_smartpay_test.rb b/test/unit/gateways/barclaycard_smartpay_test.rb
index 9b84e216b0c..ee0c11c4da3 100644
--- a/test/unit/gateways/barclaycard_smartpay_test.rb
+++ b/test/unit/gateways/barclaycard_smartpay_test.rb
@@ -152,9 +152,7 @@ def test_successful_authorize_with_alternate_address
def test_successful_authorize_with_house_number_and_street
response = stub_comms do
- @gateway.authorize(@amount,
- @credit_card,
- @options_with_house_number_and_street)
+ @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)
@@ -167,9 +165,7 @@ def test_successful_authorize_with_house_number_and_street
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)
+ @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)
diff --git a/test/unit/gateways/beanstream_test.rb b/test/unit/gateways/beanstream_test.rb
index 886a4479e1c..fa9f748c9ff 100644
--- a/test/unit/gateways/beanstream_test.rb
+++ b/test/unit/gateways/beanstream_test.rb
@@ -302,6 +302,19 @@ def test_sends_email_without_addresses
assert_success response
end
+ def test_sends_alternate_phone_number_value
+ @options[:billing_address][:phone] = nil
+ @options[:billing_address][:phone_number] = '9191234567'
+
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/ordPhoneNumber=9191234567/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_transcript_scrubbing
assert_equal scrubbed_transcript, @gateway.scrub(transcript)
end
diff --git a/test/unit/gateways/blue_pay_test.rb b/test/unit/gateways/blue_pay_test.rb
index 9385bb329ed..d1bc53db5c8 100644
--- a/test/unit/gateways/blue_pay_test.rb
+++ b/test/unit/gateways/blue_pay_test.rb
@@ -220,8 +220,7 @@ def test_cvv_result
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
+ assert_equal 'Street address matches, but postal code does not match.', @gateway.send(:parse, 'STATUS=2&CVV2=M&AVS=A&MESSAGE=FAILURE').message
end
def test_passing_stored_credentials_data_for_mit_transaction
@@ -259,12 +258,15 @@ 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,
+ @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)
+ rebill_amount: @amount * 4
+ )
end
assert_instance_of Response, response
diff --git a/test/unit/gateways/blue_snap_test.rb b/test/unit/gateways/blue_snap_test.rb
index 8f592125771..8553f4e9aea 100644
--- a/test/unit/gateways/blue_snap_test.rb
+++ b/test/unit/gateways/blue_snap_test.rb
@@ -343,6 +343,19 @@ def test_successful_authorize
assert_equal '1012082893', response.authorization
end
+ def test_successful_authorize_with_descriptor_phone_number
+ options_with_phone_number = {
+ descriptor_phone_number: '321-321-4321'
+ }
+ response = stub_comms(@gateway, :raw_ssl_request) do
+ @gateway.authorize(@amount, @credit_card, options_with_phone_number)
+ end.check_request do |_method, _url, data|
+ assert_match('321-321-4321', data)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ end
+
def test_successful_authorize_with_3ds_auth
response = stub_comms(@gateway, :raw_ssl_request) do
@gateway.authorize(@amount, @credit_card, @options_3ds2)
diff --git a/test/unit/gateways/bogus_test.rb b/test/unit/gateways/bogus_test.rb
index 4564a4d3096..301ed2ddfa6 100644
--- a/test/unit/gateways/bogus_test.rb
+++ b/test/unit/gateways/bogus_test.rb
@@ -96,6 +96,17 @@ def test_void
end
end
+ def test_verify
+ assert @gateway.verify(credit_card(CC_SUCCESS_PLACEHOLDER)).success?
+ response = @gateway.verify(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.verify(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_store
assert @gateway.store(credit_card(CC_SUCCESS_PLACEHOLDER)).success?
response = @gateway.store(credit_card(CC_FAILURE_PLACEHOLDER))
diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb
index fbe77e3595c..8550b24eb79 100644
--- a/test/unit/gateways/borgun_test.rb
+++ b/test/unit/gateways/borgun_test.rb
@@ -56,12 +56,27 @@ def test_authorize_and_capture
assert_success capture
end
+ def test_failed_preauth_3ds
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/MerchantReturnURL>#{@options[:redirect_url]}/, data)
+ assert_match(/SaleDescription>#{@options[:sale_description]}/, data)
+ assert_match(/TrCurrencyExponent>2/, data)
+ end.respond_with(failed_get_3ds_authentication_response)
+
+ assert_failure response
+ assert_equal response.message, 'Exception in PostEnrollmentRequest.'
+ assert response.authorization.blank?
+ end
+
def test_successful_preauth_3ds
response = stub_comms do
- @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_return_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
+ @gateway.purchase(@amount, @credit_card, @options.merge({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1', sale_description: 'product description' }))
end.check_request do |_endpoint, data, _headers|
- assert_match(/MerchantReturnURL>#{@options[:merchant_return_url]}/, data)
+ assert_match(/MerchantReturnURL>#{@options[:redirect_url]}/, data)
assert_match(/SaleDescription>#{@options[:sale_description]}/, data)
+ assert_match(/TrCurrencyExponent>2/, data)
end.respond_with(successful_get_3ds_authentication_response)
assert_success response
@@ -69,6 +84,7 @@ def test_successful_preauth_3ds
assert !response.params['acsformfields_actionurl'].blank?
assert !response.params['acsformfields_pareq'].blank?
assert !response.params['threedsmessageid'].blank?
+ assert response.authorization.blank?
end
def test_successful_purchase_after_3ds
@@ -76,6 +92,7 @@ def test_successful_purchase_after_3ds
@gateway.purchase(@amount, @credit_card, @options.merge({ three_ds_message_id: '98324_zzi_1234353' }))
end.check_request do |_endpoint, data, _headers|
assert_match(/ThreeDSMessageId>#{@options[:three_ds_message_id]}/, data)
+ assert_match(/TrCurrencyExponent>0/, data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -367,6 +384,25 @@ def successful_get_3ds_authentication_response
RESPONSE
end
+ def failed_get_3ds_authentication_response
+ %(
+
+
+
+
+ <?xml version="1.0" encoding="iso-8859-1"?>
+ <get3DSAuthenticationReply>
+ <Status>
+ <ResultCode>30</ResultCode>
+ <ResultText>MPI returns error</ResultText>
+ <ErrorMessage>Exception in PostEnrollmentRequest.</ErrorMessage>
+ </Status>
+ </get3DSAuthenticationReply>
+
+
+ )
+ 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"
diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb
index af00505e227..be9edb8ffc0 100644
--- a/test/unit/gateways/braintree_blue_test.rb
+++ b/test/unit/gateways/braintree_blue_test.rb
@@ -138,7 +138,15 @@ def test_verify_bad_credentials
end
def test_zero_dollar_verification_transaction
+ @gateway = BraintreeBlueGateway.new(
+ merchant_id: 'test',
+ merchant_account_id: 'present',
+ public_key: 'test',
+ private_key: 'test'
+ )
+
Braintree::CreditCardVerificationGateway.any_instance.expects(:create).
+ with(has_entries(options: { merchant_account_id: 'present' })).
returns(braintree_result(cvv_response_code: 'M', avs_error_response_code: 'P'))
card = credit_card('4111111111111111')
@@ -264,6 +272,15 @@ def test_hold_in_escrow_can_be_specified
@gateway.authorize(100, credit_card('41111111111111111111'), hold_in_escrow: true)
end
+ def test_paypal_options_can_be_specified
+ Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
+ (params[:options][:paypal][:custom_field] == 'abc')
+ (params[:options][:paypal][:description] == 'shoes')
+ end.returns(braintree_result)
+
+ @gateway.authorize(100, credit_card('4111111111111111'), paypal_custom_field: 'abc', paypal_description: 'shoes')
+ end
+
def test_merchant_account_id_absent_if_not_provided
Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
not params.has_key?(:merchant_account_id)
@@ -737,21 +754,34 @@ def test_three_d_secure_pass_thru_handling_version_1
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',
+ three_ds_expectation = {
+ 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'
+ }
+
+ Braintree::TransactionGateway.any_instance.expects(:sale).with do |params|
+ (params[:sca_exemption] == 'low_value')
+ (params[:three_d_secure_pass_thru] == three_ds_expectation)
+ end.returns(braintree_result)
+
+ options = {
+ three_ds_exemption_type: 'low_value',
+ three_d_secure: {
+ version: '2.0',
cavv: 'cavv',
- eci_flag: 'eci',
+ eci: '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' })
+ directory_response_status: 'directory',
+ authentication_response_status: 'auth'
+ }
+ }
+ @gateway.purchase(100, credit_card('41111111111111111111'), options)
end
def test_three_d_secure_pass_thru_some_fields
@@ -936,14 +966,17 @@ def test_successful_purchase_with_travel_data
(params[:industry][:data][:lodging_name] == 'Best Hotel Ever')
end.returns(braintree_result)
- @gateway.purchase(100, credit_card('41111111111111111111'),
+ @gateway.purchase(
+ 100,
+ credit_card('41111111111111111111'),
travel_data: {
travel_package: 'flight',
departure_date: '2050-07-22',
lodging_check_in_date: '2050-07-22',
lodging_check_out_date: '2050-07-25',
lodging_name: 'Best Hotel Ever'
- })
+ }
+ )
end
def test_successful_purchase_with_lodging_data
@@ -955,13 +988,16 @@ def test_successful_purchase_with_lodging_data
(params[:industry][:data][:room_rate] == '80.00')
end.returns(braintree_result)
- @gateway.purchase(100, credit_card('41111111111111111111'),
+ @gateway.purchase(
+ 100,
+ credit_card('41111111111111111111'),
lodging_data: {
folio_number: 'ABC123',
check_in_date: '2050-12-22',
check_out_date: '2050-12-25',
room_rate: '80.00'
- })
+ }
+ )
end
def test_apple_pay_card
@@ -984,17 +1020,19 @@ def test_apple_pay_card
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ 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
+ def test_google_pay_card
Braintree::TransactionGateway.any_instance.expects(:sale).
with(
amount: '1.00',
@@ -1016,18 +1054,20 @@ def test_android_pay_card
).
returns(braintree_result(id: 'transaction_id'))
- credit_card = network_tokenization_credit_card('4111111111111111',
+ credit_card = network_tokenization_credit_card(
+ '4111111111111111',
brand: 'visa',
eci: '05',
payment_cryptogram: '111111111100cryptogram',
- source: :android_pay,
- transaction_id: '1234567890')
+ 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_google_pay_card
+ def test_network_token_card
Braintree::TransactionGateway.any_instance.expects(:sale).
with(
amount: '1.00',
@@ -1036,25 +1076,24 @@ def test_google_pay_card
first_name: 'Longbob', last_name: 'Longsen' },
options: { store_in_vault: false, submit_for_settlement: nil, hold_in_escrow: nil },
custom_fields: nil,
- google_pay_card: {
+ credit_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'
+ cardholder_name: 'Longbob Longsen',
+ network_tokenization_attributes: {
+ cryptogram: '111111111100cryptogram',
+ ecommerce_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')
+ brand: 'visa',
+ eci: '05',
+ source: :network_token,
+ payment_cryptogram: '111111111100cryptogram')
response = @gateway.authorize(100, credit_card, test: true, order_id: '1')
assert_equal 'transaction_id', response.authorization
@@ -1150,6 +1189,22 @@ def test_stored_credential_recurring_cit_used
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :recurring, id: '123ABC') })
end
+ def test_stored_credential_prefers_options_for_ntid
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'vaulted',
+ previous_network_transaction_id: '321XYZ'
+ },
+ transaction_source: ''
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', network_transaction_id: '321XYZ', 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(
@@ -1320,6 +1375,21 @@ def test_stored_credential_recurring_first_cit_initial
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: { initiator: 'merchant', reason_type: 'recurring_first', initial_transaction: true } })
end
+ def test_stored_credential_v2_recurring_first_cit_initial
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'recurring_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: { initiator: 'merchant', reason_type: 'recurring_first', initial_transaction: true } })
+ end
+
def test_stored_credential_moto_cit_initial
Braintree::TransactionGateway.any_instance.expects(:sale).with(
standard_purchase_params.merge(
@@ -1335,6 +1405,98 @@ def test_stored_credential_moto_cit_initial
@gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: { initiator: 'merchant', reason_type: 'moto', initial_transaction: true } })
end
+ def test_stored_credential_v2_recurring_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'recurring_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:cardholder, :recurring, :initial) })
+ end
+
+ def test_stored_credential_v2_follow_on_recurring_first
+ 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_credentials_v2: true, stored_credential: stored_credential(:merchant, :recurring, id: '123ABC') })
+ end
+
+ def test_stored_credential_v2_installment_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'will_vault'
+ },
+ transaction_source: 'installment_first'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:cardholder, :installment, :initial) })
+ end
+
+ def test_stored_credential_v2_follow_on_installment_first
+ Braintree::TransactionGateway.any_instance.expects(:sale).with(
+ standard_purchase_params.merge(
+ {
+ external_vault: {
+ status: 'vaulted',
+ previous_network_transaction_id: '123ABC'
+ },
+ transaction_source: 'installment'
+ }
+ )
+ ).returns(braintree_result)
+
+ @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credentials_v2: true, stored_credential: stored_credential(:merchant, :installment, id: '123ABC') })
+ end
+
+ def test_stored_credential_v2_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_credentials_v2: true, stored_credential: stored_credential(:cardholder, :unscheduled, :initial) })
+ end
+
+ def test_stored_credential_v2_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_credentials_v2: true, stored_credential: stored_credential(:merchant, :unscheduled, :initial) })
+ end
+
def test_raises_exeption_when_adding_bank_account_to_customer_without_billing_address
bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
@@ -1350,6 +1512,14 @@ def test_returns_error_on_authorize_when_passing_a_bank_account
assert_equal 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.', response.message
end
+ def test_returns_error_on_general_credit_when_passing_a_bank_account
+ bank_account = check({ account_number: '1000000002', routing_number: '011000015' })
+ response = @gateway.credit(100, bank_account, {})
+
+ assert_failure response
+ assert_equal 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.', response.message
+ end
+
def test_error_on_store_bank_account_without_a_mandate
options = {
billing_address: {
@@ -1371,6 +1541,15 @@ def test_scrub_sensitive_data
assert_equal filtered_success_token_nonce, @gateway.scrub(success_create_token_nonce)
end
+ def test_setup_purchase
+ Braintree::ClientTokenGateway.any_instance.expects(:generate).with do |params|
+ (params[:merchant_account_id] == 'merchant_account_id')
+ end.returns('client_token')
+
+ response = @gateway.setup_purchase(merchant_account_id: 'merchant_account_id')
+ assert_equal 'client_token', response.params['client_token']
+ end
+
private
def braintree_result(options = {})
diff --git a/test/unit/gateways/braintree_token_nonce_test.rb b/test/unit/gateways/braintree_token_nonce_test.rb
new file mode 100644
index 00000000000..89aac7612c8
--- /dev/null
+++ b/test/unit/gateways/braintree_token_nonce_test.rb
@@ -0,0 +1,205 @@
+require 'test_helper'
+
+class BraintreeTokenNonceTest < Test::Unit::TestCase
+ def setup
+ @gateway = BraintreeBlueGateway.new(
+ merchant_id: 'test',
+ public_key: 'test',
+ private_key: 'test',
+ test: true
+ )
+
+ @braintree_backend = @gateway.instance_eval { @braintree_gateway }
+
+ @options = {
+ billing_address: {
+ name: 'Adrain',
+ address1: '96706 Onie Plains',
+ address2: '01897 Alysa Lock',
+ country: 'XXX',
+ city: 'Miami',
+ state: 'FL',
+ zip: '32191',
+ phone_number: '693-630-6935'
+ },
+ ach_mandate: 'ach_mandate'
+ }
+ @generator = TokenNonce.new(@braintree_backend, @options)
+ @no_address_generator = TokenNonce.new(@braintree_backend, { ach_mandate: 'ach_mandate' })
+ end
+
+ def test_build_nonce_request_for_credit_card
+ credit_card = credit_card('4111111111111111')
+ response = @generator.send(:build_nonce_request, credit_card)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
+ assert_includes parse_response['variables']['input'], 'creditCard'
+
+ credit_card_input = parse_response['variables']['input']['creditCard']
+
+ assert_equal credit_card_input['number'], credit_card.number
+ assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
+ assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
+ assert_equal credit_card_input['cvv'], credit_card.verification_value
+ assert_equal credit_card_input['cardholderName'], credit_card.name
+ assert_billing_address_mapping(credit_card_input, credit_card)
+ end
+
+ def test_build_nonce_request_for_bank_account
+ bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ response = @generator.send(:build_nonce_request, bank_account)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(bank_account_query)
+ assert_includes parse_response['variables']['input'], 'usBankAccount'
+
+ bank_account_input = parse_response['variables']['input']['usBankAccount']
+
+ assert_equal bank_account_input['routingNumber'], bank_account.routing_number
+ assert_equal bank_account_input['accountNumber'], bank_account.account_number
+ assert_equal bank_account_input['accountType'], bank_account.account_type.upcase
+ assert_equal bank_account_input['achMandate'], @options[:ach_mandate]
+
+ assert_billing_address_mapping(bank_account_input, bank_account)
+
+ assert_equal bank_account_input['individualOwner']['firstName'], bank_account.first_name
+ assert_equal bank_account_input['individualOwner']['lastName'], bank_account.last_name
+ end
+
+ def test_build_nonce_request_for_credit_card_without_address
+ credit_card = credit_card('4111111111111111')
+ response = @no_address_generator.send(:build_nonce_request, credit_card)
+ parse_response = JSON.parse response
+ assert_client_sdk_metadata(parse_response)
+ assert_equal normalize_graph(parse_response['query']), normalize_graph(credit_card_query)
+ assert_includes parse_response['variables']['input'], 'creditCard'
+
+ credit_card_input = parse_response['variables']['input']['creditCard']
+
+ assert_equal credit_card_input['number'], credit_card.number
+ assert_equal credit_card_input['expirationYear'], credit_card.year.to_s
+ assert_equal credit_card_input['expirationMonth'], credit_card.month.to_s.rjust(2, '0')
+ assert_equal credit_card_input['cvv'], credit_card.verification_value
+ assert_equal credit_card_input['cardholderName'], credit_card.name
+ end
+
+ def test_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_credit_response)
+ assert_match(/tokencc_/, c_token)
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_bank_response)
+ assert_match(/tokenusbankacct_/, b_token)
+ end
+
+ def test_nil_token_from
+ credit_card = credit_card(number: 4111111111111111)
+ c_token = @generator.send(:token_from, credit_card, token_bank_response)
+ assert_nil c_token
+
+ bakn_account = check({ account_number: '4012000033330125', routing_number: '011000015' })
+ b_token = @generator.send(:token_from, bakn_account, token_credit_response)
+ assert_nil b_token
+ end
+
+ def assert_billing_address_mapping(request_input, payment_method)
+ assert_equal request_input['billingAddress']['streetAddress'], @options[:billing_address][:address1]
+ assert_equal request_input['billingAddress']['extendedAddress'], @options[:billing_address][:address2]
+
+ if payment_method.is_a?(Check)
+ assert_equal request_input['billingAddress']['city'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['state'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['zipCode'], @options[:billing_address][:zip]
+ else
+ assert_equal request_input['billingAddress']['locality'], @options[:billing_address][:city]
+ assert_equal request_input['billingAddress']['region'], @options[:billing_address][:state]
+ assert_equal request_input['billingAddress']['postalCode'], @options[:billing_address][:zip]
+ end
+ end
+
+ def assert_client_sdk_metadata(parse_response)
+ assert_equal parse_response['clientSdkMetadata']['platform'], 'web'
+ assert_equal parse_response['clientSdkMetadata']['source'], 'client'
+ assert_equal parse_response['clientSdkMetadata']['integration'], 'custom'
+ assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i, parse_response['clientSdkMetadata']['sessionId'])
+ assert_equal parse_response['clientSdkMetadata']['version'], '3.83.0'
+ end
+
+ private
+
+ def normalize_graph(graph)
+ graph.gsub(/\s+/, ' ').strip
+ end
+
+ def bank_account_query
+ <<-GRAPHQL
+ mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
+ tokenizeUsBankAccount(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on UsBankAccountDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def credit_card_query
+ <<-GRAPHQL
+ mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
+ tokenizeCreditCard(input: $input) {
+ paymentMethod {
+ id
+ details {
+ ... on CreditCardDetails {
+ last4
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def token_credit_response
+ {
+ 'data' => {
+ 'tokenizeCreditCard' => {
+ 'paymentMethod' => {
+ 'id' => 'tokencc_bc_72n3ms_74wsn3_jp2vn4_gjj62v_g33',
+ 'details' => {
+ 'last4' => '1111'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => 'a093afbb-42a9-4a85-973f-0ca79dff9ba6'
+ }
+ }
+ end
+
+ def token_bank_response
+ {
+ 'data' => {
+ 'tokenizeUsBankAccount' => {
+ 'paymentMethod' => {
+ 'id' => 'tokenusbankacct_bc_zrg45z_7wz95v_nscrks_q4zpjs_5m7',
+ 'details' => {
+ 'last4' => '0125'
+ }
+ }
+ }
+ },
+ 'extensions' => {
+ 'requestId' => '769b26d5-27e4-4602-b51d-face8b6ffdd5'
+ }
+ }
+ end
+end
diff --git a/test/unit/gateways/card_stream_test.rb b/test/unit/gateways/card_stream_test.rb
index 509dc81fc38..f648a960a42 100644
--- a/test/unit/gateways/card_stream_test.rb
+++ b/test/unit/gateways/card_stream_test.rb
@@ -9,11 +9,13 @@ def setup
shared_secret: 'secret'
)
- @visacreditcard = credit_card('4929421234600821',
+ @visacreditcard = credit_card(
+ '4929421234600821',
month: '12',
year: '2014',
verification_value: '356',
- brand: :visa)
+ brand: :visa
+ )
@visacredit_options = {
billing_address: {
@@ -51,15 +53,15 @@ def setup
dynamic_descriptor: 'product'
}
- @amex = credit_card('374245455400001',
+ @amex = credit_card(
+ '374245455400001',
month: '12',
year: 2014,
verification_value: '4887',
- brand: :american_express)
+ brand: :american_express
+ )
- @declined_card = credit_card('4000300011112220',
- month: '9',
- year: '2014')
+ @declined_card = credit_card('4000300011112220', month: '9', year: '2014')
end
def test_successful_visacreditcard_authorization
diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb
new file mode 100644
index 00000000000..913e171e054
--- /dev/null
+++ b/test/unit/gateways/cecabank_rest_json_test.rb
@@ -0,0 +1,341 @@
+require 'test_helper'
+
+class CecabankJsonTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = CecabankJsonGateway.new(
+ merchant_id: '12345678',
+ acquirer_bin: '12345678',
+ terminal_id: '00000003',
+ cypher_key: 'enc_key',
+ encryption_key: '00112233445566778899AABBCCDDEEFF00001133445566778899AABBCCDDEEAA',
+ initiator_vector: '0000000000000000'
+ )
+
+ @credit_card = credit_card
+ @amex_card = credit_card('374245455400001', { month: 10, year: Time.now.year + 1, verification_value: '1234' })
+ @amount = 100
+
+ @options = {
+ order_id: '1',
+ description: 'Store Purchase'
+ }
+
+ @three_d_secure = {
+ version: '2.2.0',
+ eci: '02',
+ cavv: '4F80DF50ADB0F9502B91618E9B704790EABA35FDFC972DDDD0BF498C6A75E492',
+ ds_transaction_id: 'a2bf089f-cefc-4d2c-850f-9153827fe070',
+ acs_transaction_id: '18c353b0-76e3-4a4c-8033-f14fe9ce39dc',
+ authentication_response_status: 'Y',
+ three_ds_server_trans_id: '9bd9aa9c-3beb-4012-8e52-214cccb25ec5'
+ }
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_request).returns(successful_authorize_response)
+
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12004172282310181802446007000#1#100', 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 '27', response.error_code
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_request).returns(successful_capture_response)
+
+ assert response = @gateway.capture(@amount, 'reference', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12204172322310181826516007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_request).returns(failed_capture_response)
+ response = @gateway.capture(@amount, 'reference', @options)
+
+ assert_failure response
+ assert_equal '807', response.error_code
+ 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 '12004172192310181720006007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_stored_credentials_with_network_transaction_id_as_gsf
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ @options.merge!({ network_transaction_id: '12345678901234567890' })
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12004172192310181720006007000#1#100', 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 '27', response.error_code
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_request).returns(successful_refund_response)
+
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '12204172352310181847426007000#1#100', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_request).returns(failed_refund_response)
+
+ assert response = @gateway.refund(@amount, 'reference', @options)
+ assert_failure response
+ assert response.test?
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_request).returns(successful_void_response)
+
+ assert response = @gateway.void('12204172352310181847426007000#1#10', @options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '14204172402310181906166007000#1#10', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_request).returns(failed_void_response)
+
+ assert response = @gateway.void('reference', @options)
+ assert_failure response
+ assert response.test?
+ end
+
+ def test_purchase_without_exemption_type
+ @options[:exemption_type] = nil
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_nil three_d_secure['exemption_type']
+ assert_match params['exencionSCA'], 'NONE'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_low_value_exemption
+ @options[:exemption_type] = 'low_value_exemption'
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_match three_d_secure['exemption_type'], 'low_value_exemption'
+ assert_match params['exencionSCA'], 'LOW'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_transaction_risk_analysis_exemption
+ @options[:exemption_type] = 'transaction_risk_analysis_exemption'
+ @options[:three_d_secure] = @three_d_secure
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ three_d_secure = JSON.parse(params['ThreeDsResponse'])
+ assert_match three_d_secure['exemption_type'], 'transaction_risk_analysis_exemption'
+ assert_match params['exencionSCA'], 'TRA'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_without_threed_secure_data
+ @options[:three_d_secure] = nil
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ assert_nil params['ThreeDsResponse']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_for_amex_include_correct_verification_value
+ stub_comms do
+ @gateway.purchase(@amount, @amex_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ data = JSON.parse(data)
+ params = JSON.parse(Base64.decode64(data['parametros']))
+ credit_card_data = decrypt_sensitive_fields(@gateway.options, params['encryptedData'])
+ amex_card = JSON.parse(credit_card_data)
+ assert_nil amex_card['cvv2']
+ assert_equal amex_card['csc'], '1234'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_transcript_scrubbing
+ assert_equal scrubbed_transcript, @gateway.scrub(transcript)
+ end
+
+ private
+
+ def decrypt_sensitive_fields(options, data)
+ cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
+ cipher.key = [options[:encryption_key]].pack('H*')
+ cipher.iv = options[:initiator_vector]&.split('')&.map(&:to_i)&.pack('c*')
+ cipher.update([data].pack('H*')) + cipher.final
+ end
+
+ def transcript
+ <<~RESPONSE
+ "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\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\\nContent-Length: 1397\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6ImYxZDdlNjBlMDYzMTJiNjI5NDEzOTUxM2YwMGQ2YWM4IiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwiZW5jcnlwdGVkRGF0YSI6IjhlOWZhY2RmMDk5NDFlZTU0ZDA2ODRiNDNmNDNhMmRmOGM4ZWE5ODlmYTViYzYyOTM4ODFiYWVjNDFiYjU4OGNhNDc3MWI4OTFmNTkwMWVjMmJhZmJhOTBmMDNkM2NiZmUwNTJlYjAzMDU4Zjk1MGYyNzY4YTk3OWJiZGQxNmJlZmIyODQ2Zjc2MjkyYTFlODYzMDNhNTVhYTIzNjZkODA5MDEyYzlhNzZmYTZiOTQzOWNlNGQ3MzY5NTYwOTNhMDAwZTk5ZDMzNmVhZDgwMjBmOTk5YjVkZDkyMTFjMjE5ZWRhMjVmYjVkZDY2YzZiOTMxZWY3MjY5ZjlmMmVjZGVlYTc2MWRlMDEyZmFhMzg3MDlkODcyNTI4ODViYjI1OThmZDI2YTQzMzNhNDEwMmNmZTg4YjM1NTJjZWU0Yzc2IiwiZXhlbmNpb25TQ0EiOiJOT05FIiwiVGhyZWVEc1Jlc3BvbnNlIjoie1wiZXhlbXB0aW9uX3R5cGVcIjpudWxsLFwidGhyZWVfZHNfdmVyc2lvblwiOlwiMi4yLjBcIixcImRpcmVjdG9yeV9zZXJ2ZXJfdHJhbnNhY3Rpb25faWRcIjpcImEyYmYwODlmLWNlZmMtNGQyYy04NTBmLTkxNTM4MjdmZTA3MFwiLFwiYWNzX3RyYW5zYWN0aW9uX2lkXCI6XCIxOGMzNTNiMC03NmUzLTRhNGMtODAzMy1mMTRmZTljZTM5ZGNcIixcImF1dGhlbnRpY2F0aW9uX3Jlc3BvbnNlX3N0YXR1c1wiOlwiWVwiLFwidGhyZWVfZHNfc2VydmVyX3RyYW5zX2lkXCI6XCI5YmQ5YWE5Yy0zYmViLTQwMTItOGU1Mi0yMTRjY2NiMjVlYzVcIixcImVjb21tZXJjZV9pbmRpY2F0b3JcIjpcIjAyXCIsXCJlbnJvbGxlZFwiOm51bGwsXCJhbW91bnRcIjpcIjEwMFwifSIsIm1lcmNoYW50SUQiOiIxMDY5MDA2NDAiLCJhY3F1aXJlckJJTiI6IjAwMDA1NTQwMDAiLCJ0ZXJtaW5hbElEIjoiMDAwMDAwMDMifQ==\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"ac7e5eb06b675be6c6f58487bbbaa1ddc07518e216cb0788905caffd911eea87\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Thu, 14 Dec 2023 15:52:41 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 103\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 103 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQzOTQ4MzIzMTIxNDE2NDg0NjYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"5ce066be8892839d6aa6da15405c9be8987642f4245fac112292084a8532a538\\\",\\\"fecha\\\":\\\"231214164846089\\\",\\\"idProceso\\\":\\\"106900640-adeda8b09b84630d6247b53748ab9c66\\\"}\"\nread 300 bytes\nConn close\n"
+ RESPONSE
+ end
+
+ def scrubbed_transcript
+ <<~RESPONSE
+ "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\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\\nContent-Length: 1397\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6ImYxZDdlNjBlMDYzMTJiNjI5NDEzOTUxM2YwMGQ2YWM4IiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwiZW5jcnlwdGVkRGF0YSI6ImEyZjczODJjMDdiZGYxYWZiZDE3YWJiMGQ3NTNmMzJlYmIzYTFjNGY4ZGNmMjYxZWQ2YTkxMmQ3MzlkNzE2ZjA1MDBiOTg5NzliY2I1MzY0NTRlMGE2ZmJiYzVlNjJlNjgxZjgyMTEwNGFiNjUzOTYyMjA4NmMwZGM2MzgyYWRmNjRkOGFjZWYwY2U5MDBjMzJlZmFjM2Q5YmJhM2UxZGY3NDY2NzU3NWNiYjMzYTczMDU3NGYzMzJmMGNlNTliOTU5MzM4NjQxOGUwYjIyNDJiOTJmZDg2MDczM2QxNzhiZDZkNGIyZGMwMzE2ZGRmNTAzMTQ5N2I1YWViMjRlMzQiLCJleGVuY2lvblNDQSI6Ik5PTkUiLCJUaHJlZURzUmVzcG9uc2UiOiJ7XCJleGVtcHRpb25fdHlwZVwiOm51bGwsXCJ0aHJlZV9kc192ZXJzaW9uXCI6XCIyLjIuMFwiLFwiZGlyZWN0b3J5X3NlcnZlcl90cmFuc2FjdGlvbl9pZFwiOlwiYTJiZjA4OWYtY2VmYy00ZDJjLTg1MGYtOTE1MzgyN2ZlMDcwXCIsXCJhY3NfdHJhbnNhY3Rpb25faWRcIjpcIjE4YzM1M2IwLTc2ZTMtNGE0Yy04MDMzLWYxNGZlOWNlMzlkY1wiLFwiYXV0aGVudGljYXRpb25fcmVzcG9uc2Vfc3RhdHVzXCI6XCJZXCIsXCJ0aHJlZV9kc19zZXJ2ZXJfdHJhbnNfaWRcIjpcIjliZDlhYTljLTNiZWItNDAxMi04ZTUyLTIxNGNjY2IyNWVjNVwiLFwiZWNvbW1lcmNlX2luZGljYXRvclwiOlwiMDJcIixcImVucm9sbGVkXCI6bnVsbCxcImFtb3VudFwiOlwiMTAwXCJ9IiwibWVyY2hhbnRJRCI6IjEwNjkwMDY0MCIsImFjcXVpcmVyQklOIjoiMDAwMDU1NDAwMCIsInRlcm1pbmFsSUQiOiIwMDAwMDAwMyJ9\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"ac7e5eb06b675be6c6f58487bbbaa1ddc07518e216cb0788905caffd911eea87\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Thu, 14 Dec 2023 15:52:41 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 103\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 103 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQzOTQ4MzIzMTIxNDE2NDg0NjYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"5ce066be8892839d6aa6da15405c9be8987642f4245fac112292084a8532a538\\\",\\\"fecha\\\":\\\"231214164846089\\\",\\\"idProceso\\\":\\\"106900640-adeda8b09b84630d6247b53748ab9c66\\\"}\"\nread 300 bytes\nConn close\n"
+ RESPONSE
+ end
+
+ def successful_authorize_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQxNzIyODIzMTAxODE4MDI0NDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==",
+ "firma":"2271f18614f9e3bf1f1d0bde7c23d2d9b576087564fd6cb4474f14f5727eaff2",
+ "fecha":"231018180245479",
+ "idProceso":"106900640-9da0de26e0e81697f7629566b99a1b73"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_response
+ <<~RESPONSE
+ {
+ "fecha":"231018180927186",
+ "idProceso":"106900640-9cfe017407164563ca5aa7a0877d2ade",
+ "codResult":"27"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIyMDQxNzIzMjIzMTAxODE4MjY1MTYwMDcwMDAiLCJjb2RBdXQiOiI5MDAifQ==",
+ "firma":"9dead8ef2bf1f82cde1954cefaa9eca67b630effed7f71a5fd3bb3bd2e6e0808",
+ "fecha":"231018182651711",
+ "idProceso":"106900640-5b03c604fd76ecaf8715a29c482f3040"
+ }
+ RESPONSE
+ end
+
+ def failed_capture_response
+ <<~RESPONSE
+ {
+ "fecha":"231018183020560",
+ "idProceso":"106900640-d0cab45d2404960b65fe02445e97b7e2",
+ "codResult":"807"
+ }
+ RESPONSE
+ end
+
+ def successful_purchase_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQxNzIxOTIzMTAxODE3MjAwMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==",
+ "firma":"da751ff809f54842ff26aed009cdce2d1a3b613cb3be579bb17af2e3ab36aa37",
+ "fecha":"231018172001775",
+ "idProceso":"106900640-bd4bd321774c51ec91cf24ca6bbca913"
+ }
+ RESPONSE
+ end
+
+ def failed_purchase_response
+ <<~RESPONSE
+ {
+ "fecha":"231018174516102",
+ "idProceso":"106900640-29c9d010e2e8c33872a4194df4e7a544",
+ "codResult":"27"
+ }
+ RESPONSE
+ end
+
+ def successful_refund_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIiwibnVtT3BlcmFjaW9uIjoiOGYyOTJiYTcwMmEzMTZmODIwMmEzZGFjY2JhMjFmZWMiLCJpbXBvcnRlIjoiMTAwIiwibnVtQXV0IjoiMTAxMDAwIiwicmVmZXJlbmNpYSI6IjEyMjA0MTcyMzUyMzEwMTgxODQ3NDI2MDA3MDAwIiwidGlwb09wZXJhY2lvbiI6IkQiLCJwYWlzIjoiMDAwIiwiY29kQXV0IjoiOTAwIn0=",
+ "firma":"37591482e4d1dce6317c6d7de6a6c9b030c0618680eaefb4b42b0d8af3854773",
+ "fecha":"231018184743876",
+ "idProceso":"106900640-8f292ba702a316f8202a3daccba21fec"
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<~RESPONSE
+ {
+ "fecha":"231018185809202",
+ "idProceso":"106900640-fc93d837dba2003ad767d682e6eb5d5f",
+ "codResult":"15"
+ }
+ RESPONSE
+ end
+
+ def successful_void_response
+ <<~RESPONSE
+ {
+ "cifrado":"SHA2",
+ "parametros":"eyJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIiwibnVtT3BlcmFjaW9uIjoiMDNlMTkwNTU4NWZlMmFjM2M4N2NiYjY4NGUyMjYwZDUiLCJpbXBvcnRlIjoiMTAwIiwibnVtQXV0IjoiMTAxMDAwIiwicmVmZXJlbmNpYSI6IjE0MjA0MTcyNDAyMzEwMTgxOTA2MTY2MDA3MDAwIiwidGlwb09wZXJhY2lvbiI6IkQiLCJwYWlzIjoiMDAwIiwiY29kQXV0IjoiNDAwIn0=",
+ "firma":"af55904b24cb083e6514b86456b107fdb8ebfc715aed228321ad959b13ef2b23",
+ "fecha":"231018190618224",
+ "idProceso":"106900640-03e1905585fe2ac3c87cbb684e2260d5"
+ }
+ RESPONSE
+ end
+
+ def failed_void_response
+ <<~RESPONSE
+ {
+ "fecha":"231018191116348",
+ "idProceso":"106900640-d7ca10f4fae36b2ad81f330eeb1ce509",
+ "codResult":"15"
+ }
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/cecabank_test.rb b/test/unit/gateways/cecabank_test.rb
index 2641b1b6800..e2bfe3c749e 100644
--- a/test/unit/gateways/cecabank_test.rb
+++ b/test/unit/gateways/cecabank_test.rb
@@ -4,11 +4,11 @@ class CecabankTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = CecabankGateway.new(
+ @gateway = CecabankXmlGateway.new(
merchant_id: '12345678',
acquirer_bin: '12345678',
terminal_id: '00000003',
- key: 'enc_key'
+ cypher_key: 'enc_key'
)
@credit_card = credit_card
diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb
index 6a04bfbcbca..1fcc42989e2 100644
--- a/test/unit/gateways/checkout_v2_test.rb
+++ b/test/unit/gateways/checkout_v2_test.rb
@@ -1,15 +1,5 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CheckoutV2Gateway
- def setup_access_token
- '12345678'
- end
- end
- end
-end
-
class CheckoutV2Test < Test::Unit::TestCase
include CommStub
@@ -17,14 +7,27 @@ def setup
@gateway = CheckoutV2Gateway.new(
secret_key: '1111111111111'
)
- @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234' })
-
+ @gateway_oauth = CheckoutV2Gateway.new({ client_id: 'abcd', client_secret: '1234', access_token: '12345678' })
+ @gateway_api = CheckoutV2Gateway.new({
+ secret_key: '1111111111111',
+ public_key: '2222222222222'
+ })
@credit_card = credit_card
@amount = 100
+ @token = '2MPedsuenG2o8yFfrsdOBWmOuEf'
+ end
+
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request'))
+ @gateway.send(:setup_access_token)
+ end
+
+ assert_match(/Failed with 400 Bad Request/, error.message)
end
def test_successful_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -34,7 +37,7 @@ def test_successful_purchase
end
def test_successful_purchase_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -45,7 +48,7 @@ def test_successful_purchase_includes_avs_result
end
def test_successful_purchase_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
@@ -57,9 +60,9 @@ def test_successful_purchase_using_vts_network_token_without_eci
'4242424242424242',
{ source: :network_token, brand: 'visa' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -75,18 +78,18 @@ def test_successful_purchase_using_vts_network_token_without_eci
end
def test_successful_passing_processing_channel_id
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { processing_channel_id: '123456abcde' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['processing_channel_id'], '123456abcde')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_incremental_authorization
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card, { incremental_authorization: 'abcd1234' })
- end.check_request do |endpoint, _data, _headers|
+ end.check_request do |_method, endpoint, _data, _headers|
assert_include endpoint, 'abcd1234'
end.respond_with(successful_incremental_authorize_response)
@@ -94,18 +97,18 @@ def test_successful_passing_incremental_authorization
end
def test_successful_passing_authorization_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { authorization_type: 'Estimated' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['authorization_type'], 'Estimated')
end.respond_with(successful_purchase_response)
end
def test_successful_passing_exemption_and_challenge_indicator
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['3ds']['exemption'], 'no_preference')
assert_equal(request_data['3ds']['challenge_indicator'], 'trusted_listing')
@@ -113,9 +116,9 @@ def test_successful_passing_exemption_and_challenge_indicator
end
def test_successful_passing_capture_type
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, 'abc', { capture_type: 'NonFinal' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['capture_type'], 'NonFinal')
end.respond_with(successful_capture_response)
@@ -126,9 +129,9 @@ def test_successful_purchase_using_vts_network_token_with_eci
'4242424242424242',
{ source: :network_token, brand: 'visa', eci: '06' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -148,9 +151,9 @@ def test_successful_purchase_using_mdes_network_token
'5436031030606378',
{ source: :network_token, brand: 'master' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -170,9 +173,9 @@ def test_successful_purchase_using_apple_pay_network_token
'4242424242424242',
{ source: :apple_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -192,9 +195,9 @@ def test_successful_purchase_using_android_pay_network_token
'4242424242424242',
{ source: :android_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -214,9 +217,9 @@ def test_successful_purchase_using_google_pay_network_token
'4242424242424242',
{ source: :google_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -236,9 +239,9 @@ def test_successful_purchase_using_google_pay_pan_only_network_token
'4242424242424242',
{ source: :google_pay }
)
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, network_token)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['source']['type'], 'network_token')
@@ -257,16 +260,21 @@ def test_successful_render_for_oauth
processing_channel_id = 'abcd123'
response = stub_comms(@gateway_oauth, :ssl_request) do
@gateway_oauth.purchase(@amount, @credit_card, { processing_channel_id: processing_channel_id })
- end.check_request do |_method, _endpoint, data, headers|
- request = JSON.parse(data)
- assert_equal headers['Authorization'], 'Bearer 12345678'
- assert_equal request['processing_channel_id'], processing_channel_id
- end.respond_with(successful_purchase_response)
+ end.check_request do |_method, endpoint, data, headers|
+ if endpoint.match?(/token/)
+ assert_equal headers['Authorization'], 'Basic YWJjZDoxMjM0'
+ assert_equal data, 'grant_type=client_credentials'
+ else
+ request = JSON.parse(data)
+ assert_equal headers['Authorization'], 'Bearer 12345678'
+ assert_equal request['processing_channel_id'], processing_channel_id
+ end
+ end.respond_with(successful_access_token_response, successful_purchase_response)
assert_success response
end
def test_successful_authorize_includes_avs_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -277,7 +285,7 @@ def test_successful_authorize_includes_avs_result
end
def test_successful_authorize_includes_cvv_result
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(successful_authorize_response)
@@ -285,43 +293,158 @@ def test_successful_authorize_includes_cvv_result
end
def test_purchase_with_additional_fields
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card, { descriptor_city: 'london', descriptor_name: 'sherlock' })
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(/"billing_descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data)
end.respond_with(successful_purchase_response)
assert_success response
end
+ def test_purchase_with_recipient_fields
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, {
+ recipient: {
+ dob: '1985-05-15',
+ account_number: '5555554444',
+ zip: 'SW1A',
+ first_name: 'john',
+ last_name: 'johnny',
+ address: {
+ address_line1: '123 High St.',
+ address_line2: 'Flat 456',
+ city: 'London',
+ state: 'str',
+ zip: 'SW1A 1AA',
+ country: 'GB'
+ }
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(%r{"dob":"1985-05-15"}, data)
+ assert_match(%r{"account_number":"5555554444"}, data)
+ assert_match(%r{"zip":"SW1A"}, data)
+ assert_match(%r{"first_name":"john"}, data)
+ assert_match(%r{"last_name":"johnny"}, data)
+ assert_match(%r{"address_line1":"123 High St."}, data)
+ assert_match(%r{"address_line2":"Flat 456"}, data)
+ assert_match(%r{"city":"London"}, data)
+ assert_match(%r{"state":"str"}, data)
+ assert_match(%r{"zip":"SW1A 1AA"}, data)
+ assert_match(%r{"country":"GB"}, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_sender_fields
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, {
+ sender: {
+ type: 'individual',
+ dob: '1985-05-15',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ address: {
+ address1: '123 High St.',
+ address2: 'Flat 456',
+ city: 'London',
+ state: 'str',
+ zip: 'SW1A 1AA',
+ country: 'GB'
+ },
+ reference: '8285282045818',
+ identification: {
+ type: 'passport',
+ number: 'ABC123',
+ issuing_country: 'GB'
+ }
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)['sender']
+ assert_equal request['first_name'], 'Jane'
+ assert_equal request['last_name'], 'Doe'
+ assert_equal request['type'], 'individual'
+ assert_equal request['dob'], '1985-05-15'
+ assert_equal request['reference'], '8285282045818'
+ assert_equal request['address']['address_line1'], '123 High St.'
+ assert_equal request['address']['address_line2'], 'Flat 456'
+ assert_equal request['address']['city'], 'London'
+ assert_equal request['address']['state'], 'str'
+ assert_equal request['address']['zip'], 'SW1A 1AA'
+ assert_equal request['address']['country'], 'GB'
+ assert_equal request['identification']['type'], 'passport'
+ assert_equal request['identification']['number'], 'ABC123'
+ assert_equal request['identification']['issuing_country'], 'GB'
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_processing_fields
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, {
+ processing: {
+ aft: true
+ }
+ })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(%r{"aft":true}, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_successful_purchase_passing_metadata_with_mada_card_type
@credit_card.brand = 'mada'
- stub_comms do
+ stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request_data = JSON.parse(data)
assert_equal(request_data['metadata']['udf1'], 'mada')
end.respond_with(successful_purchase_response)
end
def test_failed_purchase
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) 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_failed_purchase_3ds_with_threeds_response_message
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing', threeds_response_message: true })
+ end.respond_with(failed_purchase_3ds_response)
+
+ assert_failure response
+ assert_equal 'Insufficient Funds', response.message
+ assert_equal nil, response.error_code
+ end
+
+ def test_failed_purchase_3ds_without_threeds_response_message
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' })
+ end.respond_with(failed_purchase_3ds_response)
+
+ assert_failure response
+ assert_equal 'Declined', response.message
+ assert_equal nil, response.error_code
+ end
+
def test_successful_authorize_and_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) 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
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -329,29 +452,25 @@ def test_successful_authorize_and_capture
end
def test_successful_authorize_and_capture_with_additional_options
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
card_on_file: true,
transaction_indicator: 2,
previous_charge_id: 'pay_123',
- processing_channel_id: 'pc_123',
- marketplace: {
- sub_entity_id: 'ent_123'
- }
+ processing_channel_id: 'pc_123'
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _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)
assert_match(%r{"processing_channel_id":"pc_123"}, data)
- assert_match(/"marketplace\":{\"sub_entity_id\":\"ent_123\"}/, data)
end.respond_with(successful_authorize_response)
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -359,15 +478,16 @@ def test_successful_authorize_and_capture_with_additional_options
end
def test_successful_purchase_with_stored_credentials
- initial_response = stub_comms do
+ initial_response = stub_comms(@gateway, :ssl_request) do
initial_options = {
stored_credential: {
+ initiator: 'cardholder',
initial_transaction: true,
reason_type: 'installment'
}
}
@gateway.purchase(@amount, @credit_card, initial_options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"Recurring"}, data)
assert_match(%r{"merchant_initiated":false}, data)
end.respond_with(successful_purchase_initial_stored_credential_response)
@@ -376,7 +496,7 @@ def test_successful_purchase_with_stored_credentials
assert_equal 'pay_7jcf4ovmwnqedhtldca3fjli2y', initial_response.params['id']
network_transaction_id = initial_response.params['id']
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false,
@@ -385,7 +505,7 @@ def test_successful_purchase_with_stored_credentials
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -396,7 +516,7 @@ def test_successful_purchase_with_stored_credentials
end
def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
stored_credential: {
initial_transaction: false
@@ -404,7 +524,7 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
merchant_initiated_transaction_id: 'pay_7jcf4ovmwnqedhtldca3fjli2y'
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['previous_payment_id'], 'pay_7jcf4ovmwnqedhtldca3fjli2y'
assert_equal request['source']['stored'], true
@@ -414,8 +534,38 @@ def test_successful_purchase_with_stored_credentials_merchant_initiated_transact
assert_equal 'Succeeded', response.message
end
+ def test_successful_purchase_with_extra_customer_data
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ phone_country_code: '1',
+ billing_address: address
+ }
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['source']['phone']['number'], '(555)555-5555'
+ assert_equal request['source']['phone']['country_code'], '1'
+ assert_equal request['customer']['name'], 'Longbob Longsen'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_no_customer_name_included_in_token_purchase
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ phone_country_code: '1',
+ billing_address: address
+ }
+ @gateway.purchase(@amount, @token, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['source']['phone']['number'], '(555)555-5555'
+ assert_equal request['source']['phone']['country_code'], '1'
+ refute_includes data, 'name'
+ end.respond_with(successful_purchase_response)
+ end
+
def test_successful_purchase_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -423,7 +573,7 @@ def test_successful_purchase_with_metadata
}
}
@gateway.purchase(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_using_stored_credential_response)
@@ -431,8 +581,19 @@ def test_successful_purchase_with_metadata
assert_success response
end
+ def test_optional_idempotency_key_header
+ stub_comms(@gateway, :ssl_request) do
+ options = {
+ idempotency_key: 'test123'
+ }
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _url, _data, headers|
+ assert_equal 'test123', headers['Cko-Idempotency-Key']
+ end.respond_with(successful_authorize_response)
+ end
+
def test_successful_authorize_and_capture_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -440,7 +601,7 @@ def test_successful_authorize_and_capture_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -448,7 +609,7 @@ def test_successful_authorize_and_capture_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -456,14 +617,14 @@ def test_successful_authorize_and_capture_with_metadata
end
def test_moto_transaction_is_properly_set
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
manual_entry: true
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"payment_type":"MOTO"}, data)
end.respond_with(successful_authorize_response)
@@ -471,13 +632,13 @@ def test_moto_transaction_is_properly_set
end
def test_3ds_passed
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
callback_url: 'https://www.example.com'
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"success_url"}, data)
assert_match(%r{"failure_url"}, data)
end.respond_with(successful_authorize_response)
@@ -489,7 +650,16 @@ 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_verify_payment_request
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.verify_payment('testValue')
+ end.check_request do |_method, endpoint, data, _headers|
+ assert_equal nil, data
+ assert_equal 'https://api.sandbox.checkout.com/payments/testValue', endpoint
+ end.respond_with(successful_verify_payment_response)
assert_success response
end
@@ -502,7 +672,7 @@ def test_failed_verify_payment
end
def test_successful_authorize_and_capture_with_3ds
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
attempt_n3d: true,
@@ -520,7 +690,7 @@ def test_successful_authorize_and_capture_with_3ds
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -528,7 +698,7 @@ def test_successful_authorize_and_capture_with_3ds
end
def test_successful_authorize_and_capture_with_3ds2
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
execute_threed: true,
three_d_secure: {
@@ -545,7 +715,7 @@ def test_successful_authorize_and_capture_with_3ds2
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- capture = stub_comms do
+ capture = stub_comms(@gateway, :ssl_request) do
@gateway.capture(@amount, response.authorization)
end.respond_with(successful_capture_response)
@@ -553,7 +723,7 @@ def test_successful_authorize_and_capture_with_3ds2
end
def test_failed_authorize
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@amount, @credit_card)
end.respond_with(failed_authorize_response)
@@ -563,7 +733,7 @@ def test_failed_authorize
end
def test_failed_capture
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.capture(100, '')
end.respond_with(failed_capture_response)
@@ -571,14 +741,14 @@ def test_failed_capture
end
def test_successful_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) 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
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -586,7 +756,7 @@ def test_successful_void
end
def test_successful_void_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -594,7 +764,7 @@ def test_successful_void_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_authorize_response)
@@ -602,7 +772,7 @@ def test_successful_void_with_metadata
assert_success response
assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization
- void = stub_comms do
+ void = stub_comms(@gateway, :ssl_request) do
@gateway.void(response.authorization)
end.respond_with(successful_void_response)
@@ -610,7 +780,7 @@ def test_successful_void_with_metadata
end
def test_failed_void
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.void('5d53a33d960c46d00f5dc061947d998c')
end.respond_with(failed_void_response)
assert_failure response
@@ -623,9 +793,9 @@ def test_successfully_passes_fund_type_and_fields
source_id: 'ca_spwmped4qmqenai7hcghquqle4',
account_holder_type: 'individual'
}
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.credit(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['instruction']['funds_transfer_type'], options[:funds_transfer_type]
assert_equal request['source']['type'], options[:source_type]
@@ -637,15 +807,122 @@ def test_successfully_passes_fund_type_and_fields
assert_success response
end
+ def test_successful_money_transfer_payout_via_credit
+ options = {
+ instruction_purpose: 'leisure',
+ account_holder_type: 'individual',
+ billing_address: address,
+ payout: true,
+ destination: {
+ account_holder: {
+ phone: {
+ number: '9108675309',
+ country_code: '1'
+ },
+ identification: {
+ type: 'passport',
+ number: '1234567890'
+ },
+ email: 'too_many_fields@checkout.com',
+ date_of_birth: '2004-10-27',
+ country_of_birth: 'US'
+ }
+ },
+ sender: {
+ type: 'individual',
+ first_name: 'Jane',
+ middle_name: 'Middle',
+ last_name: 'Doe',
+ reference: '012345',
+ reference_type: 'other',
+ source_of_funds: 'debit',
+ identification: {
+ type: 'passport',
+ number: '0987654321',
+ issuing_country: 'US',
+ date_of_expiry: '2027-07-07'
+ },
+ address: {
+ address1: '205 Main St',
+ address2: 'Apt G',
+ city: 'Winchestertonfieldville',
+ state: 'IA',
+ country: 'US',
+ zip: '12345'
+ },
+ date_of_birth: '2004-10-27',
+ country_of_birth: 'US',
+ nationality: 'US'
+ }
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.credit(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['instruction']['purpose'], 'leisure'
+ assert_equal request['destination']['account_holder']['phone']['number'], '9108675309'
+ assert_equal request['destination']['account_holder']['phone']['country_code'], '1'
+ assert_equal request['destination']['account_holder']['identification']['number'], '1234567890'
+ assert_equal request['destination']['account_holder']['identification']['type'], 'passport'
+ assert_equal request['destination']['account_holder']['email'], 'too_many_fields@checkout.com'
+ assert_equal request['destination']['account_holder']['date_of_birth'], '2004-10-27'
+ assert_equal request['destination']['account_holder']['country_of_birth'], 'US'
+ assert_equal request['sender']['type'], 'individual'
+ assert_equal request['sender']['first_name'], 'Jane'
+ assert_equal request['sender']['middle_name'], 'Middle'
+ assert_equal request['sender']['last_name'], 'Doe'
+ assert_equal request['sender']['reference'], '012345'
+ assert_equal request['sender']['reference_type'], 'other'
+ assert_equal request['sender']['source_of_funds'], 'debit'
+ assert_equal request['sender']['identification']['type'], 'passport'
+ assert_equal request['sender']['identification']['number'], '0987654321'
+ assert_equal request['sender']['identification']['issuing_country'], 'US'
+ assert_equal request['sender']['identification']['date_of_expiry'], '2027-07-07'
+ assert_equal request['sender']['address']['address_line1'], '205 Main St'
+ assert_equal request['sender']['address']['address_line2'], 'Apt G'
+ assert_equal request['sender']['address']['city'], 'Winchestertonfieldville'
+ assert_equal request['sender']['address']['state'], 'IA'
+ assert_equal request['sender']['address']['country'], 'US'
+ assert_equal request['sender']['address']['zip'], '12345'
+ assert_equal request['sender']['date_of_birth'], '2004-10-27'
+ assert_equal request['sender']['nationality'], 'US'
+ end.respond_with(successful_credit_response)
+ assert_success response
+ end
+
+ def test_transaction_successfully_reverts_to_regular_credit_when_payout_is_nil
+ options = {
+ instruction_purpose: 'leisure',
+ account_holder_type: 'individual',
+ billing_address: address,
+ payout: nil,
+ destination: {
+ account_holder: {
+ email: 'too_many_fields@checkout.com'
+ }
+ },
+ sender: {
+ type: 'individual'
+ }
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.credit(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ refute_includes data, 'email'
+ refute_includes data, 'sender'
+ end.respond_with(successful_credit_response)
+ assert_success response
+ end
+
def test_successful_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(successful_purchase_response)
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -653,7 +930,7 @@ def test_successful_refund
end
def test_successful_refund_with_metadata
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
options = {
metadata: {
coupon_code: 'NY2018',
@@ -661,7 +938,7 @@ def test_successful_refund_with_metadata
}
}
@gateway.authorize(@amount, @credit_card, options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |_method, _endpoint, data, _headers|
assert_match(%r{"coupon_code":"NY2018"}, data)
assert_match(%r{"partner_id":"123989"}, data)
end.respond_with(successful_purchase_response)
@@ -669,7 +946,7 @@ def test_successful_refund_with_metadata
assert_success response
assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization
- refund = stub_comms do
+ refund = stub_comms(@gateway, :ssl_request) do
@gateway.refund(@amount, response.authorization)
end.respond_with(successful_refund_response)
@@ -677,7 +954,7 @@ def test_successful_refund_with_metadata
end
def test_failed_refund
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.refund(nil, '')
end.respond_with(failed_refund_response)
@@ -685,7 +962,7 @@ def test_failed_refund
end
def test_successful_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(successful_verify_response)
assert_success response
@@ -693,13 +970,40 @@ def test_successful_verify
end
def test_failed_verify
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.verify(@credit_card)
end.respond_with(failed_verify_response)
assert_failure response
assert_equal 'request_invalid: card_number_invalid', response.message
end
+ def test_successful_store
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card)
+ end.check_request do |_method, endpoint, data, _headers|
+ if /tokens/.match?(endpoint)
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ elsif /instruments/.match?(endpoint)
+ assert_match(%r{"type":"token"}, data)
+ assert_match(%r{"token":"tok_}, data)
+ end
+ end.respond_with(succesful_token_response, succesful_store_response)
+ end
+
+ def test_successful_tokenize
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.send(:tokenize, @credit_card)
+ end.check_request do |_action, endpoint, data, _headers|
+ assert_match(%r{"type":"card"}, data)
+ assert_match(%r{"number":"4242424242424242"}, data)
+ assert_match(%r{"cvv":"123"}, data)
+ assert_match('/tokens', endpoint)
+ end.respond_with(succesful_token_response)
+ end
+
def test_transcript_scrubbing
assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed)
end
@@ -709,7 +1013,7 @@ def test_network_transaction_scrubbing
end
def test_invalid_json
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(invalid_json_response)
@@ -718,7 +1022,7 @@ def test_invalid_json
end
def test_error_code_returned
- response = stub_comms do
+ response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@amount, @credit_card)
end.respond_with(error_code_response)
@@ -727,7 +1031,7 @@ def test_error_code_returned
end
def test_4xx_error_message
- @gateway.expects(:ssl_post).raises(error_4xx_response)
+ @gateway.expects(:ssl_request).raises(error_4xx_response)
assert response = @gateway.purchase(@amount, @credit_card)
@@ -739,6 +1043,58 @@ def test_supported_countries
assert_equal %w[AD AE AR AT AU BE BG BH BR CH CL CN CO CY CZ DE DK EE EG ES FI FR GB GR HK HR HU IE IS IT JO JP KW LI LT LU LV MC MT MX MY NL NO NZ OM PE PL PT QA RO SA SE SG SI SK SM TR US], @gateway.supported_countries
end
+ def test_add_shipping_address
+ options = {
+ shipping_address: address()
+ }
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['shipping']['address']['address_line1'], options[:shipping_address][:address1]
+ assert_equal request['shipping']['address']['address_line2'], options[:shipping_address][:address2]
+ assert_equal request['shipping']['address']['city'], options[:shipping_address][:city]
+ assert_equal request['shipping']['address']['state'], options[:shipping_address][:state]
+ assert_equal request['shipping']['address']['country'], options[:shipping_address][:country]
+ assert_equal request['shipping']['address']['zip'], options[:shipping_address][:zip]
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_purchase_supports_alternate_credit_card_implementation
+ alternate_credit_card_class = Class.new
+ alternate_credit_card = alternate_credit_card_class.new
+
+ alternate_credit_card.expects(:credit_card?).returns(true)
+ alternate_credit_card.expects(:name).at_least_once.returns(@credit_card.name)
+ alternate_credit_card.expects(:number).returns(@credit_card.number)
+ alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
+ alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
+ alternate_credit_card.expects(:last_name).at_least_once.returns(@credit_card.first_name)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, alternate_credit_card)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_supports_alternate_credit_card_implementation
+ alternate_credit_card_class = Class.new
+ alternate_credit_card = alternate_credit_card_class.new
+
+ alternate_credit_card.expects(:credit_card?).returns(true)
+ alternate_credit_card.expects(:name).at_least_once.returns(@credit_card.name)
+ alternate_credit_card.expects(:number).returns(@credit_card.number)
+ alternate_credit_card.expects(:verification_value).returns(@credit_card.verification_value)
+ alternate_credit_card.expects(:first_name).at_least_once.returns(@credit_card.first_name)
+ alternate_credit_card.expects(:last_name).at_least_once.returns(@credit_card.first_name)
+
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, alternate_credit_card)
+ end.respond_with(successful_authorize_response)
+ end
+
private
def pre_scrubbed
@@ -769,12 +1125,24 @@ def post_scrubbed
)
end
+ def successful_access_token_response
+ %(
+ {"access_token":"12345678","expires_in":3600,"token_type":"Bearer","scope":"disputes:accept disputes:provide-evidence disputes:view files flow:events flow:workflows fx gateway gateway:payment gateway:payment-authorizations gateway:payment-captures gateway:payment-details gateway:payment-refunds gateway:payment-voids middleware middleware:merchants-secret payouts:bank-details risk sessions:app sessions:browser vault:instruments"}
+ )
+ end
+
def successful_purchase_response
%(
{"id":"pay_bgv5tmah6fmuzcmcrcro6exe6m","action_id":"act_bgv5tmah6fmuzcmcrcro6exe6m","amount":200,"currency":"USD","approved":true,"status":"Authorized","auth_code":"127172","eci":"05","scheme_id":"096091887499308","response_code":"10000","response_summary":"Approved","risk":{"flagged":false},"source":{"id":"src_fzp3cwkf4ygebbmvrxdhyrwmbm","type":"card","billing_address":{"address_line1":"456 My Street","address_line2":"Apt 1","city":"Ottawa","state":"ON","zip":"K1C2N6","country":"CA"},"expiry_month":6,"expiry_year":2025,"name":"Longbob Longsen","scheme":"Visa","last4":"4242","fingerprint":"9F3BAD2E48C6C8579F2F5DC0710B7C11A8ACD5072C3363A72579A6FB227D64BE","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","payouts":true,"fast_funds":"d"},"customer":{"id":"cus_tz76qzbwr44ezdfyzdvrvlwogy","email":"longbob.longsen@example.com","name":"Longbob Longsen"},"processed_on":"2020-09-11T13:58:32Z","reference":"1","processing":{"acquirer_transaction_id":"9819327011","retrieval_reference_number":"861613285622"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_bgv5tmah6fmuzcmcrcro6exe6m"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_bgv5tmah6fmuzcmcrcro6exe6m/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_bgv5tmah6fmuzcmcrcro6exe6m/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_bgv5tmah6fmuzcmcrcro6exe6m/voids"}}}
)
end
+ def succesful_store_response
+ %(
+ {"id":"src_vzzqipykt5ke5odazx5d7nikii","type":"card","fingerprint":"9F3BAD2E48C6C8579F2F5DC0710B7C11A8ACD5072C3363A72579A6FB227D64BE","expiry_month":6,"expiry_year":2025,"scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","customer":{"id":"cus_gmthnluatgounpoiyzbmn5fvua", "email":"longbob.longsen@example.com"}}
+ )
+ end
+
def successful_purchase_with_network_token_response
purchase_response = JSON.parse(successful_purchase_response)
purchase_response['source']['payment_account_reference'] = '2FCFE326D92D4C27EDD699560F484'
@@ -815,6 +1183,79 @@ def failed_purchase_response
)
end
+ def failed_purchase_3ds_response
+ %({
+ "id": "pay_awjzhfj776gulbp2nuslj4agbu",
+ "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": "Declined",
+ "approved": false,
+ "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": "20051",
+ "response_summary": "Insufficient Funds"
+ }
+ ],
+ "_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 successful_authorize_response
%(
{
@@ -1035,6 +1476,10 @@ def successful_verify_payment_response
)
end
+ def succesful_token_response
+ %({"type":"card","token":"tok_267wy4hwrpietkmbbp5iswwhvm","expires_on":"2023-01-03T20:18:49.0006481Z","expiry_month":6,"expiry_year":2025,"name":"Longbob Longsen","scheme":"VISA","last4":"4242","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic"})
+ 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"}}}
diff --git a/test/unit/gateways/citrus_pay_test.rb b/test/unit/gateways/citrus_pay_test.rb
index 7c96c1c1308..bc39c2034bd 100644
--- a/test/unit/gateways/citrus_pay_test.rb
+++ b/test/unit/gateways/citrus_pay_test.rb
@@ -157,7 +157,7 @@ def test_unsuccessful_verify
assert_equal 'FAILURE - DECLINED', response.message
end
- def test_north_america_region_url
+ def test_url
@gateway = TnsGateway.new(
userid: 'userid',
password: 'password',
@@ -167,23 +167,7 @@ def test_north_america_region_url
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)
+ assert_match(/secure.uat.tnspayments.com/, endpoint)
end.respond_with(successful_capture_response)
assert_success response
diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb
index baa412181c1..eef5324bd4b 100644
--- a/test/unit/gateways/commerce_hub_test.rb
+++ b/test/unit/gateways/commerce_hub_test.rb
@@ -8,44 +8,94 @@ def setup
@amount = 1204
@credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123')
- @google_pay = network_tokenization_credit_card('4005550000000019',
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :google_pay,
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- transaction_id: '13456789')
- @apple_pay = network_tokenization_credit_card('4005550000000019',
+ transaction_id: '13456789'
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :apple_pay,
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- transaction_id: '13456789')
- @no_supported_source = network_tokenization_credit_card('4005550000000019',
+ transaction_id: '13456789'
+ )
+ @no_supported_source = network_tokenization_credit_card(
+ '4005550000000019',
brand: 'visa',
eci: '05',
month: '02',
year: '2035',
source: :no_source,
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
@declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123')
+ @dynamic_descriptors = {
+ mcc: '1234',
+ merchant_name: 'Spreedly',
+ customer_service_number: '555444321',
+ service_entitlement: '123444555',
+ dynamic_descriptors_address: {
+ 'street' => '123 Main Street',
+ 'houseNumberOrName' => 'Unit B',
+ 'city' => 'Atlanta',
+ 'stateOrProvince' => 'GA',
+ 'postalCode' => '30303',
+ 'country' => 'US'
+ }
+ }
@options = {}
+ @post = {}
+ end
+
+ def test_successful_authorize_with_full_headers
+ @options.merge!(
+ headers_identifiers: {
+ 'x-originator' => 'CommerceHub-Partners-Spreedly',
+ 'user-agent' => 'CommerceHub-Partners-Spreedly-V1.00'
+ }
+ )
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, _data, headers|
+ assert_not_nil headers['Client-Request-Id']
+ assert_equal 'login', headers['Api-Key']
+ assert_not_nil headers['Timestamp']
+ assert_equal 'application/json', headers['Accept-Language']
+ assert_equal 'application/json', headers['Content-Type']
+ assert_equal 'application/json', headers['Accept']
+ assert_equal 'HMAC', headers['Auth-Token-Type']
+ assert_not_nil headers['Authorization']
+ assert_equal 'CommerceHub-Partners-Spreedly', headers['x-originator']
+ assert_equal 'CommerceHub-Partners-Spreedly-V1.00', headers['user-agent']
+ end.respond_with(successful_authorize_response)
end
def test_successful_purchase
+ @options[:order_id] = 'abc123'
+
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['transactionDetails']['captureFlag'], true
+ assert_equal request['transactionDetails']['createToken'], false
+ assert_equal request['transactionDetails']['merchantOrderId'], 'abc123'
assert_equal request['merchantDetails']['terminalId'], @gateway.options[:terminal_id]
assert_equal request['merchantDetails']['merchantId'], @gateway.options[:merchant_id]
assert_equal request['amount']['total'], (@amount / 100.0).to_f
assert_equal request['source']['card']['cardData'], @credit_card.number
assert_equal request['source']['card']['securityCode'], @credit_card.verification_value
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
assert_equal request['source']['card']['securityCodeIndicator'], 'PROVIDED'
end.respond_with(successful_purchase_response)
@@ -103,6 +153,35 @@ def test_successful_purchase_with_no_supported_source_as_apple_pay
assert_success response
end
+ def test_successful_purchase_with_all_dynamic_descriptors
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(@dynamic_descriptors))
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['dynamicDescriptors']['mcc'], @dynamic_descriptors[:mcc]
+ assert_equal request['dynamicDescriptors']['merchantName'], @dynamic_descriptors[:merchant_name]
+ assert_equal request['dynamicDescriptors']['customerServiceNumber'], @dynamic_descriptors[:customer_service_number]
+ assert_equal request['dynamicDescriptors']['serviceEntitlement'], @dynamic_descriptors[:service_entitlement]
+ assert_equal request['dynamicDescriptors']['address'], @dynamic_descriptors[:dynamic_descriptors_address]
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_some_dynamic_descriptors
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(mcc: '1234', customer_service_number: '555444321'))
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['dynamicDescriptors']['mcc'], @dynamic_descriptors[:mcc]
+ assert_nil request['dynamicDescriptors']['merchantName']
+ assert_equal request['dynamicDescriptors']['customerServiceNumber'], @dynamic_descriptors[:customer_service_number]
+ assert_nil request['dynamicDescriptors']['serviceEntitlement']
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_successful_authorize
response = stub_comms do
@gateway.authorize(@amount, @credit_card, @options)
@@ -125,7 +204,7 @@ def test_failed_purchase_and_authorize
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'HOST', response.error_code
+ assert_equal 'string', response.error_code
end
def test_successful_parsing_of_billing_and_shipping_addresses
@@ -149,11 +228,11 @@ def test_successful_parsing_of_billing_and_shipping_addresses
def test_successful_void
response = stub_comms do
- @gateway.void('authorization123', @options)
+ @gateway.void('abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
- assert_equal request['referenceTransactionDetails']['referenceTransactionType'], 'CHARGES'
+ assert_equal 'authorization123', request['referenceTransactionDetails']['referenceTransactionId']
+ assert_equal 'CHARGES', request['referenceTransactionDetails']['referenceTransactionType']
assert_nil request['transactionDetails']['captureFlag']
end.respond_with(successful_void_and_refund_response)
@@ -162,7 +241,7 @@ def test_successful_void
def test_successful_refund
response = stub_comms do
- @gateway.refund(nil, 'authorization123', @options)
+ @gateway.refund(nil, 'abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
@@ -176,7 +255,7 @@ def test_successful_refund
def test_successful_partial_refund
response = stub_comms do
- @gateway.refund(@amount - 1, 'authorization123', @options)
+ @gateway.refund(@amount - 1, 'abc123|authorization123', @options)
end.check_request do |_endpoint, data, _headers|
request = JSON.parse(data)
assert_equal request['referenceTransactionDetails']['referenceTransactionId'], 'authorization123'
@@ -189,6 +268,74 @@ def test_successful_partial_refund
assert_success response
end
+ def test_successful_credit
+ stub_comms do
+ @gateway.credit(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_not_nil request['amount']
+ assert_equal request['source']['card']['cardData'], @credit_card.number
+ end.respond_with(successful_credit_response)
+ end
+
+ def test_successful_purchase_cit_with_gsf
+ options = stored_credential_options(:cardholder, :unscheduled, :initial)
+ options[:data_entry_source] = 'MOBILE_WEB'
+ options[:pos_entry_mode] = 'MANUAL'
+ options[:pos_condition_code] = 'CARD_PRESENT'
+ response = stub_comms do
+ @gateway.purchase(@amount, 'authorization123', options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['transactionInteraction']['origin'], 'ECOM'
+ assert_equal request['transactionInteraction']['eciIndicator'], 'CHANNEL_ENCRYPTED'
+ assert_equal request['transactionInteraction']['posConditionCode'], 'CARD_PRESENT'
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
+ assert_equal request['transactionInteraction']['additionalPosInformation']['dataEntrySource'], 'MOBILE_WEB'
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def test_successful_purchase_mit_with_gsf
+ options = stored_credential_options(:merchant, :recurring)
+ options[:origin] = 'POS'
+ options[:pos_entry_mode] = 'MANUAL'
+ options[:data_entry_source] = 'MOBILE_WEB'
+ response = stub_comms do
+ @gateway.purchase(@amount, 'authorization123', options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['transactionInteraction']['origin'], 'POS'
+ assert_equal request['transactionInteraction']['eciIndicator'], 'CHANNEL_ENCRYPTED'
+ assert_equal request['transactionInteraction']['posConditionCode'], 'CARD_NOT_PRESENT_ECOM'
+ assert_equal request['transactionInteraction']['posEntryMode'], 'MANUAL'
+ assert_equal request['transactionInteraction']['additionalPosInformation']['dataEntrySource'], 'MOBILE_WEB'
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_gsf_scheme_reference_transaction_id
+ @options = stored_credential_options(:cardholder, :unscheduled, :initial)
+ @options[:physical_goods_indicator] = true
+ @options[:scheme_reference_transaction_id] = '12345'
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['storedCredentials']['schemeReferenceTransactionId'], '12345'
+ assert_equal request['transactionDetails']['physicalGoodsIndicator'], true
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def stored_credential_options(*args, ntid: nil)
+ {
+ order_id: '#1001',
+ stored_credential: stored_credential(*args, ntid: ntid)
+ }
+ end
+
def test_successful_store
response = stub_comms do
@gateway.store(@credit_card, @options)
@@ -206,16 +353,37 @@ def test_successful_store
end
def test_successful_verify
- response = stub_comms do
+ stub_comms do
@gateway.verify(@credit_card, @options)
- end.check_request do |_endpoint, data, _headers|
+ end.check_request do |endpoint, data, _headers|
request = JSON.parse(data)
- assert_equal request['transactionDetails']['captureFlag'], false
- assert_equal request['transactionDetails']['primaryTransactionType'], 'AUTH_ONLY'
- assert_equal request['transactionDetails']['accountVerification'], true
+ assert_match %r{verification}, endpoint
+ assert_equal request['source']['sourceType'], 'PaymentCard'
end.respond_with(successful_authorize_response)
+ end
- assert_success response
+ def test_getting_avs_cvv_from_response
+ gateway_resp = {
+ 'paymentReceipt' => {
+ 'processorResponseDetails' => {
+ 'bankAssociationDetails' => {
+ 'associationResponseCode' => 'V000',
+ 'avsSecurityCodeResponse' => {
+ 'streetMatch' => 'NONE',
+ 'postalCodeMatch' => 'NONE',
+ 'securityCodeMatch' => 'NOT_CHECKED',
+ 'association' => {
+ 'securityCodeResponse' => 'X',
+ 'avsCode' => 'Y'
+ }
+ }
+ }
+ }
+ }
+ }
+
+ assert_equal 'X', @gateway.send(:get_avs_cvv, gateway_resp, 'cvv')
+ assert_equal 'Y', @gateway.send(:get_avs_cvv, gateway_resp, 'avs')
end
def test_successful_scrub
@@ -223,6 +391,87 @@ def test_successful_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_uses_order_id_to_keep_transaction_references_when_provided
+ @options[:order_id] = 'abc123'
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ assert_equal 'abc123|6304d53be8d94312a620962afc9c012d', response.authorization
+ end
+
+ def test_detect_success_state_for_verify_on_success_transaction
+ gateway_resp = {
+ 'gatewayResponse' => {
+ 'transactionState' => 'VERIFIED'
+ }
+ }
+
+ assert @gateway.send :success_from, gateway_resp, 'verify'
+ end
+
+ def test_detect_success_state_for_verify_on_failure_transaction
+ gateway_resp = {
+ 'gatewayResponse' => {
+ 'transactionState' => 'NOT_VERIFIED'
+ }
+ }
+
+ refute @gateway.send :success_from, gateway_resp, 'verify'
+ end
+
+ def test_add_reference_transaction_details_capture_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :capture
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_nil @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_add_reference_transaction_details_void_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :void
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_add_reference_transaction_details_refund_reference_id
+ authorization = '|922e-59fc86a36c03'
+
+ @gateway.send :add_reference_transaction_details, @post, authorization, {}, :refund
+ assert_equal '922e-59fc86a36c03', @post[:referenceTransactionDetails][:referenceTransactionId]
+ assert_equal 'CHARGES', @post[:referenceTransactionDetails][:referenceTransactionType]
+ end
+
+ def test_successful_purchase_when_encrypted_credit_card_present
+ @options[:order_id] = 'abc123'
+ @options[:encryption_data] = {
+ keyId: SecureRandom.uuid,
+ encryptionType: 'RSA',
+ encryptionBlock: SecureRandom.alphanumeric(20),
+ encryptionBlockFields: 'card.cardData:16,card.nameOnCard:8,card.expirationMonth:2,card.expirationYear:4,card.securityCode:3',
+ encryptionTarget: 'MANUAL'
+ }
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ refute_nil request['source']['encryptionData']
+ assert_equal request['source']['sourceType'], 'PaymentCard'
+ assert_equal request['source']['encryptionData']['keyId'], @options[:encryption_data][:keyId]
+ assert_equal request['source']['encryptionData']['encryptionType'], 'RSA'
+ assert_equal request['source']['encryptionData']['encryptionBlock'], @options[:encryption_data][:encryptionBlock]
+ assert_equal request['source']['encryptionData']['encryptionBlockFields'], @options[:encryption_data][:encryptionBlockFields]
+ assert_equal request['source']['encryptionData']['encryptionTarget'], 'MANUAL'
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
private
def successful_purchase_response
@@ -479,6 +728,107 @@ def successful_void_and_refund_response
RESPONSE
end
+ def successful_credit_response
+ <<~RESPONSE
+ {
+ "gatewayResponse": {
+ "transactionType": "REFUND",
+ "transactionState": "CAPTURED",
+ "transactionOrigin": "ECOM",
+ "transactionProcessingDetails": {
+ "orderId": "CHG01edceac93c72d31489f14a994f77b5e93",
+ "transactionTimestamp": "2023-11-22T01:09:26.833753719Z",
+ "apiTraceId": "4dcb1fc8ea9d4f1084046a77cf250292",
+ "clientRequestId": "4519030",
+ "transactionId": "4dcb1fc8ea9d4f1084046a77cf250292"
+ }
+ },
+ "source": {
+ "sourceType": "PaymentCard",
+ "card": {
+ "nameOnCard": "Joe Bloggs",
+ "expirationMonth": "02",
+ "expirationYear": "2035",
+ "bin": "400555",
+ "last4": "0019",
+ "scheme": "VISA"
+ }
+ },
+ "transactionDetails": {
+ "captureFlag": true,
+ "transactionCaptureType": "host",
+ "processingCode": "200000",
+ "merchantInvoiceNumber": "593041958876",
+ "physicalGoodsIndicator": false,
+ "createToken": true,
+ "retrievalReferenceNumber": "6a77cf250292"
+ },
+ "transactionInteraction": {
+ "posEntryMode": "MANUAL",
+ "posConditionCode": "CARD_NOT_PRESENT_ECOM",
+ "additionalPosInformation": {
+ "stan": "009748",
+ "dataEntrySource": "UNSPECIFIED",
+ "posFeatures": {
+ "pinAuthenticationCapability": "UNSPECIFIED",
+ "terminalEntryCapability": "UNSPECIFIED"
+ }
+ },
+ "authorizationCharacteristicsIndicator": "N",
+ "hostPosEntryMode": "010",
+ "hostPosConditionCode": "59"
+ },
+ "merchantDetails": {
+ "tokenType": "LTDC",
+ "terminalId": "10000001",
+ "merchantId": "100039000301165"
+ },
+ "paymentReceipt": {
+ "approvedAmount": {
+ "total": 1.0,
+ "currency": "USD"
+ },
+ "processorResponseDetails": {
+ "approvalStatus": "APPROVED",
+ "approvalCode": "OK7975",
+ "referenceNumber": "6a77cf250292",
+ "processor": "FISERV",
+ "host": "NASHVILLE",
+ "networkRouted": "VISA",
+ "networkInternationalId": "0001",
+ "responseCode": "000",
+ "responseMessage": "Approved",
+ "hostResponseCode": "00",
+ "hostResponseMessage": "APPROVAL",
+ "responseIndicators": {
+ "alternateRouteDebitIndicator": false,
+ "signatureLineIndicator": false,
+ "signatureDebitRouteIndicator": false
+ },
+ "bankAssociationDetails": {
+ "associationResponseCode": "V000"
+ },
+ "additionalInfo": [
+ {
+ "name": "HOST_RAW_PROCESSOR_RESPONSE",
+ "value": "ARAyIAGADoAAAiAAAAAAAAABABEiAQknAJdIAAFZNmE3N2NmMjUwMjkyT0s3OTc1MDAwMTc2MTYxMwGRAEgxNE4wMTMzMjY4MTE5MjEwMTBJViAgICAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDAAGDIyQVBQUk9WQUwgICAgICAgIAAGVklDUkggAHRTRFhZMDAzUlNUVEMwMTU2MDExMDAwMDAwMDAwMDBSSTAxNTAwMDAwMDAwMDAwMDAwME5MMDA0VklTQVRZMDAxQ0FSMDA0VjAwMAA1QVJDSTAwM1VOS0NQMDAxP0RQMDAxSFJDMDAyMDBDQjAwMVY="
+ }
+ ]
+ }
+ },
+ "networkDetails": {
+ "network": {
+ "network": "Visa"
+ },
+ "networkResponseCode": "00",
+ "cardLevelResultCode": "CRH ",
+ "validationCode": "IV ",
+ "transactionIdentifier": "013326811921010"
+ }
+ }
+ RESPONSE
+ end
+
def successful_store_response
<<~RESPONSE
{
diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb
index 76ccef0cab4..625953f57f0 100644
--- a/test/unit/gateways/credorax_test.rb
+++ b/test/unit/gateways/credorax_test.rb
@@ -42,6 +42,27 @@ def setup
}
}
}
+
+ @nt_credit_card = network_tokenization_credit_card(
+ '4176661000001015',
+ brand: 'visa',
+ eci: '07',
+ source: :network_token,
+ payment_cryptogram: 'AgAAAAAAosVKVV7FplLgQRYAAAA='
+ )
+
+ @apple_pay_card = network_tokenization_credit_card(
+ '4176661000001015',
+ month: 10,
+ year: Time.new.year + 2,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ eci: '07',
+ transaction_id: 'abc123',
+ source: :apple_pay
+ )
end
def test_supported_card_types
@@ -986,6 +1007,7 @@ def test_stored_credential_unscheduled_mit_used
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(/a9=8/, data)
+ assert_match(/g6=abc123/, data)
end.respond_with(successful_authorize_response)
assert_success response
@@ -1049,6 +1071,26 @@ def test_3ds_2_optional_fields_does_not_empty_fields
assert_equal post, {}
end
+ def test_successful_purchase_with_network_token
+ response = stub_comms do
+ @gateway.purchase(@amount, @nt_credit_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/b21=vts_mdes_token&token_eci=07&token_crypto=AgAAAAAAosVKVV7FplLgQRYAAAA%3D/, data)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_other_than_network_token
+ response = stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/b21=applepay/, data)
+ assert_match(/token_eci=07/, data)
+ assert_not_match(/token_crypto=/, data)
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
private
def stored_credential_options(*args, id: nil)
diff --git a/test/unit/gateways/cyber_source_rest_test.rb b/test/unit/gateways/cyber_source_rest_test.rb
new file mode 100644
index 00000000000..ba3c6af8af7
--- /dev/null
+++ b/test/unit/gateways/cyber_source_rest_test.rb
@@ -0,0 +1,818 @@
+require 'test_helper'
+
+class CyberSourceRestTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = CyberSourceRestGateway.new(
+ merchant_id: 'abc123',
+ public_key: 'def345',
+ private_key: "NYlM1sgultLjvgaraWvDCXykdz1buqOW8yXE3pMlmxQ=\n"
+ )
+ @bank_account = check(account_number: '4100', routing_number: '121042882')
+ @credit_card = credit_card(
+ '4111111111111111',
+ verification_value: '987',
+ month: 12,
+ year: 2031
+ )
+ @master_card = credit_card('2222420000001113', brand: 'master')
+
+ @visa_network_token = network_tokenization_credit_card(
+ '4111111111111111',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+
+ @mastercard_network_token = network_tokenization_credit_card(
+ '5555555555554444',
+ brand: 'master',
+ eci: '05',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ source: :network_token
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569
+ )
+
+ @google_pay_mc = network_tokenization_credit_card(
+ '5555555555554444',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :google_pay,
+ verification_value: 569,
+ brand: 'master'
+ )
+
+ @apple_pay_jcb = network_tokenization_credit_card(
+ '3566111111111113',
+ payment_cryptogram: 'AceY+igABPs3jdwNaDg3MAACAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ source: :apple_pay,
+ verification_value: 569,
+ brand: 'jcb'
+ )
+ @amount = 100
+ @options = {
+ order_id: '1',
+ description: 'Store Purchase',
+ billing_address: {
+ name: 'John Doe',
+ address1: '1 Market St',
+ city: 'san francisco',
+ state: 'CA',
+ zip: '94105',
+ country: 'US',
+ phone: '4158880000'
+ },
+ email: 'test@cybs.com'
+ }
+ @discover_card = credit_card('6011111111111117', brand: 'discover')
+ @gmt_time = Time.now.httpdate
+ @digest = 'SHA-256=gXWufV4Zc7VkN9Wkv9jh/JuAVclqDusx3vkyo3uJFWU='
+ @resource = '/pts/v2/payments/'
+ end
+
+ def test_required_merchant_id_and_secret
+ error = assert_raises(ArgumentError) { CyberSourceRestGateway.new }
+ assert_equal 'Missing required parameter: merchant_id', error.message
+ end
+
+ def test_supported_card_types
+ assert_equal CyberSourceRestGateway.supported_cardtypes, %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada]
+ end
+
+ def test_properly_format_on_zero_decilmal
+ stub_comms do
+ @gateway.authorize(1000, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ card = request['paymentInformation']['card']
+ amount_details = request['orderInformation']['amountDetails']
+
+ assert_equal '1', request['clientReferenceInformation']['code']
+ assert_equal '2031', card['expirationYear']
+ assert_equal '12', card['expirationMonth']
+ assert_equal '987', card['securityCode']
+ assert_equal '001', card['type']
+ assert_equal 'USD', amount_details['currency']
+ assert_equal '10.00', amount_details['totalAmount']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_should_create_an_http_signature_for_a_post
+ signature = @gateway.send :get_http_signature, @resource, @digest, 'post', @gmt_time
+
+ parsed = parse_signature(signature)
+
+ assert_equal 'def345', parsed['keyid']
+ assert_equal 'HmacSHA256', parsed['algorithm']
+ assert_equal 'host date request-target digest v-c-merchant-id', parsed['headers']
+ assert_equal %w[algorithm headers keyid signature], signature.split(', ').map { |v| v.split('=').first }.sort
+ end
+
+ def test_should_create_an_http_signature_for_a_get
+ signature = @gateway.send :get_http_signature, @resource, nil, 'get', @gmt_time
+
+ parsed = parse_signature(signature)
+ assert_equal 'host date request-target v-c-merchant-id', parsed['headers']
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_including_customer_if_customer_id_present
+ post = { paymentInformation: {} }
+
+ @gateway.send :add_customer_id, post, {}
+ assert_nil post[:paymentInformation][:customer]
+
+ @gateway.send :add_customer_id, post, { customer_id: 10 }
+ assert_equal 10, post[:paymentInformation][:customer][:customerId]
+ end
+
+ def test_add_ammount_and_currency
+ post = { orderInformation: {} }
+
+ @gateway.send :add_amount, post, 10221, {}
+
+ assert_equal '102.21', post.dig(:orderInformation, :amountDetails, :totalAmount)
+ assert_equal 'USD', post.dig(:orderInformation, :amountDetails, :currency)
+ end
+
+ def test_add_credit_card_data
+ post = { paymentInformation: {} }
+ @gateway.send :add_credit_card, post, @credit_card
+
+ card = post[:paymentInformation][:card]
+ assert_equal @credit_card.number, card[:number]
+ assert_equal '2031', card[:expirationYear]
+ assert_equal '12', card[:expirationMonth]
+ assert_equal '987', card[:securityCode]
+ assert_equal '001', card[:type]
+ end
+
+ def test_add_ach
+ post = { paymentInformation: {} }
+ @gateway.send :add_ach, post, @bank_account
+
+ bank = post[:paymentInformation][:bank]
+ assert_equal @bank_account.account_number, bank[:account][:number]
+ assert_equal @bank_account.routing_number, bank[:routingNumber]
+ end
+
+ def test_add_billing_address
+ post = { orderInformation: {} }
+
+ @gateway.send :add_address, post, @credit_card, @options[:billing_address], @options, :billTo
+
+ address = post[:orderInformation][:billTo]
+
+ assert_equal 'John', address[:firstName]
+ assert_equal 'Doe', address[:lastName]
+ assert_equal '1 Market St', address[:address1]
+ assert_equal 'san francisco', address[:locality]
+ assert_equal 'US', address[:country]
+ assert_equal 'test@cybs.com', address[:email]
+ assert_equal '4158880000', address[:phoneNumber]
+ end
+
+ def test_add_shipping_address
+ post = { orderInformation: {} }
+ @options[:shipping_address] = @options.delete(:billing_address)
+
+ @gateway.send :add_address, post, @credit_card, @options[:shipping_address], @options, :shipTo
+
+ address = post[:orderInformation][:shipTo]
+
+ assert_equal 'John', address[:firstName]
+ assert_equal 'Doe', address[:lastName]
+ assert_equal '1 Market St', address[:address1]
+ assert_equal 'san francisco', address[:locality]
+ assert_equal 'US', address[:country]
+ assert_equal 'test@cybs.com', address[:email]
+ assert_equal '4158880000', address[:phoneNumber]
+ end
+
+ def test_authorize_network_token_visa
+ stub_comms do
+ @gateway.authorize(100, @visa_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '015', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_network_token_visa_recurring
+ @options[:stored_credential] = stored_credential(:cardholder, :recurring)
+ stub_comms do
+ @gateway.authorize(100, @visa_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '015', request['processingInformation']['paymentSolution']
+ assert_equal 'recurring', request['processingInformation']['commerceIndicator']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_network_token_visa_installment
+ @options[:stored_credential] = stored_credential(:cardholder, :installment)
+ stub_comms do
+ @gateway.authorize(100, @visa_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '015', request['processingInformation']['paymentSolution']
+ assert_equal 'install', request['processingInformation']['commerceIndicator']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_network_token_visa_unscheduled
+ @options[:stored_credential] = stored_credential(:cardholder, :unscheduled)
+ stub_comms do
+ @gateway.authorize(100, @visa_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '015', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_network_token_mastercard
+ stub_comms do
+ @gateway.authorize(100, @mastercard_network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '002', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '3', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '014', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_apple_pay_visa
+ stub_comms do
+ @gateway.authorize(100, @apple_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '001', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_equal 'AceY+igABPs3jdwNaDg3MAACAAA=', request['paymentInformation']['tokenizedCard']['cryptogram']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '001', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_include request['consumerAuthenticationInformation'], 'cavv'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_google_pay_master_card
+ stub_comms do
+ @gateway.authorize(100, @google_pay_mc, @options.merge(merchant_id: 'MerchantId'))
+ end.check_request do |_endpoint, data, headers|
+ request = JSON.parse(data)
+ assert_equal 'MerchantId', headers['V-C-Merchant-Id']
+ assert_equal '002', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '012', request['processingInformation']['paymentSolution']
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_equal request['consumerAuthenticationInformation']['ucafCollectionIndicator'], '2'
+ assert_include request['consumerAuthenticationInformation'], 'ucafAuthenticationData'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_authorize_apple_pay_jcb
+ stub_comms do
+ @gateway.authorize(100, @apple_pay_jcb, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '007', request['paymentInformation']['tokenizedCard']['type']
+ assert_equal '1', request['paymentInformation']['tokenizedCard']['transactionType']
+ assert_nil request['paymentInformation']['tokenizedCard']['requestorId']
+ assert_equal '001', request['processingInformation']['paymentSolution']
+ assert_nil request['processingInformation']['commerceIndicator']
+ assert_include request['consumerAuthenticationInformation'], 'cavv'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_url_building
+ assert_equal "#{@gateway.class.test_url}/pts/v2/action", @gateway.send(:url, 'action')
+ end
+
+ def test_stored_credential_cit_initial
+ @options[:stored_credential] = stored_credential(:cardholder, :internet, :initial)
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'internet', request['processingInformation']['commerceIndicator']
+ assert_equal 'customer', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_stored_credential_recurring_cit
+ @options[:stored_credential] = stored_credential(:cardholder, :recurring)
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'recurring', request['processingInformation']['commerceIndicator']
+ assert_equal 'customer', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_stored_credential_recurring_mit_ntid
+ @options[:stored_credential] = stored_credential(:merchant, :recurring, ntid: '123456789619999')
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'recurring', request['processingInformation']['commerceIndicator']
+ assert_equal 'merchant', request.dig('processingInformation', 'authorizationOptions', 'initiator', 'type')
+ assert_equal true, request.dig('processingInformation', 'authorizationOptions', 'initiator', 'storedCredentialUsed')
+ assert_nil request.dig('processingInformation', 'authorizationOptions', 'initiator', 'merchantInitiatedTransaction', 'originalAuthorizedAmount')
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_credit_card_purchase_single_request_ignore_avs
+ stub_comms do
+ options = @options.merge(ignore_avs: true)
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_equal json_body['processingInformation']['authorizationOptions']['ignoreAvsResult'], 'true'
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_without_ignore_avs
+ stub_comms do
+ # globally ignored AVS for gateway instance:
+ options = @options.merge(ignore_avs: false)
+ @gateway.options[:ignore_avs] = true
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_ignore_ccv
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: true))
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_equal json_body['processingInformation']['authorizationOptions']['ignoreCvResult'], 'true'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_credit_card_purchase_single_request_without_ignore_ccv
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: false))
+ end.check_request do |_endpoint, request_body, _headers|
+ json_body = JSON.parse(request_body)
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreAvsResult']
+ assert_nil json_body['processingInformation']['authorizationOptions']['ignoreCvResult']
+ end.respond_with(successful_purchase_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|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_capture_includes_mdd_fields
+ stub_comms do
+ @gateway.capture(100, '1846925324700976124593', order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_capture_response)
+ end
+
+ def test_credit_includes_mdd_fields
+ stub_comms do
+ @gateway.credit(@amount, @credit_card, mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['merchantDefinedInformation'][0]['key'], 'mdd_field_2'
+ assert_equal json_data['merchantDefinedInformation'][0]['value'], 'CustomValue2'
+ assert_equal json_data['merchantDefinedInformation'].count, 2
+ end.respond_with(successful_credit_response)
+ end
+
+ def test_authorize_includes_reconciliation_id
+ stub_comms do
+ @gateway.authorize(100, @credit_card, order_id: '1', reconciliation_id: '181537')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['clientReferenceInformation']['reconciliationId'], '181537'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_bank_account_purchase_includes_sec_code
+ stub_comms do
+ @gateway.purchase(@amount, @bank_account, order_id: '1', sec_code: 'WEB')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['processingInformation']['bankTransferOptions']['secCode'], 'WEB'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_includes_invoice_number
+ stub_comms do
+ @gateway.purchase(100, @credit_card, invoice_number: '1234567')
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['orderInformation']['invoiceDetails']['invoiceNumber'], '1234567'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_mastercard_purchase_with_3ds2
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y',
+ cavv_algorithm: '2'
+ }
+ stub_comms do
+ @gateway.purchase(100, @master_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['consumerAuthenticationInformation']['ucafAuthenticationData'], '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ assert_equal json_data['consumerAuthenticationInformation']['ucafCollectionIndicator'], '2'
+ assert_equal json_data['consumerAuthenticationInformation']['cavvAlgorithm'], '2'
+ assert_equal json_data['consumerAuthenticationInformation']['paSpecificationVersion'], '2.2.0'
+ assert_equal json_data['consumerAuthenticationInformation']['directoryServerTransactionID'], 'ODUzNTYzOTcwODU5NzY3Qw=='
+ assert_equal json_data['consumerAuthenticationInformation']['eciRaw'], '05'
+ assert_equal json_data['consumerAuthenticationInformation']['xid'], '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ assert_equal json_data['consumerAuthenticationInformation']['veresEnrolled'], 'true'
+ assert_equal json_data['consumerAuthenticationInformation']['paresStatus'], 'Y'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_visa_purchase_with_3ds2
+ @options[:three_d_secure] = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y',
+ cavv_algorithm: '2'
+ }
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['consumerAuthenticationInformation']['cavv'], '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ assert_equal json_data['consumerAuthenticationInformation']['cavvAlgorithm'], '2'
+ assert_equal json_data['consumerAuthenticationInformation']['paSpecificationVersion'], '2.2.0'
+ assert_equal json_data['consumerAuthenticationInformation']['directoryServerTransactionID'], 'ODUzNTYzOTcwODU5NzY3Qw=='
+ assert_equal json_data['consumerAuthenticationInformation']['eciRaw'], '05'
+ assert_equal json_data['consumerAuthenticationInformation']['xid'], '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ assert_equal json_data['consumerAuthenticationInformation']['veresEnrolled'], 'true'
+ assert_equal json_data['consumerAuthenticationInformation']['paresStatus'], 'Y'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_adds_application_id_as_partner_solution_id
+ partner_id = 'partner_id'
+ CyberSourceRestGateway.application_id = partner_id
+
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ json_data = JSON.parse(data)
+ assert_equal json_data['clientReferenceInformation']['partner']['solutionId'], partner_id
+ end.respond_with(successful_purchase_response)
+ ensure
+ CyberSourceRestGateway.application_id = nil
+ end
+
+ def test_purchase_with_level_2_data
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options.merge({ purchase_order_number: '13829012412' }))
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '13829012412', request['orderInformation']['invoiceDetails']['purchaseOrderNumber']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_level_3_data
+ options = {
+ purchase_order_number: '6789',
+ discount_amount: '150',
+ ships_from_postal_code: '90210',
+ line_items: [
+ {
+ productName: 'Product Name',
+ kind: 'debit',
+ quantity: 10,
+ unitPrice: '9.5000',
+ totalAmount: '95.00',
+ taxAmount: '5.00',
+ discountAmount: '0.00',
+ productCode: '54321',
+ commodityCode: '98765'
+ },
+ {
+ productName: 'Other Product Name',
+ kind: 'debit',
+ quantity: 1,
+ unitPrice: '2.5000',
+ totalAmount: '90.00',
+ taxAmount: '2.00',
+ discountAmount: '1.00',
+ productCode: '54322',
+ commodityCode: '98766'
+ }
+ ]
+ }
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options.merge(options))
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal '3', request['processingInformation']['purchaseLevel']
+ assert_equal '150', request['orderInformation']['amountDetails']['discountAmount']
+ assert_equal '90210', request['orderInformation']['shipping_details']['shipFromPostalCode']
+ end.respond_with(successful_purchase_response)
+ end
+
+ private
+
+ def parse_signature(signature)
+ signature.gsub(/=\"$/, '').delete('"').split(', ').map { |x| x.split('=') }.to_h
+ end
+
+ def pre_scrubbed
+ <<-PRE
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"08c94330-f618-42a3-b09d-e1e43be5efda\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"DJHeHWceVrsJydd8BCbGowr9dzQ/ry5cGN1FocLakEw=\"\r\nDigest: SHA-256=wuV1cxGzs6KpuUKJmlD7pKV6MZ/5G1wQVoYbf8cRChM=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"4111111111111111\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"987\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}"
+ PRE
+ end
+
+ def post_scrubbed
+ <<-POST
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"[FILTERED]\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"[FILTERED]\"\r\nDigest: SHA-256=[FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"paymentInformation\":{\"card\":{\"number\":\"[FILTERED]\",\"expirationMonth\":\"12\",\"expirationYear\":\"2031\",\"securityCode\":\"[FILTERED]\",\"type\":\"001\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"},\"shipTo\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"email\":\"test@cybs.com\"}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/6750124114786780104953\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/6750124114786780104953/captures\"}},\"clientReferenceInformation\":{\"code\":\"b8779865d140125036016a0f85db907f\"},\"id\":\"6750124114786780104953\",\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"type\":\"001\"},\"card\":{\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"111111\"},\"processorInformation\":{\"approvalCode\":\"888888\",\"networkTransactionId\":\"123456789619999\",\"transactionId\":\"123456789619999\",\"responseCode\":\"100\",\"avs\":{\"code\":\"X\",\"codeRaw\":\"I1\"}},\"reconciliationId\":\"78243988SD9YL291\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2023-01-29T17:13:31Z\"}"
+ POST
+ end
+
+ def pre_scrubbed_nt
+ <<-PRE
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"08c94330-f618-42a3-b09d-e1e43be5efda\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"DJHeHWceVrsJydd8BCbGowr9dzQ/ry5cGN1FocLakEw=\"\r\nDigest: SHA-256=wuV1cxGzs6KpuUKJmlD7pKV6MZ/5G1wQVoYbf8cRChM=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"paymentInformation\":{\"tokenizedCard\":{\"number\":\"4111111111111111\",\"expirationMonth\":9,\"expirationYear\":2025,\"cryptogram\":\"EHuWW9PiBkWvqE5juRwDzAUFBAk=\",\"type\":\"001\",\"transactionType\":\"3\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"}},\"processingInformation\":{\"commerceIndicator\":\"internet\",\"paymentSolution\":\"015\",\"authorizationOptions\":{}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/7145981349676498704951\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/captures\"}},\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"id\":\"7145981349676498704951\",\"issuerInformation\":{\"responseRaw\":\"0110322000000E10000200000000000001022105012115353420253130383141564D334B5953323833313030303030000159008000223134573031363135303730333830323039344730363400103232415050524F56414C00065649435243200034544B54523031313132313231323132313231544C3030323636504E30303431313131\"},\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"requestorId\":\"12121212121\",\"assuranceLevel\":\"66\",\"type\":\"001\"},\"card\":{\"suffix\":\"1111\",\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"01234567\"},\"processorInformation\":{\"merchantNumber\":\"000123456789012\",\"approvalCode\":\"831000\",\"networkTransactionId\":\"016150703802094\",\"transactionId\":\"016150703802094\",\"responseCode\":\"00\",\"avs\":{\"code\":\"Y\",\"codeRaw\":\"Y\"}},\"reconciliationId\":\"1081AVM3KYS2\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2024-05-01T21:15:35Z\"}"
+ PRE
+ end
+
+ def post_scrubbed_nt
+ <<-POST
+ <- "POST /pts/v2/payments/ HTTP/1.1\r\nContent-Type: application/json;charset=utf-8\r\nAccept: application/hal+json;charset=utf-8\r\nV-C-Merchant-Id: testrest\r\nDate: Sun, 29 Jan 2023 17:13:30 GMT\r\nHost: apitest.cybersource.com\r\nSignature: keyid=\"[FILTERED]\", algorithm=\"HmacSHA256\", headers=\"host date (request-target) digest v-c-merchant-id\", signature=\"[FILTERED]\"\r\nDigest: SHA-256=[FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nContent-Length: 584\r\n\r\n"
+ <- "{\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"paymentInformation\":{\"tokenizedCard\":{\"number\":\"[FILTERED]\",\"expirationMonth\":9,\"expirationYear\":2025,\"cryptogram\":\"[FILTERED]\",\"type\":\"001\",\"transactionType\":\"3\"}},\"orderInformation\":{\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"},\"billTo\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address1\":\"1 Market St\",\"locality\":\"san francisco\",\"administrativeArea\":\"CA\",\"postalCode\":\"94105\",\"country\":\"US\",\"email\":\"test@cybs.com\",\"phoneNumber\":\"4158880000\"}},\"processingInformation\":{\"commerceIndicator\":\"internet\",\"paymentSolution\":\"015\",\"authorizationOptions\":{}}}"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Cache-Control: no-cache, no-store, must-revalidate\r\n"
+ -> "Pragma: no-cache\r\n"
+ -> "Expires: -1\r\n"
+ -> "Strict-Transport-Security: max-age=31536000\r\n"
+ -> "Content-Type: application/hal+json\r\n"
+ -> "Content-Length: 905\r\n"
+ -> "x-response-time: 291ms\r\n"
+ -> "X-OPNET-Transaction-Trace: 0b1f2bd7-9545-4939-9478-4b76cf7199b6\r\n"
+ -> "Connection: close\r\n"
+ -> "v-c-correlation-id: 42969bf5-a77d-4035-9d09-58d4ca070e8c\r\n"
+ -> "\r\n"
+ reading 905 bytes...
+ -> "{\"_links\":{\"authReversal\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/reversals\"},\"self\":{\"method\":\"GET\",\"href\":\"/pts/v2/payments/7145981349676498704951\"},\"capture\":{\"method\":\"POST\",\"href\":\"/pts/v2/payments/7145981349676498704951/captures\"}},\"clientReferenceInformation\":{\"code\":\"ba20ae354e25edd1a5ab27158c0a2955\"},\"id\":\"7145981349676498704951\",\"issuerInformation\":{\"responseRaw\":\"0110322000000E10000200000000000001022105012115353420253130383141564D334B5953323833313030303030000159008000223134573031363135303730333830323039344730363400103232415050524F56414C00065649435243200034544B54523031313132313231323132313231544C3030323636504E30303431313131\"},\"orderInformation\":{\"amountDetails\":{\"authorizedAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentAccountInformation\":{\"card\":{\"type\":\"001\"}},\"paymentInformation\":{\"tokenizedCard\":{\"requestorId\":\"12121212121\",\"assuranceLevel\":\"66\",\"type\":\"001\"},\"card\":{\"suffix\":\"1111\",\"type\":\"001\"}},\"pointOfSaleInformation\":{\"terminalId\":\"01234567\"},\"processorInformation\":{\"merchantNumber\":\"000123456789012\",\"approvalCode\":\"831000\",\"networkTransactionId\":\"016150703802094\",\"transactionId\":\"016150703802094\",\"responseCode\":\"00\",\"avs\":{\"code\":\"Y\",\"codeRaw\":\"Y\"}},\"reconciliationId\":\"1081AVM3KYS2\",\"status\":\"AUTHORIZED\",\"submitTimeUtc\":\"2024-05-01T21:15:35Z\"}"
+ POST
+ end
+
+ def successful_purchase_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "authReversal": {
+ "method": "POST",
+ "href": "/pts/v2/payments/6750124114786780104953/reversals"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/payments/6750124114786780104953"
+ },
+ "capture": {
+ "method": "POST",
+ "href": "/pts/v2/payments/6750124114786780104953/captures"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "b8779865d140125036016a0f85db907f"
+ },
+ "id": "6750124114786780104953",
+ "orderInformation": {
+ "amountDetails": {
+ "authorizedAmount": "102.21",
+ "currency": "USD"
+ }
+ },
+ "paymentAccountInformation": {
+ "card": {
+ "type": "001"
+ }
+ },
+ "paymentInformation": {
+ "tokenizedCard": {
+ "type": "001"
+ },
+ "card": {
+ "type": "001"
+ }
+ },
+ "pointOfSaleInformation": {
+ "terminalId": "111111"
+ },
+ "processorInformation": {
+ "approvalCode": "888888",
+ "networkTransactiDDDonId": "123456789619999",
+ "transactionId": "123456789619999",
+ "responseCode": "100",
+ "avs": {
+ "code": "X",
+ "codeRaw": "I1"
+ }
+ },
+ "reconciliationId": "78243988SD9YL291",
+ "status": "AUTHORIZED",
+ "submitTimeUtc": "2023-01-29T17:13:31Z"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "void": {
+ "method": "POST",
+ "href": "/pts/v2/captures/6799471903876585704951/voids"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/captures/6799471903876585704951"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "TC50171_3"
+ },
+ "id": "6799471903876585704951",
+ "orderInformation": {
+ "amountDetails": {
+ "totalAmount": "102.21",
+ "currency": "USD"
+ }
+ },
+ "reconciliationId": "78243988SD9YL291",
+ "status": "PENDING",
+ "submitTimeUtc": "2023-03-27T19:59:50Z"
+ }
+ RESPONSE
+ end
+
+ def successful_credit_response
+ <<-RESPONSE
+ {
+ "_links": {
+ "void": {
+ "method": "POST",
+ "href": "/pts/v2/credits/6799499091686234304951/voids"
+ },
+ "self": {
+ "method": "GET",
+ "href": "/pts/v2/credits/6799499091686234304951"
+ }
+ },
+ "clientReferenceInformation": {
+ "code": "12345678"
+ },
+ "creditAmountDetails": {
+ "currency": "usd",
+ "creditAmount": "200.00"
+ },
+ "id": "6799499091686234304951",
+ "orderInformation": {
+ "amountDetails": {
+ "currency": "usd"
+ }
+ },
+ "paymentAccountInformation": {
+ "card": {
+ "type": "001"
+ }
+ },
+ "paymentInformation": {
+ "tokenizedCard": {
+ "type": "001"
+ },
+ "card": {
+ "type": "001"
+ }
+ },
+ "processorInformation": {
+ "approvalCode": "888888",
+ "responseCode": "100"
+ },
+ "reconciliationId": "70391830ZFKZI570",
+ "status": "PENDING",
+ "submitTimeUtc": "2023-03-27T20:45:09Z"
+ }
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb
index 3e5c97e29b5..4b6fb1c914a 100644
--- a/test/unit/gateways/cyber_source_test.rb
+++ b/test/unit/gateways/cyber_source_test.rb
@@ -18,6 +18,30 @@ def setup
@master_credit_card = credit_card('4111111111111111', brand: 'master')
@elo_credit_card = credit_card('5067310000000010', brand: 'elo')
@declined_card = credit_card('801111111111111', brand: 'visa')
+ @network_token = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :network_token)
+ @network_token_mastercard = network_tokenization_credit_card('5555555555554444',
+ brand: 'master',
+ transaction_id: '123',
+ eci: '05',
+ source: :network_token,
+ payment_cryptogram: '111111111100cryptogram')
+ @amex_network_token = network_tokenization_credit_card('378282246310005',
+ brand: 'american_express',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :network_token)
+ @apple_pay = network_tokenization_credit_card('4111111111111111',
+ brand: 'visa',
+ transaction_id: '123',
+ eci: '05',
+ payment_cryptogram: '111111111100cryptogram',
+ source: :apple_pay)
+ @google_pay = network_tokenization_credit_card('4242424242424242', source: :google_pay)
@check = check()
@options = {
@@ -34,7 +58,8 @@ def setup
national_tax: '5'
}
],
- currency: 'USD'
+ currency: 'USD',
+ reconciliation_id: '181537'
}
@subscription_options = {
@@ -66,8 +91,9 @@ def test_successful_credit_card_purchase
def test_successful_purchase_with_other_tax_fields
stub_comms do
- @gateway.purchase(100, @credit_card, @options.merge(national_tax_indicator: 1, vat_tax_rate: 1.01))
+ @gateway.purchase(100, @credit_card, @options.merge!(national_tax_indicator: 1, vat_tax_rate: 1.01, merchant_id: 'MerchantId'))
end.check_request do |_endpoint, data, _headers|
+ assert_match(/MerchantId<\/merchantID>/, data)
assert_match(/\s+1.01<\/vatTaxRate>\s+1<\/nationalTaxIndicator>\s+<\/otherTax>/m, data)
end.respond_with(successful_purchase_response)
end
@@ -97,6 +123,22 @@ def test_successful_authorize_with_cc_auth_service_fields
end.respond_with(successful_authorization_response)
end
+ def test_successful_authorize_with_cc_auth_service_first_recurring_payment
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options.merge(first_recurring_payment: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/true<\/firstRecurringPayment>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
+ def test_successful_authorize_with_cc_auth_service_aggregator_id
+ stub_comms do
+ @gateway.authorize(100, @credit_card, @options.merge(aggregator_id: 'ABCDE'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/ABCDE<\/aggregatorID>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
def test_successful_credit_card_purchase_with_elo
@gateway.expects(:ssl_post).returns(successful_purchase_response)
@@ -134,7 +176,7 @@ def test_purchase_includes_mdd_fields
def test_purchase_includes_reconciliation_id
stub_comms do
- @gateway.purchase(100, @credit_card, order_id: '1', reconciliation_id: '181537')
+ @gateway.purchase(100, @credit_card, @options.merge(order_id: '1'))
end.check_request do |_endpoint, data, _headers|
assert_match(/181537<\/reconciliationID>/, data)
end.respond_with(successful_purchase_response)
@@ -214,6 +256,22 @@ def test_purchase_includes_invoice_header
end.respond_with(successful_purchase_response)
end
+ def test_purchase_with_apple_pay_includes_payment_solution_001
+ stub_comms do
+ @gateway.purchase(100, @apple_pay)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/001<\/paymentSolution>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_with_google_pay_includes_payment_solution_012
+ stub_comms do
+ @gateway.purchase(100, @google_pay)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/012<\/paymentSolution>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
def test_purchase_includes_tax_management_indicator
stub_comms do
@gateway.purchase(100, @credit_card, tax_management_indicator: 3)
@@ -222,6 +280,22 @@ def test_purchase_includes_tax_management_indicator
end.respond_with(successful_purchase_response)
end
+ def test_auth_includes_gratuity_amount
+ stub_comms do
+ @gateway.authorize(100, @credit_card, gratuity_amount: '7.50')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/7.50<\/gratuityAmount>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_purchase_includes_gratuity_amount
+ stub_comms do
+ @gateway.purchase(100, @credit_card, gratuity_amount: '7.50')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/7.50<\/gratuityAmount>/, 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)
@@ -240,7 +314,7 @@ def test_authorize_includes_mdd_fields
def test_authorize_includes_reconciliation_id
stub_comms do
- @gateway.authorize(100, @credit_card, order_id: '1', reconciliation_id: '181537')
+ @gateway.authorize(100, @credit_card, @options.merge(order_id: '1'))
end.check_request do |_endpoint, data, _headers|
assert_match(/181537<\/reconciliationID>/, data)
end.respond_with(successful_authorization_response)
@@ -280,6 +354,22 @@ def test_authorize_includes_customer_id
end.respond_with(successful_authorization_response)
end
+ def test_authorize_with_apple_pay_includes_payment_solution_001
+ stub_comms do
+ @gateway.authorize(100, @apple_pay)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/001<\/paymentSolution>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
+ def test_authorize_with_google_pay_includes_payment_solution_012
+ stub_comms do
+ @gateway.authorize(100, @google_pay)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/012<\/paymentSolution>/, data)
+ end.respond_with(successful_authorization_response)
+ end
+
def test_authorize_includes_merchant_tax_id_in_billing_address_but_not_shipping_address
stub_comms do
@gateway.authorize(100, @credit_card, order_id: '1', merchant_tax_id: '123')
@@ -345,6 +435,18 @@ def test_successful_credit_cart_purchase_single_request_ignore_avs
assert_success response
end
+ def test_successful_network_token_purchase_single_request_ignore_avs
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_match %r'true', request_body
+ assert_not_match %r'', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ options = @options.merge(ignore_avs: true)
+ assert response = @gateway.purchase(@amount, @network_token, options)
+ 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'', request_body
@@ -377,9 +479,19 @@ def test_successful_credit_cart_purchase_single_request_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: true
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: true))
+ assert_success response
+ end
+
+ def test_successful_network_token_purchase_single_request_ignore_cvv
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_match %r'true', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ options = @options.merge(ignore_cvv: true)
+ assert response = @gateway.purchase(@amount, @network_token, options)
assert_success response
end
@@ -390,9 +502,7 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: false
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: false))
assert_success response
@gateway.expects(:ssl_post).with do |_host, request_body|
@@ -401,9 +511,50 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_ccv
true
end.returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
- ignore_cvv: 'false'
- ))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: 'false'))
+ assert_success response
+ end
+
+ def test_successful_apple_pay_purchase_subsequent_auth_visa
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_not_match %r'', request_body
+ assert_match %r'internet', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, @apple_pay, options)
+ assert_success response
+ end
+
+ def test_successful_apple_pay_purchase_subsequent_auth_mastercard
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_not_match %r'', request_body
+ assert_match %r'internet', 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',
+ source: :apple_pay)
+ options = @options.merge({
+ stored_credential: {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: '016150703802094'
+ }
+ })
+ assert response = @gateway.purchase(@amount, credit_card, options)
assert_success response
end
@@ -442,6 +593,24 @@ def test_successful_auth_request
assert response.test?
end
+ def test_successful_reconciliation_id_2
+ @gateway.stubs(:ssl_post).returns(successful_purchase_and_capture_response)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal response.params['reconciliationID'], 'abcdf'
+ assert_equal response.params['reconciliationID2'], '31159291T3XM2B13'
+ assert response.success?
+ assert response.test?
+ end
+
+ def test_successful_authorization_without_reconciliation_id_2
+ @gateway.stubs(:ssl_post).returns(successful_authorization_response)
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal response.params['reconciliationID2'], nil
+ assert_equal response.params['reconciliationID'], '23439130C40VZ2FB'
+ assert response.success?
+ 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)
@@ -814,31 +983,21 @@ def test_unsuccessful_verify
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)
+ @gateway.authorize(@amount, @network_token, @options)
end.check_request do |_endpoint, body, _headers|
assert_xml_valid_to_xsd(body)
- assert_match %r'\n 111111111100cryptogram\n vbv\n 111111111100cryptogram\n\n\n 1\n', body
+ assert_match %r(111111111100cryptogram), body
+ assert_match %r(internet), body
+ assert_match %r(3), 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)
+ @gateway.purchase(@amount, @network_token, @options)
end.check_request do |_endpoint, body, _headers|
assert_xml_valid_to_xsd(body)
assert_match %r'.+?'m, body
@@ -848,34 +1007,76 @@ def test_successful_purchase_with_network_tokenization_for_visa
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'\n 111111111100cryptogram\n 2\n\n\n spa\n\n\n 1\n', request_body
+ @gateway.expects(:ssl_post).with do |_host, body|
+ assert_xml_valid_to_xsd(body)
+ assert_match %r(111111111100cryptogram), body
+ assert_match %r(internet), body
+ assert_match %r(3), body
+ assert_match %r(trid_123), body
+ assert_match %r(014), body
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('5555555555554444',
+ credit_card = network_tokenization_credit_card(
+ '5555555555554444',
brand: 'master',
transaction_id: '123',
eci: '05',
- payment_cryptogram: '111111111100cryptogram')
+ payment_cryptogram: '111111111100cryptogram',
+ source: :network_token
+ )
- assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert response = @gateway.authorize(@amount, credit_card, @options.merge!(trid: 'trid_123'))
+ assert_success response
+ end
+
+ def test_successful_purchase_network_tokenization_mastercard
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_xml_valid_to_xsd(request_body)
+ assert_match %r'111111111100cryptogram', request_body
+ assert_match %r'internet', request_body
+ assert_match %r'014', request_body
+ assert_not_match %r'111111111100cryptogram', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ assert response = @gateway.purchase(@amount, @network_token_mastercard, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_network_tokenization_amex
+ @gateway.expects(:ssl_post).with do |_host, request_body|
+ assert_xml_valid_to_xsd(request_body)
+ assert_match %r'111111111100cryptogram', request_body
+ assert_match %r'internet', request_body
+ assert_not_match %r'014', request_body
+ assert_not_match %r'015', request_body
+ true
+ end.returns(successful_purchase_response)
+
+ assert response = @gateway.purchase(@amount, @amex_network_token, @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'\n MTExMTExMTExMTAwY3J5cHRvZ3I=\n\n aesk\n YW0=\n\n\n\n 1\n', request_body
+ assert_match %r'MTExMTExMTExMTAwY3J5cHRvZ3JhbQ==\n', request_body
+ assert_match %r'internet', request_body
+ assert_not_match %r'014', request_body
+ assert_not_match %r'015', request_body
+ assert_match %r'181537', request_body
true
end.returns(successful_purchase_response)
- credit_card = network_tokenization_credit_card('378282246310005',
+ credit_card = network_tokenization_credit_card(
+ '378282246310005',
brand: 'american_express',
transaction_id: '123',
eci: '05',
- payment_cryptogram: Base64.encode64('111111111100cryptogram'))
+ payment_cryptogram: Base64.encode64('111111111100cryptogram'),
+ source: :network_token
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
@@ -1031,6 +1232,214 @@ def test_nonfractional_currency_handling
assert_success response
end
+ # CITs/MITs For Network Tokens
+
+ def test_cit_unscheduled_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: true
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_match(/\true/, data)
+ assert_match(/\internet/, data)
+ assert_not_match(/\/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_mit_unscheduled_network_token
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\true/, data)
+ assert_match(/\true/, data)
+ assert_match(/\true/, data)
+ assert_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_subsequent_cit_unscheduled_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\true/, data)
+ assert_match(/\true/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_cit_installment_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'installment',
+ initial_transaction: true
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_match(/\true/, data)
+ assert_match(/\internet/, data)
+ assert_not_match(/\/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_mit_installment_network_token
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'installment',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\true/, data)
+ assert_match(/\true/, data)
+ assert_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_subsequent_cit_installment_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'installment',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\/, data)
+ assert_match(/\true/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_cit_recurring_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'recurring',
+ initial_transaction: true
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_match(/\true/, data)
+ assert_match(/\internet/, data)
+ assert_not_match(/\/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_mit_recurring_network_token
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\true/, data)
+ assert_match(/\true/, data)
+ assert_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ def test_subsequent_cit_recurring_network_token
+ @options[:stored_credential] = {
+ initiator: 'cardholder',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+ response = stub_comms do
+ @gateway.authorize(@amount, @network_token, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\181537/, data)
+ assert_match(/\111111111100cryptogram/, data)
+ assert_match(/\015/, data)
+ assert_match(/\3/, data)
+ assert_not_match(/\/, data)
+ assert_match(/\true/, data)
+ assert_not_match(/\true/, data)
+ assert_not_match(/\016150703802094/, data)
+ assert_match(/\internet/, data)
+ end.respond_with(successful_authorization_response)
+ assert response.success?
+ end
+
+ # CITs/MITs for Network Tokens
+
def test_malformed_xml_handling
@gateway.expects(:ssl_post).returns(malformed_xml_response)
@@ -1302,10 +1711,90 @@ def test_does_not_add_cavv_as_xid_if_xid_is_present
end.respond_with(successful_purchase_response)
end
+ def test_add_3ds_exemption_fields_except_stored_credential
+ CyberSourceGateway::THREEDS_EXEMPTIONS.keys.reject { |k| k == :stored_credential }.each do |exemption|
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options_with_normalized_3ds, three_ds_exemption_type: exemption.to_s, merchant_id: 'test', billing_address: {
+ 'address1' => '221B Baker Street',
+ 'city' => 'London',
+ 'zip' => 'NW16XE',
+ 'country' => 'GB'
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ # billing details
+ assert_match(%r(\n), data)
+ assert_match(%r(Longbob), data)
+ assert_match(%r(Longsen), data)
+ assert_match(%r(221B Baker Street), data)
+ assert_match(%r(London), data)
+ assert_match(%r(NW16XE), data)
+ assert_match(%r(GB), data)
+ # card details
+ assert_match(%r(\n), data)
+ assert_match(%r(4111111111111111), data)
+ assert_match(%r(#{@gateway.format(@credit_card.month, :two_digits)}), data)
+ assert_match(%r(#{@gateway.format(@credit_card.year, :four_digits)}), data)
+ # merchant data
+ assert_match(%r(test), data)
+ assert_match(%r(#{@options[:order_id]}), data)
+ # amount data
+ assert_match(%r(\n), data)
+ assert_match(%r(#{@gateway.send(:localized_amount, @amount.to_i, @options[:currency])}), data)
+ # 3ds exemption tag
+ assert_match %r(\n), data
+ assert_match(%r(<#{CyberSourceGateway::THREEDS_EXEMPTIONS[exemption]}>1#{CyberSourceGateway::THREEDS_EXEMPTIONS[exemption]}>), data)
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
+ def test_add_stored_credential_3ds_exemption
+ @options[:stored_credential] = {
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ initial_transaction: false,
+ network_transaction_id: '016150703802094'
+ }
+
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options_with_normalized_3ds, three_ds_exemption_type: CyberSourceGateway::THREEDS_EXEMPTIONS[:stored_credential], merchant_id: 'test', billing_address: {
+ 'address1' => '221B Baker Street',
+ 'city' => 'London',
+ 'zip' => 'NW16XE',
+ 'country' => 'GB'
+ }))
+ end.check_request do |_endpoint, data, _headers|
+ # billing details
+ assert_match(%r(\n), data)
+ assert_match(%r(Longbob), data)
+ assert_match(%r(Longsen), data)
+ assert_match(%r(221B Baker Street), data)
+ assert_match(%r(London), data)
+ assert_match(%r(NW16XE), data)
+ assert_match(%r(GB), data)
+ # card details
+ assert_match(%r(\n), data)
+ assert_match(%r(4111111111111111), data)
+ assert_match(%r(#{@gateway.format(@credit_card.month, :two_digits)}), data)
+ assert_match(%r(#{@gateway.format(@credit_card.year, :four_digits)}), data)
+ # merchant data
+ assert_match(%r(test), data)
+ assert_match(%r(#{@options[:order_id]}), data)
+ # amount data
+ assert_match(%r(\n), data)
+ assert_match(%r(#{@gateway.send(:localized_amount, @amount.to_i, @options[:currency])}), data)
+ # 3ds exemption tag
+ assert_match(%r(true), data)
+ end.respond_with(successful_purchase_response)
+ end
+
def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_scrub_network_token
+ assert_equal @gateway.scrub(pre_scrubbed_network_token), post_scrubbed_network_token
+ end
+
def test_supports_scrubbing?
assert @gateway.supports_scrubbing?
end
@@ -1408,11 +1897,41 @@ def test_invalid_field
assert_equal 'c:billTo/c:postalCode', response.params['invalidField']
end
+ def test_cvv_mismatch_successful_auto_void
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void).once.returns(ActiveMerchant::Billing::Response.new(true, 'Transaction successful'))
+
+ response = @gateway.authorize(@amount, credit_card, @options.merge!(auto_void_230: true))
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check - transaction has been auto-voided.', response.message
+ end
+
+ def test_cvv_mismatch
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void).never
+
+ response = @gateway.purchase(@amount, credit_card, @options)
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check', response.message
+ end
+
+ def test_cvv_mismatch_auto_void_failed
+ @gateway.expects(:ssl_post).returns(cvv_mismatch_response)
+ @gateway.expects(:void)
+ response = @gateway.purchase(@amount, credit_card, @options.merge!(auto_void_230: true))
+
+ assert_failure response
+ assert_equal '230', response.params['reasonCode']
+ assert_equal 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check - transaction could not be auto-voided.', response.message
+ end
+
def test_able_to_properly_handle_40bytes_cryptogram
long_cryptogram = "NZwc40C4eTDWHVDXPekFaKkNYGk26w+GYDZmU50cATbjqOpNxR/eYA==\n"
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'american_express',
- payment_cryptogram: long_cryptogram)
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'american_express', payment_cryptogram: long_cryptogram)
stub_comms do
@gateway.authorize(@amount, credit_card, @options)
@@ -1426,9 +1945,7 @@ def test_able_to_properly_handle_40bytes_cryptogram
end
def test_able_to_properly_handle_20bytes_cryptogram
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'american_express',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'american_express', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
stub_comms do
@gateway.authorize(@amount, credit_card, @options)
@@ -1439,27 +1956,37 @@ def test_able_to_properly_handle_20bytes_cryptogram
end
end
- def test_raises_error_on_network_token_with_an_underlying_discover_card
- error = assert_raises ArgumentError do
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'discover',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ def test_returns_error_on_network_token_with_an_underlying_discover_card
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'discover', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', source: :network_token)
+ response = @gateway.authorize(100, credit_card, @options)
- @gateway.authorize(100, credit_card, @options)
- end
- assert_equal 'Payment method discover is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html', error.message
+ assert_equal response.message, 'Discover is not supported by NetworkToken at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html'
end
- def test_raises_error_on_network_token_with_an_underlying_apms
- error = assert_raises ArgumentError do
- credit_card = network_tokenization_credit_card('4111111111111111',
- brand: 'sodexo',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=')
+ def test_returns_error_on_apple_pay_with_an_underlying_discover_card
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'discover', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', source: :apple_pay)
+ response = @gateway.purchase(100, credit_card, @options)
- @gateway.authorize(100, credit_card, @options)
- end
+ assert_equal response.message, 'Discover is not supported by ApplePay at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html'
+ end
+
+ def test_returns_error_on_google_pay_with_an_underlying_discover_card
+ credit_card = network_tokenization_credit_card('4111111111111111', brand: 'discover', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', source: :google_pay)
+ response = @gateway.store(credit_card, @options)
- assert_equal 'Payment method sodexo is not supported, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html', error.message
+ assert_equal response.message, 'Discover is not supported by GooglePay at CyberSource, check https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/CreatingOnlineAuth/CreatingAuthReqPNT.html'
+ end
+
+ def test_routing_number_formatting_with_regular_routing_number
+ assert_equal @gateway.send(:format_routing_number, '012345678', { currency: 'USD' }), '012345678'
+ end
+
+ def test_routing_number_formatting_with_canadian_routing_number
+ assert_equal @gateway.send(:format_routing_number, '12345678', { currency: 'USD' }), '12345678'
+ end
+
+ def test_routing_number_formatting_with_canadian_routing_number_and_padding
+ assert_equal @gateway.send(:format_routing_number, '012345678', { currency: 'CAD' }), '12345678'
end
private
@@ -1521,6 +2048,54 @@ def pre_scrubbed
PRE_SCRUBBED
end
+ def pre_scrubbed_network_token
+ <<-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"
+ <- "\n\n \n \n \n l\n p\n \n \n \n \n \n l\n 1000\n Ruby Active Merchant\n 1.135.0\n arm64-darwin22\n\n Longbob\n Longsen\n Unspecified\n Unspecified\n NC\n 00000\n US\n null@cybersource.com\n 127.0.0.1\n\n\n Longbob\n Longsen\n \n \n \n \n null@cybersource.com\n\n- \n 1.00\n 2\n default\n Giant Walrus\n WA323232323232323\n 10\n 5\n
\n\n USD\n 1.00\n\n\n 5555555555554444\n 09\n 2025\n 123\n 002\n\n\n 111111111100cryptogram\n internet\n\n\n\n\n trid_123\n 3\n\n014\n \n \n\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...
+ -> "\n\n2015-06-05T13:01:57.974Z734dda9bb6446f2f2638ab7faf34682f4335093172165000001515ACCEPT100Ahj//wSR1gMBn41YRu/WIkGLlo3asGzCbBky4VOjHT9/xXHSYBT9/xXHSbSA+RQkhk0ky3SA3+mwMCcjrAYDPxqwjd+sKWXLUSD1001.00888888XI12015-06-05T13:01:57Z10019475060MAIKBSQG1002015-06-05T13:01:57Z1.0019475060MAIKBSQG"
+ read 1572 bytes
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed_network_token
+ <<-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"
+ <- "\n\n \n \n \n l\n [FILTERED]\n \n \n \n \n \n l\n 1000\n Ruby Active Merchant\n 1.135.0\n arm64-darwin22\n\n Longbob\n Longsen\n Unspecified\n Unspecified\n NC\n 00000\n US\n null@cybersource.com\n 127.0.0.1\n\n\n Longbob\n Longsen\n \n \n \n \n null@cybersource.com\n\n- \n 1.00\n 2\n default\n Giant Walrus\n WA323232323232323\n 10\n 5\n
\n\n USD\n 1.00\n\n\n [FILTERED]\n 09\n 2025\n [FILTERED]\n 002\n\n\n [FILTERED]\n internet\n\n\n\n\n [FILTERED]\n 3\n\n014\n \n \n\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...
+ -> "\n\n2015-06-05T13:01:57.974Z734dda9bb6446f2f2638ab7faf34682f4335093172165000001515ACCEPT100Ahj//wSR1gMBn41YRu/WIkGLlo3asGzCbBky4VOjHT9/xXHSYBT9/xXHSbSA+RQkhk0ky3SA3+mwMCcjrAYDPxqwjd+sKWXLUSD1001.00888888XI12015-06-05T13:01:57Z10019475060MAIKBSQG1002015-06-05T13:01:57Z1.0019475060MAIKBSQG"
+ read 1572 bytes
+ Conn close
+ PRE_SCRUBBED
+ end
+
def post_scrubbed
<<-POST_SCRUBBED
opening connection to ics2wstest.ic3.com:443...
@@ -1553,6 +2128,14 @@ def successful_purchase_response
XML
end
+ def successful_purchase_and_capture_response
+ <<~XML
+
+
+ 2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00abcdf123456YYMM2008-01-15T21:42:03Z00U1002007-07-17T17:15:32Z1.0031159291T3XM2B13
+ XML
+ end
+
def successful_authorization_response
<<~XML
@@ -1776,6 +2359,15 @@ def invalid_field_response
XML
end
+ def cvv_mismatch_response
+ <<~XML
+
+
+ 2019-09-05T14:10:46.665Z5676926465076767004068REJECT230c:billTo/c:postalCodeAhjzbwSTM78uTleCsJWkEAJRqivRidukDssiQgRm0ky3SA7oegDUiwLm
+
+ XML
+ end
+
def invalid_xml_response
"What's all this then, govna?
"
end
diff --git a/test/unit/gateways/d_local_test.rb b/test/unit/gateways/d_local_test.rb
index 33ea7361c74..a1bf4c354ff 100644
--- a/test/unit/gateways/d_local_test.rb
+++ b/test/unit/gateways/d_local_test.rb
@@ -36,6 +36,14 @@ def test_successful_purchase
assert response.test?
end
+ def test_purchase_with_save
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(save: true))
+ end.check_request do |_endpoint, data, _headers|
+ assert_equal true, JSON.parse(data)['card']['save']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase
@gateway.expects(:ssl_post).returns(failed_purchase_response)
@@ -57,8 +65,7 @@ def test_purchase_with_installments
end
def test_purchase_with_network_tokens
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card)
end.check_request do |_endpoint, data, _headers|
@@ -68,9 +75,8 @@ def test_purchase_with_network_tokens
end
def test_purchase_with_network_tokens_and_store_credential_type_subscription
- options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, network_transaction_id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -81,9 +87,8 @@ def test_purchase_with_network_tokens_and_store_credential_type_subscription
end
def test_purchase_with_network_tokens_and_store_credential_type_uneschedule
- options = @options.merge!(stored_credential: stored_credential(:merchant, :unscheduled, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ options = @options.merge!(stored_credential: stored_credential(:merchant, :unscheduled, network_transaction_id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -95,8 +100,7 @@ def test_purchase_with_network_tokens_and_store_credential_type_uneschedule
def test_purchase_with_network_tokens_and_store_credential_usage_first
options = @options.merge!(stored_credential: stored_credential(:cardholder, :initial))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -107,9 +111,8 @@ def test_purchase_with_network_tokens_and_store_credential_usage_first
end
def test_purchase_with_network_tokens_and_store_credential_type_card_on_file_and_credential_usage_used
- options = @options.merge!(stored_credential: stored_credential(:cardholder, :unscheduled, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ options = @options.merge!(stored_credential: stored_credential(:cardholder, :unscheduled, network_transaction_id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -121,9 +124,8 @@ def test_purchase_with_network_tokens_and_store_credential_type_card_on_file_and
end
def test_purchase_with_network_tokens_and_store_credential_usage
- options = @options.merge!(stored_credential: stored_credential(:cardholder, :recurring, ntid: 'abc123'))
- credit_card = network_tokenization_credit_card('4242424242424242',
- payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ options = @options.merge!(stored_credential: stored_credential(:cardholder, :recurring, network_transaction_id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
stub_comms do
@gateway.purchase(@amount, credit_card, options)
end.check_request do |_endpoint, data, _headers|
@@ -133,6 +135,22 @@ def test_purchase_with_network_tokens_and_store_credential_usage
end.respond_with(successful_purchase_response)
end
+ def test_purchase_with_ntid_and_store_credential_for_mit
+ options = @options.merge!(stored_credential: stored_credential(:merchant, :recurring, network_transaction_id: 'abc123'))
+ credit_card = network_tokenization_credit_card('4242424242424242', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=')
+ response = stub_comms do
+ @gateway.purchase(@amount, credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ response = JSON.parse(data)
+ assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', response['card']['cryptogram']
+ assert_equal '4242424242424242', response['card']['network_token']
+ assert_equal 'USED', response['card']['stored_credential_usage']
+ assert_equal 'abc123', response['card']['network_payment_reference']
+ end.respond_with(successful_purchase_with_network_tx_reference_response)
+
+ assert_equal 'MCC000000355', response.network_transaction_id
+ end
+
def test_successful_purchase_with_additional_data
additional_data = { 'submerchant' => { 'name' => 'socks' } }
@@ -566,4 +584,8 @@ def successful_void_response
def failed_void_response
'{"code":5002,"message":"Invalid transaction status"}'
end
+
+ def successful_purchase_with_network_tx_reference_response
+ '{"id":"D-4-80ca7fbd-67ad-444a-aa88-791ca4a0c2b2","amount":120.00,"currency":"BRL","country":"BR","payment_method_id":"VD","payment_method_flow":"DIRECT","payer":{"name":"ThiagoGabriel","email":"thiago@example.com","document":"53033315550","user_reference":"12345","address":{"state":"RiodeJaneiro","city":"VoltaRedonda","zip_code":"27275-595","street":"ServidaoB-1","number":"1106"}},"card":{"holder_name":"ThiagoGabriel","expiration_month":10,"expiration_year":2040,"brand":"VI","network_tx_reference":"MCC000000355"},"order_id":"657434343","status":"PAID","notification_url":"http://merchant.com/notifications"}'
+ end
end
diff --git a/test/unit/gateways/datatrans_test.rb b/test/unit/gateways/datatrans_test.rb
new file mode 100644
index 00000000000..532cea6b645
--- /dev/null
+++ b/test/unit/gateways/datatrans_test.rb
@@ -0,0 +1,374 @@
+require 'test_helper'
+
+class DatatransTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = DatatransGateway.new(fixtures(:datatrans))
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ order_id: SecureRandom.random_number(1000000000),
+ email: 'john.smith@test.com'
+ }
+
+ @three_d_secure_options = @options.merge({
+ three_d_secure: {
+ eci: '05',
+ cavv: '3q2+78r+ur7erb7vyv66vv8=',
+ cavv_algorithm: '1',
+ xid: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'Y',
+ authentication_response_status: 'Y',
+ directory_response_status: 'Y',
+ version: '2',
+ ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC'
+ }
+ })
+
+ @transaction_reference = '240214093712238757|093712'
+
+ @billing_address = address
+
+ @nt_credit_card = network_tokenization_credit_card(
+ '4111111111111111',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ eci: '07',
+ source: :network_token,
+ verification_value: '737',
+ brand: 'visa'
+ )
+
+ @apple_pay_card = network_tokenization_credit_card(
+ '4900000000000094',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '06',
+ year: '2025',
+ source: 'apple_pay',
+ verification_value: 569
+ )
+ end
+
+ def test_authorize_with_credit_card
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_equal(@credit_card.number, parsed_data['card']['number'])
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ end
+
+ def test_authorize_with_credit_card_and_billing_address
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@amount, @credit_card, @options.merge({ billing_address: @billing_address }))
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_equal(@credit_card.number, parsed_data['card']['number'])
+
+ billing = parsed_data['billing']
+ assert_equal('Jim Smith', billing['name'])
+ assert_equal(@billing_address[:address1], billing['street'])
+ assert_match(@billing_address[:address2], billing['street2'])
+ assert_match(@billing_address[:city], billing['city'])
+ assert_match(@billing_address[:country], billing['country'])
+ assert_match(@billing_address[:phone], billing['phoneNumber'])
+ assert_match(@billing_address[:zip], billing['zipCode'])
+ assert_match(@options[:email], billing['email'])
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_credit_card
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_equal(@credit_card.number, parsed_data['card']['number'])
+
+ assert_equal(true, parsed_data['autoSettle'])
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_network_token
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @nt_credit_card, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_match('"autoSettle":true', data)
+
+ assert_equal(@nt_credit_card.number, parsed_data['card']['token'])
+ assert_equal('NETWORK_TOKEN', parsed_data['card']['type'])
+ assert_equal('VISA', parsed_data['card']['tokenType'])
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_authorize_with_apple_pay
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @apple_pay_card, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_match('"autoSettle":true', data)
+
+ assert_equal(@apple_pay_card.number, parsed_data['card']['token'])
+ assert_equal('DEVICE_TOKEN', parsed_data['card']['type'])
+ assert_equal('APPLE_PAY', parsed_data['card']['tokenType'])
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_3ds
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @three_d_secure_options)
+ end.check_request do |_action, endpoint, data, _headers|
+ three_d_secure = @three_d_secure_options[:three_d_secure]
+ parsed_data = JSON.parse(data)
+ common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_include(parsed_data, 'card')
+ assert_include(parsed_data['card'], '3D')
+
+ parsed_3d = parsed_data['card']['3D']
+
+ assert_equal('05', parsed_3d['eci'])
+ assert_equal(three_d_secure[:xid], parsed_3d['xid'])
+ assert_equal(three_d_secure[:ds_transaction_id], parsed_3d['threeDSTransactionId'])
+ assert_equal(three_d_secure[:cavv], parsed_3d['cavv'])
+ assert_equal('2', parsed_3d['threeDSVersion'])
+ assert_equal(three_d_secure[:cavv_algorithm], parsed_3d['cavvAlgorithm'])
+ assert_equal(three_d_secure[:authentication_response_status], parsed_3d['authenticationResponse'])
+ assert_equal(three_d_secure[:directory_response_status], parsed_3d['directoryResponse'])
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_capture
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.capture(@amount, @transaction_reference, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ assert_match('240214093712238757/settle', endpoint)
+ assert_equal(@options[:order_id], parsed_data['refno'])
+ assert_equal('CHF', parsed_data['currency'])
+ assert_equal('100', parsed_data['amount'])
+ end.respond_with(successful_capture_response)
+
+ assert_success response
+ end
+
+ def test_refund
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.refund(@amount, @transaction_reference, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ parsed_data = JSON.parse(data)
+ assert_match('240214093712238757/credit', endpoint)
+ assert_equal(@options[:order_id], parsed_data['refno'])
+ assert_equal('CHF', parsed_data['currency'])
+ assert_equal('100', parsed_data['amount'])
+ end.respond_with(successful_refund_response)
+
+ assert_success response
+ end
+
+ def test_voids
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.void(@transaction_reference, @options)
+ end.check_request do |_action, endpoint, data, _headers|
+ assert_match('240214093712238757/cancel', endpoint)
+ assert_equal data, '{}'
+ end.respond_with(successful_void_response)
+
+ assert_success response
+ end
+
+ def test_required_merchant_id_and_password
+ error = assert_raises ArgumentError do
+ DatatransGateway.new
+ end
+
+ assert_equal 'Missing required parameter: merchant_id', error.message
+ end
+
+ def test_supported_card_types
+ assert_equal DatatransGateway.supported_cardtypes, %i[master visa american_express unionpay diners_club discover jcb maestro dankort]
+ end
+
+ def test_supported_countries
+ assert_equal DatatransGateway.supported_countries, %w[CH GR US]
+ end
+
+ def test_support_scrubbing_flag_enabled
+ assert @gateway.supports_scrubbing?
+ end
+
+ def test_detecting_successfull_response_from_capture
+ assert @gateway.send :success_from, 'settle', { 'response_code' => 204 }
+ end
+
+ def test_detecting_successfull_response_from_purchase
+ assert @gateway.send :success_from, 'authorize', { 'transactionId' => '2124504', 'acquirerAuthorizationCode' => '12345t' }
+ end
+
+ def test_detecting_successfull_response_from_authorize
+ assert @gateway.send :success_from, 'authorize', { 'transactionId' => '2124504', 'acquirerAuthorizationCode' => '12345t' }
+ end
+
+ def test_detecting_successfull_response_from_refund
+ assert @gateway.send :success_from, 'credit', { 'transactionId' => '2124504', 'acquirerAuthorizationCode' => '12345t' }
+ end
+
+ def test_detecting_successfull_response_from_void
+ assert @gateway.send :success_from, 'cancel', { 'response_code' => 204 }
+ end
+
+ def test_get_response_message_from_messages_key
+ message = @gateway.send :message_from, false, { 'error' => { 'message' => 'hello' } }
+ assert_equal 'hello', message
+
+ message = @gateway.send :message_from, true, {}
+ assert_equal nil, message
+ end
+
+ def test_get_response_message_from_message_user
+ message = @gateway.send :message_from, 'order', { other_key: 'something_else' }
+ assert_nil message
+ end
+
+ def test_url_generation_from_action
+ action = 'test'
+ assert_equal "#{@gateway.test_url}#{action}", @gateway.send(:url, action)
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed)
+ end
+
+ def test_authorization_from
+ assert_equal '1234|9248', @gateway.send(:authorization_from, { 'transactionId' => '1234', 'acquirerAuthorizationCode' => '9248' })
+ assert_equal '1234|', @gateway.send(:authorization_from, { 'transactionId' => '1234' })
+ assert_equal '|9248', @gateway.send(:authorization_from, { 'acquirerAuthorizationCode' => '9248' })
+ assert_equal nil, @gateway.send(:authorization_from, {})
+ end
+
+ def test_parse
+ assert_equal @gateway.send(:parse, '{"response_code":204}'), { 'response_code' => 204 }
+ assert_equal @gateway.send(:parse, '{"transactionId":"240418170233899207","acquirerAuthorizationCode":"170233"}'), { 'transactionId' => '240418170233899207', 'acquirerAuthorizationCode' => '170233' }
+
+ assert_equal @gateway.send(:parse,
+ '{"transactionId":"240418170233899207",acquirerAuthorizationCode":"170233"}'),
+ { 'successful' => false,
+ 'response' => {},
+ 'errors' =>
+ ['Invalid JSON response received from Datatrans. Please contact them for support if you continue to receive this message. (The raw response returned by the API was "{\\"transactionId\\":\\"240418170233899207\\",acquirerAuthorizationCode\\":\\"170233\\"}")'] }
+ end
+
+ private
+
+ def successful_authorize_response
+ '{
+ "transactionId":"240214093712238757",
+ "acquirerAuthorizationCode":"093712"
+ }'
+ end
+
+ def successful_capture_response
+ '{"response_code": 204}'
+ end
+
+ def common_assertions_authorize_purchase(endpoint, parsed_data)
+ assert_match('authorize', endpoint)
+ assert_equal(@options[:order_id], parsed_data['refno'])
+ assert_equal('CHF', parsed_data['currency'])
+ assert_equal('100', parsed_data['amount'])
+ end
+
+ alias successful_purchase_response successful_authorize_response
+ alias successful_refund_response successful_authorize_response
+ alias successful_void_response successful_capture_response
+
+ def pre_scrubbed
+ <<~PRE_SCRUBBED
+ "opening connection to api.sandbox.datatrans.com:443...\n
+ opened\n
+ starting SSL for api.sandbox.datatrans.com:443...\n
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n
+ <- \"POST /v1/transactions/authorize HTTP/1.1\\r\\n
+ Content-Type: application/json; charset=UTF-8\\r\\n
+ Authorization: Basic someDataAuth\\r\\n
+ Connection: close\\r\\n
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\n
+ Accept: */*\\r\\n
+ User-Agent: Ruby\\r\\n
+ Host: api.sandbox.datatrans.com\\r\\n
+ Content-Length: 157\\r\\n\\r\\n\"\n
+ <- \"{\\\"card\\\":{\\\"number\\\":\\\"4242424242424242\\\",\\\"cvv\\\":\\\"123\\\",\\\"expiryMonth\\\":\\\"06\\\",\\\"expiryYear\\\":\\\"25\\\"},\\\"refno\\\":\\\"683040814\\\",\\\"currency\\\":\\\"CHF\\\",\\\"amount\\\":\\\"756\\\",\\\"autoSettle\\\":true}\"\n
+ -> \"HTTP/1.1 200 \\r\\n\"\n
+ -> \"Server: nginx\\r\\n\"\n
+ -> \"Date: Thu, 18 Apr 2024 15:02:34 GMT\\r\\n\"\n
+ -> \"Content-Type: application/json\\r\\n\"\n
+ -> \"Content-Length: 86\\r\\n\"\n
+ -> \"Connection: close\\r\\n\"\n
+ -> \"Strict-Transport-Security: max-age=31536000; includeSubdomains\\r\\n\"\n
+ -> \"P3P: CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"\\r\\n\"\n
+ -> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n
+ -> \"Correlation-Id: abda35b0-44ac-4a42-8811-941488acc21b\\r\\n\"\n
+ -> \"\\r\\n\"\nreading 86 bytes...\n
+ -> \"{\\n
+ \\\"transactionId\\\" : \\\"240418170233899207\\\",\\n
+ \\\"acquirerAuthorizationCode\\\" : \\\"170233\\\"\\n
+ }\"\n
+ read 86 bytes\n
+ Conn close\n"
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed
+ <<~POST_SCRUBBED
+ "opening connection to api.sandbox.datatrans.com:443...\n
+ opened\n
+ starting SSL for api.sandbox.datatrans.com:443...\n
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n
+ <- \"POST /v1/transactions/authorize HTTP/1.1\\r\\n
+ Content-Type: application/json; charset=UTF-8\\r\\n
+ Authorization: Basic [FILTERED]\\r\\n
+ Connection: close\\r\\n
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\n
+ Accept: */*\\r\\n
+ User-Agent: Ruby\\r\\n
+ Host: api.sandbox.datatrans.com\\r\\n
+ Content-Length: 157\\r\\n\\r\\n\"\n
+ <- \"{\\\"card\\\":{\\\"number\\\":\\\"[FILTERED]\\\",\\\"cvv\\\":\\\"[FILTERED]\\\",\\\"expiryMonth\\\":\\\"06\\\",\\\"expiryYear\\\":\\\"25\\\"},\\\"refno\\\":\\\"683040814\\\",\\\"currency\\\":\\\"CHF\\\",\\\"amount\\\":\\\"756\\\",\\\"autoSettle\\\":true}\"\n
+ -> \"HTTP/1.1 200 \\r\\n\"\n
+ -> \"Server: nginx\\r\\n\"\n
+ -> \"Date: Thu, 18 Apr 2024 15:02:34 GMT\\r\\n\"\n
+ -> \"Content-Type: application/json\\r\\n\"\n
+ -> \"Content-Length: 86\\r\\n\"\n
+ -> \"Connection: close\\r\\n\"\n
+ -> \"Strict-Transport-Security: max-age=31536000; includeSubdomains\\r\\n\"\n
+ -> \"P3P: CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"\\r\\n\"\n
+ -> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n
+ -> \"Correlation-Id: abda35b0-44ac-4a42-8811-941488acc21b\\r\\n\"\n
+ -> \"\\r\\n\"\nreading 86 bytes...\n
+ -> \"{\\n
+ \\\"transactionId\\\" : \\\"240418170233899207\\\",\\n
+ \\\"acquirerAuthorizationCode\\\" : \\\"170233\\\"\\n
+ }\"\n
+ read 86 bytes\n
+ Conn close\n"
+ POST_SCRUBBED
+ end
+end
diff --git a/test/unit/gateways/decidir_test.rb b/test/unit/gateways/decidir_test.rb
index 300d7d40974..be2c78a3f96 100644
--- a/test/unit/gateways/decidir_test.rb
+++ b/test/unit/gateways/decidir_test.rb
@@ -38,6 +38,13 @@ def setup
amount: 1500
}
]
+
+ @network_token = network_tokenization_credit_card(
+ '4012001037141112',
+ brand: 'visa',
+ eci: '05',
+ payment_cryptogram: '000203016912340000000FA08400317500000000'
+ )
end
def test_successful_purchase
@@ -159,6 +166,19 @@ def test_successful_purchase_with_sub_payments
assert_success response
end
+ def test_successful_purchase_with_customer_object
+ options = @options.merge(customer_id: 'John', customer_email: 'decidir@decidir.com')
+
+ response = stub_comms(@gateway_for_purchase, :ssl_request) do
+ @gateway_for_purchase.purchase(@amount, @credit_card, options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert data =~ /"email":"decidir@decidir.com"/
+ assert data =~ /"id":"John"/
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_failed_purchase
@gateway_for_purchase.expects(:ssl_request).returns(failed_purchase_response)
@@ -378,6 +398,22 @@ def test_successful_inquire_with_authorization
assert response.test?
end
+ def test_network_token_payment_method
+ options = {
+ card_holder_name: 'Tesest payway',
+ card_holder_door_number: 1234,
+ card_holder_birthday: '200988',
+ card_holder_identification_type: 'DNI',
+ card_holder_identification_number: '44444444',
+ last_4: @credit_card.last_digits
+ }
+ @gateway_for_auth.expects(:ssl_request).returns(successful_network_token_response)
+ response = @gateway_for_auth.authorize(100, @network_token, options)
+
+ assert_success response
+ assert_equal 49120515, response.authorization
+ end
+
def test_scrub
assert @gateway_for_purchase.supports_scrubbing?
assert_equal @gateway_for_purchase.scrub(pre_scrubbed), post_scrubbed
@@ -549,6 +585,59 @@ def failed_authorize_response
)
end
+ def successful_network_token_response
+ %(
+ {"id": 49120515,
+ "site_transaction_id": "Tx1673372774",
+ "payment_method_id": 1,
+ "card_brand": "Visa",
+ "amount": 1200,
+ "currency": "ars",
+ "status": "approved",
+ "status_details": {
+ "ticket": "88",
+ "card_authorization_code": "B45857",
+ "address_validation_code": "VTE2222",
+ "error": null
+ },
+ "date": "2023-01-10T14:46Z",
+ "customer": null,
+ "bin": "450799",
+ "installments": 1,
+ "first_installment_expiration_date": null,
+ "payment_type": "single",
+ "sub_payments": [],
+ "site_id": "09001000",
+ "fraud_detection": null,
+ "aggregate_data": {
+ "indicator": "1",
+ "identification_number": "30598910045",
+ "bill_to_pay": "Payway_Test",
+ "bill_to_refund": "Payway_Test",
+ "merchant_name": "PAYWAY",
+ "street": "Lavarden",
+ "number": "247",
+ "postal_code": "C1437FBE",
+ "category": "05044",
+ "channel": "005",
+ "geographic_code": "C1437",
+ "city": "Buenos Aires",
+ "merchant_id": "id_Aggregator",
+ "province": "Buenos Aires",
+ "country": "Argentina",
+ "merchant_email": "qa@test.com",
+ "merchant_phone": "+541135211111"
+ },
+ "establishment_name": null,
+ "spv":null,
+ "confirmed":null,
+ "bread":null,
+ "customer_token":null,
+ "card_data":"/tokens/49120515",
+ "token":"b7b6ca89-ed81-44e0-9d1f-3b3cf443cd74"}
+ )
+ 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"}
diff --git a/test/unit/gateways/deepstack_test.rb b/test/unit/gateways/deepstack_test.rb
new file mode 100644
index 00000000000..c355a5e6a18
--- /dev/null
+++ b/test/unit/gateways/deepstack_test.rb
@@ -0,0 +1,284 @@
+require 'test_helper'
+
+class DeepstackTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+ @gateway = DeepstackGateway.new(fixtures(:deepstack))
+ @credit_card = credit_card
+ @amount = 100
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4111111111111111',
+ verification_value: '999',
+ month: '01',
+ year: '2029',
+ first_name: 'Bob',
+ last_name: 'Bobby'
+ )
+
+ address = {
+ address1: '123 Some st',
+ address2: '',
+ first_name: 'Bob',
+ last_name: 'Bobberson',
+ city: 'Some City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ email: 'test@test.com'
+ }
+
+ shipping_address = {
+ address1: '321 Some st',
+ address2: '#9',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ city: 'Other City',
+ state: 'CA',
+ zip: '12345',
+ country: 'USA',
+ phone: '1231231234',
+ email: 'test@test.com'
+ }
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ shipping_address: shipping_address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_token
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+ response = @gateway.get_token(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_failed_token
+ @gateway.expects(:ssl_post).returns(failed_token_response)
+ response = @gateway.get_token(@credit_card, @options)
+ 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 'ch_IoSx345fOU6SP67MRXgqWw', 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)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'ch_vfndMRFdEUac0SnBNAAT6g', 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_not_equal 'Approved', response.message
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+ response = @gateway.capture(@amount, response.authorization)
+ 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_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ response = @gateway.refund(@amount, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+ response = @gateway.refund(@amount, '')
+
+ assert_failure response
+ 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)
+ response = @gateway.void(@amount, response.authorization)
+ 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_authorize_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(@credit_card, @options)
+
+ assert_failure response
+ assert_match %r{Invalid Request: Card number is invalid.}, 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 api.sandbox.deepstack.io:443...
+ opened
+ starting SSL for api.sandbox.deepstack.io:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256
+ I, [2023-07-25T08:47:29.985581 #86287] INFO -- : [ActiveMerchant::Billing::DeepstackGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2023-07-25T08:47:29.985687 #86287] DEBUG -- : {"source":{"type":"credit_card","credit_card":{"account_number":"4111111111111111","cvv":"999","expiration":"0129","customer_id":""},"billing_contact":{"first_name":"Bob","last_name":"Bobberson","phone":"1231231234","address":{"line_1":"123 Some st","line_2":"","city":"Some City","state":"CA","postal_code":"12345","country_code":"USA"}}},"transaction":{"amount":100,"cof_type":"UNSCHEDULED_CARDHOLDER","capture":false,"currency_code":"USD","avs":true,"save_payment_instrument":false},"meta":{"shipping_info":{"first_name":"Jane","last_name":"Doe","phone":"1231231234","email":"test@test.com","address":{"line_1":"321 Some st","line_2":"#9","city":"Other City","state":"CA","postal_code":"12345","country_code":"USA"}},"client_transaction_id":"1","client_transaction_description":"Store Purchase"}}
+ <- "POST /api/v1/payments/charge HTTP/1.1\r\nContent-Type: application/json\r\nAccept: text/plain\r\nHmac: YWZhMjVkZWEtNThlMy00ZGEwLWE1MWUtYmI2ZGNhOTQ5YzkwfFBPU1R8MjAyMy0wNy0yNVQxNTo0NzoyOC43NzZafDIwMmIwZDJjLTdhZWMtNDk2Yy1hMTBlLWQ3ZDUzYTRhNTAzZHxpQmxXTFNNNFdjSjFkSGdlczJYb2JqWUpMVUlGM2tkeUg2b1RFbWtFRUVFPQ==\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: api.sandbox.deepstack.io\r\nContent-Length: 799\r\n\r\n"
+ <- "{\"source\":{\"type\":\"credit_card\",\"credit_card\":{\"account_number\":\"4111111111111111\",\"cvv\":\"999\",\"expiration\":\"0129\",\"customer_id\":\"\"},\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"phone\":\"1231231234\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}}},\"transaction\":{\"amount\":100,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"capture\":false,\"currency_code\":\"USD\",\"avs\":true,\"save_payment_instrument\":false},\"meta\":{\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"phone\":\"1231231234\",\"email\":\"test@test.com\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\"}}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Tue, 25 Jul 2023 15:47:30 GMT\r\n"
+ -> "Content-Type: application/json; charset=utf-8\r\n"
+ -> "Content-Length: 1389\r\n"
+ -> "Connection: close\r\n"
+ -> "server: Kestrel\r\n"
+ -> "apigw-requestid: IoI23jbrPHcESNQ=\r\n"
+ -> "api-supported-versions: 1.0\r\n"
+ -> "\r\n"
+ reading 1389 bytes...
+ -> "{\"id\":\"ch_gSuF1hGsU0CpPPAUs1dg-Q\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null},\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-25T15:47:30.183095Z\"}"
+ read 1389 bytes
+ Conn close
+ '
+ end
+
+ def post_scrubbed
+ '
+ opening connection to api.sandbox.deepstack.io:443...
+ opened
+ starting SSL for api.sandbox.deepstack.io:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256
+ I, [2023-07-25T08:47:29.985581 #86287] INFO -- : [ActiveMerchant::Billing::DeepstackGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2023-07-25T08:47:29.985687 #86287] DEBUG -- : {"source":{"type":"credit_card","credit_card":{"account_number":"4111111111111111","cvv":"999","expiration":"0129","customer_id":""},"billing_contact":{"first_name":"Bob","last_name":"Bobberson","phone":"1231231234","address":{"line_1":"123 Some st","line_2":"","city":"Some City","state":"CA","postal_code":"12345","country_code":"USA"}}},"transaction":{"amount":100,"cof_type":"UNSCHEDULED_CARDHOLDER","capture":false,"currency_code":"USD","avs":true,"save_payment_instrument":false},"meta":{"shipping_info":{"first_name":"Jane","last_name":"Doe","phone":"1231231234","email":"test@test.com","address":{"line_1":"321 Some st","line_2":"#9","city":"Other City","state":"CA","postal_code":"12345","country_code":"USA"}},"client_transaction_id":"1","client_transaction_description":"Store Purchase"}}
+ <- "POST /api/v1/payments/charge HTTP/1.1\r\nContent-Type: application/json\r\nAccept: text/plain\r\nHmac: [FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: api.sandbox.deepstack.io\r\nContent-Length: 799\r\n\r\n"
+ <- "{\"source\":{\"type\":\"credit_card\",\"credit_card\":{\"account_number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiration\":\"[FILTERED]\",\"customer_id\":\"\"},\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"phone\":\"1231231234\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}}},\"transaction\":{\"amount\":100,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"capture\":false,\"currency_code\":\"USD\",\"avs\":true,\"save_payment_instrument\":false},\"meta\":{\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"phone\":\"1231231234\",\"email\":\"test@test.com\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"}},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\"}}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Tue, 25 Jul 2023 15:47:30 GMT\r\n"
+ -> "Content-Type: application/json; charset=utf-8\r\n"
+ -> "Content-Length: 1389\r\n"
+ -> "Connection: close\r\n"
+ -> "server: Kestrel\r\n"
+ -> "apigw-requestid: IoI23jbrPHcESNQ=\r\n"
+ -> "api-supported-versions: 1.0\r\n"
+ -> "\r\n"
+ reading 1389 bytes...
+ -> "{\"id\":\"ch_gSuF1hGsU0CpPPAUs1dg-Q\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"[FILTERED]\",\"expiration\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":null},\"shipping_info\":{\"first_name\":\"Jane\",\"last_name\":\"Doe\",\"address\":{\"line_1\":\"321 Some st\",\"line_2\":\"#9\",\"city\":\"Other City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-25T15:47:30.183095Z\"}"
+ read 1389 bytes
+ Conn close
+ '
+ end
+
+ def successful_purchase_response_2
+ %(
+ Easy to capture by setting the DEBUG_ACTIVE_MERCHANT environment variable
+ to "true" when running remote tests:
+
+ $ DEBUG_ACTIVE_MERCHANT=true ruby -Itest \
+ test/remote/gateways/remote_deepstack_test.rb \
+ -n test_successful_purchase
+ )
+ end
+
+ def successful_purchase_response
+ %({\"id\":\"ch_IoSx345fOU6SP67MRXgqWw\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":true,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-14T17:08:33.5004521Z\"})
+ end
+
+ def failed_purchase_response
+ %({\"id\":\"ch_xbaPjifXN0Gum4vzdup6iA\",\"response_code\":\"03\",\"message\":\"Invalid Request: Card number is invalid.\",\"approved\":false,\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************0051\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"MasterCard\",\"last_four\":\"0051\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":null,\"address_postal_code_check\":null,\"cvc_check\":null},\"completed\":\"2023-07-14T17:11:24.972201Z\"})
+ end
+
+ def successful_authorize_response
+ %({\"id\":\"ch_vfndMRFdEUac0SnBNAAT6g\",\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"cvv_result\":\"Y\",\"avs_result\":\"Y\",\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************1111\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"Visa\",\"last_four\":\"1111\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":\"pass\",\"address_postal_code_check\":\"pass\",\"cvc_check\":null},\"completed\":\"2023-07-14T17:36:18.4817926Z\"})
+ end
+
+ def failed_authorize_response
+ %({\"id\":\"ch_CBue2iT3pUibJ7QySysTrA\",\"response_code\":\"03\",\"message\":\"Invalid Request: Card number is invalid.\",\"approved\":false,\"source\":{\"id\":\"\",\"credit_card\":{\"account_number\":\"************0051\",\"expiration\":\"0129\",\"cvv\":\"999\",\"brand\":\"MasterCard\",\"last_four\":\"0051\"},\"type\":\"credit_card\",\"client_customer_id\":null,\"billing_contact\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"}},\"amount\":100,\"captured\":false,\"cof_type\":\"UNSCHEDULED_CARDHOLDER\",\"currency_code\":\"USD\",\"country_code\":0,\"billing_info\":{\"first_name\":\"Bob Bobberson\",\"last_name\":\"\",\"address\":{\"line_1\":\"123 Some st \",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"client_transaction_id\":\"1\",\"client_transaction_description\":\"Store Purchase\",\"client_invoice_id\":null,\"save_payment_instrument\":false,\"kount_score\":null,\"checks\":{\"address_line1_check\":null,\"address_postal_code_check\":null,\"cvc_check\":null},\"completed\":\"2023-07-14T17:42:30.1835831Z\"})
+ end
+
+ def successful_capture_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_KpmspGEiSUCgavxiE-xPTw\",\"amount\":100,\"recurring\":false,\"completed\":\"2023-07-14T19:58:49.3255779+00:00\"})
+ end
+
+ def failed_capture_response
+ %({\"response_code\":\"02\",\"message\":\"Current transaction does not exist or is in an invalid state.\",\"approved\":false,\"charge_transaction_id\":\"\",\"amount\":100,\"recurring\":false,\"completed\":\"2023-07-14T21:33:54.2518371Z\"})
+ end
+
+ def successful_refund_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_w5A8LS3C1kqdtrCJxWeRqQ\",\"amount\":10000,\"completed\":\"2023-07-15T01:01:58.3190631+00:00\"})
+ end
+
+ def failed_refund_response
+ %({\"type\":\"https://httpstatuses.com/400\",\"title\":\"Invalid Request\",\"status\":400,\"detail\":\"Specified transaction does not exist.\",\"traceId\":\"00-e9b47344b951b400c34ce541a22e96a7-5ece5267ae02ef3d-00\"})
+ end
+
+ def successful_void_response
+ %({\"response_code\":\"00\",\"message\":\"Approved\",\"approved\":true,\"auth_code\":\"asdefr\",\"charge_transaction_id\":\"ch_w5A8LS3C1kqdtrCJxWeRqQ\",\"amount\":10000,\"completed\":\"2023-07-15T01:01:58.3190631+00:00\"})
+ end
+
+ def failed_void_response
+ %({\"type\":\"https://httpstatuses.com/400\",\"title\":\"Invalid Request\",\"status\":400,\"detail\":\"Specified transaction does not exist.\",\"traceId\":\"00-e9b47344b951b400c34ce541a22e96a7-5ece5267ae02ef3d-00\"})
+ end
+
+ def successful_token_response
+ %({\"id\":\"tok_Ub1AHj7x1U6cUF8x8KDKAw\",\"type\":\"credit_card\",\"customer_id\":null,\"brand\":\"Visa\",\"bin\":\"411111\",\"last_four\":\"1111\",\"expiration\":\"0129\",\"billing_contact\":{\"first_name\":\"Bob\",\"last_name\":\"Bobberson\",\"address\":{\"line_1\":\"123 Some st\",\"line_2\":\"\",\"city\":\"Some City\",\"state\":\"CA\",\"postal_code\":\"12345\",\"country_code\":\"USA\"},\"phone\":\"1231231234\",\"email\":\"test@test.com\"},\"is_default\":false})
+ end
+
+ def failed_token_response
+ %({\"id\":\"Ji-YEeijmUmiFB6mz_iIUA\",\"response_code\":\"400\",\"message\":\"InvalidRequestException: Card number is invalid.\",\"approved\":false,\"completed\":\"2023-07-15T01:10:47.9188024Z\",\"success\":false})
+ end
+end
diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb
index 06e3d4b6db0..423a1c0f83f 100644
--- a/test/unit/gateways/ebanx_test.rb
+++ b/test/unit/gateways/ebanx_test.rb
@@ -28,7 +28,7 @@ def test_successful_purchase
def test_successful_purchase_with_optional_processing_type_header
response = stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@accepted_amount, @credit_card, @options.merge(processing_type: 'local'))
+ @gateway.purchase(@amount, @credit_card, @options.merge(processing_type: 'local'))
end.check_request do |_method, _endpoint, _data, headers|
assert_equal 'local', headers['x-ebanx-api-processing-type']
end.respond_with(successful_purchase_response)
@@ -136,15 +136,7 @@ def test_failed_void
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)
+ @gateway.expects(:ssl_request).returns(successful_verify_response)
response = @gateway.verify(@credit_card, @options)
assert_success response
@@ -152,11 +144,11 @@ def test_successful_verify_with_failed_void
end
def test_failed_verify
- @gateway.expects(:ssl_request).returns(failed_authorize_response)
+ @gateway.expects(:ssl_request).returns(failed_verify_response)
response = @gateway.verify(@credit_card, @options)
assert_failure response
- assert_equal 'NOK', response.error_code
+ assert_equal 'Not accepted', response.message
end
def test_successful_store_and_purchase
@@ -172,6 +164,18 @@ def test_successful_store_and_purchase
assert_success response
end
+ def test_successful_purchase_and_inquire
+ @gateway.expects(:ssl_request).returns(successful_purchase_response)
+
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ @gateway.expects(:ssl_request).returns(successful_purchase_response)
+ response = @gateway.inquire(purchase.authorization)
+
+ assert_success response
+ end
+
def test_error_response_with_invalid_creds
@gateway.expects(:ssl_request).returns(invalid_cred_response)
@@ -223,6 +227,18 @@ def failed_authorize_response
)
end
+ def successful_verify_response
+ %(
+ {"status":"SUCCESS","payment_type_code":"creditcard","card_verification":{"transaction_status":{"code":"OK","description":"Accepted"},"transaction_type":"ZERO DOLLAR"}}
+ )
+ end
+
+ def failed_verify_response
+ %(
+ {"status":"SUCCESS","payment_type_code":"discover","card_verification":{"transaction_status":{"code":"NOK", "description":"Not accepted"}, "transaction_type":"GHOST AUTHORIZATION"}}
+ )
+ end
+
def successful_capture_response
%(
{"payment":{"hash":"5dee94502bd59660b801c441ad5a703f2c4123f5fc892ccb","pin":"675968133","country":"br","merchant_payment_code":"b98b2892b80771b9dadf2ebc482cb65d","order_number":null,"status":"CO","status_date":"2019-12-09 18:37:05","open_date":"2019-12-09 18:37:04","confirm_date":"2019-12-09 18:37:05","transfer_date":null,"amount_br":"4.19","amount_ext":"1.00","amount_iof":"0.02","currency_rate":"4.1700","currency_ext":"USD","due_date":"2019-12-12","instalments":"1","payment_type_code":"visa","details":{"billing_descriptor":"DEMONSTRATION"},"transaction_status":{"acquirer":"EBANX","code":"OK","description":"Accepted"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"}
diff --git a/test/unit/gateways/elavon_test.rb b/test/unit/gateways/elavon_test.rb
index da97607f95a..49f8ca8c207 100644
--- a/test/unit/gateways/elavon_test.rb
+++ b/test/unit/gateways/elavon_test.rb
@@ -32,6 +32,16 @@ def setup
billing_address: address,
description: 'Store Purchase'
}
+
+ @google_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
+ source: :google_pay,
+ payment_data: "{ 'version': 'EC_v1', 'data': 'QlzLxRFnNP9/GTaMhBwgmZ2ywntbr9'}"
+ })
+
+ @apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
+ source: :apple_pay,
+ payment_data: "{ 'version': 'EC_v1', 'data': 'QlzLxRFnNP9/GTaMhBwgmZ2ywntbr9'}"
+ })
end
def test_successful_purchase
@@ -145,6 +155,22 @@ def test_successful_purchase_with_unscheduled
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_apple_pay
+ stub_comms do
+ @gateway.purchase(@amount, @apple_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/%7B %27version%27%3A %27EC_v1%27%2C %27data%27%3A %27QlzLxRFnNP9%2FGTaMhBwgmZ2ywntbr9%27%7D<\/ssl_applepay_web>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_purchase_with_google_pay
+ stub_comms do
+ @gateway.purchase(@amount, @google_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/%7B %27version%27%3A %27EC_v1%27%2C %27data%27%3A %27QlzLxRFnNP9%2FGTaMhBwgmZ2ywntbr9%27%7D<\/ssl_google_pay>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
def test_sends_ssl_add_token_field
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(add_recurring_token: 'Y'))
diff --git a/test/unit/gateways/element_test.rb b/test/unit/gateways/element_test.rb
index ece96d068e5..694af43d9a9 100644
--- a/test/unit/gateways/element_test.rb
+++ b/test/unit/gateways/element_test.rb
@@ -25,6 +25,18 @@ def test_successful_purchase
assert_equal '2005831886|100', response.authorization
end
+ def test_successful_purchase_without_name
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+
+ @credit_card.first_name = nil
+ @credit_card.last_name = nil
+
+ 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)
@@ -154,6 +166,50 @@ def test_successful_purchase_with_card_present_code
assert_success response
end
+ def test_successful_purchase_with_lodging_and_other_fields
+ lodging_options = {
+ order_id: '2',
+ billing_address: address.merge(zip: '87654'),
+ description: 'Store Purchase',
+ duplicate_override_flag: 'true',
+ lodging: {
+ agreement_number: 182726718192,
+ check_in_date: 20250910,
+ check_out_date: 20250915,
+ room_amount: 1000,
+ room_tax: 0,
+ no_show_indicator: 0,
+ duration: 5,
+ customer_name: 'francois dubois',
+ client_code: 'Default',
+ extra_charges_detail: '01',
+ extra_charges_amounts: 'Default',
+ prestigious_property_code: 'DollarLimit500',
+ special_program_code: 'Sale',
+ charge_type: 'Restaurant'
+ }
+ }
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, lodging_options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match '182726718192', data
+ assert_match '20250910', data
+ assert_match '20250915', data
+ assert_match '1000', data
+ assert_match '0', data
+ assert_match '0', data
+ assert_match '5', data
+ assert_match 'francois dubois', data
+ assert_match 'Default', data
+ assert_match '01', data
+ assert_match 'Default', data
+ assert_match 'DollarLimit500', data
+ assert_match 'Sale', data
+ assert_match 'Restaurant', data
+ end.respond_with(successful_purchase_response)
+ assert_success response
+ end
+
def test_successful_purchase_with_payment_type
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed'))
diff --git a/test/unit/gateways/epay_test.rb b/test/unit/gateways/epay_test.rb
index cfebf4b8447..91a03ed50e5 100644
--- a/test/unit/gateways/epay_test.rb
+++ b/test/unit/gateways/epay_test.rb
@@ -26,8 +26,7 @@ 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
+ assert_equal 'The payment was declined. Try again in a moment or try with another credit card.', response.message
end
def test_successful_3ds_purchase
@@ -51,8 +50,7 @@ def test_invalid_characters_in_response
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.
Denied - Call your bank for information',
- response.message
+ assert_equal 'The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.
Denied - Call your bank for information', response.message
end
def test_failed_response_on_purchase
diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb
index 4ed739ca9a4..ec70917a51a 100644
--- a/test/unit/gateways/eway_rapid_test.rb
+++ b/test/unit/gateways/eway_rapid_test.rb
@@ -194,7 +194,9 @@ def test_failed_purchase_with_multiple_messages
def test_purchase_with_all_options
response = stub_comms do
- @gateway.purchase(200, @credit_card,
+ @gateway.purchase(
+ 200,
+ @credit_card,
transaction_type: 'CustomTransactionType',
redirect_url: 'http://awesomesauce.com',
ip: '0.0.0.0',
@@ -230,7 +232,8 @@ def test_purchase_with_all_options
country: 'US',
phone: '1115555555',
fax: '1115556666'
- })
+ }
+ )
end.check_request do |_endpoint, data, _headers|
assert_match(%r{"TransactionType":"CustomTransactionType"}, data)
assert_match(%r{"RedirectUrl":"http://awesomesauce.com"}, data)
diff --git a/test/unit/gateways/exact_test.rb b/test/unit/gateways/exact_test.rb
index 5a1257add89..2986777bfa6 100644
--- a/test/unit/gateways/exact_test.rb
+++ b/test/unit/gateways/exact_test.rb
@@ -49,9 +49,10 @@ def test_failed_purchase
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
diff --git a/test/unit/gateways/fat_zebra_test.rb b/test/unit/gateways/fat_zebra_test.rb
index 4e3e8a2b00f..3caf94852dc 100644
--- a/test/unit/gateways/fat_zebra_test.rb
+++ b/test/unit/gateways/fat_zebra_test.rb
@@ -18,6 +18,15 @@ def setup
description: 'Store Purchase',
extra: { card_on_file: false }
}
+
+ @three_ds_secure = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ xid: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
end
def test_successful_purchase
@@ -212,6 +221,52 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_three_ds_v2_object_construction
+ post = {}
+ @options[:three_d_secure] = @three_ds_secure
+
+ @gateway.send(:add_three_ds, post, @options)
+
+ assert post[:extra]
+ ds_data = post[:extra]
+ ds_options = @options[:three_d_secure]
+
+ assert_equal ds_options[:version], ds_data[:threeds_version]
+ assert_equal ds_options[:cavv], ds_data[:cavv]
+ assert_equal ds_options[:eci], ds_data[:sli]
+ assert_equal ds_options[:xid], ds_data[:xid]
+ assert_equal ds_options[:ds_transaction_id], ds_data[:ds_transaction_id]
+ assert_equal 'Y', ds_data[:ver]
+ assert_equal ds_options[:authentication_response_status], ds_data[:par]
+ end
+
+ def test_purchase_with_three_ds
+ @options[:three_d_secure] = @three_ds_secure
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
+ three_ds_params = JSON.parse(data)['extra']
+ assert_equal '2.2.0', three_ds_params['threeds_version']
+ assert_equal '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', three_ds_params['cavv']
+ assert_equal '05', three_ds_params['sli']
+ assert_equal 'ODUzNTYzOTcwODU5NzY3Qw==', three_ds_params['xid']
+ assert_equal 'Y', three_ds_params['ver']
+ assert_equal 'Y', three_ds_params['par']
+ end
+ end
+
+ def test_formatted_enrollment
+ assert_equal 'Y', @gateway.send('formatted_enrollment', 'Y')
+ assert_equal 'Y', @gateway.send('formatted_enrollment', 'true')
+ assert_equal 'Y', @gateway.send('formatted_enrollment', true)
+
+ assert_equal 'N', @gateway.send('formatted_enrollment', 'N')
+ assert_equal 'N', @gateway.send('formatted_enrollment', 'false')
+ assert_equal 'N', @gateway.send('formatted_enrollment', false)
+
+ assert_equal 'U', @gateway.send('formatted_enrollment', 'U')
+ end
+
private
def pre_scrubbed
diff --git a/test/unit/gateways/first_pay_json_test.rb b/test/unit/gateways/first_pay_json_test.rb
new file mode 100644
index 00000000000..d1d917acab6
--- /dev/null
+++ b/test/unit/gateways/first_pay_json_test.rb
@@ -0,0 +1,599 @@
+require 'test_helper'
+
+class FirstPayJsonTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = FirstPayJsonGateway.new(
+ processor_id: 1234,
+ merchant_key: 'a91c38c3-7d7f-4d29-acc7-927b4dca0dbe'
+ )
+
+ @credit_card = credit_card
+ @google_pay = network_tokenization_credit_card(
+ '4005550000000019',
+ brand: 'visa',
+ eci: '05',
+ month: '02',
+ year: '2035',
+ source: :google_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ transaction_id: '13456789'
+ )
+ @apple_pay = network_tokenization_credit_card(
+ '4005550000000019',
+ brand: 'visa',
+ eci: '05',
+ month: '02',
+ year: '2035',
+ source: :apple_pay,
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ transaction_id: '13456789'
+ )
+ @amount = 100
+
+ @options = {
+ order_id: SecureRandom.hex(24),
+ billing_address: address
+ }
+ end
+
+ def test_successful_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"cardNumber\":\"4242424242424242\"/, data)
+ assert_match(/\"cardExpMonth\":9/, data)
+ assert_match(/\"cardExpYear\":\"25\"/, data)
+ assert_match(/\"cvv\":\"123\"/, data)
+ assert_match(/\"ownerName\":\"Jim Smith\"/, data)
+ assert_match(/\"ownerStreet\":\"456 My Street\"/, data)
+ assert_match(/\"ownerCity\":\"Ottawa\"/, data)
+ assert_match(/\"ownerState\":\"ON\"/, data)
+ assert_match(/\"ownerZip\":\"K1C2N6\"/, data)
+ assert_match(/\"ownerCountry\":\"CA\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31076534', response.authorization
+ assert_equal 'Approved 735498', response.message
+ end
+
+ def test_failed_purchase
+ response = stub_comms do
+ @gateway.purchase(200, @credit_card, @options)
+ end.respond_with(failed_purchase_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_failure response
+ assert_equal '31076656', response.authorization
+ assert_equal 'Auth Declined', response.message
+ end
+
+ def test_successful_google_pay_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @google_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"walletType\":\"GooglePay\"/, data)
+ assert_match(/\"paymentCryptogram\":\"EHuWW9PiBkWvqE5juRwDzAUFBAk=\"/, data)
+ assert_match(/\"eciIndicator\":\"05\"/, data)
+ assert_match(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"cardNumber\":\"4005550000000019\"/, data)
+ assert_match(/\"cardExpMonth\":2/, data)
+ assert_match(/\"cardExpYear\":\"35\"/, data)
+ assert_match(/\"ownerName\":\"Jim Smith\"/, data)
+ assert_match(/\"ownerStreet\":\"456 My Street\"/, data)
+ assert_match(/\"ownerCity\":\"Ottawa\"/, data)
+ assert_match(/\"ownerState\":\"ON\"/, data)
+ assert_match(/\"ownerZip\":\"K1C2N6\"/, data)
+ assert_match(/\"ownerCountry\":\"CA\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_purchase_google_pay_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31079731', response.authorization
+ assert_equal 'Approved 507983', response.message
+ end
+
+ def test_successful_apple_pay_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @apple_pay, @options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"walletType\":\"ApplePay\"/, data)
+ assert_match(/\"paymentCryptogram\":\"EHuWW9PiBkWvqE5juRwDzAUFBAk=\"/, data)
+ assert_match(/\"eciIndicator\":\"05\"/, data)
+ assert_match(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"cardNumber\":\"4005550000000019\"/, data)
+ assert_match(/\"cardExpMonth\":2/, data)
+ assert_match(/\"cardExpYear\":\"35\"/, data)
+ assert_match(/\"ownerName\":\"Jim Smith\"/, data)
+ assert_match(/\"ownerStreet\":\"456 My Street\"/, data)
+ assert_match(/\"ownerCity\":\"Ottawa\"/, data)
+ assert_match(/\"ownerState\":\"ON\"/, data)
+ assert_match(/\"ownerZip\":\"K1C2N6\"/, data)
+ assert_match(/\"ownerCountry\":\"CA\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_purchase_apple_pay_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31080040', response.authorization
+ assert_equal 'Approved 576126', 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(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"cardNumber\":\"4242424242424242\"/, data)
+ assert_match(/\"cardExpMonth\":9/, data)
+ assert_match(/\"cardExpYear\":\"25\"/, data)
+ assert_match(/\"cvv\":\"123\"/, data)
+ assert_match(/\"ownerName\":\"Jim Smith\"/, data)
+ assert_match(/\"ownerStreet\":\"456 My Street\"/, data)
+ assert_match(/\"ownerCity\":\"Ottawa\"/, data)
+ assert_match(/\"ownerState\":\"ON\"/, data)
+ assert_match(/\"ownerZip\":\"K1C2N6\"/, data)
+ assert_match(/\"ownerCountry\":\"CA\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_authorize_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31076755', response.authorization
+ assert_equal 'Approved 487154', 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 '31076792', response.authorization
+ assert_equal 'Auth Declined', response.message
+ end
+
+ def test_successful_capture
+ response = stub_comms do
+ @gateway.capture(@amount, '31076883')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"refNumber\":\"31076883\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_capture_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31076883', response.authorization
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_capture
+ @gateway.stubs(:ssl_post).returns(failed_capture_response)
+ response = @gateway.capture(@amount, '1234')
+
+ assert_failure response
+ assert_equal '1234', response.authorization
+ assert response.message.include?('Settle Failed')
+ end
+
+ def test_successful_refund
+ response = stub_comms do
+ @gateway.refund(@amount, '31077003')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"transactionAmount\":\"1.00\"/, data)
+ assert_match(/\"refNumber\":\"31077003\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_refund_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31077004', response.authorization
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_refund
+ @gateway.stubs(:ssl_post).returns(failed_refund_response)
+ response = @gateway.refund(@amount, '1234')
+
+ assert_failure response
+ assert_equal '', response.authorization
+ assert response.message.include?('No transaction was found to refund.')
+ end
+
+ def test_successful_void
+ response = stub_comms do
+ @gateway.void('31077140')
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/\"refNumber\":\"31077140\"/, data)
+ assert_match(/\"processorId\":1234/, data)
+ assert_match(/\"merchantKey\":\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\"/, data)
+ end.respond_with(successful_void_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '31077142', response.authorization
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_void
+ @gateway.stubs(:ssl_post).returns(failed_void_response)
+ response = @gateway.void('1234')
+
+ assert_failure response
+ assert_equal '', response.authorization
+ assert response.message.include?('Void Failed. Transaction cannot be voided.')
+ end
+
+ def test_error_message
+ @gateway.stubs(:ssl_post).returns(failed_login_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_equal 'isError', response.error_code
+ assert response.message.include?('Unable to retrieve merchant information')
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ private
+
+ def successful_purchase_response
+ <<~RESPONSE
+ {
+ "data": {
+ "authResponse": "Approved 735498",
+ "authCode": "735498",
+ "referenceNumber": "31076534",
+ "isPartial": false,
+ "partialId": "",
+ "originalFullAmount": 1.0,
+ "partialAmountApproved": 0.0,
+ "avsResponse": "Y",
+ "cvv2Response": "",
+ "orderId": "638430008263685218",
+ "cardType": "Visa",
+ "last4": "1111",
+ "maskedPan": "411111******1111",
+ "token": "1266392642841111",
+ "cardExpMonth": "9",
+ "cardExpYear": "25",
+ "hasFee": false,
+ "fee": null,
+ "billingAddress": { "ownerName": "Jim Smith", "ownerStreet": "456 My Street", "ownerStreet2": null, "ownerCity": "Ottawa", "ownerState": "ON", "ownerZip": "K1C2N6", "ownerCountry": "CA", "ownerEmail": null, "ownerPhone": null }
+ },
+ "isError": false,
+ "errorMessages": [],
+ "validationHasFailed": false,
+ "validationFailures": [],
+ "isSuccess": true,
+ "action": "Sale"
+ }
+ RESPONSE
+ end
+
+ def failed_purchase_response
+ <<~RESPONSE
+ {
+ "data": {
+ "authResponse": "Auth Declined",
+ "authCode": "200",
+ "referenceNumber": "31076656",
+ "isPartial": false,
+ "partialId": "",
+ "originalFullAmount": 2.0,
+ "partialAmountApproved": 0.0,
+ "avsResponse": "",
+ "cvv2Response": "",
+ "orderId": "",
+ "cardType": "Visa",
+ "last4": "1111",
+ "maskedPan": "411111******1111",
+ "token": "1266392642841111",
+ "cardExpMonth": "9",
+ "cardExpYear": "25",
+ "hasFee": false,
+ "fee": null,
+ "billingAddress": { "ownerName": "Jim Smith", "ownerStreet": "456 My Street", "ownerStreet2": null, "ownerCity": "Ottawa", "ownerState": "ON", "ownerZip": "K1C2N6", "ownerCountry": "CA", "ownerEmail": null, "ownerPhone": null }
+ },
+ "isError": true,
+ "errorMessages": ["Auth Declined"],
+ "validationHasFailed": false,
+ "validationFailures": [],
+ "isSuccess": false,
+ "action": "Sale"
+ }
+ RESPONSE
+ end
+
+ def successful_purchase_google_pay_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"Approved 507983",
+ "authCode":"507983",
+ "referenceNumber":"31079731",
+ "isPartial":false,
+ "partialId":"",
+ "originalFullAmount":1.0,
+ "partialAmountApproved":0.0,
+ "avsResponse":"Y",
+ "cvv2Response":"",
+ "orderId":"bbabd4c3b486eed0935a0e12bf4b000579274dfea330223a",
+ "cardType":"Visa-GooglePay",
+ "last4":"0019",
+ "maskedPan":"400555******0019",
+ "token":"8257959132340019",
+ "cardExpMonth":"2",
+ "cardExpYear":"35",
+ "hasFee":false,
+ "fee":null,
+ "billingAddress":{"ownerName":"Jim Smith", "ownerStreet":"456 My Street", "ownerStreet2":null, "ownerCity":"Ottawa", "ownerState":"ON", "ownerZip":"K1C2N6", "ownerCountry":"CA", "ownerEmail":null, "ownerPhone":null}
+ },
+ "isError":false,
+ "errorMessages":[],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":true,
+ "action":"Sale"
+ }
+ RESPONSE
+ end
+
+ def successful_purchase_apple_pay_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"Approved 576126",
+ "authCode":"576126",
+ "referenceNumber":"31080040",
+ "isPartial":false,
+ "partialId":"",
+ "originalFullAmount":1.0,
+ "partialAmountApproved":0.0,
+ "avsResponse":"Y",
+ "cvv2Response":"",
+ "orderId":"f6527d4f5ebc29a60662239be0221f612797030cde82d50c",
+ "cardType":"Visa-ApplePay",
+ "last4":"0019",
+ "maskedPan":"400555******0019",
+ "token":"8257959132340019",
+ "cardExpMonth":"2",
+ "cardExpYear":"35",
+ "hasFee":false,
+ "fee":null,
+ "billingAddress":{"ownerName":"Jim Smith", "ownerStreet":"456 My Street", "ownerStreet2":null, "ownerCity":"Ottawa", "ownerState":"ON", "ownerZip":"K1C2N6", "ownerCountry":"CA", "ownerEmail":null, "ownerPhone":null}
+ },
+ "isError":false,
+ "errorMessages":[],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":true,
+ "action":"Sale"
+ }
+ RESPONSE
+ end
+
+ def successful_authorize_response
+ <<~RESPONSE
+ {
+ "data": {
+ "authResponse": "Approved 487154",
+ "authCode": "487154",
+ "referenceNumber": "31076755",
+ "isPartial": false,
+ "partialId": "",
+ "originalFullAmount": 1.0,
+ "partialAmountApproved": 0.0,
+ "avsResponse": "Y",
+ "cvv2Response": "",
+ "orderId": "638430019493711407",
+ "cardType": "Visa",
+ "last4": "1111",
+ "maskedPan": "411111******1111",
+ "token": "1266392642841111",
+ "hasFee": false,
+ "fee": null
+ },
+ "isError": false,
+ "errorMessages": [],
+ "validationHasFailed": false,
+ "validationFailures": [],
+ "isSuccess": true,
+ "action": "Auth"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_response
+ <<~RESPONSE
+ {
+ "data": {
+ "authResponse": "Auth Declined",
+ "authCode": "200",
+ "referenceNumber": "31076792",
+ "isPartial": false,
+ "partialId": "",
+ "originalFullAmount": 2.0,
+ "partialAmountApproved": 0.0,
+ "avsResponse": "",
+ "cvv2Response": "",
+ "orderId": "",
+ "cardType": "Visa",
+ "last4": "1111",
+ "maskedPan": "411111******1111",
+ "token": "1266392642841111",
+ "hasFee": false,
+ "fee": null
+ },
+ "isError": true,
+ "errorMessages": ["Auth Declined"],
+ "validationHasFailed": false,
+ "validationFailures": [],
+ "isSuccess": false,
+ "action": "Auth"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_response
+ <<~RESPONSE
+ {
+ "data": {
+ "authResponse": "APPROVED",
+ "referenceNumber": "31076883",
+ "settleAmount": "1",
+ "batchNumber": "20240208"
+ },
+ "isError": false,
+ "errorMessages": [],
+ "validationHasFailed": false,
+ "validationFailures": [],
+ "isSuccess": true,
+ "action": "Settle"
+ }
+ RESPONSE
+ end
+
+ def failed_capture_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"Settle Failed. Transaction cannot be settled. Make sure the settlement amount does not exceed the original auth amount and that is was authorized less than 30 days ago.",
+ "referenceNumber":"1234",
+ "settleAmount":"1",
+ "batchNumber":"20240208"
+ },
+ "isError":true,
+ "errorMessages":["Settle Failed. Transaction cannot be settled. Make sure the settlement amount does not exceed the original auth amount and that is was authorized less than 30 days ago."],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":false,
+ "action":"Settle"
+ }
+ RESPONSE
+ end
+
+ def successful_refund_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"APPROVED",
+ "referenceNumber":"31077004",
+ "parentReferenceNumber":"31077003",
+ "refundAmount":"1.00",
+ "refundType":"void"
+ },
+ "isError":false,
+ "errorMessages":[],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":true,
+ "action":"Refund"
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"No transaction was found to refund.",
+ "referenceNumber":"",
+ "parentReferenceNumber":"",
+ "refundAmount":"",
+ "refundType":"void"
+ },
+ "isError":true,
+ "errorMessages":["No transaction was found to refund."],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":false,
+ "action":"Refund"
+ }
+ RESPONSE
+ end
+
+ def successful_void_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"APPROVED",
+ "referenceNumber":"31077142",
+ "parentReferenceNumber":"31077140"
+ },
+ "isError":false,
+ "errorMessages":[],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":true,
+ "action":"Void"
+ }
+ RESPONSE
+ end
+
+ def failed_void_response
+ <<~RESPONSE
+ {
+ "data":{
+ "authResponse":"Void Failed. Transaction cannot be voided.",
+ "referenceNumber":"",
+ "parentReferenceNumber":""
+ },
+ "isError":true,
+ "errorMessages":["Void Failed. Transaction cannot be voided."],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":false,
+ "action":"Void"
+ }
+ RESPONSE
+ end
+
+ def failed_login_response
+ <<~RESPONSE
+ {
+ "isError":true,
+ "errorMessages":["Unable to retrieve merchant information"],
+ "validationHasFailed":false,
+ "validationFailures":[],
+ "isSuccess":false,
+ "action":"Sale"
+ }
+ RESPONSE
+ end
+
+ def pre_scrubbed
+ <<~RESPONSE
+ "opening connection to secure.1stpaygateway.net:443...\nopened\nstarting SSL for secure.1stpaygateway.net:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256\n<- \"POST /secure/RestGW/Gateway/Transaction/Sale HTTP/1.1\\r\\nContent-Type: application/json\\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.1stpaygateway.net\\r\\nContent-Length: 314\\r\\n\\r\\n\"\n<- \"{\\\"transactionAmount\\\":\\\"1.00\\\",\\\"cardNumber\\\":\\\"4111111111111111\\\",\\\"cardExpMonth\\\":9,\\\"cardExpYear\\\":\\\"25\\\",\\\"cvv\\\":789,\\\"ownerName\\\":\\\"Jim Smith\\\",\\\"ownerStreet\\\":\\\"456 My Street\\\",\\\"ownerCity\\\":\\\"Ottawa\\\",\\\"ownerState\\\":\\\"ON\\\",\\\"ownerZip\\\":\\\"K1C2N6\\\",\\\"ownerCountry\\\":\\\"CA\\\",\\\"processorId\\\":\\\"15417\\\",\\\"merchantKey\\\":\\\"a91c38c3-7d7f-4d29-acc7-927b4dca0dbe\\\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Cache-Control: no-cache\\r\\n\"\n-> \"Pragma: no-cache\\r\\n\"\n-> \"Content-Type: application/json; charset=utf-8\\r\\n\"\n-> \"Expires: -1\\r\\n\"\n-> \"Server: Microsoft-IIS/8.5\\r\\n\"\n-> \"cacheControlHeader: max-age=604800\\r\\n\"\n-> \"X-Frame-Options: SAMEORIGIN\\r\\n\"\n-> \"Server-Timing: dtSInfo;desc=\\\"0\\\", dtRpid;desc=\\\"6653911\\\"\\r\\n\"\n-> \"Set-Cookie: dtCookie=v_4_srv_25_sn_229120735766FEB2E6DDFF943AAE854B_perc_100000_ol_0_mul_1_app-3A9b02c199f0b03d02_1_rcs-3Acss_0; Path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Date: Thu, 08 Feb 2024 16:01:55 GMT\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Length: 728\\r\\n\"\n-> \"Set-Cookie: visid_incap_1062257=eHvRBa+XQCW1gGR0YBPEY/P6xGUAAAAAQUIPAAAAAACnSZS9oi5gsXdpeLLAD5GF; expires=Fri, 07 Feb 2025 06:54:02 GMT; HttpOnly; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Set-Cookie: nlbi_1062257=dhZJMDyfcwOqd4xnV7L7rwAAAAC5FWzum6uW3m7ncs3yPd5v; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Set-Cookie: incap_ses_1431_1062257=KaP3NrSI5RQVmH3mPu/bE/P6xGUAAAAAjL9pVzaGFN+QxtEAMI1qbQ==; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"X-CDN: Imperva\\r\\n\"\n-> \"X-Iinfo: 12-32874223-32874361 NNNN CT(38 76 0) RT(1707408112989 881) q(0 0 1 -1) r(17 17) U24\\r\\n\"\n-> \"\\r\\n\"\nreading 728 bytes...\n-> \"{\\\"data\\\":{\\\"authResponse\\\":\\\"Approved 360176\\\",\\\"authCode\\\":\\\"360176\\\",\\\"referenceNumber\\\":\\\"31077352\\\",\\\"isPartial\\\":false,\\\"partialId\\\":\\\"\\\",\\\"originalFullAmount\\\":1.0,\\\"partialAmountApproved\\\":0.0,\\\"avsResponse\\\":\\\"Y\\\",\\\"cvv2Response\\\":\\\"\\\",\\\"orderId\\\":\\\"638430049144239976\\\",\\\"cardType\\\":\\\"Visa\\\",\\\"last4\\\":\\\"1111\\\",\\\"maskedPan\\\":\\\"411111******1111\\\",\\\"token\\\":\\\"1266392642841111\\\",\\\"cardExpMonth\\\":\\\"9\\\",\\\"cardExpYear\\\":\\\"25\\\",\\\"hasFee\\\":false,\\\"fee\\\":null,\\\"billi\"\n-> \"ngAddress\\\":{\\\"ownerName\\\":\\\"Jim Smith\\\",\\\"ownerStreet\\\":\\\"456 My Street\\\",\\\"ownerStreet2\\\":null,\\\"ownerCity\\\":\\\"Ottawa\\\",\\\"ownerState\\\":\\\"ON\\\",\\\"ownerZip\\\":\\\"K1C2N6\\\",\\\"ownerCountry\\\":\\\"CA\\\",\\\"ownerEmail\\\":null,\\\"ownerPhone\\\":null}},\\\"isError\\\":false,\\\"errorMessages\\\":[],\\\"validationHasFailed\\\":false,\\\"validationFailures\\\":[],\\\"isSuccess\\\":true,\\\"action\\\":\\\"Sale\\\"}\"\nread 728 bytes\nConn close\n"
+ RESPONSE
+ end
+
+ def post_scrubbed
+ <<~RESPONSE
+ "opening connection to secure.1stpaygateway.net:443...\nopened\nstarting SSL for secure.1stpaygateway.net:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256\n<- \"POST /secure/RestGW/Gateway/Transaction/Sale HTTP/1.1\\r\\nContent-Type: application/json\\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.1stpaygateway.net\\r\\nContent-Length: 314\\r\\n\\r\\n\"\n<- \"{\\\"transactionAmount\\\":\\\"1.00\\\",\\\"cardNumber\\\":\\\"[FILTERED]\",\\\"cardExpMonth\\\":9,\\\"cardExpYear\\\":\\\"25\\\",\\\"cvv\\\":[FILTERED],\\\"ownerName\\\":\\\"Jim Smith\\\",\\\"ownerStreet\\\":\\\"456 My Street\\\",\\\"ownerCity\\\":\\\"Ottawa\\\",\\\"ownerState\\\":\\\"ON\\\",\\\"ownerZip\\\":\\\"K1C2N6\\\",\\\"ownerCountry\\\":\\\"CA\\\",\\\"processorId\\\":\\\"[FILTERED]\",\\\"merchantKey\\\":\\\"[FILTERED]\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Cache-Control: no-cache\\r\\n\"\n-> \"Pragma: no-cache\\r\\n\"\n-> \"Content-Type: application/json; charset=utf-8\\r\\n\"\n-> \"Expires: -1\\r\\n\"\n-> \"Server: Microsoft-IIS/8.5\\r\\n\"\n-> \"cacheControlHeader: max-age=604800\\r\\n\"\n-> \"X-Frame-Options: SAMEORIGIN\\r\\n\"\n-> \"Server-Timing: dtSInfo;desc=\\\"0\\\", dtRpid;desc=\\\"6653911\\\"\\r\\n\"\n-> \"Set-Cookie: dtCookie=v_4_srv_25_sn_229120735766FEB2E6DDFF943AAE854B_perc_100000_ol_0_mul_1_app-3A9b02c199f0b03d02_1_rcs-3Acss_0; Path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Date: Thu, 08 Feb 2024 16:01:55 GMT\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Length: 728\\r\\n\"\n-> \"Set-Cookie: visid_incap_1062257=eHvRBa+XQCW1gGR0YBPEY/P6xGUAAAAAQUIPAAAAAACnSZS9oi5gsXdpeLLAD5GF; expires=Fri, 07 Feb 2025 06:54:02 GMT; HttpOnly; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Set-Cookie: nlbi_1062257=dhZJMDyfcwOqd4xnV7L7rwAAAAC5FWzum6uW3m7ncs3yPd5v; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"Set-Cookie: incap_ses_1431_1062257=KaP3NrSI5RQVmH3mPu/bE/P6xGUAAAAAjL9pVzaGFN+QxtEAMI1qbQ==; path=/; Domain=.1stpaygateway.net\\r\\n\"\n-> \"X-CDN: Imperva\\r\\n\"\n-> \"X-Iinfo: 12-32874223-32874361 NNNN CT(38 76 0) RT(1707408112989 881) q(0 0 1 -1) r(17 17) U24\\r\\n\"\n-> \"\\r\\n\"\nreading 728 bytes...\n-> \"{\\\"data\\\":{\\\"authResponse\\\":\\\"Approved 360176\\\",\\\"authCode\\\":\\\"360176\\\",\\\"referenceNumber\\\":\\\"31077352\\\",\\\"isPartial\\\":false,\\\"partialId\\\":\\\"\\\",\\\"originalFullAmount\\\":1.0,\\\"partialAmountApproved\\\":0.0,\\\"avsResponse\\\":\\\"Y\\\",\\\"cvv2Response\\\":\\\"\\\",\\\"orderId\\\":\\\"638430049144239976\\\",\\\"cardType\\\":\\\"Visa\\\",\\\"last4\\\":\\\"1111\\\",\\\"maskedPan\\\":\\\"411111******1111\\\",\\\"token\\\":\\\"1266392642841111\\\",\\\"cardExpMonth\\\":\\\"9\\\",\\\"cardExpYear\\\":\\\"25\\\",\\\"hasFee\\\":false,\\\"fee\\\":null,\\\"billi\"\n-> \"ngAddress\\\":{\\\"ownerName\\\":\\\"Jim Smith\\\",\\\"ownerStreet\\\":\\\"456 My Street\\\",\\\"ownerStreet2\\\":null,\\\"ownerCity\\\":\\\"Ottawa\\\",\\\"ownerState\\\":\\\"ON\\\",\\\"ownerZip\\\":\\\"K1C2N6\\\",\\\"ownerCountry\\\":\\\"CA\\\",\\\"ownerEmail\\\":null,\\\"ownerPhone\\\":null}},\\\"isError\\\":false,\\\"errorMessages\\\":[],\\\"validationHasFailed\\\":false,\\\"validationFailures\\\":[],\\\"isSuccess\\\":true,\\\"action\\\":\\\"Sale\\\"}\"\nread 728 bytes\nConn close\n"
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/firstdata_e4_test.rb b/test/unit/gateways/firstdata_e4_test.rb
index 21a0a0da180..c18b5941cc9 100755
--- a/test/unit/gateways/firstdata_e4_test.rb
+++ b/test/unit/gateways/firstdata_e4_test.rb
@@ -63,8 +63,7 @@ def test_successful_purchase_with_token
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 response = @gateway.purchase(@amount, '8938737759041111;visa;Longbob;Longsen;9;2014', options_with_specified_currency)
assert_success response
assert_equal 'GBP', response.params['currency']
end
@@ -1049,7 +1048,7 @@ def no_transaction_response
read: true
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1086,7 +1085,7 @@ def bad_credentials_response
http_version: '1.1'
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['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
index 02f982bb551..a4301598f65 100644
--- a/test/unit/gateways/firstdata_e4_v27_test.rb
+++ b/test/unit/gateways/firstdata_e4_v27_test.rb
@@ -1001,7 +1001,7 @@ def no_transaction_response
read: true
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1038,7 +1038,7 @@ def bad_credentials_response
http_version: '1.1'
socket:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def successful_void_response
diff --git a/test/unit/gateways/flex_charge_test.rb b/test/unit/gateways/flex_charge_test.rb
new file mode 100644
index 00000000000..4d0acc69b80
--- /dev/null
+++ b/test/unit/gateways/flex_charge_test.rb
@@ -0,0 +1,538 @@
+require 'test_helper'
+
+class FlexChargeTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = FlexChargeGateway.new(
+ app_key: 'SOMECREDENTIAL',
+ app_secret: 'SOMECREDENTIAL',
+ site_id: 'SOMECREDENTIAL',
+ mid: 'SOMECREDENTIAL'
+ )
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ is_declined: true,
+ order_id: SecureRandom.uuid,
+ idempotency_key: SecureRandom.uuid,
+ email: 'test@gmail.com',
+ response_code: '100',
+ response_code_source: 'nmi',
+ avs_result_code: '200',
+ cvv_result_code: '111',
+ cavv_result_code: '111',
+ timezone_utc_offset: '-5',
+ billing_address: address.merge(name: 'Cure Tester')
+ }
+
+ @cit_options = {
+ is_mit: false,
+ phone: '+99.2001a/+99.2001b'
+ }.merge(@options)
+
+ @mit_options = {
+ is_mit: true,
+ is_recurring: false,
+ mit_expiry_date_utc: (Time.now + 1.day).getutc.iso8601,
+ description: 'MyShoesStore'
+ }.merge(@options)
+
+ @mit_recurring_options = {
+ is_recurring: true,
+ subscription_id: SecureRandom.uuid,
+ subscription_interval: 'monthly'
+ }.merge(@mit_options)
+
+ @three_d_secure_options = {
+ three_d_secure: {
+ eci: '05',
+ cavv: 'AAABCSIIAAAAAAACcwgAEMCoNh=',
+ xid: 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=',
+ version: '2.1.0',
+ ds_transaction_id: 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=',
+ cavv_algorithm: 'AAABCSIIAAAAAAACcwgAEMCoNh=',
+ directory_response_status: 'Y',
+ authentication_response_status: 'Y',
+ enrolled: 'Y'
+ }
+ }.merge(@options)
+ end
+
+ def test_supported_countries
+ assert_equal %w(US), FlexChargeGateway.supported_countries
+ end
+
+ def test_supported_cardtypes
+ assert_equal %i[visa master american_express discover], @gateway.supported_cardtypes
+ end
+
+ def test_build_request_url_for_purchase
+ action = :purchase
+ assert_equal @gateway.send(:url, action), "#{@gateway.test_url}evaluate"
+ end
+
+ def test_build_request_url_with_id_param
+ action = :refund
+ id = 123
+ assert_equal @gateway.send(:url, action, id), "#{@gateway.test_url}orders/123/refund"
+ end
+
+ def test_build_request_url_for_store
+ action = :store
+ assert_equal @gateway.send(:url, action), "#{@gateway.test_url}tokenize"
+ end
+
+ def test_invalid_instance
+ error = assert_raises(ArgumentError) { FlexChargeGateway.new }
+ assert_equal 'Missing required parameter: app_key', error.message
+ end
+
+ def test_successful_purchase
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_method, endpoint, data, headers|
+ request = JSON.parse(data)
+ if /token/.match?(endpoint)
+ assert_equal request['AppKey'], @gateway.options[:app_key]
+ assert_equal request['AppSecret'], @gateway.options[:app_secret]
+ end
+
+ if /evaluate/.match?(endpoint)
+ assert_equal headers['Authorization'], "Bearer #{@gateway.options[:access_token]}"
+ assert_equal request['siteId'], @gateway.options[:site_id]
+ assert_equal request['mid'], @gateway.options[:mid]
+ assert_equal request['isDeclined'], @options[:is_declined]
+ assert_equal request['orderId'], @options[:order_id]
+ assert_equal request['idempotencyKey'], @options[:idempotency_key]
+ assert_equal request['transaction']['timezoneUtcOffset'], @options[:timezone_utc_offset]
+ assert_equal request['transaction']['amount'], @amount
+ assert_equal request['transaction']['responseCode'], @options[:response_code]
+ assert_equal request['transaction']['responseCodeSource'], @options[:response_code_source]
+ assert_equal request['transaction']['avsResultCode'], @options[:avs_result_code]
+ assert_equal request['transaction']['cvvResultCode'], @options[:cvv_result_code]
+ assert_equal request['transaction']['cavvResultCode'], @options[:cavv_result_code]
+ assert_equal request['payer']['email'], @options[:email]
+ assert_equal request['description'], @options[:description]
+ end
+ end.respond_with(successful_access_token_response, successful_purchase_response)
+
+ assert_success response
+
+ assert_equal 'ca7bb327-a750-412d-a9c3-050d72b3f0c5', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_three_ds_global
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @three_d_secure_options)
+ end.respond_with(successful_access_token_response, successful_purchase_response)
+ assert_success response
+ assert_equal 'ca7bb327-a750-412d-a9c3-050d72b3f0c5', response.authorization
+ assert response.test?
+ end
+
+ def test_succeful_request_with_three_ds_global
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @three_d_secure_options)
+ end.check_request do |_method, endpoint, data, _headers|
+ if /evaluate/.match?(endpoint)
+ request = JSON.parse(data)
+ assert_equal request['threeDSecure']['EcommerceIndicator'], @three_d_secure_options[:three_d_secure][:eci]
+ assert_equal request['threeDSecure']['authenticationValue'], @three_d_secure_options[:three_d_secure][:cavv]
+ assert_equal request['threeDSecure']['xid'], @three_d_secure_options[:three_d_secure][:xid]
+ assert_equal request['threeDSecure']['threeDsVersion'], @three_d_secure_options[:three_d_secure][:version]
+ assert_equal request['threeDSecure']['directoryServerTransactionId'], @three_d_secure_options[:three_d_secure][:ds_transaction_id]
+ assert_equal request['threeDSecure']['authenticationValueAlgorithm'], @three_d_secure_options[:three_d_secure][:cavv_algorithm]
+ assert_equal request['threeDSecure']['directoryResponseStatus'], @three_d_secure_options[:three_d_secure][:directory_response_status]
+ assert_equal request['threeDSecure']['authenticationResponseStatus'], @three_d_secure_options[:three_d_secure][:authentication_response_status]
+ assert_equal request['threeDSecure']['enrolled'], @three_d_secure_options[:three_d_secure][:enrolled]
+ end
+ end.respond_with(successful_access_token_response, successful_purchase_response)
+ end
+
+ def test_failed_purchase
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_access_token_response, failed_purchase_response)
+
+ assert_failure response
+ assert_equal '400', response.error_code
+ assert_equal '400', response.message
+ end
+
+ def test_failed_refund
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.refund(@amount, 'reference', @options)
+ end.check_request do |_method, endpoint, data, _headers|
+ request = JSON.parse(data)
+
+ if /token/.match?(endpoint)
+ assert_equal request['AppKey'], @gateway.options[:app_key]
+ assert_equal request['AppSecret'], @gateway.options[:app_secret]
+ end
+
+ assert_equal request['amountToRefund'], (@amount.to_f / 100).round(2) if /orders\/reference\/refund/.match?(endpoint)
+ end.respond_with(successful_access_token_response, failed_refund_response)
+
+ assert_failure response
+ assert response.test?
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_address_names_from_address
+ names = @gateway.send(:address_names, @options[:billing_address][:name], @credit_card)
+
+ assert_equal 'Cure', names.first
+ assert_equal 'Tester', names.last
+ end
+
+ def test_address_names_from_credit_card
+ names = @gateway.send(:address_names, 'Doe', @credit_card)
+
+ assert_equal 'Longbob', names.first
+ assert_equal 'Doe', names.last
+ end
+
+ def test_successful_store
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.store(@credit_card, @options)
+ end.respond_with(successful_access_token_response, successful_store_response)
+
+ assert_success response
+ assert_equal 'd3e10716-6aac-4eb8-a74d-c1a3027f1d96', response.authorization
+ end
+
+ def test_successful_inquire_request
+ session_id = 'f8da8dc7-17de-4b5e-858d-4bdc47cd5dbf'
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.inquire(session_id, {})
+ end.check_request do |_method, endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['orderSessionKey'], session_id if /outcome/.match?(endpoint)
+ end.respond_with(successful_access_token_response, successful_purchase_response)
+ end
+
+ private
+
+ def pre_scrubbed
+ "opening connection to api-sandbox.flex-charge.com:443...
+ opened
+ starting SSL for api-sandbox.flex-charge.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256
+ <- \"POST /v1/oauth2/token HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api-sandbox.flex-charge.com\\r\
+ Content-Length: 153\\r\
+ \\r\
+ \"
+ <- \"{\\\"AppKey\\\":\\\"2/tprAqlvujvIZonWkLntQMj3CbH7Y9sKLqTTdWu\\\",\\\"AppSecret\\\":\\\"AQAAAAEAACcQAAAAEFb/TYEfAlzWhb6SDXEbS06A49kc/P6Cje6 MDta3o61GGS4tLLk8m/BZuJOyZ7B99g==\\\"}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 04 Apr 2024 13:29:08 GMT\\r\
+ \"
+ -> \"Content-Type: application/json; charset=utf-8\\r\
+ \"
+ -> \"Content-Length: 902\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"server: Kestrel\\r\
+ \"
+ -> \"set-cookie: AWSALB=n2vt9daKLxUPgxF+n3g+4uQDgxt1PNVOY/HwVuLZdkf0Ye8XkAFuEVrnu6xh/xf7k2ZYZHqaPthqR36D3JxPJIs7QfNbcfAhvxTlPEVx8t/IyB1Kb/Vinasi3vZD; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/\\r\
+ \"
+ -> \"set-cookie: AWSALBCORS=n2vt9daKLxUPgxF+n3g+4uQDgxt1PNVOY/HwVuLZdkf0Ye8XkAFuEVrnu6xh/xf7k2ZYZHqaPthqR36D3JxPJIs7QfNbcfAhvxTlPEVx8t/IyB1Kb/Vinasi3vZD; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/; SameSite=None; Secure\\r\
+ \"
+ -> \"apigw-requestid: Vs-twgfMoAMEaEQ=\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 902 bytes...
+ -> \"{\\\"accessToken\\\":\\\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYmE4NGY2ZS03YTllLTQzZjEtYWU2ZC1jNTA4YjQ2NjQyNGEiLCJ1bmlxdWVfbmFtZSI6IjBiYTg0ZjZlLTdhOWUtNDNmMS1hZTZkLWM1MDhiNDY2NDI0YSIsImp0aSI6IjI2NTQxY2FlLWM3ZjUtNDU0MC04MTUyLTZiNGExNzQ3ZTJmMSIsImlhdCI6IjE3MTIyMzczNDg1NjUiLCJhdWQiOlsicGF5bWVudHMiLCJvcmRlcnMiLCJtZXJjaGFudHMiLCJlbGlnaWJpbGl0eS1zZnRwIiwiZWxpZ2liaWxpdHkiLCJjb250YWN0Il0sImN1c3RvbTptaWQiOiJkOWQwYjVmZC05NDMzLTQ0ZDMtODA1MS02M2ZlZTI4NzY4ZTgiLCJuYmYiOjE3MTIyMzczNDgsImV4cCI6MTcxMjIzNzk0OCwiaXNzIjoiQXBpLUNsaWVudC1TZXJ2aWNlIn0.ZGYzd6NA06o2zP-qEWf6YpyrY-v-Jb-i1SGUOUkgRPo\\\",\\\"refreshToken\\\":\\\"AQAAAAEAACcQAAAAEG5H7emaTnpUcVSWrbwLlPBEEdQ3mTCCHT5YMLBNauXxilaXHwL8oFiI4heg6yA\\\",\\\"expires\\\":1712237948565,\\\"id\\\":\\\"0ba84f6e-7a9e-43f1-ae6d-c508b466424a\\\",\\\"session\\\":null,\\\"daysToEnforceMFA\\\":null,\\\"skipAvailable\\\":null,\\\"success\\\":true,\\\"result\\\":null,\\\"status\\\":null,\\\"statusCode\\\":null,\\\"errors\\\":[],\\\"customProperties\\\":{}}\"
+ read 902 bytes
+ Conn close
+ opening connection to api-sandbox.flex-charge.com:443...
+ opened
+ starting SSL for api-sandbox.flex-charge.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256
+ <- \"POST /v1/evaluate HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYmE4NGY2ZS03YTllLTQzZjEtYWU2ZC1jNTA4YjQ2NjQyNGEiLCJ1bmlxdWVfbmFtZSI6IjBiYTg0ZjZlLTdhOWUtNDNmMS1hZTZkLWM1MDhiNDY2NDI0YSIsImp0aSI6IjI2NTQxY2FlLWM3ZjUtNDU0MC04MTUyLTZiNGExNzQ3ZTJmMSIsImlhdCI6IjE3MTIyMzczNDg1NjUiLCJhdWQiOlsicGF5bWVudHMiLCJvcmRlcnMiLCJtZXJjaGFudHMiLCJlbGlnaWJpbGl0eS1zZnRwIiwiZWxpZ2liaWxpdHkiLCJjb250YWN0Il0sImN1c3RvbTptaWQiOiJkOWQwYjVmZC05NDMzLTQ0ZDMtODA1MS02M2ZlZTI4NzY4ZTgiLCJuYmYiOjE3MTIyMzczNDgsImV4cCI6MTcxMjIzNzk0OCwiaXNzIjoiQXBpLUNsaWVudC1TZXJ2aWNlIn0.ZGYzd6NA06o2zP-qEWf6YpyrY-v-Jb-i1SGUOUkgRPo\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api-sandbox.flex-charge.com\\r\
+ Content-Length: 999\\r\
+ \\r\
+ \"
+ <- \"{\\\"siteId\\\":\\\"ffae80fd-2b8e-487a-94c3-87503a0c71bb\\\",\\\"mid\\\":\\\"d9d0b5fd-9433-44d3-8051-63fee28768e8\\\",\\\"isDeclined\\\":true,\\\"orderId\\\":\\\"b53827df-1f19-4dd9-9829-25a108255ba1\\\",\\\"idempotencyKey\\\":\\\"46902e30-ae70-42c5-a0d3-1994133b4f52\\\",\\\"transaction\\\":{\\\"id\\\":\\\"b53827df-1f19-4dd9-9829-25a108255ba1\\\",\\\"dynamicDescriptor\\\":\\\"MyShoesStore\\\",\\\"timezoneUtcOffset\\\":\\\"-5\\\",\\\"amount\\\":100,\\\"currency\\\":\\\"USD\\\",\\\"responseCode\\\":\\\"100\\\",\\\"responseCodeSource\\\":\\\"nmi\\\",\\\"avsResultCode\\\":\\\"200\\\",\\\"cvvResultCode\\\":\\\"111\\\",\\\"cavvResultCode\\\":\\\"111\\\",\\\"cardNotPresent\\\":true},\\\"paymentMethod\\\":{\\\"holderName\\\":\\\"Longbob Longsen\\\",\\\"cardType\\\":\\\"CREDIT\\\",\\\"cardBrand\\\":\\\"VISA\\\",\\\"cardCountry\\\":\\\"CA\\\",\\\"expirationMonth\\\":9,\\\"expirationYear\\\":2025,\\\"cardBinNumber\\\":\\\"411111\\\",\\\"cardLast4Digits\\\":\\\"1111\\\",\\\"cardNumber\\\":\\\"4111111111111111\\\"},\\\"billingInformation\\\":{\\\"firstName\\\":\\\"Cure\\\",\\\"lastName\\\":\\\"Tester\\\",\\\"country\\\":\\\"CA\\\",\\\"phone\\\":\\\"(555)555-5555\\\",\\\"countryCode\\\":\\\"CA\\\",\\\"addressLine1\\\":\\\"456 My Street\\\",\\\"state\\\":\\\"ON\\\",\\\"city\\\":\\\"Ottawa\\\",\\\"zipCode\\\":\\\"K1C2N6\\\"},\\\"payer\\\":{\\\"email\\\":\\\"test@gmail.com\\\",\\\"phone\\\":\\\"+99.2001a/+99.2001b\\\"}}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 04 Apr 2024 13:29:11 GMT\\r\
+ \"
+ -> \"Content-Type: application/json; charset=utf-8\\r\
+ \"
+ -> \"Content-Length: 230\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"server: Kestrel\\r\
+ \"
+ -> \"set-cookie: AWSALB=Mw7gQis/D9qOm0eQvpkNsEOvZerr+YBDNyfJyJ2T2BGel3cg8AX9OtpuXXR/UCCgNRf5J9UTY+soHqLEJuxIEdEK5lNPelLtQbO0oKGB12q0gPRI7T5H1ijnf+RF; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/\\r\
+ \"
+ -> \"set-cookie: AWSALBCORS=Mw7gQis/D9qOm0eQvpkNsEOvZerr+YBDNyfJyJ2T2BGel3cg8AX9OtpuXXR/UCCgNRf5J9UTY+soHqLEJuxIEdEK5lNPelLtQbO0oKGB12q0gPRI7T5H1ijnf+RF; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/; SameSite=None; Secure\\r\
+ \"
+ -> \"apigw-requestid: Vs-t0g9gIAMES8w=\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 230 bytes...
+ -> \"{\\\"orderSessionKey\\\":\\\"e97b1ff1-4449-46da-bc6c-a76d23f16353\\\",\\\"senseKey\\\":null,\\\"orderId\\\":\\\"e97b1ff1-4449-46da-bc6c-a76d23f16353\\\",\\\"success\\\":true,\\\"result\\\":\\\"Success\\\",\\\"status\\\":\\\"CHALLENGE\\\",\\\"statusCode\\\":null,\\\"errors\\\":[],\\\"customProperties\\\":{}}\"
+ read 230 bytes
+ Conn close
+ "
+ end
+
+ def post_scrubbed
+ "opening connection to api-sandbox.flex-charge.com:443...
+ opened
+ starting SSL for api-sandbox.flex-charge.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256
+ <- \"POST /v1/oauth2/token HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api-sandbox.flex-charge.com\\r\
+ Content-Length: 153\\r\
+ \\r\
+ \"
+ <- \"{\\\"AppKey\\\":\\\"[FILTERED]\",\\\"AppSecret\\\":\\\"[FILTERED]\"}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 04 Apr 2024 13:29:08 GMT\\r\
+ \"
+ -> \"Content-Type: application/json; charset=utf-8\\r\
+ \"
+ -> \"Content-Length: 902\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"server: Kestrel\\r\
+ \"
+ -> \"set-cookie: AWSALB=n2vt9daKLxUPgxF+n3g+4uQDgxt1PNVOY/HwVuLZdkf0Ye8XkAFuEVrnu6xh/xf7k2ZYZHqaPthqR36D3JxPJIs7QfNbcfAhvxTlPEVx8t/IyB1Kb/Vinasi3vZD; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/\\r\
+ \"
+ -> \"set-cookie: AWSALBCORS=n2vt9daKLxUPgxF+n3g+4uQDgxt1PNVOY/HwVuLZdkf0Ye8XkAFuEVrnu6xh/xf7k2ZYZHqaPthqR36D3JxPJIs7QfNbcfAhvxTlPEVx8t/IyB1Kb/Vinasi3vZD; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/; SameSite=None; Secure\\r\
+ \"
+ -> \"apigw-requestid: Vs-twgfMoAMEaEQ=\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 902 bytes...
+ -> \"{\\\"accessToken\\\":\\\"[FILTERED]\",\\\"refreshToken\\\":\\\"AQAAAAEAACcQAAAAEG5H7emaTnpUcVSWrbwLlPBEEdQ3mTCCHT5YMLBNauXxilaXHwL8oFiI4heg6yA\\\",\\\"expires\\\":1712237948565,\\\"id\\\":\\\"0ba84f6e-7a9e-43f1-ae6d-c508b466424a\\\",\\\"session\\\":null,\\\"daysToEnforceMFA\\\":null,\\\"skipAvailable\\\":null,\\\"success\\\":true,\\\"result\\\":null,\\\"status\\\":null,\\\"statusCode\\\":null,\\\"errors\\\":[],\\\"customProperties\\\":{}}\"
+ read 902 bytes
+ Conn close
+ opening connection to api-sandbox.flex-charge.com:443...
+ opened
+ starting SSL for api-sandbox.flex-charge.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256
+ <- \"POST /v1/evaluate HTTP/1.1\\r\
+ Content-Type: application/json\\r\
+ Authorization: Bearer [FILTERED]\\r\
+ Connection: close\\r\
+ Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\
+ Accept: */*\\r\
+ User-Agent: Ruby\\r\
+ Host: api-sandbox.flex-charge.com\\r\
+ Content-Length: 999\\r\
+ \\r\
+ \"
+ <- \"{\\\"siteId\\\":\\\"[FILTERED]\",\\\"mid\\\":\\\"[FILTERED]\",\\\"isDeclined\\\":true,\\\"orderId\\\":\\\"b53827df-1f19-4dd9-9829-25a108255ba1\\\",\\\"idempotencyKey\\\":\\\"46902e30-ae70-42c5-a0d3-1994133b4f52\\\",\\\"transaction\\\":{\\\"id\\\":\\\"b53827df-1f19-4dd9-9829-25a108255ba1\\\",\\\"dynamicDescriptor\\\":\\\"MyShoesStore\\\",\\\"timezoneUtcOffset\\\":\\\"-5\\\",\\\"amount\\\":100,\\\"currency\\\":\\\"USD\\\",\\\"responseCode\\\":\\\"100\\\",\\\"responseCodeSource\\\":\\\"nmi\\\",\\\"avsResultCode\\\":\\\"200\\\",\\\"cvvResultCode\\\":\\\"111\\\",\\\"cavvResultCode\\\":\\\"111\\\",\\\"cardNotPresent\\\":true},\\\"paymentMethod\\\":{\\\"holderName\\\":\\\"Longbob Longsen\\\",\\\"cardType\\\":\\\"CREDIT\\\",\\\"cardBrand\\\":\\\"VISA\\\",\\\"cardCountry\\\":\\\"CA\\\",\\\"expirationMonth\\\":9,\\\"expirationYear\\\":2025,\\\"cardBinNumber\\\":\\\"411111\\\",\\\"cardLast4Digits\\\":\\\"1111\\\",\\\"cardNumber\\\":\\\"[FILTERED]\"},\\\"billingInformation\\\":{\\\"firstName\\\":\\\"Cure\\\",\\\"lastName\\\":\\\"Tester\\\",\\\"country\\\":\\\"CA\\\",\\\"phone\\\":\\\"(555)555-5555\\\",\\\"countryCode\\\":\\\"CA\\\",\\\"addressLine1\\\":\\\"456 My Street\\\",\\\"state\\\":\\\"ON\\\",\\\"city\\\":\\\"Ottawa\\\",\\\"zipCode\\\":\\\"K1C2N6\\\"},\\\"payer\\\":{\\\"email\\\":\\\"test@gmail.com\\\",\\\"phone\\\":\\\"+99.2001a/+99.2001b\\\"}}\"
+ -> \"HTTP/1.1 200 OK\\r\
+ \"
+ -> \"Date: Thu, 04 Apr 2024 13:29:11 GMT\\r\
+ \"
+ -> \"Content-Type: application/json; charset=utf-8\\r\
+ \"
+ -> \"Content-Length: 230\\r\
+ \"
+ -> \"Connection: close\\r\
+ \"
+ -> \"server: Kestrel\\r\
+ \"
+ -> \"set-cookie: AWSALB=Mw7gQis/D9qOm0eQvpkNsEOvZerr+YBDNyfJyJ2T2BGel3cg8AX9OtpuXXR/UCCgNRf5J9UTY+soHqLEJuxIEdEK5lNPelLtQbO0oKGB12q0gPRI7T5H1ijnf+RF; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/\\r\
+ \"
+ -> \"set-cookie: AWSALBCORS=Mw7gQis/D9qOm0eQvpkNsEOvZerr+YBDNyfJyJ2T2BGel3cg8AX9OtpuXXR/UCCgNRf5J9UTY+soHqLEJuxIEdEK5lNPelLtQbO0oKGB12q0gPRI7T5H1ijnf+RF; Expires=Thu, 11 Apr 2024 13:29:08 GMT; Path=/; SameSite=None; Secure\\r\
+ \"
+ -> \"apigw-requestid: Vs-t0g9gIAMES8w=\\r\
+ \"
+ -> \"\\r\
+ \"
+ reading 230 bytes...
+ -> \"{\\\"orderSessionKey\\\":\\\"e97b1ff1-4449-46da-bc6c-a76d23f16353\\\",\\\"senseKey\\\":null,\\\"orderId\\\":\\\"e97b1ff1-4449-46da-bc6c-a76d23f16353\\\",\\\"success\\\":true,\\\"result\\\":\\\"Success\\\",\\\"status\\\":\\\"CHALLENGE\\\",\\\"statusCode\\\":null,\\\"errors\\\":[],\\\"customProperties\\\":{}}\"
+ read 230 bytes
+ Conn close
+ "
+ end
+
+ def successful_access_token_response
+ <<~RESPONSE
+ {
+ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYmE4NGY2ZS03YTllLTQzZjEtYWU2ZC1jNTA4YjQ2NjQyNGEiLCJ1bmlxdWVfbmFtZSI6IjBiYTg0ZjZlLTdhOWUtNDNmMS1hZTZkLWM1MDhiNDY2NDI0YSIsImp0aSI6ImY5NzdlZDE3LWFlZDItNGIxOC1hMjY1LWY0NzkwNTY0ZDc1NSIsImlhdCI6IjE3MTIwNzE1NDMyNDYiLCJhdWQiOlsicGF5bWVudHMiLCJvcmRlcnMiLCJtZXJjaGFudHMiLCJlbGlnaWJpbGl0eS1zZnRwIiwiZWxpZ2liaWxpdHkiLCJjb250YWN0Il0sImN1c3RvbTptaWQiOiJkOWQwYjVmZC05NDMzLTQ0ZDMtODA1MS02M2ZlZTI4NzY4ZTgiLCJuYmYiOjE3MTIwNzE1NDMsImV4cCI6MTcxMjA3MjE0MywiaXNzIjoiQXBpLUNsaWVudC1TZXJ2aWNlIn0.S9xgOejudB93Gf9Np9S8jtudhbY9zJj_j7n5al_SKZg",
+ "refreshToken": "AQAAAAEAACcQAAAAEKd3NvUOrqgJXW8FtE22UbdZzuMWcbq7kSMIGss9OcV2aGzCXMNrOJgAW5Zg",
+ "expires": #{(DateTime.now + 10.minutes).strftime('%Q').to_i},
+ "id": "0ba84f6e-7a9e-43f1-ae6d-c508b466424a",
+ "session": null,
+ "daysToEnforceMFA": null,
+ "skipAvailable": null,
+ "success": true,
+ "result": null,
+ "status": null,
+ "statusCode": null,
+ "errors": [],
+ "customProperties": {}
+ }
+ RESPONSE
+ end
+
+ def successful_purchase_response
+ <<~RESPONSE
+ {
+ "orderSessionKey": "ca7bb327-a750-412d-a9c3-050d72b3f0c5",
+ "senseKey": null,
+ "orderId": "ca7bb327-a750-412d-a9c3-050d72b3f0c5",
+ "success": true,
+ "result": "Success",
+ "status": "CHALLENGE",
+ "statusCode": null,
+ "errors": [],
+ "customProperties": {}
+ }
+ RESPONSE
+ end
+
+ def successful_store_response
+ <<~RESPONSE
+ {
+ "transaction": {
+ "on_test_gateway": true,
+ "created_at": "2024-05-14T13:44:25.3179186Z",
+ "updated_at": "2024-05-14T13:44:25.3179187Z",
+ "succeeded": true,
+ "state": null,
+ "token": null,
+ "transaction_type": null,
+ "order_id": null,
+ "ip": null,
+ "description": null,
+ "email": null,
+ "merchant_name_descriptor": null,
+ "merchant_location_descriptor": null,
+ "gateway_specific_fields": null,
+ "gateway_specific_response_fields": null,
+ "gateway_transaction_id": null,
+ "gateway_latency_ms": null,
+ "amount": 0,
+ "currency_code": null,
+ "retain_on_success": null,
+ "payment_method_added": false,
+ "message_key": null,
+ "message": null,
+ "response": null,
+ "payment_method": {
+ "token": "d3e10716-6aac-4eb8-a74d-c1a3027f1d96",
+ "created_at": "2024-05-14T13:44:25.3179205Z",
+ "updated_at": "2024-05-14T13:44:25.3179206Z",
+ "email": null,
+ "data": null,
+ "storage_state": null,
+ "test": false,
+ "metadata": null,
+ "last_four_digits": "1111",
+ "first_six_digits": "41111111",
+ "card_type": null,
+ "first_name": "Cure",
+ "last_name": "Tester",
+ "month": 9,
+ "year": 2025,
+ "address1": null,
+ "address2": null,
+ "city": null,
+ "state": null,
+ "zip": null,
+ "country": null,
+ "phone_number": null,
+ "company": null,
+ "full_name": null,
+ "payment_method_type": null,
+ "errors": null,
+ "fingerprint": null,
+ "verification_value": null,
+ "number": null
+ }
+ },
+ "cardBinInfo": null,
+ "success": true,
+ "result": null,
+ "status": null,
+ "statusCode": null,
+ "errors": [],
+ "customProperties": {},
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwYmE4NGY2ZS03YTllLTQzZjEtYWU2ZC1jNTA4YjQ2NjQyNGEiLCJ1bmlxdWVfbmFtZSI6IjBiYTg0ZjZlLTdhOWUtNDNmMS1hZTZkLWM1MDhiNDY2NDI0YSIsImp0aSI6IjczZTVkOGZiLWYxMDMtNGVlYy1iYTAzLTM2MmY1YjA5MmNkMCIsImlhdCI6IjE3MTU2OTQyNjQ3MDMiLCJhdWQiOlsicGF5bWVudHMiLCJvcmRlcnMiLCJtZXJjaGFudHMiLCJlbGlnaWJpbGl0eS1zZnRwIiwiZWxpZ2liaWxpdHkiLCJjb250YWN0Il0sImN1c3RvbTptaWQiOiJkOWQwYjVmZC05NDMzLTQ0ZDMtODA1MS02M2ZlZTI4NzY4ZTgiLCJuYmYiOjE3MTU2OTQyNjQsImV4cCI6MTcxNTY5NDg2NCwiaXNzIjoiQXBpLUNsaWVudC1TZXJ2aWNlIn0.oB9xtWGthG6tcDie8Q3fXPc1fED8pBAlv8yZQuoiEkA",
+ "token_expires": 1715694864703
+ }
+ RESPONSE
+ end
+
+ def failed_purchase_response
+ <<~RESPONSE
+ {
+ "status": "400",
+ "errors": {
+ "OrderId": ["Merchant's orderId is required"],
+ "TraceId": ["00-3b4af05c51be4aa7dd77104ac75f252b-004c728c64ca280d-01"],
+ "IsDeclined": ["The IsDeclined field is required."],
+ "IdempotencyKey": ["The IdempotencyKey field is required."],
+ "Transaction.Id": ["The Id field is required."],
+ "Transaction.ResponseCode": ["The ResponseCode field is required."],
+ "Transaction.AvsResultCode": ["The AvsResultCode field is required."],
+ "Transaction.CvvResultCode": ["The CvvResultCode field is required."]
+ }
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<~RESPONSE
+ {
+ "responseCode": "2001",
+ "responseMessage": "Amount to refund (1.00) is greater than maximum refund amount in (0.00))",
+ "transactionId": null,
+ "success": false,
+ "result": null,
+ "status": "FAILED",
+ "statusCode": null,
+ "errors": [
+ {
+ "item1": "Amount to refund (1.00) is greater than maximum refund amount in (0.00))",
+ "item2": "2001",
+ "item3": "2001",
+ "item4": true
+ }
+ ],
+ "customProperties": {}
+ }
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/forte_test.rb b/test/unit/gateways/forte_test.rb
index 2d3254244db..fb448d46abb 100644
--- a/test/unit/gateways/forte_test.rb
+++ b/test/unit/gateways/forte_test.rb
@@ -192,6 +192,7 @@ def test_scrub
class MockedResponse
attr_reader :code, :body
+
def initialize(body, code = 200)
@code = code
@body = body
diff --git a/test/unit/gateways/garanti_test.rb b/test/unit/gateways/garanti_test.rb
index cc3416e0f89..ad8ed645b9c 100644
--- a/test/unit/gateways/garanti_test.rb
+++ b/test/unit/gateways/garanti_test.rb
@@ -9,7 +9,7 @@ def setup
Base.mode = :test
@gateway = GarantiGateway.new(login: 'a', password: 'b', terminal_id: 'c', merchant_id: 'd')
- @credit_card = credit_card(4242424242424242)
+ @credit_card = credit_card('4242424242424242')
@amount = 1000 # 1000 cents, 10$
@options = {
diff --git a/test/unit/gateways/gateway_test.rb b/test/unit/gateways/gateway_test.rb
index 68d6d0a441e..27ccbd14215 100644
--- a/test/unit/gateways/gateway_test.rb
+++ b/test/unit/gateways/gateway_test.rb
@@ -28,8 +28,7 @@ def test_should_validate_supported_countries
assert_nothing_raised do
Gateway.supported_countries = all_country_codes
- assert Gateway.supported_countries == all_country_codes,
- 'List of supported countries not properly set'
+ assert Gateway.supported_countries == all_country_codes, 'List of supported countries not properly set'
end
end
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index 0e888bff9d3..964e2739e83 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -9,26 +9,21 @@ def setup
secret_api_key: '109H/288H*50Y18W4/0G8571F245KA=')
@credit_card = credit_card('4567350000427977')
- @apple_pay_network_token = network_tokenization_credit_card('4444333322221111',
+ @apple_pay_network_token = network_tokenization_credit_card(
+ '4444333322221111',
month: 10,
year: 24,
first_name: 'John',
last_name: 'Smith',
eci: '05',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- source: :apple_pay)
+ source: :apple_pay
+ )
- @google_pay_network_token = network_tokenization_credit_card('4444333322221111',
- payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
- month: '01',
- year: Time.new.year + 2,
+ @google_pay_network_token = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
source: :google_pay,
- transaction_id: '123456789',
- eci: '05')
-
- @google_pay_pan_only = credit_card('4444333322221111',
- month: '01',
- year: Time.new.year + 2)
+ payment_data: "{ 'version': 'EC_v1', 'data': 'QlzLxRFnNP9/GTaMhBwgmZ2ywntbr9'}"
+ })
@declined_card = credit_card('5424180279791732')
@accepted_amount = 4005
@@ -46,7 +41,8 @@ def setup
ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC',
acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9',
cavv_algorithm: 1,
- authentication_response_status: 'Y'
+ authentication_response_status: 'Y',
+ flow: 'frictionless'
}
)
end
@@ -79,7 +75,7 @@ def test_successful_preproduction_url
stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@accepted_amount, @credit_card)
end.check_request do |_method, endpoint, _data, _headers|
- assert_match(/world\.preprod\.api-ingenico\.com\/v1\/#{@gateway.options[:merchant_id]}/, endpoint)
+ assert_match(/api\.preprod\.connect\.worldline-solutions\.com\/v1\/#{@gateway.options[:merchant_id]}/, endpoint)
end.respond_with(successful_authorize_response)
end
@@ -92,17 +88,23 @@ def test_successful_purchase_with_requires_approval_true
end.respond_with(successful_authorize_response, successful_capture_response)
end
- def test_purchase_request_with_google_pay
+ def test_purchase_request_with_encrypted_google_pay
+ google_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({
+ source: :google_pay,
+ payment_data: "{ 'version': 'EC_v1', 'data': 'QlzLxRFnNP9/GTaMhBwgmZ2ywntbr9'}"
+ })
+
stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@accepted_amount, @google_pay_network_token)
+ @gateway.purchase(@accepted_amount, google_pay, { use_encrypted_payment_data: true })
end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
assert_equal '320', JSON.parse(data)['mobilePaymentMethodSpecificInput']['paymentProductId']
+ assert_equal google_pay.payment_data, JSON.parse(data)['mobilePaymentMethodSpecificInput']['encryptedPaymentData']
end
end
- def test_purchase_request_with_google_pay_pan_only
+ def test_purchase_request_with_google_pay
stub_comms(@gateway, :ssl_request) do
- @gateway.purchase(@accepted_amount, @google_pay_pan_only, @options.merge(customer: 'GP1234ID', google_pay_pan_only: true))
+ @gateway.purchase(@accepted_amount, @google_pay_network_token)
end.check_request(skip_response: true) do |_method, _endpoint, data, _headers|
assert_equal '320', JSON.parse(data)['mobilePaymentMethodSpecificInput']['paymentProductId']
end
@@ -129,26 +131,7 @@ def test_add_payment_for_google_pay
assert_includes post.keys.first, 'mobilePaymentMethodSpecificInput'
assert_equal post['mobilePaymentMethodSpecificInput']['paymentProductId'], '320'
assert_equal post['mobilePaymentMethodSpecificInput']['authorizationMode'], 'FINAL_AUTHORIZATION'
- assert_includes post['mobilePaymentMethodSpecificInput'].keys, 'decryptedPaymentData'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['dpan'], '4444333322221111'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['cryptogram'], 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['eci'], '05'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['expiryDate'], "01#{payment.year.to_s[-2..-1]}"
- assert_equal 'TOKENIZED_CARD', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['paymentMethod']
- end
-
- def test_add_payment_for_google_pay_pan_only
- post = {}
- options = { google_pay_pan_only: true }
- payment = @google_pay_pan_only
- @gateway.send('add_payment', post, payment, options)
- assert_includes post.keys.first, 'mobilePaymentMethodSpecificInput'
- assert_equal post['mobilePaymentMethodSpecificInput']['paymentProductId'], '320'
- assert_equal post['mobilePaymentMethodSpecificInput']['authorizationMode'], 'FINAL_AUTHORIZATION'
- assert_includes post['mobilePaymentMethodSpecificInput'].keys, 'decryptedPaymentData'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['pan'], '4444333322221111'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['expiryDate'], "01#{payment.year.to_s[-2..-1]}"
- assert_equal 'CARD', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['paymentMethod']
+ assert_equal post['mobilePaymentMethodSpecificInput']['encryptedPaymentData'], @google_pay_network_token.payment_data
end
def test_add_payment_for_apple_pay
@@ -166,47 +149,6 @@ def test_add_payment_for_apple_pay
assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['expiryDate'], '1024'
end
- def test_add_decrypted_data_google_pay_pan_only
- post = { 'mobilePaymentMethodSpecificInput' => {} }
- payment = @google_pay_pan_only
- options = { google_pay_pan_only: true }
- expirydate = '0124'
-
- @gateway.send('add_decrypted_payment_data', post, payment, options, expirydate)
- assert_includes post['mobilePaymentMethodSpecificInput'].keys, 'decryptedPaymentData'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['pan'], '4444333322221111'
- assert_equal 'CARD', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['paymentMethod']
- end
-
- def test_add_decrypted_data_for_google_pay
- post = { 'mobilePaymentMethodSpecificInput' => {} }
- payment = @google_pay_network_token
- options = {}
- expirydate = '0124'
-
- @gateway.send('add_decrypted_payment_data', post, payment, options, expirydate)
- assert_includes post['mobilePaymentMethodSpecificInput'].keys, 'decryptedPaymentData'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['cryptogram'], 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['eci'], '05'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['dpan'], '4444333322221111'
- assert_equal 'TOKENIZED_CARD', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['paymentMethod']
- assert_equal '0124', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['expiryDate']
- end
-
- def test_add_decrypted_data_for_apple_pay
- post = { 'mobilePaymentMethodSpecificInput' => {} }
- payment = @google_pay_network_token
- options = {}
- expirydate = '0124'
-
- @gateway.send('add_decrypted_payment_data', post, payment, options, expirydate)
- assert_includes post['mobilePaymentMethodSpecificInput'].keys, 'decryptedPaymentData'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['cryptogram'], 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['eci'], '05'
- assert_equal post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['dpan'], '4444333322221111'
- assert_equal '0124', post['mobilePaymentMethodSpecificInput']['decryptedPaymentData']['expiryDate']
- end
-
def test_purchase_request_with_apple_pay
stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@accepted_amount, @apple_pay_network_token)
@@ -231,6 +173,7 @@ def test_successful_purchase_airline_fields
name: 'Spreedly Airlines',
flight_date: '20190810',
passenger_name: 'Randi Smith',
+ agent_numeric_code: '12345',
flight_legs: [
{ arrival_airport: 'BDL',
origin_airport: 'RDU',
@@ -379,9 +322,9 @@ def test_successful_authorization_with_extra_options
response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@accepted_amount, @credit_card, options)
end.check_request do |_method, _endpoint, data, _headers|
- assert_match %r("fraudFields":{"website":"www.example.com","giftMessage":"Happy Day!","customerIpAddress":"127.0.0.1"}), data
+ assert_match %r("fraudFields":{"website":"www.example.com","giftMessage":"Happy Day!"}), 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
+ assert_match %r("customer":{"personalInformation":{"name":{"firstName":"Longbob","surname":"Longsen"}},"merchantCustomerId":"123987","contactDetails":{"emailAddress":"example@example.com","phoneNumber":"\(555\)555-5555"},"billingAddress":{"street":"My Street","houseNumber":"456","additionalInfo":"Apt 1","zip":"K1C2N6","city":"Ottawa","state":"ON","countryCode":"CA"}}}), data
assert_match %r("paymentProductId":"123ABC"), data
end.respond_with(successful_authorize_response)
@@ -392,7 +335,8 @@ def test_successful_authorize_with_3ds_auth
response = stub_comms(@gateway, :ssl_request) do
@gateway.authorize(@accepted_amount, @credit_card, @options_3ds2)
end.check_request do |_method, _endpoint, data, _headers|
- assert_match(/"threeDSecure\":{\"externalCardholderAuthenticationData\":{/, data)
+ assert_match(/threeDSecure/, data)
+ assert_match(/externalCardholderAuthenticationData/, data)
assert_match(/"eci\":\"05\"/, data)
assert_match(/"cavv\":\"jJ81HADVRtXfCBATEp01CJUAAAA=\"/, data)
assert_match(/"xid\":\"BwABBJQ1AgAAAAAgJDUCAAAAAAA=\"/, data)
@@ -424,6 +368,16 @@ def test_does_not_send_3ds_auth_when_empty
assert_success response
end
+ def test_successful_authorize_with_3ds_exemption
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.authorize(@accepted_amount, @credit_card, { three_ds_exemption_type: 'moto' })
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_match(/"transactionChannel\":\"MOTO\"/, data)
+ end.respond_with(successful_authorize_with_3ds2_data_response)
+
+ assert_success response
+ end
+
def test_truncates_first_name_to_15_chars
credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname' })
@@ -447,7 +401,7 @@ def test_handles_blank_names
assert_success response
end
- def test_truncates_address_fields
+ def test_truncates_split_address_fields
response = stub_comms(@gateway, :ssl_request) do
@gateway.purchase(@accepted_amount, @credit_card, {
billing_address: {
@@ -460,7 +414,8 @@ def test_truncates_address_fields
}
})
end.check_request do |_method, _endpoint, data, _headers|
- refute_match(/Supercalifragilisticexpialidociousthiscantbemorethanfiftycharacters/, data)
+ assert_equal(JSON.parse(data)['order']['customer']['billingAddress']['houseNumber'], '1234')
+ assert_equal(JSON.parse(data)['order']['customer']['billingAddress']['street'], 'Supercalifragilisticexpialidociousthiscantbemoreth')
end.respond_with(successful_capture_response)
assert_success response
end
diff --git a/test/unit/gateways/hi_pay_test.rb b/test/unit/gateways/hi_pay_test.rb
new file mode 100644
index 00000000000..af35d72f1fe
--- /dev/null
+++ b/test/unit/gateways/hi_pay_test.rb
@@ -0,0 +1,435 @@
+require 'test_helper'
+
+class HiPayTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = HiPayGateway.new(fixtures(:hi_pay))
+ @credit_card = credit_card
+ @amount = 100
+
+ @options = {
+ order_id: SecureRandom.random_number(1000000000),
+ description: 'Short_description',
+ email: 'john.smith@test.com'
+ }
+
+ @billing_address = address
+ end
+
+ def test_tokenize_pm_with_authorize
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create',
+ all_of(
+ includes("card_number=#{@credit_card.number}"),
+ includes("card_expiry_month=#{@credit_card.month}"),
+ includes("card_expiry_year=#{@credit_card.year}"),
+ includes("card_holder=#{@credit_card.first_name}+#{@credit_card.last_name}"),
+ includes("cvc=#{@credit_card.verification_value}"),
+ includes('multi_use=0'),
+ includes('generate_request_id=0')
+ ),
+ anything
+ ).
+ returns(successful_tokenize_response)
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order', anything, anything).returns(successful_authorize_response)
+ @gateway.authorize(@amount, @credit_card, @options)
+ end
+
+ def test_tokenize_pm_with_store
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create',
+ all_of(
+ includes("card_number=#{@credit_card.number}"),
+ includes("card_expiry_month=#{@credit_card.month}"),
+ includes("card_expiry_year=#{@credit_card.year}"),
+ includes("card_holder=#{@credit_card.first_name}+#{@credit_card.last_name}"),
+ includes("cvc=#{@credit_card.verification_value}"),
+ includes('multi_use=1'),
+ includes('generate_request_id=0')
+ ),
+ anything
+ ).
+ returns(successful_tokenize_response)
+ @gateway.store(@credit_card, @options)
+ end
+
+ def test_authorize_with_credit_card
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create',
+ all_of(
+ includes("card_number=#{@credit_card.number}"),
+ includes("card_expiry_month=#{@credit_card.month}"),
+ includes("card_expiry_year=#{@credit_card.year}"),
+ includes("card_holder=#{@credit_card.first_name}+#{@credit_card.last_name}"),
+ includes("cvc=#{@credit_card.verification_value}"),
+ includes('multi_use=0'),
+ includes('generate_request_id=0')
+ ),
+ anything
+ ).
+ returns(successful_tokenize_response)
+
+ tokenize_response_token = JSON.parse(successful_tokenize_response)['token']
+
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order',
+ all_of(
+ includes('payment_product=visa'),
+ includes('operation=Authorization'),
+ regexp_matches(%r{orderid=\d+}),
+ includes("description=#{@options[:description]}"),
+ includes('currency=EUR'),
+ includes('amount=1.00'),
+ includes("cardtoken=#{tokenize_response_token}")
+ ),
+ anything
+ ).
+ returns(successful_capture_response)
+
+ @gateway.authorize(@amount, @credit_card, @options)
+ end
+
+ def test_authorize_with_credit_card_and_billing_address
+ @gateway.expects(:ssl_request).returns(successful_tokenize_response)
+
+ tokenize_response_token = JSON.parse(successful_tokenize_response)['token']
+
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order',
+ all_of(
+ includes('payment_product=visa'),
+ includes('operation=Authorization'),
+ includes('streetaddress=456+My+Street'),
+ includes('streetaddress2=Apt+1'),
+ includes('city=Ottawa'),
+ includes('recipient_info=Widgets+Inc'),
+ includes('state=ON'),
+ includes('country=CA'),
+ includes('zipcode=K1C2N6'),
+ includes('phone=%28555%29555-5555'),
+ regexp_matches(%r{orderid=\d+}),
+ includes("description=#{@options[:description]}"),
+ includes('currency=EUR'),
+ includes('amount=1.00'),
+ includes("cardtoken=#{tokenize_response_token}")
+ ),
+ anything
+ ).
+ returns(successful_capture_response)
+
+ @gateway.authorize(@amount, @credit_card, @options.merge({ billing_address: @billing_address }))
+ end
+
+ def test_successfull_brand_mapping_mastercard
+ stub_comms do
+ @gateway.purchase(@amount, 'authorization_value|card_token|master', @options)
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match(/payment_product=mastercard/, data)
+ end
+ end
+
+ def test_purchase_with_stored_pm
+ stub_comms do
+ @gateway.purchase(@amount, 'authorization_value|card_token|card_brand', @options)
+ end.check_request do |_endpoint, data, _headers|
+ params = data.split('&').map { |param| param.split('=') }.to_h
+ assert_equal 'card_brand', params['payment_product']
+ assert_equal 'Sale', params['operation']
+ assert_equal @options[:order_id].to_s, params['orderid']
+ assert_equal @options[:description], params['description']
+ assert_equal 'EUR', params['currency']
+ assert_equal '1.00', params['amount']
+ assert_equal 'card_token', params['cardtoken']
+ end.respond_with(successful_capture_response)
+ end
+
+ def test_authorization_string_with_nil_values
+ auth_string_nil_value_first = @gateway.send :authorization_string, [nil, '123456', 'visa']
+ assert_equal '123456|visa', auth_string_nil_value_first
+
+ auth_string_nil_values = @gateway.send :authorization_string, [nil, 'token', nil]
+ assert_equal 'token', auth_string_nil_values
+
+ auth_string_two_nil_values = @gateway.send :authorization_string, [nil, nil, 'visa']
+ assert_equal 'visa', auth_string_two_nil_values
+
+ auth_string_nil_values = @gateway.send :authorization_string, ['reference', nil, nil]
+ assert_equal 'reference', auth_string_nil_values
+
+ auth_string_nil_values = @gateway.send :authorization_string, [nil, nil, nil]
+ assert_equal '', auth_string_nil_values
+ end
+
+ def test_authorization_string_with_full_values
+ complete_auth_string = @gateway.send :authorization_string, %w(86786788 123456 visa)
+ assert_equal '86786788|123456|visa', complete_auth_string
+ end
+
+ def test_purhcase_with_credit_card; end
+
+ def test_capture
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create', anything, anything).returns(successful_tokenize_response)
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order', anything, anything).returns(successful_authorize_response)
+
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ transaction_reference, _card_token, _brand = authorize_response.authorization.split('|')
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ "https://stage-secure-gateway.hipay-tpp.com/rest/v1/maintenance/transaction/#{transaction_reference}",
+ all_of(
+ includes('operation=capture'),
+ includes('currency=EUR'),
+ includes('amount=1.00')
+ ),
+ anything
+ ).
+ returns(successful_capture_response)
+ @gateway.capture(@amount, transaction_reference, @options)
+ end
+
+ def test_refund
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create', anything, anything).returns(successful_tokenize_response)
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order', anything, anything).returns(successful_capture_response)
+
+ authorize_response = @gateway.purchase(@amount, @credit_card, @options)
+ transaction_reference, _card_token, _brand = authorize_response.authorization.split('|')
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ "https://stage-secure-gateway.hipay-tpp.com/rest/v1/maintenance/transaction/#{transaction_reference}",
+ all_of(
+ includes('operation=refund'),
+ includes('currency=EUR'),
+ includes('amount=1.00')
+ ),
+ anything
+ ).
+ returns(successful_refund_response)
+ @gateway.refund(@amount, transaction_reference, @options)
+ end
+
+ def test_void
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure2-vault.hipay-tpp.com/rest/v2/token/create', anything, anything).returns(successful_tokenize_response)
+ @gateway.expects(:ssl_request).with(:post, 'https://stage-secure-gateway.hipay-tpp.com/rest/v1/order', anything, anything).returns(successful_authorize_response)
+
+ authorize_response = @gateway.authorize(@amount, @credit_card, @options)
+ transaction_reference, _card_token, _brand = authorize_response.authorization.split('|')
+ @gateway.expects(:ssl_request).
+ with(
+ :post,
+ "https://stage-secure-gateway.hipay-tpp.com/rest/v1/maintenance/transaction/#{transaction_reference}",
+ all_of(
+ includes('operation=cancel'),
+ includes('currency=EUR')
+ ),
+ anything
+ ).
+ returns(successful_void_response)
+ @gateway.void(transaction_reference, @options)
+ end
+
+ def test_required_client_id_and_client_secret
+ error = assert_raises ArgumentError do
+ HiPayGateway.new
+ end
+
+ assert_equal 'Missing required parameter: username', error.message
+ end
+
+ def test_supported_card_types
+ assert_equal HiPayGateway.supported_cardtypes, %i[visa master american_express]
+ end
+
+ def test_supported_countries
+ assert_equal HiPayGateway.supported_countries, ['FR']
+ end
+
+ # def test_support_scrubbing_flag_enabled
+ # assert @gateway.supports_scrubbing?
+ # end
+
+ def test_detecting_successfull_response_from_capture
+ assert @gateway.send :success_from, 'capture', { 'status' => '118', 'message' => 'Captured' }
+ end
+
+ def test_detecting_successfull_response_from_purchase
+ assert @gateway.send :success_from, 'order', { 'state' => 'completed' }
+ end
+
+ def test_detecting_successfull_response_from_authorize
+ assert @gateway.send :success_from, 'order', { 'state' => 'completed' }
+ end
+
+ def test_detecting_successfull_response_from_store
+ assert @gateway.send :success_from, 'store', { 'token' => 'random_token' }
+ end
+
+ def test_get_response_message_from_messages_key
+ message = @gateway.send :message_from, 'order', { 'message' => 'hello' }
+ assert_equal 'hello', message
+ end
+
+ def test_get_response_message_from_message_user
+ message = @gateway.send :message_from, 'order', { other_key: 'something_else' }
+ assert_nil message
+ end
+
+ def test_url_generation_from_action
+ action = 'test'
+ assert_equal "#{@gateway.test_url}/v1/#{action}", @gateway.send(:url, action)
+ end
+
+ def test_request_headers_building
+ gateway = HiPayGateway.new(username: 'abc123', password: 'def456')
+ headers = gateway.send :request_headers
+
+ assert_equal 'application/json', headers['Accept']
+ assert_equal 'application/x-www-form-urlencoded', headers['Content-Type']
+ assert_equal 'Basic YWJjMTIzOmRlZjQ1Ng==', headers['Authorization']
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ private
+
+ def successful_tokenize_response
+ '{"token":"5fc03718289f58d1ce38482faa79aa4c640c44a5d182ad3d849761ed9ea33155","request_id":"0","card_id":"9fd81707-8f41-4a01-b6ed-279954336ada","multi_use":0,"brand":"VISA","pan":"411111xxxxxx1111","card_holder":"John Smith","card_expiry_month":"12","card_expiry_year":"2025","issuer":"JPMORGAN CHASE BANK, N.A.","country":"US","card_type":"CREDIT","forbidden_issuer_country":false}'
+ end
+
+ def successful_authorize_response
+ '{"state":"completed","reason":"","forwardUrl":"","test":"true","mid":"00001331069","attemptId":"1","authorizationCode":"no_code","transactionReference":"800271033524","dateCreated":"2023-12-05T23:36:43+0000","dateUpdated":"2023-12-05T23:36:48+0000","dateAuthorized":"2023-12-05T23:36:48+0000","status":"116","message":"Authorized","authorizedAmount":"500.00","capturedAmount":"0.00","refundedAmount":"0.00","creditedAmount":"0.00","decimals":"2","currency":"EUR","ipAddress":"0.0.0.0","ipCountry":"","deviceId":"","cdata1":"","cdata2":"","cdata3":"","cdata4":"","cdata5":"","cdata6":"","cdata7":"","cdata8":"","cdata9":"","cdata10":"","avsResult":"","eci":"7","paymentProduct":"visa","paymentMethod":{"token":"5fc03718289f58d1ce38482faa79aa4c640c44a5d182ad3d849761ed9ea33155","cardId":"9fd81707-8f41-4a01-b6ed-279954336ada","brand":"VISA","pan":"411111******1111","cardHolder":"JOHN SMITH","cardExpiryMonth":"12","cardExpiryYear":"2025","issuer":"JPMORGAN CHASE BANK, N.A.","country":"US"},"threeDSecure":{"eci":"","authenticationStatus":"Y","authenticationMessage":"Authentication Successful","authenticationToken":"","xid":""},"fraudScreening":{"scoring":"0","result":"ACCEPTED","review":""},"order":{"id":"Sp_ORDER_272437225","dateCreated":"2023-12-05T23:36:43+0000","attempts":"1","amount":"500.00","shipping":"0.00","tax":"0.00","decimals":"2","currency":"EUR","customerId":"","language":"en_US","email":""},"debitAgreement":{"id":"","status":""}}'
+ end
+
+ def successful_capture_response
+ '{"operation":"capture","test":"true","mid":"00001331069","authorizationCode":"no_code","transactionReference":"800271033524","dateCreated":"2023-12-05T23:36:43+0000","dateUpdated":"2023-12-05T23:37:21+0000","dateAuthorized":"2023-12-05T23:36:48+0000","status":"118","message":"Captured","authorizedAmount":"500.00","capturedAmount":"500.00","refundedAmount":"0.00","decimals":"2","currency":"EUR"}'
+ end
+
+ def successful_refund_response
+ '{"operation":"refund","test":"true","mid":"00001331069","authorizationCode":"no_code","transactionReference":"800272279241","dateCreated":"2023-12-12T16:36:46+0000","dateUpdated":"2023-12-12T16:36:54+0000","dateAuthorized":"2023-12-12T16:36:50+0000","status":"124","message":"Refund Requested","authorizedAmount":"500.00","capturedAmount":"500.00","refundedAmount":"500.00","decimals":"2","currency":"EUR"}'
+ end
+
+ def successful_void_response
+ '{"operation":"cancel","test":"true","mid":"00001331069","authorizationCode":"no_code","transactionReference":"800272279254","dateCreated":"2023-12-12T16:38:49+0000","dateUpdated":"2023-12-12T16:38:55+0000","dateAuthorized":"2023-12-12T16:38:53+0000","status":"175","message":"Authorization Cancellation requested","authorizedAmount":"500.00","capturedAmount":"0.00","refundedAmount":"0.00","decimals":"2","currency":"EUR"}'
+ end
+
+ def pre_scrubbed
+ <<~PRE_SCRUBBED
+ opening connection to stage-secure2-vault.hipay-tpp.com:443...
+ opened
+ starting SSL for stage-secure2-vault.hipay-tpp.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- "POST /rest/v2/token/create HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nAuthorization: Basic OTQ2NTgzNjUuc3RhZ2Utc2VjdXJlLWdhdGV3YXkuaGlwYXktdHBwLmNvbTpUZXN0X1JoeXBWdktpUDY4VzNLQUJ4eUdoS3Zlcw==\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: stage-secure2-vault.hipay-tpp.com\r\nContent-Length: 136\r\n\r\n"
+ <- "card_number=4111111111111111&card_expiry_month=12&card_expiry_year=2025&card_holder=John+Smith&cvc=514&multi_use=0&generate_request_id=0"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Server: nginx\r\n"
+ -> "Date: Tue, 12 Dec 2023 14:49:44 GMT\r\n"
+ -> "Content-Type: application/json\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Connection: close\r\n"
+ -> "Vary: Authorization\r\n"
+ -> "Cache-Control: max-age=0, must-revalidate, private\r\n"
+ -> "Expires: Tue, 12 Dec 2023 14:49:44 GMT\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Set-Cookie: PHPSESSID=j9bfv7gaml9uslij70e15kvrm6; path=/; HttpOnly\r\n"
+ -> "Strict-Transport-Security: max-age=86400\r\n"
+ -> "\r\n"
+ -> "17c\r\n"
+ reading 380 bytes...
+ -> "{\"token\":\"0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e\",\"request_id\":\"0\",\"card_id\":\"9fd81707-8f41-4a01-b6ed-279954336ada\",\"multi_use\":0,\"brand\":\"VISA\",\"pan\":\"411111xxxxxx1111\",\"card_holder\":\"John Smith\",\"card_expiry_month\":\"12\",\"card_expiry_year\":\"2025\",\"issuer\":\"JPMORGAN CHASE BANK, N.A.\",\"country\":\"US\",\"card_type\":\"CREDIT\",\"forbidden_issuer_country\":false}"
+ reading 2 bytes...
+ -> "\r\n"
+ 0
+ \r\nConn close
+ opening connection to stage-secure-gateway.hipay-tpp.com:443...
+ opened
+ starting SSL for stage-secure-gateway.hipay-tpp.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- "POST /rest/v1/order HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nAuthorization: Basic OTQ2NTgzNjUuc3RhZ2Utc2VjdXJlLWdhdGV3YXkuaGlwYXktdHBwLmNvbTpUZXN0X1JoeXBWdktpUDY4VzNLQUJ4eUdoS3Zlcw==\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: stage-secure-gateway.hipay-tpp.com\r\nContent-Length: 186\r\n\r\n"
+ <- "payment_product=visa&operation=Sale&cardtoken=0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e&order_id=Sp_ORDER_100432071&description=An+authorize¤cy=EUR&amount=500"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "date: Tue, 12 Dec 2023 14:49:45 GMT\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"
+ -> "access-control-allow-origin: \r\n"
+ -> "access-control-allow-headers: \r\n"
+ -> "access-control-allow-credentials: true\r\n"
+ -> "content-length: 1472\r\n"
+ -> "content-type: application/json; encoding=UTF-8\r\n"
+ -> "connection: close\r\n"
+ -> "\r\n"
+ reading 1472 bytes...
+ -> "{\"state\":\"completed\",\"reason\":\"\",\"forwardUrl\":\"\",\"test\":\"true\",\"mid\":\"00001331069\",\"attemptId\":\"1\",\"authorizationCode\":\"no_code\",\"transactionReference\":\"800272278410\",\"referenceToPay\":\"\",\"dateCreated\":\"2023-12-12T14:49:45+0000\",\"dateUpdated\":\"2023-12-12T14:49:50+0000\",\"dateAuthorized\":\"2023-12-12T14:49:49+0000\",\"status\":\"118\",\"message\":\"Captured\",\"authorizedAmount\":\"500.00\",\"capturedAmount\":\"500.00\",\"refundedAmount\":\"0.00\",\"creditedAmount\":\"0.00\",\"decimals\":\"2\",\"currency\":\"EUR\",\"ipAddress\":\"0.0.0.0\",\"ipCountry\":\"\",\"deviceId\":\"\",\"cdata1\":\"\",\"cdata2\":\"\",\"cdata3\":\"\",\"cdata4\":\"\",\"cdata5\":\"\",\"cdata6\":\"\",\"cdata7\":\"\",\"cdata8\":\"\",\"cdata9\":\"\",\"cdata10\":\"\",\"avsResult\":\"\",\"eci\":\"7\",\"paymentProduct\":\"visa\",\"paymentMethod\":{\"token\":\"0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e\",\"cardId\":\"9fd81707-8f41-4a01-b6ed-279954336ada\",\"brand\":\"VISA\",\"pan\":\"411111******1111\",\"cardHolder\":\"JOHN SMITH\",\"cardExpiryMonth\":\"12\",\"cardExpiryYear\":\"2025\",\"issuer\":\"JPMORGAN CHASE BANK, N.A.\",\"country\":\"US\"},\"threeDSecure\":{\"eci\":\"\",\"authenticationStatus\":\"Y\",\"authenticationMessage\":\"Authentication Successful\",\"authenticationToken\":\"\",\"xid\":\"\"},\"fraudScreening\":{\"scoring\":\"0\",\"result\":\"ACCEPTED\",\"review\":\"\"},\"order\":{\"id\":\"Sp_ORDER_100432071\",\"dateCreated\":\"2023-12-12T14:49:45+0000\",\"attempts\":\"1\",\"amount\":\"500.00\",\"shipping\":\"0.00\",\"tax\":\"0.00\",\"decimals\":\"2\",\"currency\":\"EUR\",\"customerId\":\"\",\"language\":\"en_US\",\"email\":\"\"},\"debitAgreement\":{\"id\":\"\",\"status\":\"\"}}"
+ reading 1472 bytes...
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed
+ <<~POST_SCRUBBED
+ opening connection to stage-secure2-vault.hipay-tpp.com:443...
+ opened
+ starting SSL for stage-secure2-vault.hipay-tpp.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- "POST /rest/v2/token/create HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: 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\nUser-Agent: Ruby\r\nHost: stage-secure2-vault.hipay-tpp.com\r\nContent-Length: 136\r\n\r\n"
+ <- "card_number=[FILTERED]&card_expiry_month=12&card_expiry_year=2025&card_holder=John+Smith&cvc=[FILTERED]&multi_use=0&generate_request_id=0"
+ -> "HTTP/1.1 201 Created\r\n"
+ -> "Server: nginx\r\n"
+ -> "Date: Tue, 12 Dec 2023 14:49:44 GMT\r\n"
+ -> "Content-Type: application/json\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Connection: close\r\n"
+ -> "Vary: Authorization\r\n"
+ -> "Cache-Control: max-age=0, must-revalidate, private\r\n"
+ -> "Expires: Tue, 12 Dec 2023 14:49:44 GMT\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Set-Cookie: PHPSESSID=j9bfv7gaml9uslij70e15kvrm6; path=/; HttpOnly\r\n"
+ -> "Strict-Transport-Security: max-age=86400\r\n"
+ -> "\r\n"
+ -> "17c\r\n"
+ reading 380 bytes...
+ -> "{\"token\":\"0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e\",\"request_id\":\"0\",\"card_id\":\"9fd81707-8f41-4a01-b6ed-279954336ada\",\"multi_use\":0,\"brand\":\"VISA\",\"pan\":\"411111xxxxxx1111\",\"card_holder\":\"John Smith\",\"card_expiry_month\":\"12\",\"card_expiry_year\":\"2025\",\"issuer\":\"JPMORGAN CHASE BANK, N.A.\",\"country\":\"US\",\"card_type\":\"CREDIT\",\"forbidden_issuer_country\":false}"
+ reading 2 bytes...
+ -> "\r\n"
+ 0
+ \r\nConn close
+ opening connection to stage-secure-gateway.hipay-tpp.com:443...
+ opened
+ starting SSL for stage-secure-gateway.hipay-tpp.com:443...
+ SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384
+ <- "POST /rest/v1/order HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: 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\nUser-Agent: Ruby\r\nHost: stage-secure-gateway.hipay-tpp.com\r\nContent-Length: 186\r\n\r\n"
+ <- "payment_product=visa&operation=Sale&cardtoken=0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e&order_id=Sp_ORDER_100432071&description=An+authorize¤cy=EUR&amount=500"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "date: Tue, 12 Dec 2023 14:49:45 GMT\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"
+ -> "access-control-allow-origin: \r\n"
+ -> "access-control-allow-headers: \r\n"
+ -> "access-control-allow-credentials: true\r\n"
+ -> "content-length: 1472\r\n"
+ -> "content-type: application/json; encoding=UTF-8\r\n"
+ -> "connection: close\r\n"
+ -> "\r\n"
+ reading 1472 bytes...
+ -> "{\"state\":\"completed\",\"reason\":\"\",\"forwardUrl\":\"\",\"test\":\"true\",\"mid\":\"00001331069\",\"attemptId\":\"1\",\"authorizationCode\":\"no_code\",\"transactionReference\":\"800272278410\",\"referenceToPay\":\"\",\"dateCreated\":\"2023-12-12T14:49:45+0000\",\"dateUpdated\":\"2023-12-12T14:49:50+0000\",\"dateAuthorized\":\"2023-12-12T14:49:49+0000\",\"status\":\"118\",\"message\":\"Captured\",\"authorizedAmount\":\"500.00\",\"capturedAmount\":\"500.00\",\"refundedAmount\":\"0.00\",\"creditedAmount\":\"0.00\",\"decimals\":\"2\",\"currency\":\"EUR\",\"ipAddress\":\"0.0.0.0\",\"ipCountry\":\"\",\"deviceId\":\"\",\"cdata1\":\"\",\"cdata2\":\"\",\"cdata3\":\"\",\"cdata4\":\"\",\"cdata5\":\"\",\"cdata6\":\"\",\"cdata7\":\"\",\"cdata8\":\"\",\"cdata9\":\"\",\"cdata10\":\"\",\"avsResult\":\"\",\"eci\":\"7\",\"paymentProduct\":\"visa\",\"paymentMethod\":{\"token\":\"0acbbfcbd5bf202a05acc0e9c00f79158a2fe8b60caad2213b09e901b89dc28e\",\"cardId\":\"9fd81707-8f41-4a01-b6ed-279954336ada\",\"brand\":\"VISA\",\"pan\":\"411111******1111\",\"cardHolder\":\"JOHN SMITH\",\"cardExpiryMonth\":\"12\",\"cardExpiryYear\":\"2025\",\"issuer\":\"JPMORGAN CHASE BANK, N.A.\",\"country\":\"US\"},\"threeDSecure\":{\"eci\":\"\",\"authenticationStatus\":\"Y\",\"authenticationMessage\":\"Authentication Successful\",\"authenticationToken\":\"\",\"xid\":\"\"},\"fraudScreening\":{\"scoring\":\"0\",\"result\":\"ACCEPTED\",\"review\":\"\"},\"order\":{\"id\":\"Sp_ORDER_100432071\",\"dateCreated\":\"2023-12-12T14:49:45+0000\",\"attempts\":\"1\",\"amount\":\"500.00\",\"shipping\":\"0.00\",\"tax\":\"0.00\",\"decimals\":\"2\",\"currency\":\"EUR\",\"customerId\":\"\",\"language\":\"en_US\",\"email\":\"\"},\"debitAgreement\":{\"id\":\"\",\"status\":\"\"}}"
+ reading 1472 bytes...
+ Conn close
+ POST_SCRUBBED
+ end
+end
diff --git a/test/unit/gateways/hps_test.rb b/test/unit/gateways/hps_test.rb
index 6707d9d703e..1f8832e7ab7 100644
--- a/test/unit/gateways/hps_test.rb
+++ b/test/unit/gateways/hps_test.rb
@@ -288,11 +288,13 @@ def test_account_number_scrubbing
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -301,11 +303,13 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -314,10 +318,12 @@ def test_failed_purchase_with_apple_pay_raw_cryptogram_with_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -326,10 +332,12 @@ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -338,11 +346,13 @@ def test_failed_purchase_with_apple_pay_raw_cryptogram_without_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -351,11 +361,13 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -364,10 +376,12 @@ def test_failed_auth_with_apple_pay_raw_cryptogram_with_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -376,10 +390,12 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
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',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :apple_pay)
+ source: :apple_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -388,11 +404,13 @@ def test_failed_auth_with_apple_pay_raw_cryptogram_without_eci
def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -401,11 +419,13 @@ def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci
def test_failed_purchase_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -414,10 +434,12 @@ def test_failed_purchase_with_android_pay_raw_cryptogram_with_eci
def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -426,10 +448,12 @@ def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci
def test_failed_purchase_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -438,11 +462,13 @@ def test_failed_purchase_with_android_pay_raw_cryptogram_without_eci
def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -451,11 +477,13 @@ def test_successful_auth_with_android_pay_raw_cryptogram_with_eci
def test_failed_auth_with_android_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -464,10 +492,12 @@ def test_failed_auth_with_android_pay_raw_cryptogram_with_eci
def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -476,10 +506,12 @@ def test_successful_auth_with_android_pay_raw_cryptogram_without_eci
def test_failed_auth_with_android_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :android_pay)
+ source: :android_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -488,11 +520,13 @@ def test_failed_auth_with_android_pay_raw_cryptogram_without_eci
def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -501,11 +535,13 @@ def test_successful_purchase_with_google_pay_raw_cryptogram_with_eci
def test_failed_purchase_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -514,10 +550,12 @@ def test_failed_purchase_with_google_pay_raw_cryptogram_with_eci
def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_charge_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -526,10 +564,12 @@ def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci
def test_failed_purchase_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_charge_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -538,11 +578,13 @@ def test_failed_purchase_with_google_pay_raw_cryptogram_without_eci
def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -551,11 +593,13 @@ def test_successful_auth_with_google_pay_raw_cryptogram_with_eci
def test_failed_auth_with_google_pay_raw_cryptogram_with_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
eci: '05',
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
@@ -564,10 +608,12 @@ def test_failed_auth_with_google_pay_raw_cryptogram_with_eci
def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(successful_authorize_response)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_success response
assert_equal 'Success', response.message
@@ -576,10 +622,12 @@ def test_successful_auth_with_google_pay_raw_cryptogram_without_eci
def test_failed_auth_with_google_pay_raw_cryptogram_without_eci
@gateway.expects(:ssl_post).returns(failed_authorize_response_decline)
- credit_card = network_tokenization_credit_card('4242424242424242',
+ credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
verification_value: nil,
- source: :google_pay)
+ source: :google_pay
+ )
assert response = @gateway.authorize(@amount, credit_card, @options)
assert_failure response
assert_equal 'The card was declined.', response.message
diff --git a/test/unit/gateways/ipg_test.rb b/test/unit/gateways/ipg_test.rb
index d92cea9daa0..f2c8f658969 100644
--- a/test/unit/gateways/ipg_test.rb
+++ b/test/unit/gateways/ipg_test.rb
@@ -5,6 +5,7 @@ class IpgTest < Test::Unit::TestCase
def setup
@gateway = IpgGateway.new(fixtures(:ipg))
+ @gateway_ma = IpgGateway.new(fixtures(:ipg_ma))
@credit_card = credit_card
@amount = 100
@@ -19,6 +20,7 @@ def test_successful_purchase
end.check_request do |_endpoint, data, _headers|
doc = REXML::Document.new(data)
assert_match('sale', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text)
+ assert_match('1.00', REXML::XPath.first(doc, '//v1:Transaction//v1:ChargeTotal').text)
end.respond_with(successful_purchase_response)
assert_success response
@@ -129,6 +131,31 @@ def test_successful_purchase_with_store_id
assert_success response
end
+ def test_successful_ma_purchase_with_store_id
+ response = stub_comms(@gateway_ma) do
+ @gateway_ma.purchase(@amount, @credit_card, @options.merge({ store_id: '1234' }))
+ end.check_request do |_endpoint, data, _headers|
+ doc = REXML::Document.new(data)
+ assert_match('1234', REXML::XPath.first(doc, '//v1:StoreId').text)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_basic_auth_builds_correctly_with_differing_ma_credential_structures
+ user_id_without_ws = fixtures(:ipg_ma)[:user_id].sub(/^WS/, '')
+ gateway_ma2 = IpgGateway.new(fixtures(:ipg_ma).merge({ user_id: user_id_without_ws }))
+
+ assert_equal(@gateway_ma.send(:build_header), gateway_ma2.send(:build_header))
+ end
+
+ def test_basic_auth_builds_correctly_with_differing_credential_structures
+ user_id_without_ws = fixtures(:ipg)[:user_id].sub(/^WS/, '')
+ gateway2 = IpgGateway.new(fixtures(:ipg).merge({ user_id: user_id_without_ws }))
+
+ assert_equal(@gateway.send(:build_header), gateway2.send(:build_header))
+ end
+
def test_successful_purchase_with_payment_token
payment_token = 'ABC123'
@@ -173,7 +200,7 @@ def test_failed_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'DECLINED', response.message
+ assert_match 'DECLINED', response.message
end
def test_successful_authorize
@@ -194,7 +221,7 @@ def test_failed_authorize
response = @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: 'ORD03' }))
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_capture
@@ -215,7 +242,7 @@ def test_failed_capture
response = @gateway.capture(@amount, '123', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_refund
@@ -236,7 +263,7 @@ def test_failed_refund
response = @gateway.refund(@amount, '123', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_void
@@ -257,7 +284,7 @@ def test_failed_void
response = @gateway.void('', @options)
assert_failure response
- assert_equal 'FAILED', response.message
+ assert_match 'FAILED', response.message
end
def test_successful_verify
@@ -332,6 +359,23 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_message_from_just_with_transaction_result
+ am_response = { TransactionResult: 'success !' }
+ assert_equal 'success !', @gateway.send(:message_from, am_response)
+ end
+
+ def test_message_from_with_an_error
+ am_response = { TransactionResult: 'DECLINED', ErrorMessage: 'CODE: this is an error message' }
+ assert_equal 'DECLINED, this is an error message', @gateway.send(:message_from, am_response)
+ end
+
+ def test_failed_without_store_id
+ bad_gateway = IpgGateway.new(fixtures(:ipg).merge({ store_id: nil }))
+ assert_raises(ArgumentError) do
+ bad_gateway.purchase(@amount, @credit_card, @options)
+ end
+ end
+
private
def successful_purchase_response
@@ -728,7 +772,7 @@ def post_scrubbed
starting SSL for test.ipg-online.com:443...
SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
<- "POST /ipgapi/services HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\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: test.ipg-online.com\r\nContent-Length: 850\r\n\r\n"
- <- "\n \n \n \n \n \n [FILTERED]\n sale\n \n\n [FILTERED]\n 12\n 22\n [FILTERED]\n\n\n 100\n 032\n\n\n\n \n \n \n\n"
+ <- "\n \n \n \n \n \n 5921102002\n sale\n \n\n [FILTERED]\n 12\n 22\n [FILTERED]\n\n\n 100\n 032\n\n\n\n \n \n \n\n"
-> "HTTP/1.1 200 \r\n"
-> "Date: Fri, 29 Oct 2021 19:31:23 GMT\r\n"
-> "Strict-Transport-Security: max-age=63072000; includeSubdomains\r\n"
diff --git a/test/unit/gateways/kushki_test.rb b/test/unit/gateways/kushki_test.rb
index b13d04653f5..8f104d53065 100644
--- a/test/unit/gateways/kushki_test.rb
+++ b/test/unit/gateways/kushki_test.rb
@@ -41,7 +41,27 @@ def test_successful_purchase_with_options
metadata: {
productos: 'bananas',
nombre_apellido: 'Kirk'
- }
+ },
+ months: 2,
+ deferred_grace_months: '05',
+ deferred_credit_type: '01',
+ deferred_months: 3,
+ product_details: [
+ {
+ id: 'test1',
+ title: 'tester1',
+ price: 10,
+ sku: 'abcde',
+ quantity: 1
+ },
+ {
+ id: 'test2',
+ title: 'tester2',
+ price: 5,
+ sku: 'edcba',
+ quantity: 2
+ }
+ ]
}
amount = 100 * (
@@ -58,6 +78,9 @@ def test_successful_purchase_with_options
@gateway.purchase(amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_includes data, 'metadata'
+ assert_includes data, 'months'
+ assert_includes data, 'deferred'
+ assert_includes data, 'productDetails'
end.respond_with(successful_token_response, successful_charge_response)
assert_success response
@@ -272,6 +295,49 @@ def test_failed_refund
assert_equal 'K010', refund.error_code
end
+ def test_partial_refund
+ @gateway.expects(:ssl_post).returns(successful_charge_response)
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+
+ options = { currency: 'PEN' }
+
+ purchase = @gateway.purchase(100, @credit_card, options)
+
+ refund = stub_comms(@gateway, :ssl_request) do
+ refund_options = {
+ currency: 'PEN',
+ partial_refund: true,
+ full_response: true
+ }
+ @gateway.refund(50, purchase.authorization, refund_options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['amount']['subtotalIva0'], 0.5
+ end.respond_with(successful_refund_response)
+ assert_success refund
+ end
+
+ def test_full_refund_does_not_have_request_body
+ @gateway.expects(:ssl_post).returns(successful_charge_response)
+ @gateway.expects(:ssl_post).returns(successful_token_response)
+
+ options = { currency: 'PEN' }
+
+ purchase = @gateway.purchase(@amount, @credit_card, options)
+ assert_success purchase
+
+ refund = stub_comms(@gateway, :ssl_request) do
+ refund_options = {
+ currency: 'PEN',
+ full_response: true
+ }
+ @gateway.refund(@amount, purchase.authorization, refund_options)
+ end.check_request do |_method, _endpoint, data, _headers|
+ assert_nil(data)
+ end.respond_with(successful_refund_response)
+ assert_success refund
+ end
+
def test_successful_capture
@gateway.expects(:ssl_post).returns(successful_authorize_response)
@gateway.expects(:ssl_post).returns(successful_token_response)
diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb
index 88b81d9d23c..4af53261b61 100644
--- a/test/unit/gateways/litle_test.rb
+++ b/test/unit/gateways/litle_test.rb
@@ -54,7 +54,8 @@ def setup
name: 'John Smith',
routing_number: '011075150',
account_number: '1099999999',
- account_type: 'checking'
+ account_type: nil,
+ account_holder_type: 'checking'
)
@long_address = {
@@ -76,7 +77,55 @@ def test_successful_purchase
end.respond_with(successful_purchase_response)
assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal '100000000000000006;sale;100', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_prepaid_card_141
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_purchase_for_prepaid_cards_141)
+
+ assert_success response
+ assert_equal 'Consumer non-reloadable prepaid card, Approved', response.message
+ assert_equal '141', response.params['response']
+ end
+
+ def test_successful_purchase_prepaid_card_142
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_purchase_for_prepaid_cards_142)
+
+ assert_success response
+ assert_equal 'Consumer single-use virtual card number, Approved', response.message
+ assert_equal '142', response.params['response']
+ end
+
+ def test_successful_purchase_with_010_response
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, _data, _headers|
+ # Counterpoint to test_successful_postlive_url:
+ assert_match(/www\.testvantivcnp\.com/, endpoint)
+ end.respond_with(successful_purchase_response('010', 'Partially Approved'))
+
+ assert_success response
+ assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message
+ assert_equal '100000000000000006;sale;100', response.authorization
+ assert response.test?
+ end
+ def test_successful_purchase_with_001_response
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, _data, _headers|
+ # Counterpoint to test_successful_postlive_url:
+ assert_match(/www\.testvantivcnp\.com/, endpoint)
+ end.respond_with(successful_purchase_response('001', 'Transaction Received'))
+
+ assert_success response
+ assert_equal 'Transaction Received: This is sent to acknowledge that the submitted transaction has been received.', response.message
assert_equal '100000000000000006;sale;100', response.authorization
assert response.test?
end
@@ -104,6 +153,21 @@ def test_successful_postlive_url
def test_successful_purchase_with_echeck
response = stub_comms do
@gateway.purchase(2004, @check)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(%r(Checking), data)
+ end.respond_with(successful_purchase_with_echeck_response)
+
+ assert_success response
+
+ assert_equal '621100411297330000;echeckSales;2004', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_echeck_and_account_holder_type
+ response = stub_comms do
+ @gateway.purchase(2004, @authorize_check)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(%r(Checking), data)
end.respond_with(successful_purchase_with_echeck_response)
assert_success response
@@ -569,7 +633,7 @@ def test_stored_credential_cit_card_on_file_used
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(%r(cardholderInitiatedCOF), data)
- assert_match(%r(#{network_transaction_id}), data)
+ assert_not_match(%r(#{network_transaction_id}), data)
assert_match(%r(ecommerce), data)
end.respond_with(successful_authorize_stored_credentials)
@@ -593,8 +657,8 @@ def test_stored_credential_cit_cof_doesnt_override_order_source
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
assert_match(%r(cardholderInitiatedCOF), data)
- assert_match(%r(#{network_transaction_id}), data)
- assert_match(%r(3dsAuthenticated), data)
+ assert_not_match(%r(#{network_transaction_id}), data)
+ assert_match(%r(ecommerce), data)
end.respond_with(successful_authorize_stored_credentials)
assert_success response
@@ -613,6 +677,7 @@ def test_stored_credential_mit_card_on_file_initial
response = stub_comms do
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
+ assert_match(%r(ecommerce), data)
assert_match(%r(initialCOF), data)
end.respond_with(successful_authorize_stored_credentials)
@@ -672,6 +737,7 @@ def test_stored_credential_installment_used
response = stub_comms do
@gateway.authorize(@amount, @credit_card, options)
end.check_request do |_endpoint, data, _headers|
+ assert_not_match(%r(), data)
assert_match(%r(#{network_transaction_id}), data)
assert_match(%r(installment), data)
end.respond_with(successful_authorize_stored_credentials)
@@ -732,15 +798,15 @@ def network_transaction_id
'63225578415568556365452427825'
end
- def successful_purchase_response
+ def successful_purchase_response(code = '000', message = 'Approved')
%(
100000000000000006
1
- 000
+ #{code}
2014-03-31T11:34:39
- Approved
+ #{message}
11111
01
@@ -784,6 +850,48 @@ def successful_purchase_with_echeck_response
)
end
+ def successful_purchase_for_prepaid_cards_141
+ %(
+
+
+ 456342657452
+ 123456
+ 141
+ 2024-04-09T19:50:30
+ 2024-04-09
+ Consumer non-reloadable prepaid card, Approved
+ 382410
+
+ 01
+ M
+
+ MPMMPMPMPMPU
+
+
+ )
+ end
+
+ def successful_purchase_for_prepaid_cards_142
+ %(
+
+
+ 456342657452
+ 123456
+ 142
+ 2024-04-09T19:50:30
+ 2024-04-09
+ Consumer single-use virtual card number, Approved
+ 382410
+
+ 01
+ M
+
+ MPMMPMPMPMPU
+
+
+ )
+ end
+
def successful_authorize_stored_credentials
%(
diff --git a/test/unit/gateways/mercado_pago_test.rb b/test/unit/gateways/mercado_pago_test.rb
index 6af0e181c6d..a25401c202d 100644
--- a/test/unit/gateways/mercado_pago_test.rb
+++ b/test/unit/gateways/mercado_pago_test.rb
@@ -6,24 +6,30 @@ class MercadoPagoTest < Test::Unit::TestCase
def setup
@gateway = MercadoPagoGateway.new(access_token: 'access_token')
@credit_card = credit_card
- @elo_credit_card = credit_card('5067268650517446',
+ @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',
+ verification_value: '737'
+ )
+ @cabal_credit_card = credit_card(
+ '6035227716427021',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
- verification_value: '737')
- @naranja_credit_card = credit_card('5895627823453005',
+ verification_value: '737'
+ )
+ @naranja_credit_card = credit_card(
+ '5895627823453005',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
- verification_value: '123')
+ verification_value: '123'
+ )
@amount = 100
@options = {
diff --git a/test/unit/gateways/merchant_warrior_test.rb b/test/unit/gateways/merchant_warrior_test.rb
index 7838382d861..b06fdb8320b 100644
--- a/test/unit/gateways/merchant_warrior_test.rb
+++ b/test/unit/gateways/merchant_warrior_test.rb
@@ -19,6 +19,14 @@ def setup
address: address,
transaction_product: 'TestProduct'
}
+ @three_ds_secure = {
+ version: '2.2.0',
+ cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=',
+ eci: '05',
+ xid: 'ODUzNTYzOTcwODU5NzY3Qw==',
+ enrolled: 'true',
+ authentication_response_status: 'Y'
+ }
end
def test_successful_authorize
@@ -299,6 +307,34 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_three_ds_v2_object_construction
+ post = {}
+ @options[:three_d_secure] = @three_ds_secure
+
+ @gateway.send(:add_three_ds, post, @options)
+ ds_options = @options[:three_d_secure]
+
+ assert_equal ds_options[:version], post[:threeDSV2Version]
+ assert_equal ds_options[:cavv], post[:threeDSCavv]
+ assert_equal ds_options[:eci], post[:threeDSEci]
+ assert_equal ds_options[:xid], post[:threeDSXid]
+ assert_equal ds_options[:authentication_response_status], post[:threeDSStatus]
+ end
+
+ def test_purchase_with_three_ds
+ @options[:three_d_secure] = @three_ds_secure
+ stub_comms(@gateway) do
+ @gateway.purchase(@success_amount, @credit_card, @options)
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ params = URI.decode_www_form(data).to_h
+ assert_equal '2.2.0', params['threeDSV2Version']
+ assert_equal '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', params['threeDSCavv']
+ assert_equal '05', params['threeDSEci']
+ assert_equal 'ODUzNTYzOTcwODU5NzY3Qw==', params['threeDSXid']
+ assert_equal 'Y', params['threeDSStatus']
+ end
+ end
+
private
def successful_purchase_response
diff --git a/test/unit/gateways/mit_test.rb b/test/unit/gateways/mit_test.rb
index 16a80355f4f..4ae7b4932be 100644
--- a/test/unit/gateways/mit_test.rb
+++ b/test/unit/gateways/mit_test.rb
@@ -169,88 +169,100 @@ def failed_void_response
end
def pre_scrubbed
- <<-PRE_SCRUBBED
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"1aUSihtRXgd+1nycRfVWgv0JDZsGLsrpsNkahpkx4jmnBRRAPPao+zJYqsN4xrGMIeVdJ3Y5LlQYXg5qu8O7iZmDPTqWbyKmsurCxJidr6AkFszwvRfugElyb5sAYpUcrnFSpVUgz2NGcIuMRalr0irf7q30+TzbLRHQc1Z5QTe6am3ndO8aSKKLwYYmfHcO8E/+dPiCsSP09P2heNqpMbf5IKdSwGCVS1Rtpcoijl3wXB8zgeBZ1PXHAmmkC1/CWRs/fh1qmvYFzb8YAiRy5q80Tyq09IaeSpQ1ydq3r95QBSJy6H4gz2OV/v2xdm1A63XEh2+6N6p2XDyzGWQrxKE41wmqRCxie7qY2xqdv4S8Cl8ldSMEpZY46A68hKIN6zrj6eMWxauwdi6ZkZfMDuh9Pn9x5gwwgfElLopIpR8fejB6G4hAQHtq2jhn5D4ccmAqNxkrB4w5k+zc53Rupk2u3MDp5T5sRkqvNyIN2kCE6i0DD9HlqkCjWV+bG9WcUiO4D7m5fWRE5f9OQ2XjeA==IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 320\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- reading 320 bytes...
- -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
- read 320 bytes
- Conn close
- opening connection to wpy.mitec.com.mx:443...
- opened
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"Z6l24tZG2YfTOQTne8NVygr/YeuVRNya8ZUCM5NvRgOEL/Mt8PO0voNnspoiFSg+RVamC4V2BipmU3spPVBg6Dr0xMpPL7ryVB9mlM4PokUdHkZTjXJHbbr1GWdyEPMYYSH0f+M1qUDO57EyUuZv8o6QSv+a/tuOrrBwsHI8cnsv+y9qt5L9LuGRMeBYvZkkK+xw53eDqYsJGoCvpk/pljCCkGU7Q/sKsLOx0MT6dA/BLVGrGeo8ngO+W/cnOigGfIZJSPFTcrUKI/Q7AsHuP+3lG6q9VAri9UJZXm5pWOg=IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 280\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- reading 280 bytes...
- -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
- read 280 bytes
- Conn close
+ <<~PRE_SCRUBBED
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
+ <- "{\"payload\":\"1aUSihtRXgd+1nycRfVWgv0JDZsGLsrpsNkahpkx4jmnBRRAPPao+zJYqsN4xrGMIeVdJ3Y5LlQYXg5qu8O7iZmDPTqWbyKmsurCxJidr6AkFszwvRfugElyb5sAYpUcrnFSpVUgz2NGcIuMRalr0irf7q30+TzbLRHQc1Z5QTe6am3ndO8aSKKLwYYmfHcO8E/+dPiCsSP09P2heNqpMbf5IKdSwGCVS1Rtpcoijl3wXB8zgeBZ1PXHAmmkC1/CWRs/fh1qmvYFzb8YAiRy5q80Tyq09IaeSpQ1ydq3r95QBSJy6H4gz2OV/v2xdm1A63XEh2+6N6p2XDyzGWQrxKE41wmqRCxie7qY2xqdv4S8Cl8ldSMEpZY46A68hKIN6zrj6eMWxauwdi6ZkZfMDuh9Pn9x5gwwgfElLopIpR8fejB6G4hAQHtq2jhn5D4ccmAqNxkrB4w5k+zc53Rupk2u3MDp5T5sRkqvNyIN2kCE6i0DD9HlqkCjWV+bG9WcUiO4D7m5fWRE5f9OQ2XjeA==IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 320\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 320 bytes...
+ -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
+ read 320 bytes
+ Conn close
+ opening connection to wpy.mitec.com.mx:443...
+ opened
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
+ <- "{\"payload\":\"Z6l24tZG2YfTOQTne8NVygr/YeuVRNya8ZUCM5NvRgOEL/Mt8PO0voNnspoiFSg+RVamC4V2BipmU3spPVBg6Dr0xMpPL7ryVB9mlM4PokUdHkZTjXJHbbr1GWdyEPMYYSH0f+M1qUDO57EyUuZv8o6QSv+a/tuOrrBwsHI8cnsv+y9qt5L9LuGRMeBYvZkkK+xw53eDqYsJGoCvpk/pljCCkGU7Q/sKsLOx0MT6dA/BLVGrGeo8ngO+W/cnOigGfIZJSPFTcrUKI/Q7AsHuP+3lG6q9VAri9UJZXm5pWOg=IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 280\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 280 bytes...
+ -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
+ read 280 bytes
+ Conn close
PRE_SCRUBBED
end
def post_scrubbed
- <<-POST_SCRUBBED
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Authorize","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","amount":"11.15","currency":"MXN","reference":"721","transaction_id":"721","installments":1,"card":"[FILTERED]","expmonth":9,"expyear":2025,"cvv":"[FILTERED]","name_client":"Pedro Flores Valdes","email":"nadie@mit.test","key_session":"[FILTERED]"}IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 320\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- response: {"folio_cdp":"095492846","auth":"928468","response":"approved","message":"0C- Pago aprobado (test)","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:08 06:09:2021","operation":"Authorize"}read 320 bytes
- Conn close
- opening connection to wpy.mitec.com.mx:443...
- opened
- starting SSL for wpy.mitec.com.mx:443...
- SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
- <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
- <- "{\"payload\":\"{"operation":"Capture","commerce_id":"147","user":"IVCA33721","apikey":"[FILTERED]","testMode":"YES","transaction_id":"721","amount":"11.15","key_session":"[FILTERED]"}IVCA33721\"}"
- -> "HTTP/1.1 200 \r\n"
- -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
- -> "X-Content-Type-Options: nosniff\r\n"
- -> "X-XSS-Protection: 1; mode=block\r\n"
- -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
- -> "Content-Length: 280\r\n"
- -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
- -> "Connection: close\r\n"
- -> "Server: \r\n"
- -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
- -> "\r\n"
- response: {"folio_cdp":"095492915","auth":"929151","response":"approved","message":"0C- ","id_comercio":"147","reference":"721","amount":"11.15","time":"19:02:09 06:09:2021","operation":"Capture"}read 280 bytes
- Conn close
+ <<~POST_SCRUBBED
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 607\r\n\r\n"
+ <- "{\"payload\":\"{\"operation\":\"Authorize\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"amount\":\"11.15\",\"currency\":\"MXN\",\"reference\":\"721\",\"transaction_id\":\"721\",\"installments\":1,\"card\":\"[FILTERED]\",\"expmonth\":9,\"expyear\":2025,\"cvv\":\"[FILTERED]\",\"name_client\":\"Pedro Flores Valdes\",\"email\":\"nadie@mit.test\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 320\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1I4cyJQ__N2M; Expires=Mon, 06-Sep-2021 19:03:38 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 320 bytes...
+ -> "hl0spHqAAamtY47Vo+W+dZcpDyK8QRqpx/gWzIM1F3X1VFV/zNUcKCuqaSL6F4S7MqOGUMOC3BXIZYaS9TpJf6xsMYeRDyMpiv+sE0VpY2a4gULhLv1ztgGHgF3OpMjD8ucgLbd9FMA5OZjd8wlaqn46JCiYNcNIPV7hkHWNCqSWow+C+SSkWZeaa9YpNT3E6udixbog30/li1FcSI+Ti80EWBIdH3JDcQvjQbqecNb87JYad0EhgqL1o7ZEMehfZ2kW9FG6OXjGzWyhiWd2GEFKe8em4vEJxARFdXsaHe3tX0jqnF2gYOiFRclqFkbk"
+ read 320 bytes
+
+ {\"folio_cdp\":\"095492846\",\"auth\":\"928468\",\"response\":\"approved\",\"message\":\"0C- Pago aprobado (test)\",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:08 06:09:2021\",\"operation\":\"Authorize\"}
+
+ {\"folio_cdp\":\"095492915\",\"auth\":\"929151\",\"response\":\"approved\",\"message\":\"0C- \",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:09 06:09:2021\",\"operation\":\"Capture\"}
+ Conn close
+ opening connection to wpy.mitec.com.mx:443...
+ opened
+ starting SSL for wpy.mitec.com.mx:443...
+ SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384
+ <- "POST /ModuloUtilWS/activeCDP.htm HTTP/1.1\r\nContent-Type: application/json\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: wpy.mitec.com.mx\r\nContent-Length: 359\r\n\r\n"
+ <- "{\"payload\":\"{\"operation\":\"Capture\",\"commerce_id\":\"147\",\"user\":\"IVCA33721\",\"apikey\":\"[FILTERED]\",\"testMode\":\"YES\",\"transaction_id\":\"721\",\"amount\":\"11.15\",\"key_session\":\"[FILTERED]\"}IVCA33721\"}"
+ -> "HTTP/1.1 200 \r\n"
+ -> "Strict-Transport-Security: max-age=31536000;includeSubDomains\r\n"
+ -> "X-Content-Type-Options: nosniff\r\n"
+ -> "X-XSS-Protection: 1; mode=block\r\n"
+ -> "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ -> "Content-Length: 280\r\n"
+ -> "Date: Mon, 06 Sep 2021 19:02:08 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "Server: \r\n"
+ -> "Set-Cookie: UqZBpD3n=v1JocyJQ__9tu; Expires=Mon, 06-Sep-2021 19:03:39 GMT; Path=/; Secure; HttpOnly\r\n"
+ -> "\r\n"
+ reading 280 bytes...
+ -> "BnuAgMOx9USBreICk027VY2ZqJA7xQcRT9Ytz8WpabDnqIglj43J/I03pKLtDlFrerKIAzhW1YCroDOS7mvtA5YnWezLstoOK0LbIcYqLzj1dCFW2zLb9ssTCxJa6ZmEQdzQdl8pyY4mC0QQ0JrOrsSA9QfX1XhkdcSVnsxQV1cEooL8/6EsVFCb6yVIMhVnGL6GRCc2J+rPigHsljLWRovgRKqFIURJjNWbfqepDRPG2hCNKsabM/lE2DFtKLMs4J5iwY9HiRbrAMG6BaGNiQ=="
+ read 280 bytes
+
+ {\"folio_cdp\":\"095492846\",\"auth\":\"928468\",\"response\":\"approved\",\"message\":\"0C- Pago aprobado (test)\",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:08 06:09:2021\",\"operation\":\"Authorize\"}
+
+ {\"folio_cdp\":\"095492915\",\"auth\":\"929151\",\"response\":\"approved\",\"message\":\"0C- \",\"id_comercio\":\"147\",\"reference\":\"721\",\"amount\":\"11.15\",\"time\":\"19:02:09 06:09:2021\",\"operation\":\"Capture\"}
+ Conn close
POST_SCRUBBED
end
end
diff --git a/test/unit/gateways/moneris_test.rb b/test/unit/gateways/moneris_test.rb
index 5c7ee922fbc..feefeace8c6 100644
--- a/test/unit/gateways/moneris_test.rb
+++ b/test/unit/gateways/moneris_test.rb
@@ -36,35 +36,58 @@ def test_successful_purchase
end
def test_successful_mpi_cavv_purchase
- @gateway.expects(:ssl_post).returns(successful_cavv_purchase_response)
+ options = @options.merge(
+ three_d_secure: {
+ version: '2',
+ cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ eci: @fully_authenticated_eci,
+ three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
+ ds_transaction_id: '12345'
+ }
+ )
+
+ response = stub_comms do
+ @gateway.purchase(100, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/12345<\/ds_trans_id>/, data)
+ assert_match(/d0f461f8-960f-40c9-a323-4e43a4e16aaa<\/threeds_server_trans_id>/, data)
+ assert_match(/2<\/threeds_version>/, data)
+ end.respond_with(successful_cavv_purchase_response)
+
+ assert_success response
+ assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
+ end
+
+ def test_successful_purchase_with_cust_id
+ response = stub_comms do
+ @gateway.purchase(100, @credit_card, @options.merge(cust_id: 'test1234'))
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/test1234<\/cust_id>/, data)
+ end.respond_with(successful_cavv_purchase_response)
- assert response = @gateway.purchase(100, @credit_card,
- @options.merge(
- three_d_secure: {
- version: '2',
- cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- eci: @fully_authenticated_eci,
- three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
- ds_transaction_id: '12345'
- }
- ))
assert_success response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
def test_failed_mpi_cavv_purchase
- @gateway.expects(:ssl_post).returns(failed_cavv_purchase_response)
+ options = @options.merge(
+ three_d_secure: {
+ version: '2',
+ cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ eci: @fully_authenticated_eci,
+ three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
+ ds_transaction_id: '12345'
+ }
+ )
+
+ response = stub_comms do
+ @gateway.purchase(100, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/12345<\/ds_trans_id>/, data)
+ assert_match(/d0f461f8-960f-40c9-a323-4e43a4e16aaa<\/threeds_server_trans_id>/, data)
+ assert_match(/2<\/threeds_version>/, data)
+ end.respond_with(failed_cavv_purchase_response)
- assert response = @gateway.purchase(100, @credit_card,
- @options.merge(
- three_d_secure: {
- version: '2',
- cavv: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- eci: @fully_authenticated_eci,
- three_ds_server_trans_id: 'd0f461f8-960f-40c9-a323-4e43a4e16aaa',
- ds_transaction_id: '12345'
- }
- ))
assert_failure response
assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization
end
@@ -128,9 +151,11 @@ def test_successful_subsequent_purchase_with_credential_on_file
def test_successful_purchase_with_network_tokenization
@gateway.expects(:ssl_post).returns(successful_purchase_network_tokenization)
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.purchase(100, @credit_card, @options)
assert_success response
assert_equal '101965-0_10;0bbb277b543a17b6781243889a689573', response.authorization
@@ -277,9 +302,11 @@ def test_successful_purchase_with_vault
def test_successful_authorize_with_network_tokenization
@gateway.expects(:ssl_post).returns(successful_authorization_network_tokenization)
- @credit_card = network_tokenization_credit_card('4242424242424242',
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
- verification_value: nil)
+ verification_value: nil
+ )
assert response = @gateway.authorize(100, @credit_card, @options)
assert_success response
assert_equal '109232-0_10;d88d9f5f3472898832c54d6b5572757e', response.authorization
diff --git a/test/unit/gateways/mundipagg_test.rb b/test/unit/gateways/mundipagg_test.rb
index 7c9f4d923a8..a66d824333e 100644
--- a/test/unit/gateways/mundipagg_test.rb
+++ b/test/unit/gateways/mundipagg_test.rb
@@ -41,26 +41,26 @@ def setup
@submerchant_options = {
submerchant: {
- "merchant_category_code": '44444',
- "payment_facilitator_code": '5555555',
- "code": 'code2',
- "name": 'Sub Tony Stark',
- "document": '123456789',
- "type": 'individual',
- "phone": {
- "country_code": '55',
- "number": '000000000',
- "area_code": '21'
+ merchant_category_code: '44444',
+ payment_facilitator_code: '5555555',
+ code: 'code2',
+ name: 'Sub Tony Stark',
+ document: '123456789',
+ type: 'individual',
+ phone: {
+ country_code: '55',
+ number: '000000000',
+ area_code: '21'
},
- "address": {
- "street": 'Malibu Point',
- "number": '10880',
- "complement": 'A',
- "neighborhood": 'Central Malibu',
- "city": 'Malibu',
- "state": 'CA',
- "country": 'US',
- "zip_code": '24210-460'
+ address: {
+ street: 'Malibu Point',
+ number: '10880',
+ complement: 'A',
+ neighborhood: 'Central Malibu',
+ city: 'Malibu',
+ state: 'CA',
+ country: 'US',
+ zip_code: '24210-460'
}
}
}
diff --git a/test/unit/gateways/nab_transact_test.rb b/test/unit/gateways/nab_transact_test.rb
index b4afc7c7313..264d40dac66 100644
--- a/test/unit/gateways/nab_transact_test.rb
+++ b/test/unit/gateways/nab_transact_test.rb
@@ -228,9 +228,7 @@ def valid_metadata(name, location)
end
def assert_metadata(name, location, &block)
- stub_comms(@gateway, :ssl_request) do
- yield
- end.check_request do |_method, _endpoint, data, _headers|
+ stub_comms(@gateway, :ssl_request, &block).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)
diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb
index 7cbf9ef6510..f2817a6a38e 100644
--- a/test/unit/gateways/nmi_test.rb
+++ b/test/unit/gateways/nmi_test.rb
@@ -125,6 +125,70 @@ def test_purchase_with_options
assert_success response
end
+ def test_purchase_with_surcharge
+ options = @transaction_options.merge({ surcharge: '1.00' })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/surcharge=1.00/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_fields
+ options = @transaction_options.merge({ shipping_address: shipping_address })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/shipping_firstname=Jon/, data)
+ assert_match(/shipping_lastname=Smith/, data)
+ assert_match(/shipping_address1=123\+Your\+Street/, data)
+ assert_match(/shipping_address2=Apt\+2/, data)
+ assert_match(/shipping_city=Toronto/, data)
+ assert_match(/shipping_state=ON/, data)
+ assert_match(/shipping_country=CA/, data)
+ assert_match(/shipping_zip=K2C3N7/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_fields_omits_blank_name
+ options = @transaction_options.merge({ shipping_address: shipping_address(name: nil) })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ refute_match(/shipping_firstname/, data)
+ refute_match(/shipping_lastname/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_purchase_with_shipping_email
+ options = @transaction_options.merge({ shipping_address: shipping_address, shipping_email: 'test@example.com' })
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ test_transaction_options(data)
+
+ assert_match(/shipping_email=test%40example\.com/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
def test_failed_purchase
response = stub_comms do
@gateway.purchase(@amount, @credit_card)
@@ -524,8 +588,7 @@ def test_blank_cvv_not_sent
end
def test_supported_countries
- assert_equal 1,
- (['US'] | NmiGateway.supported_countries).size
+ assert_equal 2, (%w[US CA] | NmiGateway.supported_countries).size
end
def test_supported_card_types
@@ -594,6 +657,21 @@ def test_stored_credential_recurring_mit_used
assert_success response
end
+ def test_stored_credential_ntid_override_recurring_mit_used
+ options = stored_credential_options(:merchant, :recurring)
+ options[:network_transaction_id] = 'test123'
+ 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=test123/, 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
@@ -760,8 +838,7 @@ def test_verify(options = {})
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_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)
diff --git a/test/unit/gateways/ogone_test.rb b/test/unit/gateways/ogone_test.rb
index 906ea36c184..8adb208a32a 100644
--- a/test/unit/gateways/ogone_test.rb
+++ b/test/unit/gateways/ogone_test.rb
@@ -453,6 +453,27 @@ def test_transcript_scrubbing
assert_equal @gateway.scrub(pre_scrub), post_scrub
end
+ def test_signatire_calculation_with_with_space
+ payload = {
+ orderID: 'abc123',
+ currency: 'EUR',
+ amount: '100',
+ PM: 'CreditCard',
+ ACCEPTANCE: 'test123',
+ STATUS: '9',
+ CARDNO: 'XXXXXXXXXXXX3310',
+ ED: '1029',
+ DCC_INDICATOR: '0',
+ DCC_EXCHRATE: ''
+ }
+
+ signature_with = @gateway.send(:calculate_signature, payload, 'sha512', 'ABC123')
+ payload.delete(:DCC_EXCHRATE)
+ signature_without = @gateway.send(:calculate_signature, payload, 'sha512', 'ABC123')
+
+ assert_equal signature_without, signature_with
+ end
+
private
def string_to_digest
diff --git a/test/unit/gateways/opp_test.rb b/test/unit/gateways/opp_test.rb
index 6dfcf179097..ec7af16f8bf 100644
--- a/test/unit/gateways/opp_test.rb
+++ b/test/unit/gateways/opp_test.rb
@@ -202,7 +202,8 @@ def post_scrubbed
end
def successful_response(type, id)
- OppMockResponse.new(200,
+ OppMockResponse.new(
+ 200,
JSON.generate({
'id' => id,
'paymentType' => type,
@@ -224,11 +225,13 @@ def successful_response(type, id)
'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,
+ OppMockResponse.new(
+ 200,
JSON.generate({
'id' => id,
'result' => {
@@ -245,11 +248,13 @@ def successful_store_response(id)
'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,
+ OppMockResponse.new(
+ 400,
JSON.generate({
'id' => id,
'paymentType' => type,
@@ -268,11 +273,13 @@ def failed_response(type, id, code = '100.100.101')
'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,
+ OppMockResponse.new(
+ 400,
JSON.generate({
'id' => id,
'result' => {
@@ -289,7 +296,8 @@ def failed_store_response(id, code = '100.100.101')
'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage',
'timestamp' => '2015-06-20 20:40:26+0000',
'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d'
- }))
+ })
+ )
end
class OppMockResponse
diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb
index 0e4b306bc5b..150cc874ac1 100644
--- a/test/unit/gateways/orbital_test.rb
+++ b/test/unit/gateways/orbital_test.rb
@@ -30,7 +30,15 @@ def setup
address2: address[:address2],
city: address[:city],
state: address[:state],
- zip: address[:zip]
+ zip: address[:zip],
+ requestor_name: 'ArtVandelay123',
+ total_tax_amount: '75',
+ national_tax: '625',
+ pst_tax_reg_number: '8675309',
+ customer_vat_reg_number: '1234567890',
+ merchant_vat_reg_number: '987654321',
+ commodity_code: 'SUMM',
+ local_tax_rate: '6250'
}
@level3 = {
@@ -42,7 +50,11 @@ def setup
vat_tax: '25',
alt_tax: '30',
vat_rate: '7',
- alt_ind: 'Y'
+ alt_ind: 'Y',
+ invoice_discount_treatment: 1,
+ tax_treatment: 1,
+ ship_vat_rate: 10,
+ unique_vat_invoice_ref: 'ABC123'
}
@line_items =
@@ -123,7 +135,7 @@ def test_successful_purchase
assert response = @gateway.purchase(50, credit_card, order_id: '1')
assert_instance_of Response, response
assert_success response
- assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization
+ assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
end
def test_successful_purchase_with_echeck
@@ -133,7 +145,7 @@ def test_successful_purchase_with_echeck
assert_instance_of Response, response
assert_equal 'Approved', response.message
assert_success response
- assert_equal '5F8E8BEE7299FD339A38F70CFF6E5D010EF55498;9baedc697f2cf06457de78', response.authorization
+ assert_equal '5F8E8BEE7299FD339A38F70CFF6E5D010EF55498;9baedc697f2cf06457de78;EC', response.authorization
end
def test_successful_purchase_with_commercial_echeck
@@ -143,6 +155,7 @@ def test_successful_purchase_with_commercial_echeck
@gateway.purchase(50, commercial_echeck, order_id: '9baedc697f2cf06457de78')
end.check_request do |_endpoint, data, _headers|
assert_match %{X}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_with_echeck_response)
end
@@ -163,7 +176,7 @@ def test_successful_force_capture_with_echeck
assert_instance_of Response, response
assert_match 'APPROVAL', response.message
assert_equal 'Approved and Completed', response.params['status_msg']
- assert_equal '5F8ED3D950A43BD63369845D5385B6354C3654B4;2930847bc732eb4e8102cf', response.authorization
+ assert_equal '5F8ED3D950A43BD63369845D5385B6354C3654B4;2930847bc732eb4e8102cf;EC', response.authorization
end
def test_successful_force_capture_with_echeck_prenote
@@ -173,7 +186,7 @@ def test_successful_force_capture_with_echeck_prenote
assert_instance_of Response, response
assert_match 'APPROVAL', response.message
assert_equal 'Approved and Completed', response.params['status_msg']
- assert_equal '5F8ED3D950A43BD63369845D5385B6354C3654B4;2930847bc732eb4e8102cf', response.authorization
+ assert_equal '5F8ED3D950A43BD63369845D5385B6354C3654B4;2930847bc732eb4e8102cf;EC', response.authorization
end
def test_failed_force_capture_with_echeck_prenote
@@ -202,6 +215,15 @@ def test_level2_data
assert_match %{#{@level2[:address2]}}, data
assert_match %{#{@level2[:city]}}, data
assert_match %{#{@level2[:state]}}, data
+ assert_match %{#{@level2[:requestor_name]}}, data
+ assert_match %{#{@level2[:total_tax_amount]}}, data
+ assert_match %{#{@level2[:national_tax]}}, data
+ assert_match %{#{@level2[:pst_tax_reg_number]}}, data
+ assert_match %{#{@level2[:customer_vat_reg_number]}}, data
+ assert_match %{#{@level2[:merchant_vat_reg_number]}}, data
+ assert_match %{#{@level2[:commodity_code]}}, data
+ assert_match %{#{@level2[:local_tax_rate]}}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -218,6 +240,11 @@ def test_level3_data
assert_match %{#{@level3[:vat_rate].to_i}}, data
assert_match %{#{@level3[:alt_tax].to_i}}, data
assert_match %{#{@level3[:alt_ind]}}, data
+ assert_match %{#{@level3[:invoice_discount_treatment]}}, data
+ assert_match %{#{@level3[:tax_treatment]}}, data
+ assert_match %{#{@level3[:ship_vat_rate]}}, data
+ assert_match %{#{@level3[:unique_vat_invoice_ref]}}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -238,6 +265,7 @@ def test_line_items_data
assert_match %{#{@line_items[1][:gross_net]}}, data
assert_match %{#{@line_items[1][:disc_ind]}}, data
assert_match %{2}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -264,11 +292,15 @@ def test_network_tokenization_credit_card_data
assert_match %{5}, data
assert_match %{Y}, data
assert_match %{DigitalTokenCryptogram}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
def test_schema_for_soft_descriptors_with_network_tokenization_credit_card_data
options = @options.merge(
+ level_2_data: @level2,
+ level_3_data: @level3,
+ line_items: @line_items,
soft_descriptors: {
merchant_name: 'Merch',
product_description: 'Description',
@@ -278,8 +310,7 @@ def test_schema_for_soft_descriptors_with_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|
- # Soft descriptor fields should come before dpan and cryptogram fields
- assert_match %{email@example<\/SDMerchantEmail>Y<\/DPANInd>5}, data
assert_match %{TESTCAVV}, data
assert_match %{TESTXID}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -300,6 +332,7 @@ def test_three_d_secure_data_on_visa_authorization
assert_match %{5}, data
assert_match %{TESTCAVV}, data
assert_match %{TESTXID}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -312,6 +345,7 @@ def test_three_d_secure_data_on_master_purchase
assert_match %{2}, data
assert_match %{97267598FAE648F28083C23433990FBC}, data
assert_match %{4}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -324,6 +358,7 @@ def test_three_d_secure_data_on_master_authorization
assert_match %{2}, data
assert_match %{97267598FAE648F28083C23433990FBC}, data
assert_match %{4}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -348,6 +383,7 @@ def test_three_d_secure_data_on_master_sca_recurring
assert_match %{97267598FAE648F28083C23433990FBC}, data
assert_match %{Y}, data
assert_match %{4}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -509,9 +545,9 @@ def test_order_id_format
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;1;VI001.1;VI', order_id: '#1001.1')
end.check_request do |_endpoint, data, _headers|
- assert_match(/1001-1<\/OrderID>/, data)
+ assert_match(/1<\/OrderID>/, data)
end.respond_with(successful_purchase_response)
assert_success response
end
@@ -524,9 +560,10 @@ def test_numeric_merchant_id_for_caputre
)
response = stub_comms(gateway) do
- gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', @options)
+ gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', @options)
end.check_request do |_endpoint, data, _headers|
assert_match(/700000123456<\/MerchantID>/, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
end
@@ -558,9 +595,7 @@ def test_truncates_address
end
def test_truncates_name
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: address)
@@ -644,13 +679,13 @@ def test_address_format
assert_match(/Luxury Suite, data)
assert_match(/Winnipeg, data)
assert_match(/MB, data)
+ assert_xml_valid_to_xsd(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)
+ @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)
@@ -661,9 +696,7 @@ def test_address_format
end
def test_truncates_by_byte_length
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
long_address = address(
address1: '456 Stréêt Name is Really Long',
@@ -699,8 +732,7 @@ def test_truncates_by_byte_length
response = stub_comms do
assert_deprecation_warning do
- @gateway.add_customer_profile(credit_card,
- billing_address: long_address)
+ @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)
@@ -753,6 +785,7 @@ def test_dest_address
assert_match(/Joan Smith/, data)
assert_match(/1234567890/, data)
assert_match(/US/, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -777,9 +810,7 @@ def test_name_sends_for_credit_card_with_address
dest_country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, address: address)
@@ -817,9 +848,7 @@ def test_name_sends_for_echeck_with_no_address
end
def test_does_not_send_for_credit_card_with_no_address
- card = credit_card('4242424242424242',
- first_name: 'John',
- last_name: 'Jacob Jingleheimer Smith-Jones')
+ card = credit_card('4242424242424242', first_name: 'John', last_name: 'Jacob Jingleheimer Smith-Jones')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, address: nil, billing_address: nil)
@@ -840,9 +869,7 @@ def test_avs_name_falls_back_to_billing_address
country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: nil,
- last_name: '')
+ card = credit_card('4242424242424242', first_name: nil, last_name: '')
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: billing_address)
@@ -863,9 +890,7 @@ def test_completely_blank_name
country: 'US'
)
- card = credit_card('4242424242424242',
- first_name: nil,
- last_name: nil)
+ card = credit_card('4242424242424242', first_name: nil, last_name: nil)
response = stub_comms do
@gateway.purchase(50, card, order_id: 1, billing_address: billing_address)
@@ -881,6 +906,7 @@ def test_successful_purchase_with_negative_stored_credentials_indicator
end.check_request do |_endpoint, data, _headers|
assert_no_match(//, data)
assert_no_match(//, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -891,6 +917,7 @@ def test_successful_purchase_with_stored_credentials
assert_match %{#{@options_stored_credentials[:mit_msg_type]}}, data
assert_match %{#{@options_stored_credentials[:mit_stored_credential_ind]}}, data
assert_match %{#{@options_stored_credentials[:mit_submitted_transaction_id]}}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -915,6 +942,7 @@ def test_stored_credential_recurring_cit_initial
end.check_request do |_endpoint, data, _headers|
assert_match(/CSTO, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -928,6 +956,7 @@ def test_stored_credential_recurring_cit_used
end.check_request do |_endpoint, data, _headers|
assert_match(/CREC, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -940,6 +969,7 @@ def test_stored_credential_recurring_mit_initial
end.check_request do |_endpoint, data, _headers|
assert_match(/CSTO, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -954,6 +984,7 @@ def test_stored_credential_recurring_mit_used
assert_match(/MREC, data)
assert_match(/Y, data)
assert_match(/abc123, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -966,6 +997,7 @@ def test_stored_credential_unscheduled_cit_initial
end.check_request do |_endpoint, data, _headers|
assert_match(/CSTO, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -979,6 +1011,7 @@ def test_stored_credential_unscheduled_cit_used
end.check_request do |_endpoint, data, _headers|
assert_match(/CUSE, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -991,6 +1024,7 @@ def test_stored_credential_unscheduled_mit_initial
end.check_request do |_endpoint, data, _headers|
assert_match(/CSTO, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -1005,6 +1039,7 @@ def test_stored_credential_unscheduled_mit_used
assert_match(/MUSE, data)
assert_match(/Y, data)
assert_match(/abc123, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -1017,6 +1052,7 @@ def test_stored_credential_installment_cit_initial
end.check_request do |_endpoint, data, _headers|
assert_match(/CSTO, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -1030,6 +1066,7 @@ def test_stored_credential_installment_cit_used
end.check_request do |_endpoint, data, _headers|
assert_match(/CINS, data)
assert_match(/Y, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
@@ -1061,6 +1098,45 @@ def test_stored_credential_installment_mit_used
assert_success response
end
+ def test_successful_store_request
+ stub_comms do
+ @gateway.store(credit_card, @options)
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match %{GT}, data
+ end
+ end
+
+ def test_successful_payment_request_with_token_stored
+ stub_comms do
+ @gateway.purchase(50, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;2521002395820006;VI', @options.merge(card_brand: 'VI'))
+ end.check_request(skip_response: true) do |_endpoint, data, _headers|
+ assert_match %{VI}, data
+ assert_match %{2521002395820006}, data
+ end
+ 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_equal 'Approved', response.message
+ assert_equal '4556761607723886', response.params['safetech_token']
+ assert_equal 'VI', response.params['card_brand']
+ end
+
+ def test_successful_purchase_with_stored_token
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ assert store = @gateway.store(@credit_card, @options)
+ assert_instance_of Response, store
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ assert auth = @gateway.purchase(100, store.authorization, @options)
+ assert_instance_of Response, auth
+
+ assert_equal 'Approved', auth.message
+ 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))
@@ -1068,6 +1144,7 @@ def test_successful_purchase_with_overridden_normalized_stored_credentials
assert_match %{MRSB}, data
assert_match %{Y}, data
assert_match %{123456abcdef}, data
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
end
@@ -1083,6 +1160,7 @@ def test_default_managed_billing
assert_match(/IO/, data)
assert_match(/10102014/, data)
assert_match(/N/, data)
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_profile_response)
assert_success response
end
@@ -1091,13 +1169,15 @@ def test_managed_billing
response = stub_comms do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
assert_deprecation_warning do
- @gateway.add_customer_profile(credit_card,
+ @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|
@@ -1214,7 +1294,7 @@ def test_successful_authorize_with_echeck
assert_instance_of Response, response
assert_equal 'Approved', response.message
assert_success response
- assert_equal '5F8E8D2B077217F3EF1ACD3B61610E4CD12954A3;2', response.authorization
+ assert_equal '5F8E8D2B077217F3EF1ACD3B61610E4CD12954A3;2;EC', response.authorization
end
def test_failed_authorize_with_echeck
@@ -1359,10 +1439,7 @@ 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_PTI83.xsd")
- doc = Nokogiri::XML(data)
- xsd = Nokogiri::XML::Schema(schema_file)
- assert xsd.valid?(doc), 'Request does not adhere to DTD'
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
end
@@ -1371,10 +1448,7 @@ 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_PTI83.xsd")
- doc = Nokogiri::XML(data)
- xsd = Nokogiri::XML::Schema(schema_file)
- assert xsd.valid?(doc), 'Request does not adhere to DTD'
+ assert_xml_valid_to_xsd(data)
end.respond_with(successful_purchase_response)
assert_success response
end
@@ -1545,8 +1619,7 @@ def test_cc_account_num_is_removed_from_response
response = nil
assert_deprecation_warning do
- response = @gateway.add_customer_profile(credit_card,
- billing_address: address)
+ response = @gateway.add_customer_profile(credit_card, billing_address: address)
end
assert_instance_of Response, response
@@ -1559,7 +1632,7 @@ def test_successful_verify
@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 '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
assert_equal 'Approved', response.message
end
@@ -1587,7 +1660,7 @@ def test_successful_verify_zero_auth_different_cards
@gateway.verify(@credit_card, @options)
end.respond_with(successful_purchase_response)
assert_success response
- assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization
+ assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
assert_equal 'Approved', response.message
end
@@ -1606,7 +1679,7 @@ def test_successful_verify_with_discover_brand
@gateway.verify(@credit_card, @options)
end.respond_with(successful_purchase_response, successful_void_response)
assert_success response
- assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization
+ assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
assert_equal 'Approved', response.message
end
@@ -1616,7 +1689,7 @@ def test_successful_verify_and_failed_void_discover_brand
@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 '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
assert_equal 'Approved', response.message
end
@@ -1625,7 +1698,7 @@ def test_successful_verify_and_failed_void
@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 '4A5398CF9B87744GG84A1D30F2F2321C66249416;1;VI', response.authorization
assert_equal 'Approved', response.message
end
@@ -1770,6 +1843,10 @@ def successful_refund_response
'R253997001VI45567610299838860c1792db5d167e0b96dd9c60D1E12322FD50E1517A2598593A48EEA9965469201003 tst743Approved100 090955'
end
+ def successful_store_response
+ 'AC492310001VI4556761029983886f9269cbc7eb453d75adb1d6536A0990C37C45D0000082B0001A64E4156534A101007 Mtst443Approved100IUM123433455676160772388600A'
+ end
+
def failed_refund_response
'881The LIDM you supplied (3F3F3F) does not match with any existing transaction'
end
@@ -1923,4 +2000,14 @@ def post_scrubbed_echeck
Conn close
REQUEST
end
+
+ def assert_xml_valid_to_xsd(data)
+ doc = Nokogiri::XML(data)
+ xsd = Nokogiri::XML::Schema(schema_file)
+ assert xsd.valid?(doc), 'Request does not adhere to DTD'
+ end
+
+ def schema_file
+ @schema_file ||= File.read("#{File.dirname(__FILE__)}/../../schema/orbital/Request_PTI95.xsd")
+ end
end
diff --git a/test/unit/gateways/pac_net_raven_test.rb b/test/unit/gateways/pac_net_raven_test.rb
index e98214fe6bd..d206b211448 100644
--- a/test/unit/gateways/pac_net_raven_test.rb
+++ b/test/unit/gateways/pac_net_raven_test.rb
@@ -337,7 +337,8 @@ 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",
+ 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),
@@ -347,6 +348,7 @@ def test_post_data
'BillingStreetAddressLineFour' => 'Address 2',
'BillingPostalCode' => 'ZIP123'
})
+ )
end
def test_signature_for_cc_preauth_action
diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb
index 09f13807e83..0f44fb59822 100644
--- a/test/unit/gateways/pay_trace_test.rb
+++ b/test/unit/gateways/pay_trace_test.rb
@@ -1,20 +1,10 @@
require 'test_helper'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class PayTraceGateway < Gateway
- def acquire_access_token
- @options[:access_token] = SecureRandom.hex(16)
- end
- end
- end
-end
-
class PayTraceTest < Test::Unit::TestCase
include CommStub
def setup
- @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
+ @gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator', access_token: SecureRandom.hex(16))
@credit_card = credit_card
@echeck = check(account_number: '123456', routing_number: '325070760')
@amount = 100
@@ -24,17 +14,38 @@ def setup
}
end
+ def test_setup_access_token_should_rise_an_exception_under_bad_request
+ error = assert_raises(ActiveMerchant::OAuthResponseError) do
+ access_token_response = {
+ error: 'invalid_grant',
+ error_description: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ }.to_json
+ @gateway.expects(:ssl_post).returns(access_token_response)
+ @gateway.send(:acquire_access_token)
+ end
+
+ assert_match(/Failed with The provided authorization grant is invalid/, error.message)
+ end
+
def test_successful_purchase
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ response = stub_comms(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['amount'], '1.00'
+ assert_equal request['credit_card']['number'], @credit_card.number
+ assert_equal request['integrator_id'], @gateway.options[:integrator_id]
+ assert_equal request['csc'], @credit_card.verification_value
+ end.respond_with(successful_purchase_response)
- response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 392483066, response.authorization
end
def test_successful_purchase_with_ach
+ @echeck.name = 'Test Name'
response = stub_comms(@gateway) do
- @gateway.purchase(@amount, @echeck, @options)
+ @gateway.purchase(@amount, @echeck, {})
end.check_request do |endpoint, data, _headers|
request = JSON.parse(data)
assert_include endpoint, 'checks/sale/by_account'
@@ -42,11 +53,8 @@ def test_successful_purchase_with_ach
assert_equal request['check']['account_number'], @echeck.account_number
assert_equal request['check']['routing_number'], @echeck.routing_number
assert_equal request['integrator_id'], @gateway.options[:integrator_id]
- assert_equal request['billing_address']['name'], @options[:billing_address][:name]
- assert_equal request['billing_address']['street_address'], @options[:billing_address][:address1]
- assert_equal request['billing_address']['city'], @options[:billing_address][:city]
- assert_equal request['billing_address']['state'], @options[:billing_address][:state]
- assert_equal request['billing_address']['zip'], @options[:billing_address][:zip]
+ assert_equal request['billing_address']['name'], @echeck.name
+ assert_equal request.dig('billing_address', 'street_address'), nil
end.respond_with(successful_ach_processing_response)
assert_success response
@@ -397,7 +405,7 @@ def pre_scrubbed
starting SSL for api.paytrace.com:443...
SSL established
<- "POST /v1/transactions/sale/keyed HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer 96e647567627164796f6e63704370727565646c697e236f6d6:5427e43707866415555426a68723848763574533d476a466:QryC8bI6hfidGVcFcwnago3t77BSzW8ItUl9GWhsx9Y\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.paytrace.com\r\nContent-Length: 335\r\n\r\n"
- <- "{\"amount\":\"1.00\",\"credit_card\":{\"number\":\"4012000098765439\",\"expiration_month\":9,\"expiration_year\":2022},\"billing_address\":{\"name\":\"Longbob Longsen\",\"street_address\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\"},\"password\":\"ErNsphFQUEbjx2Hx6uT3MgJf\",\"username\":\"integrations@spreedly.com\",\"integrator_id\":\"9575315uXt4u\"}"
+ <- "{\"amount\":\"1.00\",\"credit_card\":{\"number\":\"4012000098765439\",\"expiration_month\":9,\"expiration_year\":2022},\"csc\":\"123\",\"billing_address\":{\"name\":\"Longbob Longsen\",\"street_address\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\"},\"password\":\"ErNsphFQUEbjx2Hx6uT3MgJf\",\"username\":\"integrations@spreedly.com\",\"integrator_id\":\"9575315uXt4u\"}"
-> "HTTP/1.1 200 OK\r\n"
-> "Date: Thu, 03 Jun 2021 22:03:24 GMT\r\n"
-> "Content-Type: application/json; charset=utf-8\r\n"
@@ -440,7 +448,7 @@ def post_scrubbed
starting SSL for api.paytrace.com:443...
SSL established
<- "POST /v1/transactions/sale/keyed HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [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: api.paytrace.com\r\nContent-Length: 335\r\n\r\n"
- <- "{\"amount\":\"1.00\",\"credit_card\":{\"number\":\"[FILTERED]\",\"expiration_month\":9,\"expiration_year\":2022},\"billing_address\":{\"name\":\"Longbob Longsen\",\"street_address\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\"},\"password\":\"[FILTERED]\",\"username\":\"[FILTERED]\"}"
+ <- "{\"amount\":\"1.00\",\"credit_card\":{\"number\":\"[FILTERED]\",\"expiration_month\":9,\"expiration_year\":2022},\"csc\":\"[FILTERED]\",\"billing_address\":{\"name\":\"Longbob Longsen\",\"street_address\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\"},\"password\":\"[FILTERED]\",\"username\":\"[FILTERED]\"}"
-> "HTTP/1.1 200 OK\r\n"
-> "Date: Thu, 03 Jun 2021 22:03:24 GMT\r\n"
-> "Content-Type: application/json; charset=utf-8\r\n"
diff --git a/test/unit/gateways/paybox_direct_test.rb b/test/unit/gateways/paybox_direct_test.rb
index a10fab948bc..0676dc82ef9 100644
--- a/test/unit/gateways/paybox_direct_test.rb
+++ b/test/unit/gateways/paybox_direct_test.rb
@@ -9,8 +9,7 @@ def setup
password: 'p'
)
- @credit_card = credit_card('1111222233334444',
- brand: 'visa')
+ @credit_card = credit_card('1111222233334444', brand: 'visa')
@amount = 100
@options = {
diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb
index 22eab291105..53a7c090975 100644
--- a/test/unit/gateways/payeezy_test.rb
+++ b/test/unit/gateways/payeezy_test.rb
@@ -16,7 +16,7 @@ def setup
ta_token: '123'
}
@options_stored_credentials = {
- cardbrand_original_transaction_id: 'abc123',
+ cardbrand_original_transaction_id: 'original_transaction_id_abc123',
sequence: 'FIRST',
is_scheduled: true,
initiator: 'MERCHANT',
@@ -24,7 +24,7 @@ def setup
}
@options_standardized_stored_credentials = {
stored_credential: {
- network_transaction_id: 'abc123',
+ network_transaction_id: 'stored_credential_abc123',
initial_transaction: false,
reason_type: 'recurring',
initiator: 'cardholder'
@@ -55,6 +55,16 @@ def setup
source: :apple_pay,
verification_value: 569
)
+ @apple_pay_card_amex = network_tokenization_credit_card(
+ '373953192351004',
+ brand: 'american_express',
+ payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ month: '11',
+ year: Time.now.year + 1,
+ eci: 5,
+ source: :apple_pay,
+ verification_value: 569
+ )
end
def test_invalid_credentials
@@ -88,8 +98,15 @@ def test_invalid_token_on_integration
end
def test_successful_purchase
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ @credit_card.first_name = nil
+ @credit_card.last_name = nil
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal 'Jim Smith', request.dig('credit_card', 'cardholder_name')
+ end.respond_with(successful_purchase_response)
assert_success response
assert_equal 'ET114541|55083431|credit_card|1', response.authorization
assert response.test?
@@ -107,6 +124,29 @@ def test_successful_purchase_with_apple_pay
end.respond_with(successful_purchase_response)
end
+ def test_successful_purchase_with_apple_pay_no_cryptogram
+ @apple_pay_card.payment_cryptogram = ''
+ @apple_pay_card.eci = nil
+ stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert_equal request['eci_indicator'], '5'
+ assert_nil request['3DS']['xid']
+ assert_nil request['3DS']['cavv']
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_successful_purchase_with_apple_pay_amex
+ stub_comms do
+ @gateway.purchase(@amount, @apple_pay_card_amex, @options)
+ end.check_request do |_endpoint, data, _headers|
+ request = JSON.parse(data)
+ assert request['3DS']['cavv'], @apple_pay_card_amex.payment_cryptogram
+ assert_nil request['3DS']['xid']
+ end.respond_with(successful_purchase_response)
+ end
+
def test_failed_purchase_no_name
@apple_pay_card.first_name = nil
@apple_pay_card.last_name = nil
@@ -188,11 +228,34 @@ def test_successful_purchase_with_customer_ref
assert_success response
end
+ def test_successful_purchase_with_customer_ref_top_level
+ options = @options.merge(customer_ref: 'abcde')
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"customer_ref":"abcde"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ end
+
+ def test_successful_purchase_with_reference_3
+ options = @options.merge(reference_3: '12345')
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, options)
+ end.check_request do |_endpoint, data, _headers|
+ assert_match(/"reference_3":"12345"/, data)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ 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)
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'original_transaction_id_abc123'
end.respond_with(successful_purchase_stored_credentials_response)
assert_success response
@@ -204,7 +267,38 @@ def test_successful_purchase_with_standardized_stored_credentials
response = stub_comms do
@gateway.purchase(@amount, @credit_card, @options.merge(@options_standardized_stored_credentials))
end.check_request do |_endpoint, data, _headers|
- assert_match(/stored_credentials/, data)
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'stored_credential_abc123'
+ end.respond_with(successful_purchase_stored_credentials_response)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with__stored_credential_and_cardbrand_original_transaction_id
+ options = @options_standardized_stored_credentials.merge!(cardbrand_original_transaction_id: 'original_transaction_id_abc123')
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ end.check_request do |_endpoint, data, _headers|
+ stored_credentials = JSON.parse(data)['stored_credentials']['cardbrand_original_transaction_id']
+ assert_equal stored_credentials, 'original_transaction_id_abc123'
+ end.respond_with(successful_purchase_stored_credentials_response)
+
+ assert_success response
+ assert response.test?
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with_no_ntid
+ @options_standardized_stored_credentials[:stored_credential].delete(:network_transaction_id)
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge(@options_standardized_stored_credentials))
+ end.check_request do |_endpoint, data, _headers|
+ stored_credentials = JSON.parse(data)['stored_credentials']
+ assert_equal stored_credentials.include?(:cardbrand_original_transaction_id), false
end.respond_with(successful_purchase_stored_credentials_response)
assert_success response
@@ -778,7 +872,7 @@ def failed_purchase_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def failed_purchase_response_for_insufficient_funds
@@ -863,7 +957,7 @@ def failed_refund_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def successful_void_response
@@ -908,7 +1002,7 @@ def failed_void_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def failed_capture_response
@@ -948,7 +1042,7 @@ def failed_capture_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError'])
end
def invalid_token_response
@@ -987,7 +1081,7 @@ def invalid_token_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def invalid_token_response_integration
@@ -1012,7 +1106,7 @@ def invalid_token_response_integration
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError'])
end
def bad_credentials_response
@@ -1037,6 +1131,6 @@ def bad_credentials_response
body_exist: true
message:
RESPONSE
- YAML.safe_load(yamlexcep, ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError'])
+ YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError'])
end
end
diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb
index c87aedd255a..69fc901992f 100644
--- a/test/unit/gateways/payflow_test.rb
+++ b/test/unit/gateways/payflow_test.rb
@@ -465,9 +465,12 @@ def test_store_returns_error
def test_initial_recurring_transaction_missing_parameters
assert_raises ArgumentError do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(@amount, @credit_card,
+ @gateway.recurring(
+ @amount,
+ @credit_card,
periodicity: :monthly,
- initial_transaction: {})
+ initial_transaction: {}
+ )
end
end
end
@@ -475,9 +478,12 @@ def test_initial_recurring_transaction_missing_parameters
def test_initial_purchase_missing_amount
assert_raises ArgumentError do
assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
- @gateway.recurring(@amount, @credit_card,
+ @gateway.recurring(
+ @amount,
+ @credit_card,
periodicity: :monthly,
- initial_transaction: { amount: :purchase })
+ initial_transaction: { amount: :purchase }
+ )
end
end
end
@@ -1144,7 +1150,7 @@ def xpath_prefix_for_transaction_type(tx_type)
def threeds_xpath_for_extdata(attr_name, tx_type: 'Authorization')
xpath_prefix = xpath_prefix_for_transaction_type(tx_type)
- %(string(#{xpath_prefix}/PayData/ExtData[@Name='#{attr_name}']/@Value)")
+ %(string(#{xpath_prefix}/PayData/ExtData[@Name='#{attr_name}']/@Value))
end
def authorize_buyer_auth_result_path
diff --git a/test/unit/gateways/paymentez_test.rb b/test/unit/gateways/paymentez_test.rb
index b8b1924d096..c3e303be2e2 100644
--- a/test/unit/gateways/paymentez_test.rb
+++ b/test/unit/gateways/paymentez_test.rb
@@ -6,13 +6,15 @@ class PaymentezTest < Test::Unit::TestCase
def setup
@gateway = PaymentezGateway.new(application_code: 'foo', app_key: 'bar')
@credit_card = credit_card
- @elo_credit_card = credit_card('6362970000457013',
+ @elo_credit_card = credit_card(
+ '6362970000457013',
month: 10,
year: 2020,
first_name: 'John',
last_name: 'Smith',
verification_value: '737',
- brand: 'elo')
+ brand: 'elo'
+ )
@amount = 100
@options = {
@@ -28,8 +30,8 @@ def setup
@eci = '01'
@three_ds_v1_version = '1.0.2'
@three_ds_v2_version = '2.1.0'
- @three_ds_server_trans_id = 'three-ds-v2-trans-id'
@authentication_response_status = 'Y'
+ @directory_server_transaction_id = 'directory_server_transaction_id'
@three_ds_v1_mpi = {
cavv: @cavv,
@@ -42,8 +44,8 @@ def setup
cavv: @cavv,
eci: @eci,
version: @three_ds_v2_version,
- three_ds_server_trans_id: @three_ds_server_trans_id,
- authentication_response_status: @authentication_response_status
+ authentication_response_status: @authentication_response_status,
+ ds_transaction_id: @directory_server_transaction_id
}
end
@@ -57,6 +59,22 @@ def test_successful_purchase
assert response.test?
end
+ def test_rejected_purchase
+ @gateway.expects(:ssl_post).returns(purchase_rejected_status)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Fondos Insuficientes', response.message
+ end
+
+ def test_cancelled_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response_with_cancelled)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'ApprovedTimeOutReversal', response.message
+ end
+
def test_successful_purchase_with_elo
@gateway.expects(:ssl_post).returns(successful_purchase_with_elo_response)
@@ -87,7 +105,6 @@ def test_successful_purchase_with_token
response = @gateway.purchase(@amount, '123456789012345678901234567890', @options)
assert_success response
-
assert_equal 'PR-926', response.authorization
assert response.test?
end
@@ -116,7 +133,7 @@ def test_purchase_3ds2_mpi_fields
cavv: @cavv,
eci: @eci,
version: @three_ds_v2_version,
- reference_id: @three_ds_server_trans_id,
+ reference_id: @directory_server_transaction_id,
status: @authentication_response_status
}
@@ -189,13 +206,14 @@ def test_authorize_3ds1_mpi_fields
end
def test_authorize_3ds2_mpi_fields
+ @options.merge!(new_reference_id_field: true)
@options[:three_d_secure] = @three_ds_v2_mpi
expected_auth_data = {
cavv: @cavv,
eci: @eci,
version: @three_ds_v2_version,
- reference_id: @three_ds_server_trans_id,
+ reference_id: @directory_server_transaction_id,
status: @authentication_response_status
}
@@ -254,18 +272,32 @@ def test_successful_refund
response = @gateway.refund(nil, '1234', @options)
assert_success response
- assert response.test?
+ assert_equal 'Completed', response.message
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)
+ end.respond_with(pending_response_current_status_cancelled)
assert_success response
- assert_equal 'Completed', response.message
- assert response.test?
+ assert_equal 'Completed partial refunded with 1.9', response.message
+ end
+
+ def test_partial_refund_with_pending_request_status
+ response = stub_comms do
+ @gateway.refund(@amount, '1234', @options)
+ end.respond_with(pending_response_with_pending_request_status)
+ assert_success response
+ assert_equal 'Waiting gateway confirmation for partial refund with 17480.0', response.message
+ end
+
+ def test_duplicate_partial_refund
+ response = stub_comms do
+ @gateway.refund(@amount, '1234', @options)
+ end.respond_with(failed_pending_response_current_status)
+ assert_failure response
+
+ assert_equal 'Transaction already refunded', response.message
end
def test_failed_refund
@@ -288,7 +320,6 @@ def test_successful_void
def test_failed_void
@gateway.expects(:ssl_post).returns(failed_void_response)
-
response = @gateway.void('1234', @options)
assert_equal 'Invalid Status', response.message
assert_failure response
@@ -341,6 +372,18 @@ def test_scrub
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
+ def test_successful_inquire_with_transaction_id
+ response = stub_comms(@gateway, :ssl_get) do
+ @gateway.inquire('CI-635')
+ end.check_request do |method, _endpoint, _data, _headers|
+ assert_match('https://ccapi-stg.paymentez.com/v2/transaction/CI-635', method)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal 'CI-635', response.authorization
+ assert response.test?
+ end
+
private
def pre_scrubbed
@@ -396,6 +439,7 @@ def successful_purchase_response
{
"transaction": {
"status": "success",
+ "current_status": "APPROVED",
"payment_date": "2017-12-19T20:29:12.715",
"amount": 1,
"authorization_code": "123456",
@@ -423,6 +467,7 @@ def successful_purchase_with_elo_response
{
"transaction": {
"status": "success",
+ "current_status": "APPROVED",
"payment_date": "2019-03-06T16:47:13.430",
"amount": 1,
"authorization_code": "TEST00",
@@ -478,6 +523,7 @@ def successful_authorize_response
{
"transaction": {
"status": "success",
+ "current_status": "PENDING",
"payment_date": "2017-12-21T18:04:42",
"amount": 1,
"authorization_code": "487897",
@@ -507,6 +553,7 @@ def successful_authorize_with_elo_response
{
"transaction": {
"status": "success",
+ "current_status": "PENDING",
"payment_date": "2019-03-06T16:53:36.336",
"amount": 1,
"authorization_code": "TEST00",
@@ -567,6 +614,7 @@ def successful_capture_response
{
"transaction": {
"status": "success",
+ "current_status": "APPROVED",
"payment_date": "2017-12-21T18:04:42",
"amount": 1,
"authorization_code": "487897",
@@ -596,6 +644,7 @@ def successful_capture_with_elo_response
{
"transaction": {
"status": "success",
+ "current_status": "APPROVED",
"payment_date": "2019-03-06T16:53:36",
"amount": 1,
"authorization_code": "TEST00",
@@ -645,7 +694,7 @@ def failed_void_response
end
def successful_void_response_with_more_info
- '{"status": "success", "detail": "Completed", "transaction": {"carrier_code": "00", "message": "Reverse by mock"}}'
+ '{"status": "success", "detail": "Completed", "transaction": {"carrier_code": "00", "message": "Reverse by mock", "status_detail":7}}'
end
alias successful_refund_response successful_void_response
@@ -727,4 +776,24 @@ def crash_response