From 8f17326e9a9cf695e71f7976e787b0f526362689 Mon Sep 17 00:00:00 2001 From: Marvin Frederickson Date: Mon, 19 Feb 2024 20:46:26 -0500 Subject: [PATCH] merge staging branch into master branch (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sync up 20231110 (#4) * Reach: remove raise exception when stored-credentials can't be identified * Reach: fixing typo on store credentials uncheduled * Reach: refactor response methods and extra test for not paymentMethod * Revert "Reach: refactor response methods and extra test for not paymentMethod" This reverts commit e169025f2e1eb5ae7e4f5b52aea6ffa59b787572. * Revert "Reach: fixing typo on store credentials uncheduled" This reverts commit ad2e33306ebe0d9e4ff344c28217d9935924aedd. * Revert "Reach: remove raise exception when stored-credentials can't be identified" This reverts commit 4e1d3cd5ee9481bfcfde960b3d2d29b8f395f6a7. * Revert "Reach: remove raise exception when pymentMethod is not allowed" This reverts commit 7b973d9aef5133a12f33d251a35a57874cacb810. * Revert "Reach: using transaction token as ReferenceId on refunds" This reverts commit e40e1ee50e3687f5b123546dcf83141be1440073. * Reach: fixing store credentials and Exception issues Summary: ------------------------------ This PR solves 4 issues: 1) Remove exception for no allowed store credentials combination and leave the field empty. 2) Remove exception for not supported card brand and sent instead a not supported string. 3) Fixes a Typo on stored credentials code and test related with 'unscheduled' transactions. 4) Fixes and add the corresponding tests for 1 to 3. Remote Test: ------------------------------ Finished in 136.495605 seconds. 27 tests, 70 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 33.332219 seconds. 5419 tests, 76964 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 756 files inspected, no offenses detected * EBANX: add soft_descriptor field This field can be optionally passed in with the `creditcard` hash CER-317 * CommerceHub: Add Apple Pay and Google Pay (#4648) Summary: ------------------------------ In order to perform transactions using Apple Pay and Google Pay this commit adds the fields required and optionals to made succesful requests. Remote Tests: ------------------------------ Finished in 76.906747 seconds. 19 tests, 52 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 0.193742 seconds. 13 tests, 111 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop: ------------------------------ 756 files inspected, no offenses detected Co-authored-by: Gustavo Sanmartin Co-authored-by: Nick Ashton * Global Collect & Alelo: Fixing year dependent failing tests Summary ----------------------- Changes a couple of tests that depend on date year Closes #4665 Unit test ----------------------- Finished in 40.268913 seconds. 5424 tests, 77006 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop ----------------------- 756 files inspected, no offenses detected * Moneris: Add Google Pay Description ------------------------- Add google pay to moneris gateway Closes #4666 Unit test ------------------------- Finished in 32.996352 seconds. 5424 tests, 77006 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 164.38 tests/s, 2333.77 assertions/s Rubocop ------------------------- Running RuboCop... Inspecting 756 files 756 files inspected, no offenses detected * Element/Vantiv: Add google pay and apple pay support Summary: This PR updates element/vantiv gateway to support apple/google pay transactions Remote Tests Finished in 35.250506 seconds. 31 tests, 86 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 756 files inspected, no offenses detected * Reach: fix amount handling to work with cents properly (#4670) Summary: ------------------------------ Provides proper format handling on reach gateway to receive amount as cents on authorize/purchase and refund. Remote Test: ------------------------------ Finished in 105.613556 seconds. 28 tests, 70 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 30.429736 seconds. 5425 tests, 77010 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 756 files inspected, no offenses detected * GlobalCollect: Add transaction inquire request Get payment status by payment id. Unit: 44 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 41 tests, 108 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 97.561% passed * Stripe PI: Add Level 3 support Remote: 79 tests, 346 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 88.6076% passed Unit: 43 tests, 219 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Braintree: return additional processor response Adds the `additional_processor_response` to the transaction hash for unsuccessful transactions CER-284 LOCAL 5414 tests, 76942 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 756 files inspected, no offenses detected REMOTE 100 tests, 491 assertions, 4 failures, 4 errors, 0 pendings, 0 omissions, 0 notifications 92% passed These failures/errors are present on the master branch as well * Payeezy name from `billing_address` on `purchase` Allow for the `billing_address` name value to be used if the `name` value is blank for the payment method Unit: 45 tests, 206 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 45 tests, 180 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Stripe: add reverse_transfer to void transactions Both the void and refund methods call Stripe's /refunds endpoint, but the ability to sendoptions[:reverse_transfer] was not initially added to the void method CER-360 UNIT 5425 tests, 77010 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 756 files inspected, no offenses detected REMOTE 77 tests, 362 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Update Changelog for PR #4668 * GlobalCollect: fix inquire bug * Credorax: Support google pay and apple pay Summary: ------------------------------ Being able to use google pay and apple pay as payment methods by Credorax gateway Closes #4661 Remote Test: ------------------------------ Finished in 117.823171 seconds. 25 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 37.864568 seconds. 5416 tests, 76949 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 756 files inspected, no offenses detected * Plexo: add 5 credit card brands (#4652) This change adds 5 new credit card brands (passcard, edenred, anda, tarjeta-d, sodexo bins ) and allows plexo gateway to use it. Remote Tests: Finished in 45.329665 seconds. 28 tests, 50 assertions, 0 failures, 0 errors, 0 pendings, 3 omissions, 0 notifications 100% passed Unit Tests: Finished in 0.013674 seconds.d 20 tests, 115 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop 756 files inspected, no offenses detected Update CHANGELOG * Authorize.net: Google pay token support Added remote test cases to Authorize for google pay authorize GWI-404 Closes #4659 ................................................................................... Finished in 96.632847 seconds. ---------------------------------------------------------------------------------------------------------------------------------------------------- 83 tests, 297 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed ---------------------------------------------------------------------------------------------------------------------------------------------------- 0.86 tests/s, 3.07 assertions/s * Credorax: Add support for Network Tokens Summary: ------------------------------ Enable the Credorax gateway to support payments via network_token Closes #4679 Remote Test: ------------------------------ 51 tests, 163 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 76.4706% passed tests failures not related with the changes Unit Tests: ------------------------------ 26 tests, 132 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 756 files inspected, no offenses detected * Stripe PI: use MultiResponse in create_setup_intent This change makes it possible to access the result of the call to /payment_methods alongside the ultimate result of the call to /setup_intents CER-357 REMOTE 80 tests, 378 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed UNIT 5439 tests, 77065 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 756 files inspected, no offenses detected * Payeezy change `method` on capture (#4684) For Apple Pay transactions, the value for `method` is set to `3DS`, but when executing a `capture`, this value should be changed to `credit_card`. This differs from other use cases where the value provided on auth transactions should be the same one given for capture. Unit: 45 tests, 206 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Credorax: Update MIT logic Credorax is changing the requirements for MIT transactions in regards to NTIDs. Moving forward, all MIT transactions need to pass in the NTID if present. Additionally the NTID value is from the `Z50` flag and passed directly from the card scheme. Credorax notification: For any MIT transactions, this Trace ID must be stored from the original Card Holder initiated (CIT) transaction where the Card details were stored on file originally. When using Finaro gateway/acquiring this Trace ID is returned in the z50 parameter and must be sent in the g6 parameter for any subsequent MIT transactions, (if using an external token engine or your own token engine and the original CIT is not processed through Finaro, please liaise with your processor to obtain this Trace ID). Additionally, this original Card holder initiated transaction must be Fully authenticated with 3DSecure to comply with PSD2/SCA regulation in place. Please make sure that you comply with this for any customers saving their card details on file for subsequent MIT processed through Finaro ECS-2650 Test Summary The remote tests with stored creds now work but take ~17 minutes for me. Additionally there are some failing tests for me (on master as well). Can anyone else run these? * Adyen: Add support for `skip_mpi_data` flag CER-333 This change will allow for recurring payments with Apple Pay on Adyen by eliminating mpi data hash from request after initial payment. Unit: 104 tests, 528 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 131 tests, 440 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 90.8397% passed Local: 5430 tests, 77037 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Add Canadian Institution Numbers Adds two additional valid Canadian Institution Numbers: 618, 842 https://en.wikipedia.org/wiki/Routing_number_(Canada) CER-403 5439 tests, 77066 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 756 files inspected, no offenses detected * Payeezy: Handle nil and empty values for Apple Pay Payeezy support has indicated that passing empty or nil values in place of the `xid` and `cavv` may result in failed transactions for AMEX based AP tokens. They also informed us that the `eci_indicator` value should default to `5` instead of passing a nil value, following a similar pattern. This PR ignores empty `payment_cryptogram` and defaults the `eci_indicator` to `5` if that value is `nil`. Unit: 6 tests, 211 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Tns: update test URL Update test URL to be only secure.uat.tnspayments. Unit Test: 15 tests, 68 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * TrustCommerce: Update `authorization_from` to handle `store` response (#4691) Fix Store/Unstore features Remote tests -------------------------------------------------------------------------- 21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 85.7143% passed Failing tests not related with changes unit tests -------------------------------------------------------------------------- 20 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * TrustCommerce Verify feature added (#4692) Enable verify feature on TrustCommerce Gateway Remote tests -------------------------------------------------------------------------- 21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 85.7143% passed Failing tests not related with changes unit tests -------------------------------------------------------------------------- 22 tests, 72 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Rapyd: Add customer object to transactions (#4664) Description ------------------------- Rapyd Gateway send customer info on store transactions, with this commit it will be able to send customer on authorize and purchase transaction, when it use a US “PMT”s include the addresses object into the customer data in order to be able to perform US transactions properly. Unit test ------------------------- Finished in 0.123245 seconds. 22 tests, 103 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- Finished in 100.082672 seconds. 33 tests, 92 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 0.33 tests/s, 0.92 assertions/s Rubocop ------------------------- 756 files inspected, no offenses detected Co-authored-by: Javier Pedroza * CybersourceRest: Add new gateway with authorize and purchase Summary: ------------------------------ Adding CybersourceRest gateway with authorize and purchase calls. Closes #4690 GWI-474 Remote Test: ------------------------------ Finished in 3.6855 seconds. 6 tests, 17 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 35.528692 seconds. 5441 tests, 77085 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * CheckoutV2: Add store/unstore (#4677) Summary: ------------------------------ In order to use Third Party Vaulting (TPV), this commit adds store and unstore methods. For ApplePay (and NT in general) the verify method is used, to get a similar response, and formatted to be equal to the store response using the /instruments endpoint. Remote test ----------------------- Finished in 240.268913 seconds. 82 tests, 203 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 97.561% passed Unit test ----------------------- Finished in 0.268913 seconds. 52 tests, 289 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop ----------------------- 756 files inspected, no offenses detected Co-authored-by: Gustavo Sanmartin Co-authored-by: Nick Ashton * Revert "CheckoutV2: Add store/unstore (#4677)" (#4703) This reverts commit e769cdb908d4a8543bc68f13a4f7aa04c60daf59. * Moneris: Fix google pay (update apple pay) (#4689) Description ------------------------- Truncate google pay and apple pay order id to 100 (max length of characters that google pay / apple pay accepts) Unit test ------------------------- Finished in 37.884991 seconds. 5439 tests, 77066 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 143.57 tests/s, 2034.21 assertions/s Rubocop ------------------------- 756 files inspected, no offenses detected Co-authored-by: Luis * Litle: Add prelive url This commit adds a prelive URL to the Vantiv/Litle gateway. It relies on the existing url_override logic to set the prelive_url Test Summary Remote: 56 tests, 226 assertions, 13 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 76.7857% passed * CommerceHub: Adding changes for certification purposes (#4705) Summary: ------------------------------ Add changes to address some observations / improvements in the CommerceHub certification. * SER-494 / SER-498: setting and using order_id as reference * SER-495: use the correct end-point for verify * SER-496 / SER-497: get avs and cvv verification codes * SER-500: getting an error code from response Remote Test: ------------------------------ Finished in 108.050361 seconds. 23 tests, 63 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 46.103032 seconds. 5458 tests, 77155 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * CommerceHub: Fixing verify status and prevent tokenization (#4716) Summary: ------------------------------ * SER-495: Fix success detection for verify * SER-507: set default `create_token` for purchase and authorize transactions Remote Test: ------------------------------ Finished in 290.029817 seconds. 23 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 46.103032 seconds. 5458 tests, 77155 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Payeezy: Update Stored Credentials Payeezy requires sending original network transaction id to as cardbrand_original_transaction_id. Unit: 47 tests, 217 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 46 tests, 184 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Remove raise ArgumentError on get requests (#4714) The case for initiating a GET request raises a frivolous ArgumentError when the code already ignores any attempt to pass in a body to the request. * ChekoutV2:Add store/unstore (#4712) Summary: ------------------------------ In order to use Third Party Vaulting (TPV), this commit adds store and unstore methods. For apple pay/google pay the actual credentials could work, but for credit cards, and bank account the credentials required are secret_api_key and public_api_key. This commits also, refactor the initialize method removing the rescue ArgumentError, and modifing the commit method to use ssl_request. Remote test ----------------------- Finished in 164.163913 seconds. 88 tests, 216 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit test ----------------------- Finished in 0.046893 seconds. 53 tests, 297 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop ----------------------- 756 files inspected, no offenses detected Co-authored-by: Gustavo Sanmartin Co-authored-by: Nick Ashton * CybersourceREST - Refund | Credit Description ------------------------- This integration support the following payment operations: - Refund - Credit Closes #4700 Unit test ------------------------- Finished in 40.91494 seconds. 5454 tests, 77134 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 133.30 tests/s, 1885.23 assertions/s Rubocop ------------------------- 760 files inspected, no offenses detected GWI-471 * Payeezy: Ignore `xid` for AP Amex (#4721) Payeezy has stated the inclusion of `xid` values for AP (Amex underlying) transactions could result in failures. Add guard to ignore adding this field if the underlying is `american_express` Unit: 49 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 47 tests, 186 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * TrustCommerce Verify feature added (#4699) Enable verify feature on TrustCommerce Gateway Remote tests -------------------------------------------------------------------------- 21 tests, 74 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 85.7143% passed Failing tests not related with changes unit tests -------------------------------------------------------------------------- 22 tests, 72 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * CER-440 Add papypal_custom_field and paypal_description gateway specific fields to braintree_blue gateway. Local: 5455 tests, 77085 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications 99.6517% passed Unit: 94 tests, 207 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 101 tests, 493 assertions, 4 failures, 4 errors, 0 pendings, 0 omissions, 0 notifications 92.0792% passed * CER-460 Add descriptor phone number to blue_snap Local: 5457 tests, 77095 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications 99.6518% passed Unit: 45 tests, 269 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 53 tests, 171 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 83.0189% passed * Braintree: Update transaction hash method This PR is to update the transaction method to include the processor_authorization_code. * CyberSourceRest: Add apple pay, google pay Summary: ----------------------- In order to perform ApplePay and GooglePay transaction this commit, adds support. Closes #4708 Remote test ----------------------- Finished in 7.216327 seconds. 18 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit test ----------------------- Finished in 0.032725 seconds. 15 tests, 80 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop ----------------------- 760 files inspected, no offenses detected * CyberSourceRest: Add apple pay, google pay Summary: ----------------------- In order to perform ApplePay and GooglePay transaction this commit, adds support. Closes #4708 Remote test ----------------------- Finished in 7.216327 seconds. 18 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit test ----------------------- Finished in 0.032725 seconds. 15 tests, 80 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop ----------------------- 760 files inspected, no offenses detected * CybersourceREST - Void | Verify Description ------------------------- This integration support the following payment operations: Verify Void Closes #4695 Unit test ------------------------- Finished in 29.20384 seconds. 5468 tests, 77209 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 186.76 tests/s, 2641.23 assertions/s Rubocop ------------------------- Inspecting 760 files 760 files inspected, no offenses detected * CommerceHub: adjusting reference details (#4723) Summary: ------------------------------ Changes reference details to properly send `referenceTransactionDetails` on capture requests Remote Test: ------------------------------ Finished in 290.029817 seconds. 23 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 47.993895 seconds. 5463 tests, 77178 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Orbital: dismiss CardSecValInd restriction (#4724) GWI-567 Remote: 122 tests, 509 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit: 144 tests, 817 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Rubocop: 760 files inspected, no offenses detected * Credorax: Set default ECI values for token transactions Condition eci field depending on payment_method Closes #4693 * CyberSource Rest: Add ACH Support Adding ACH/Bank Accounts to CyberSource Rest Closes #4722 Unit test: 13 tests, 57 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test: 10 tests, 26 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed GWI-480 * CommerceHub: setting transactionReferenceId for refunds (#4727) Summary: ------------------------------ Updating the refund reference to only use referenceTransactionId Remote Test: ------------------------------ Finished in 291.397602 seconds. 23 tests, 64 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 95.6522% passed Unit Tests: ------------------------------ Finished in 37.637689 seconds. 5474 tests, 77230 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Cybersource REST: Adding capture request Summary: ------------------------------ Adding the capture functionality to the Cybersource REST gateway Closes #4726 Remote Test: ------------------------------ Finished in 25.504733 seconds. 25 tests, 89 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 39.743032 seconds. 5468 tests, 77209 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Paymentez: Add inquire by transaction_id Get payment status by Paymentez transaction id Unit: 30 tests, 127 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 34 tests, 73 assertions, 9 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 73.5294% passed ** These failures also existed on the main branch ** * Cybersource Rest - update message response on error Description ------------------------- Update message response on error in order to get a more redeable response GWI-571 Unit test ------------------------- Finished in 30.871357 seconds. 5476 tests, 77239 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 177.38 tests/s, 2501.96 assertions/s Rubocop ------------------------- 760 files inspected, no offenses detected * Ebanx: Add transaction inquire request Get transaction by authorization. Remote: 27 tests, 73 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96.2963% passed Unit: 20 tests, 89 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * CommerceHub: Update fields for transactions with sotred credentials (#4733) Description ------------------------- This commit add new fields for transactions with stored credentials options and remove the current referenceMerchantTransactionId in order to use referenceTransactionId [SER-504](https://spreedly.atlassian.net/browse/SER-504) [SER-536](https://spreedly.atlassian.net/browse/SER-536) Unit test ------------------------- Finished in 0.01392 seconds. 22 tests, 147 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 1580.46 tests/s, 10560.34 assertions/s Remote test ------------------------- Finished in 296.371956 seconds. 24 tests, 63 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 0.08 tests/s, 0.21 assertions/s Rubocop ------------------------- 760 files inspected, no offenses detected Co-authored-by: Javier Pedroza * Ebanx: Add support of Elo & Hipercard For all credit card transactions Ebanx only requires payment_type_code to be 'creditcard' no matter the card. This removes the need of specifiying the card brand in Ebanx transaction. Unit: 19 tests, 84 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 28 tests, 74 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96.4286% passed * Checkout_v2: Add idempotency key support This PR is to add the support for an optional idempotency key through the header during requests and should be available to all actions 'purchase, authorize, and etc'. I did note that the failing remote tests were sending back 401 unauthorize even when on the latest upstream master. Test results below: Local: 5469 tests, 77162 assertions, 0 failures, 19 errors, 0 pendings, 0 omissions, 0 notifications 99.6526% passed Unit: 54 tests, 300 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 89 tests, 213 assertions, 4 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 95.5056% passed * Adyen: add support for shopper_statement field for capture action * Checkout_v2: update idmepotency_key names * * Payeezy: Enable external 3DS Summary: ------------------------------ This PR includes the fields and logic required to send external 3ds data for purchases and auths. Closes #4715 Test Execution: ------------------------------ Unit test Finished in 0.067186 seconds. 46 tests, 211 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test Finished in 140.523393 seconds. 48 tests, 194 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Failures not related with the added code RuboCop: ------------------------------ 760 files inspected, no offenses detected * Shift4: Fix `Content-type` value (#4740) Change `Content-type` value to `applicaiton/json` instead of xml Unit: 23 tests, 147 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 24 tests, 56 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 91.6667% passed * Ebanx: Remove default email ECS-2829 Ebanx requires that merchants pass in an email in order to complete a transaction. Previously, ActiveMerchant was sending in a default email if one was not provided which causes Ebanx's fraud detection to mark these transactions as failed for fraud. This incorrect failure message has led to merchant frustration since they could not quickly know the root of the problem Test Summary Remote: 32 tests, 88 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96.875% passed * CyberSourceRest: Add stored credentials support Description ------------------------- This commit adds support for stored credentials to the CyberSourceRest gateway and according to their docs CyberSource has two type of flows [initial](https://developer.cybersource.com/docs/cybs/en-us/payments/developer/ctv/rest/payments/credentials-intro/credentials-maxtrix/credentials-maxtrix-initial.html) and [subsequent](https://developer.cybersource.com/docs/cybs/en-us/payments/developer/ctv/rest/payments/credentials-matrix/credentials-matrix-sub.html) Closes #4707 Unit test ------------------------- Finished in 0.025301 seconds. 18 tests, 97 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 711.43 tests/s, 3833.84 assertions/s Remote test ------------------------- Finished in 25.932718 seconds. 29 tests, 107 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 1.12 tests/s, 4.13 assertions/s Rubocop ------------------------- 760 files inspected, no offenses detected * Payeezy: Add `last_name` for `add_network_tokenization` This change updates the `add_network_tokenization` method to include the `last_name` in the `cardholder_name` value when getting the name from a payment method Closes #4743 Unit: 49 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 50 tests, 201 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Stripe PI: tokenize PM for verify ECS-2867 For the Stripe PI gateway, when a merchant runs a verify they want to get back the resulting card information that Stripe provides. In off_session cases we are not tokenizing the card at Stripe which prevents us from getting back valuable card details. This commit updates the logic to always get the card details back from Stripe on verify transactions. This commit also improves the resiliency of Stripe PI remote tests by dynamically creating a customer object before running the remote tests so that they do not error out with a too many payment methods for customer message. Test Summary Remote: 80 tests, 380 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Kushki Gateway: Add support for the months and deferred fields * Borgun: Update TrCurrencyExponent ECS-2861 A merchant using the Borgun gateway reported that when a user was completing the challenge, the gateway was displaying a value 100 times greater than what was requested. This casused the ccardholders to stop the 3DS flow and abandon the cart. After reaching out to the Borgun gateway they explained that the ISK currency on Borgun is sometimes a 0 decimal currency and sometimes a 2 decimal currency. The explanation given via email is that we must provide the TrCurrencyExponent of 2 when utilizing the 3DS flow but not on the finish sale portion. Remote: 22 tests, 47 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 90.9091% passed Note: these 2 tests fail on master * CyberSourceRest: Add gateway specific fields handling Summary: In order to handle several gateway specific fields this commit add the following ones in the cybersource rest gateway file - ignore_avs - ignore_cvv - mdd_fields - reconciliation_id - customer_id - zero_amount_verify - sec_code Closes #4746 Remote Test: Finished in 36.507289 seconds. 35 tests, 108 assertions, 0 failures, 0 errors, 0 pendings,0 omissions, 0 notifications 100% passed Unit Tests: Finished in 0.06123 seconds. 2718 tests, 150 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: 760 files inspected, no offenses detected * IPG: Improve error handling Summary: ------------------------------ This change improves the amount of detail in the response message when the gateway responds with an error. Closes #4753 Remote Test: ------------------------------ Remote: 18 tests, 54 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 29.462929 seconds 5483 tests, 77277 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * * Shift4: Handle access token failed calls Summary: ------------------------------ Adding changes to handle failed calls to get the access token GWS-46 Closes #4745 Remote Test: ------------------------------ Finished in 172.659123 seconds. 24 tests, 56 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 91.6667% passed Unit Tests: ------------------------------ Finished in 40.296092 seconds. 5480 tests, 77260 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Bogus: Add verify, plus assoc. test * Checkout v2: Add Shipping Address Add shipping address local and remote tests Remove Marketplace object as not supported for Sandbox testing * Release 1.128.0 * Adyen: update selectedBrand mapping for Google Pay Adyen advised that `googlepay` is the correct value to send for `selectedBrand` CER-550 LOCAL 5498 tests, 77340 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected GATEWAY - UNIT TESTS 105 tests, 531 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed GATEWAY - REMOTE TESTS 132 tests, 443 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 90.9091% passed * Shift4: add vendorReference field This change maps `options[:order_id]` to Shift4's `vendorReference` field CER-563 LOCAL 5498 tests, 77341 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected GATEWAY UNIT TESTS 25 tests, 154 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed GATEWAY REMOTE TESTS 25 tests, 58 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 92% passed (These failures also exist on the master branch) * Litle update the successful_from method Add 001 and 010 to be considered successful responses for Litle. Remote 57 tests, 251 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit 58 tests, 255 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Improve error handling: OAuth ECS-2845 OAuth has become a standard authentication mechanism for many gateways in recent years however AM has not been updated to support error handling when a merchant passes incorrect details to the gateway. In non OAuth flows we would return a failed response. This commit now raises a new exception type indicating that the request failed at the OAuth stage rather than the transaction stage of a request Remote: 25 tests, 57 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 92% passed * Stripe PI: Add billing address when tokenizing for ApplePay and GooglePay This adds functionality to add the card's billing address to digital wallets ApplePay and GooglePay. The billing address is available on the result of the card tokenization and is saved to the created PaymentIntent. The remote test failures also exist on `master`. Test Summary Local: 5500 tests, 77348 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit: 83 tests, 368 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 93.9759% passed * Rexml is no longer a default gem in Ruby 3 (#3852) * Revert "Rexml is no longer a default gem in Ruby 3 (#3852)" (#4767) This reverts commit dad65a7cb70985e1b477231c4d7e33455d40e04c. It caused ci to fail due to a rexml issue. * Add rexml as a gem dependency (#4768) * Add rexml as a gem dependency Rexml is no longer included with Ruby 3+, we therefore need to add the dependency explicitely. * Remove garbage character from test file * Release v1.129.0 * Mit: Changed how the payload was sent to the gateway Closes #4655 * Revert "Mit: Changed how the payload was sent to the gateway" This reverts commit 6e3cd4b431fdd521e955d80fa3133f6dc2653899. * PayuLatam: The original method of surfacing error codes was redundant and didn't actually surface a network code that is particularly useful. This PR aims to fix that issue for failing transactions. * CyberSource: Handling Canadian bank accounts Summary: ------------------------------ Add changes to CyberSource to properly format canadian routing numbers when payment methods is bank account. GWS-48 Closes #4764 Remote Test: ------------------------------ Finished in 114.912426 seconds. 121 tests, 611 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 95.8678% passed *Notes on erros*: The test suite was ran with a single account rason for errors: - 2 errors correspondsto account not enabled for canadian bank transactions. - 2 errors correspond to outdated 3ds PAR values. - 1 error related with account not enabled for async. Unit Tests: ------------------------------ Finished in 41.573188 seconds. 5501 tests, 77346 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Update Changelog * CyberSource Rest: Fixing currency detection Summary: ------------------------------ Fix bug on Cybersource Rest to use other currencies different than USD. Closes #4777 Remote Test: ------------------------------ Finished in 46.080483 seconds. 43 tests, 141 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 33.72359 seconds. 5506 tests, 77384 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected * Cybersource: Add business rules for NT ECS-2849 A previous commit from 2015 restricted the ability to pass business rules such as `ignoreAVSResult` and `ignoreCVResult` on API requests with NetworkTokenization cards. Merchants are now asking for this to be allowed on requests with payment methods such as NT/AP/GP and the remote tests seem to indicate we can add these fields for these types of payment methods. Remote CBYS SOAP: 119 tests, 607 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 97.479% passed Remote CYBS Rest: 46 tests, 152 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Adyen: Update Mastercard error messaging Adyen error messaging uses the generic refusalReasonRaw field as part of the response message. Adyen offers a more detailed Mastercard-specific field called merchantAdviceCode that should be present for failed Mastercard transactions. Adyen error messaging now checks for the merchantAdviceCode first. If it is not present (i.e. this is not a Mastercard transaction or it is a Mastercard transaction and this field is missing for some reason) then the default refusalReasonRaw field is used (like previous functionality). ECS-2767 Unit: 107 tests, 539 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 132 tests, 443 assertions, 12 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 90.9091% passed *These 12 tests fail on master Closes #4770 * Authorize.net: update mapping for billing address phone number Adds a bit of logic to the Authorize.net gateway so that phone number can be passed via `billing_address[phone_number]` in addition to `billing_address[phone]` This is similar to #4138 CER-590 LOCAL 5503 tests, 77374 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed UNIT 121 tests, 681 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed REMOTE 84 tests, 301 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Braintree: update mapping for billing address phone number Adds a bit of logic to the Braintree gateway so that phone number can be passed via billing_address[phone_number] in addition to billing_address[phone] This is similar to #4138 CER-603 LOCAL 5505 tests, 77373 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected UNIT 94 tests, 207 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed REMOTE 103 tests, 550 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * CommerceHub: Enabling multi-use public key encryption (#4771) Summary: ------------------------------ Changes the payment method so is possible to send an encrypted credit card following the CommerceHub Multi-User public key methodology. SER-555 Note on failing test: You need the proper account permissions and credentials to use the encrypted credit card. Remote Test: ------------------------------ Finished in 288.325843 seconds. 25 tests, 63 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: ------------------------------ Finished in 38.640945 seconds. 5506 tests, 77384 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected Co-authored-by: cristian Co-authored-by: Nick Ashton * Ogone: Enable 3ds Global for Ogone Gateway (#4776) Description ------------------------------------------- This commit introduces the support for 3DS Global payments using the Ogone Direct API through GlobalCollect. As Ogone and GlobalCollect share the same underlying payment service provider (PSP), Worldline, we can leverage the new attribute 'ogone_direct' to use the appropriate credentials and endpoint to connect with the Ogone Direct API. [SER-562](https://spreedly.atlassian.net/browse/SER-562) UNIT TEST ------------------------------------------- Finished in 0.253826 seconds. 44 tests, 225 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 173.35 tests/s, 886.43 assertions/s REMOTE TEST ------------------------------------------- Finished in 71.318909 seconds. 43 tests, 115 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 97.6744% passed 0.60 tests/s, 1.61 assertions/s Note: During testing, a single failure related to installment processing was identified with GlobalCollect. The error message "NO_ACQUIRER_CONFIGURED_FOR_INSTALLMENTS" I think that the issue may be related to GlobalCollect's account configuration, which is outside the scope of this update. RUBOCOP ------------------------------------------- 760 files inspected, no offenses detected Co-authored-by: Javier Pedroza * Borgun change default TrCurrencyExponent and MerchantReturnUrl (#4788) Borgun default TrCurrencyExponent to 2 for all 3DS txn and 0 for all else. Change MerchantReturnUrl to `redirect_url`. Unit: 11 tests, 56 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 22 tests, 43 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 77.2727% passed * Borgun support for GBP currency (#4789) Add support to Borgun for GPB based on [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) Unit: Remote: * Worlpay: Fix Google Pay Ensure that we don't send cardHolderName if empty and that Google Pay and Apple Pay fall into the network tokenization code path and not the credit card path. Remote Tests: 100 tests, 416 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests: 107 tests, 633 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Stripe PI: Update paramters for creating of customer Start sending address, shipping, phone and email when creating a customer. Remote Tests 84 tests, 396 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests 42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Revert "Stripe PI: Update paramters for creating of customer" This reverts commit 46f7bbc8b7b6afa16f26b6cc12f7c13b4d1d3ea3. * Cybersource: auto void r230 ECS-2870 Cybersource transactions that fail with a reasonCode of `230` are in a state where the gateway advises the merchant to decline but has not declined it themselves. Instead the transaction is pending capture which can create a mismatch in reporting. This commit attempts to auto void transactions that have this response code and a flag that indicates the merchants would like to auto void these kinds of transactions. Remote: 121 tests, 611 assertions, 5 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 95.8678% passed 5 test failures on master as well * Redsys: Set appropriate request fields for stored credentials with CITs and MITs Following pre-determined guidance for CIT/MIT request fields for this gateway. We were getting it mostly right, but almost didn't count, so the `DS_MERCHANT_DIRECTPAYMENT=false` value was added for initial CITs. Both CITs and MITs should be indicated with the `stored_credential` field `recurring`, so as long as that happens, `DS_MERCHANT_COF_TYPE` should have the value as `R` in both transactions. An outstanding task is to pass `DS_MERCHANT_IDENTIFIER` as 'REQUIRED' for CITs. I'm currently blocked on the credentials for the Redsys sandbox environment (getting the error `SIS0042` - Signature calculation error). This means that I'm unable to confirm that Redsys indeed returns a `DS_MERCHANT_IDENTIFIER` value in their response, in order to pass in a future MIT. Test Summary Local: 5512 tests, 77418 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit: 35 tests, 122 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Stripe & Stripe PI: Validate API Key Stripe Unit: 145 tests, 765 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Stripe Remote: 77 tests, 362 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Stripe PI Unit: 42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Stripe PI Remote: 83 tests, 391 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Remove last validation for Stripe API Key * Add BIN for Maestro Adds one new BIN to the Maestro BIN list: 501623 CER-640 5514 tests, 77426 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected * DLocal: Add save field on card object * Add support for MsgSubID on PayPal Express requests (#4798) * Adds support for PayPal Express MsgSubID property * Removes unrequired PayPal changes for MsgSubID * Updates CHANGELOG with HEAD changes * Checkout_v2: use `credit_card?`, not case equality with `CreditCard` (#4803) Checkout_v2: use `credit_card?`, not case equality with `CreditCard` * Shift4: Enable general credit feature. (#4790) Summary:------------------------------ Enabling general credit feature Remote Test: ------------------------------ Finished in 171.436961 seconds. 28 tests, 63 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 92.8571% passed failing test not related to PR changes Unit Tests: ------------------------------ 26 tests, 163 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detected Co-authored-by: Nick Ashton * Release v1.130.0 * Redsys: Add supported countries This updates the list of countries that Redsys supports by adding France (FR), Great Britain (GB), Italy (IT), Poland (PL), and Portugal (PT) CER-643 5527 tests, 77497 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected * Authorize.net: truncate nameOnAccount for bank refunds The API specification requires that the string be no longer than 22 characters; refunds will fail if this limit is exceeded CER-670 REMOTE 85 tests, 304 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed UNIT 122 tests, 688 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed LOCAL 5525 tests, 77482 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected * Checkout: Add support for several customer data fields CER-595 Ran into a lot of tricky data collisions and had to do some finagling to make sure existing test cases would pass. I left open two possibilities for passing in the phone number depending on how users would like to pass it in: manually via options or via the phone number that’s attached to credit card billing data on the payment method. It’s possible to pay with a stored payment method which would be a `String`. In this case there’s no name data attached so added some guarding against NoMethodErrors that were resulting from trying to call payment_method.name. Remote Tests: 92 tests, 221 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96.7391% passed Unit Tests: 57 tests, 319 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Local Tests: 5516 tests, 77434 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Worldpay: check payment_method responds to payment_cryptogram and eci (#4812) * Release v1.131.0 * Stripe PI: Add new stored credential flag Stripe has a field called `stored_credential_transaction_type` to assist merchants who vault outside of Stripe to recognize card on file transactions at Stripe. This field does require Stripe enabling your account with this field. The standard stored credential fields map to the various possibilities that Stripe makes available. Test Summary Remote: 87 tests, 409 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Commerce Hub - Add a couple of GSFs (#4786) Description ------------------------- Add: physical_goods_indicator maps to physicalGoodsIndicator inside of transactionDetails scheme_reference_transaction_id maps to schemeReferenceTransactionId inside of storedCredentials SER-501 Unit test ------------------------- Finished in 33.616793 seconds. 5511 tests, 77405 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 163.94 tests/s, 2302.57 assertions/s Rubocop ------------------------- Inspecting 760 files 760 files inspected, no offenses detected Co-authored-by: Luis Co-authored-by: Nick Ashton * Nuvei (formerly SafeCharge): Add customer details to credit action * IPG: Update live url to correct endpoint The live_url for IPG was likely always incorrect, this updates it to hit the actual endpoint. Also changes test data to prevent a scrub test failure. Remote (same failures on master): 18 tests, 40 assertions, 8 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications Unit: 27 tests, 123 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications * vPos: Adding Panal Credit Card type (#4814) Summary: ------------------------------ Add the panal credit card type and enables it for vPos gateway Unit Tests: ------------------------------ Finished in 38.864306 seconds. 5542 tests, 77546 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed RuboCop: ------------------------------ 760 files inspected, no offenses detectednitial commit * Stripe PI: Update parameters for creation of customer Start sending address, shipping, phone and email when creating a customer. Remote Tests 84 tests, 396 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit Tests 42 tests, 224 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * WorldPay: Update xml tag for Credit Cards Update the xml tag for Credit Cards to be CARD-SSL instead of being specific to card brand. Remote: 100 tests, 416 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit: 109 tests, 637 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * PaywayDotCom: Update live url The gateway has advised to direct traffic to their failover .net endpoint LOCAL 5542 tests, 77550 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected UNIT 16 tests, 64 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed REMOTE 16 tests, 43 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Stripe: Update login key validation Remote: Stripe PI 87 tests, 409 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Stripe 77 tests, 362 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Unit: Stripe PI 51 tests, 252 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Stripe 146 tests, 769 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * CheckoutV2: Parse AVS and CVV checks The CheckoutV2 gateway had logic in the response parsing method that would limit the scope of parsing to only authorize or purchase. This presents an issue for merchants using `verify-payment` or other methods that may have the AVS and CVV checks in the response. This commit also updates the AVS and CVV checks to use `dig` to safely try parsing out the values Test Summary Remote: 93 tests, 227 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields CER-666 CER-673 LOCAL 5547 tests, 77613 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected UNIT 56 tests, 454 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed REMOTE 51 tests, 184 assertions, 3 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 94.1176% passed *Test failures are related to Apple Pay and eCheck, and are also failing on master * Borgun: Update authorization_from & message_from Update authorization_from to return nil if the transaction failed or it is a 3DS transaction. Update message_from to return ErrorMessage if present. Unit: 12 tests, 66 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 22 tests, 43 assertions, 6 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 72.7273% passed * Kushki: Add Brazil as supported country Unit 17 tests, 109 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote 18 tests, 57 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Adyen: Add additional data for airline and lodging Description ------------------------- GWS-67 This commit adds additional data for Adyen in order to be able to send information regarding the airline and lodging. To send this new data, it sends fields additional_data_airline and additional_data_lodging as a GSF. Unit test ------------------------- Finished in 0.157969 seconds. 109 tests, 567 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 690.01 tests/s, 3589.31 assertions/s Remote test ------------------------- Finished in 176.357086 seconds. 134 tests, 447 assertions, 11 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 91.791% passed 0.76 tests/s, 2.53 assertions/s Rubocop ------------------------- 760 files inspected, no offenses detected * MIT: Changed how the payload was sent to the gateway Closes #4655 * Nuvie/SafeCharge: Add unreferenced refund field * CyberSource: include `paymentSolution` for ApplePay and GooglePay (#4835) See - https://developer.cybersource.com/content/dam/docs/cybs/en-us/apple-pay/developer/fdiglobal/rest/applepay.pdf - https://developer.cybersource.com/content/dam/docs/cybs/en-us/google-pay/developer/fdiglobal/rest/googlepay.pdf Schema: - https://developer.cybersource.com/library/documentation/dev_guides/Simple_Order_API_Clients/html/Topics/Using_XML1.htm - https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.211.xsd * Release v1.132.0 * Fix CHANGELOG after Version 1.132.0 (#4837) * CyberSource: remove credentials from tests (#4836) * Release v1.133.0 * Paysafe: Map order_id to merchantRefNum If options[:merchant_ref_num] is not supplied, options[:order_id] will be used as the fallback value CER-683 LOCAL 5559 tests, 77691 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed 760 files inspected, no offenses detected UNIT 18 tests, 89 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed REMOTE 33 tests, 82 assertions, 8 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 75.7576% passed * Stripe PI: Gate sending NTID Don't send NTID in add_stored_credential if post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_on_session' and setup_future_usage=off_session. * Update required Ruby version Updated required Ruby version to be 2.7 and Rubocop to 0.72.0. All unit tests and rubocop: 5532 tests, 77501 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed * Release v1.134.0 * Kushki: Enable 3ds2 Summary: Enable 3ds version 2 on the gateway above SER-625 Unit Test Finished in 0.019977 seconds. ------------------------------------------------------------------------------------------------------ 17 tests, 109 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed ------------------------------------------------------------------------------------------------------ Remote Test Finished in 82.28609 seconds. ------------------------------------------------------------------------------------------------------ 23 tests, 68 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed ------------------------------------------------------------------------------------------------------ * Paymen… * debug * puts instead of logging * fix typo * remove debug code --------- Co-authored-by: cristian Co-authored-by: Joe Reiff Co-authored-by: Gustavo Sanmartin Co-authored-by: Gustavo Sanmartin Co-authored-by: Nick Ashton Co-authored-by: Luis Co-authored-by: Johan Herrera Co-authored-by: Alma Malambo Co-authored-by: Edgar Villamarin Co-authored-by: Santiago Castillo Garzón Co-authored-by: aenand Co-authored-by: Rachel Kirk Co-authored-by: Javier Pedroza Co-authored-by: Javier Pedroza Co-authored-by: Luis Mario Urrea Murillo Co-authored-by: Gustavo Sanmartin Co-authored-by: yunnydang Co-authored-by: Willem Kappers Co-authored-by: Nicolas Maalouf Co-authored-by: Britney Smith Co-authored-by: Espen Antonsen Co-authored-by: Pierre Nespo Co-authored-by: Pierre Nespo Co-authored-by: Alejandro Flores Co-authored-by: kylene-spreedly Co-authored-by: Daniel Herzog Co-authored-by: Bertrand Braschi Co-authored-by: M. Ocaña Co-authored-by: David Perry Co-authored-by: Steve Hoeksema Co-authored-by: khoi_nguyen_deepstack Co-authored-by: aenand <89794007+aenand@users.noreply.github.com> Co-authored-by: Gustavo Sanmartin Co-authored-by: Luis Urrea Co-authored-by: Dustin A Haefele <45601251+DustinHaefele@users.noreply.github.com> --- .github/workflows/ruby-ci.yml | 44 + .github/workflows/stale.yml | 19 + .rubocop.yml | 10 +- .rubocop_todo.yml | 359 +- .travis.yml | 33 - CHANGELOG | 1301 ++++ CONTRIBUTING.md | 42 - Gemfile | 10 +- README.md | 26 +- Rakefile | 10 +- activemerchant.gemspec | 14 +- circle.yml | 2 +- gemfiles/Gemfile.rails60 | 3 + generators/gateway/templates/gateway.rb | 41 +- generators/gateway/templates/gateway_test.rb | 68 +- .../gateway/templates/remote_gateway_test.rb | 9 +- lib/active_merchant.rb | 4 +- lib/active_merchant/billing.rb | 1 + lib/active_merchant/billing/avs_result.rb | 28 +- lib/active_merchant/billing/base.rb | 13 - lib/active_merchant/billing/check.rb | 70 +- lib/active_merchant/billing/compatibility.rb | 9 +- lib/active_merchant/billing/credit_card.rb | 71 +- .../billing/credit_card_formatting.rb | 5 + .../billing/credit_card_methods.rb | 337 +- lib/active_merchant/billing/cvv_result.rb | 1 - lib/active_merchant/billing/gateway.rb | 93 +- lib/active_merchant/billing/gateways/adyen.rb | 718 ++- .../billing/gateways/airwallex.rb | 384 ++ lib/active_merchant/billing/gateways/alelo.rb | 274 + .../billing/gateways/allied_wallet.rb | 28 +- .../billing/gateways/authorize_net.rb | 391 +- .../billing/gateways/authorize_net_arb.rb | 51 +- .../billing/gateways/authorize_net_cim.rb | 99 +- .../billing/gateways/axcessms.rb | 32 +- .../billing/gateways/balanced.rb | 79 +- .../billing/gateways/bambora_apac.rb | 36 +- .../billing/gateways/bank_frick.rb | 26 +- .../billing/gateways/banwire.rb | 14 +- .../billing/gateways/barclaycard_smartpay.rb | 126 +- .../gateways/barclays_epdq_extra_plus.rb | 2 +- .../billing/gateways/be2bill.rb | 10 +- .../billing/gateways/beanstream.rb | 41 +- .../gateways/beanstream/beanstream_core.rb | 82 +- .../billing/gateways/beanstream_interac.rb | 2 +- .../billing/gateways/blue_pay.rb | 93 +- .../billing/gateways/blue_snap.rb | 252 +- lib/active_merchant/billing/gateways/bogus.rb | 44 +- .../billing/gateways/borgun.rb | 130 +- .../billing/gateways/bpoint.rb | 38 +- .../billing/gateways/braintree.rb | 2 +- .../gateways/braintree/braintree_common.rb | 10 +- .../billing/gateways/braintree/token_nonce.rb | 158 + .../billing/gateways/braintree_blue.rb | 702 ++- .../billing/gateways/bridge_pay.rb | 26 +- lib/active_merchant/billing/gateways/cams.rb | 20 +- .../billing/gateways/card_connect.rb | 61 +- .../billing/gateways/card_save.rb | 7 +- .../billing/gateways/card_stream.rb | 107 +- .../billing/gateways/cardknox.rb | 26 +- .../billing/gateways/cardprocess.rb | 6 +- .../billing/gateways/cashnet.rb | 40 +- lib/active_merchant/billing/gateways/cc5.rb | 18 +- .../billing/gateways/cecabank.rb | 247 +- .../gateways/cecabank/cecabank_common.rb | 36 + .../gateways/cecabank/cecabank_json.rb | 273 + .../billing/gateways/cecabank/cecabank_xml.rb | 220 + .../billing/gateways/cenpos.rb | 57 +- .../billing/gateways/checkout.rb | 16 +- .../billing/gateways/checkout_v2.rb | 585 +- .../billing/gateways/citrus_pay.rb | 3 +- .../billing/gateways/clearhaus.rb | 65 +- .../billing/gateways/commerce_hub.rb | 370 ++ .../billing/gateways/commercegate.rb | 8 +- .../billing/gateways/conekta.rb | 14 +- .../billing/gateways/creditcall.rb | 35 +- .../billing/gateways/credorax.rb | 272 +- .../billing/gateways/ct_payment.rb | 29 +- lib/active_merchant/billing/gateways/culqi.rb | 58 +- .../billing/gateways/cyber_source.rb | 788 ++- .../cyber_source/cyber_source_common.rb | 36 + .../billing/gateways/cyber_source_rest.rb | 454 ++ .../billing/gateways/d_local.rb | 179 +- .../billing/gateways/data_cash.rb | 75 +- .../billing/gateways/decidir.rb | 384 ++ .../billing/gateways/decidir_plus.rb | 344 ++ .../billing/gateways/deepstack.rb | 382 ++ lib/active_merchant/billing/gateways/dibs.rb | 20 +- .../billing/gateways/digitzs.rb | 19 +- lib/active_merchant/billing/gateways/ebanx.rb | 164 +- .../billing/gateways/efsnet.rb | 76 +- .../billing/gateways/elavon.rb | 538 +- .../billing/gateways/element.rb | 135 +- lib/active_merchant/billing/gateways/epay.rb | 109 +- .../billing/gateways/evo_ca.rb | 29 +- lib/active_merchant/billing/gateways/eway.rb | 20 +- .../billing/gateways/eway_managed.rb | 115 +- .../billing/gateways/eway_rapid.rb | 111 +- lib/active_merchant/billing/gateways/exact.rb | 47 +- lib/active_merchant/billing/gateways/ezic.rb | 18 +- .../billing/gateways/fat_zebra.rb | 49 +- .../billing/gateways/federated_canada.rb | 17 +- .../billing/gateways/finansbank.rb | 6 +- .../billing/gateways/first_giving.rb | 9 +- .../billing/gateways/first_pay.rb | 14 +- .../billing/gateways/firstdata_e4.rb | 81 +- .../billing/gateways/firstdata_e4_v27.rb | 86 +- .../billing/gateways/flo2cash.rb | 48 +- .../billing/gateways/flo2cash_simple.rb | 2 +- lib/active_merchant/billing/gateways/forte.rb | 60 +- .../billing/gateways/garanti.rb | 31 +- .../billing/gateways/global_collect.rb | 438 +- .../billing/gateways/global_transport.rb | 17 +- lib/active_merchant/billing/gateways/hdfc.rb | 45 +- lib/active_merchant/billing/gateways/hps.rb | 263 +- .../billing/gateways/iats_payments.rb | 79 +- .../gateways/in_context_paypal_express.rb | 2 +- .../billing/gateways/inspire.rb | 52 +- .../billing/gateways/instapay.rb | 17 +- lib/active_merchant/billing/gateways/ipg.rb | 425 ++ lib/active_merchant/billing/gateways/ipp.rb | 18 +- .../billing/gateways/iridium.rb | 77 +- .../billing/gateways/itransact.rb | 27 +- lib/active_merchant/billing/gateways/iveri.rb | 85 +- .../billing/gateways/ixopay.rb | 320 + .../billing/gateways/jetpay.rb | 29 +- .../billing/gateways/jetpay_v2.rb | 33 +- .../billing/gateways/komoju.rb | 2 +- .../billing/gateways/kushki.rb | 174 +- .../billing/gateways/latitude19.rb | 39 +- .../billing/gateways/linkpoint.rb | 147 +- lib/active_merchant/billing/gateways/litle.rb | 226 +- .../billing/gateways/mastercard.rb | 63 +- .../billing/gateways/maxipago.rb | 4 +- .../billing/gateways/mercado_pago.rb | 107 +- .../billing/gateways/merchant_e_solutions.rb | 86 +- .../billing/gateways/merchant_one.rb | 17 +- .../billing/gateways/merchant_partners.rb | 38 +- .../billing/gateways/merchant_ware.rb | 47 +- .../gateways/merchant_ware_version_four.rb | 37 +- .../billing/gateways/merchant_warrior.rb | 54 +- .../billing/gateways/mercury.rb | 48 +- .../billing/gateways/metrics_global.rb | 62 +- .../billing/gateways/micropayment.rb | 23 +- lib/active_merchant/billing/gateways/migs.rb | 51 +- .../billing/gateways/migs/migs_codes.rb | 14 +- lib/active_merchant/billing/gateways/mit.rb | 260 + .../billing/gateways/modern_payments_cim.rb | 40 +- lib/active_merchant/billing/gateways/moka.rb | 290 + lib/active_merchant/billing/gateways/monei.rb | 393 +- .../billing/gateways/moneris.rb | 221 +- .../billing/gateways/moneris_us.rb | 352 -- .../billing/gateways/money_movers.rb | 17 +- .../billing/gateways/mundipagg.rb | 128 +- .../billing/gateways/nab_transact.rb | 48 +- .../billing/gateways/ncr_secure_pay.rb | 22 +- .../billing/gateways/net_registry.rb | 21 +- .../billing/gateways/netaxept.rb | 24 +- .../billing/gateways/netbanx.rb | 174 +- .../billing/gateways/netbilling.rb | 43 +- .../billing/gateways/netpay.rb | 9 +- .../billing/gateways/network_merchants.rb | 21 +- lib/active_merchant/billing/gateways/nmi.rb | 134 +- lib/active_merchant/billing/gateways/ogone.rb | 99 +- lib/active_merchant/billing/gateways/omise.rb | 31 +- .../billing/gateways/openpay.rb | 50 +- lib/active_merchant/billing/gateways/opp.rb | 88 +- .../billing/gateways/optimal_payment.rb | 91 +- .../billing/gateways/orbital.rb | 1003 ++-- .../orbital/orbital_soft_descriptors.rb | 8 +- .../billing/gateways/pac_net_raven.rb | 44 +- .../billing/gateways/pagarme.rb | 39 +- .../billing/gateways/pago_facil.rb | 10 +- .../billing/gateways/pay_arc.rb | 392 ++ .../billing/gateways/pay_conex.rb | 34 +- .../billing/gateways/pay_gate_xml.rb | 67 +- .../billing/gateways/pay_hub.rb | 20 +- .../billing/gateways/pay_junction.rb | 66 +- .../billing/gateways/pay_junction_v2.rb | 54 +- .../billing/gateways/pay_secure.rb | 25 +- .../billing/gateways/pay_trace.rb | 459 ++ .../billing/gateways/paybox_direct.rb | 104 +- .../billing/gateways/payeezy.rb | 169 +- lib/active_merchant/billing/gateways/payex.rb | 49 +- .../billing/gateways/payflow.rb | 196 +- .../gateways/payflow/payflow_common_api.rb | 34 +- .../payflow/payflow_express_response.rb | 3 +- .../billing/gateways/payflow_express.rb | 8 +- .../billing/gateways/payflow_uk.rb | 2 +- .../billing/gateways/payment_express.rb | 48 +- .../billing/gateways/paymentez.rb | 113 +- .../billing/gateways/paymill.rb | 35 +- .../billing/gateways/paypal.rb | 29 +- .../gateways/paypal/paypal_common_api.rb | 3 +- .../paypal/paypal_express_response.rb | 4 + .../billing/gateways/paypal_ca.rb | 2 +- .../billing/gateways/paypal_digital_goods.rb | 2 +- .../billing/gateways/paypal_express.rb | 5 +- .../billing/gateways/paypal_express_common.rb | 2 +- .../billing/gateways/paysafe.rb | 421 ++ .../billing/gateways/payscout.rb | 21 +- .../billing/gateways/paystation.rb | 19 +- .../billing/gateways/payu_in.rb | 39 +- .../billing/gateways/payu_latam.rb | 113 +- .../billing/gateways/payway.rb | 48 +- .../billing/gateways/payway_dot_com.rb | 253 + lib/active_merchant/billing/gateways/pin.rb | 81 +- lib/active_merchant/billing/gateways/plexo.rb | 308 + .../billing/gateways/plugnpay.rb | 45 +- .../billing/gateways/priority.rb | 392 ++ .../billing/gateways/pro_pay.rb | 27 +- .../billing/gateways/psigate.rb | 62 +- .../billing/gateways/psl_card.rb | 19 +- lib/active_merchant/billing/gateways/qbms.rb | 55 +- .../billing/gateways/quantum.rb | 24 +- .../billing/gateways/quickbooks.rb | 168 +- .../billing/gateways/quickpay.rb | 1 - .../gateways/quickpay/quickpay_common.rb | 196 +- .../billing/gateways/quickpay/quickpay_v10.rb | 46 +- .../gateways/quickpay/quickpay_v4to7.rb | 10 +- .../billing/gateways/qvalent.rb | 90 +- lib/active_merchant/billing/gateways/rapyd.rb | 393 ++ lib/active_merchant/billing/gateways/reach.rb | 284 + .../billing/gateways/realex.rb | 101 +- .../billing/gateways/redsys.rb | 333 +- lib/active_merchant/billing/gateways/s5.rb | 19 +- .../billing/gateways/safe_charge.rb | 105 +- lib/active_merchant/billing/gateways/sage.rb | 46 +- .../billing/gateways/sage_pay.rb | 118 +- .../billing/gateways/sallie_mae.rb | 11 +- .../billing/gateways/secure_net.rb | 49 +- .../billing/gateways/secure_pay.rb | 61 +- .../billing/gateways/secure_pay_au.rb | 46 +- .../billing/gateways/secure_pay_tech.rb | 13 +- .../billing/gateways/securion_pay.rb | 79 +- .../billing/gateways/shift4.rb | 345 ++ .../billing/gateways/shift4_v2.rb | 58 + .../billing/gateways/simetrik.rb | 374 ++ .../billing/gateways/skip_jack.rb | 42 +- .../billing/gateways/smart_ps.rb | 62 +- .../billing/gateways/so_easy_pay.rb | 54 +- .../billing/gateways/spreedly_core.rb | 108 +- .../billing/gateways/stripe.rb | 289 +- .../gateways/stripe_payment_intents.rb | 651 ++ .../billing/gateways/sum_up.rb | 205 + .../billing/gateways/swipe_checkout.rb | 17 +- lib/active_merchant/billing/gateways/telr.rb | 35 +- lib/active_merchant/billing/gateways/tns.rb | 15 +- .../billing/gateways/trans_first.rb | 19 +- .../trans_first_transaction_express.rb | 56 +- .../billing/gateways/transact_pro.rb | 28 +- .../billing/gateways/transax.rb | 3 +- .../billing/gateways/trexle.rb | 7 +- .../billing/gateways/trust_commerce.rb | 141 +- .../billing/gateways/usa_epay.rb | 3 +- .../billing/gateways/usa_epay_advanced.rb | 373 +- .../billing/gateways/usa_epay_transaction.rb | 121 +- .../billing/gateways/vanco/vanco.rb | 27 +- .../billing/gateways/vanco/vanco_common.rb | 422 +- .../billing/gateways/vanco/vanco_nvp.rb | 43 +- .../billing/gateways/verifi.rb | 31 +- .../billing/gateways/viaklix.rb | 27 +- .../billing/gateways/visanet_peru.rb | 81 +- lib/active_merchant/billing/gateways/vpos.rb | 223 + .../billing/gateways/webpay.rb | 8 +- lib/active_merchant/billing/gateways/wepay.rb | 30 +- .../billing/gateways/wirecard.rb | 36 +- lib/active_merchant/billing/gateways/wompi.rb | 197 + .../billing/gateways/world_net.rb | 71 +- .../billing/gateways/worldpay.rb | 861 ++- .../gateways/worldpay_online_payments.rb | 105 +- .../billing/gateways/worldpay_us.rb | 32 +- lib/active_merchant/billing/gateways/xpay.rb | 135 + .../network_tokenization_credit_card.rb | 4 +- lib/active_merchant/billing/response.rb | 31 +- .../billing/three_d_secure_eci_mapper.rb | 27 + lib/active_merchant/connection.rb | 29 +- lib/active_merchant/country.rb | 5 +- lib/active_merchant/errors.rb | 17 +- .../net_http_ssl_connection.rb | 1 + .../network_connection_retries.rb | 32 +- lib/active_merchant/post_data.rb | 5 +- lib/active_merchant/posts_data.rb | 4 +- lib/active_merchant/version.rb | 2 +- lib/certs/cacert.pem | 3948 +++++------- lib/support/gateway_support.rb | 2 +- lib/support/ssl_verify.rb | 18 +- lib/support/ssl_version.rb | 17 +- test/comm_stub.rb | 26 +- test/fixtures.yml | 255 +- test/remote/gateways/remote_adyen_test.rb | 1367 ++++- test/remote/gateways/remote_airwallex_test.rb | 260 + test/remote/gateways/remote_alelo_test.rb | 204 + .../remote_alelo_test_certification.rb | 136 + .../gateways/remote_allied_wallet_test.rb | 7 +- .../remote_authorize_net_apple_pay_test.rb | 8 +- .../gateways/remote_authorize_net_arb_test.rb | 26 +- .../gateways/remote_authorize_net_cim_test.rb | 519 +- .../gateways/remote_authorize_net_test.rb | 284 +- test/remote/gateways/remote_axcessms_test.rb | 16 +- test/remote/gateways/remote_balanced_test.rb | 2 +- .../gateways/remote_bambora_apac_test.rb | 6 +- .../remote/gateways/remote_bank_frick_test.rb | 6 +- test/remote/gateways/remote_banwire_test.rb | 13 +- .../remote_barclaycard_smartpay_test.rb | 265 +- .../remote_barclays_epdq_extra_plus_test.rb | 188 +- test/remote/gateways/remote_be2bill_test.rb | 18 +- .../remote_beanstream_interac_test.rb | 41 +- .../remote/gateways/remote_beanstream_test.rb | 133 +- test/remote/gateways/remote_blue_pay_test.rb | 74 +- test/remote/gateways/remote_blue_snap_test.rb | 331 +- test/remote/gateways/remote_borgun_test.rb | 69 +- test/remote/gateways/remote_bpoint_test.rb | 14 +- .../gateways/remote_braintree_blue_test.rb | 782 ++- .../gateways/remote_braintree_orange_test.rb | 27 +- .../remote_braintree_token_nonce_test.rb | 92 + .../remote/gateways/remote_bridge_pay_test.rb | 21 +- test/remote/gateways/remote_cams_test.rb | 4 +- .../gateways/remote_card_connect_test.rb | 161 +- test/remote/gateways/remote_card_save_test.rb | 21 +- .../gateways/remote_card_stream_test.rb | 229 +- test/remote/gateways/remote_cardknox_test.rb | 16 +- .../gateways/remote_cardprocess_test.rb | 8 +- test/remote/gateways/remote_cashnet_test.rb | 27 +- .../remote_cecabank_rest_json_test.rb | 170 + test/remote/gateways/remote_cecabank_test.rb | 27 +- test/remote/gateways/remote_cenpos_test.rb | 32 +- test/remote/gateways/remote_checkout_test.rb | 13 +- .../gateways/remote_checkout_v2_test.rb | 778 ++- .../remote/gateways/remote_citrus_pay_test.rb | 9 +- test/remote/gateways/remote_clearhaus_test.rb | 15 +- .../gateways/remote_commerce_hub_test.rb | 291 + .../gateways/remote_commercegate_test.rb | 2 +- test/remote/gateways/remote_conekta_test.rb | 37 +- .../remote/gateways/remote_creditcall_test.rb | 4 +- test/remote/gateways/remote_credorax_test.rb | 480 +- .../remote_ct_payment_certification_test.rb | 1 - .../remote/gateways/remote_ct_payment_test.rb | 7 +- test/remote/gateways/remote_culqi_test.rb | 4 +- .../gateways/remote_cyber_source_rest_test.rb | 515 ++ .../gateways/remote_cyber_source_test.rb | 1177 +++- test/remote/gateways/remote_d_local_test.rb | 194 +- test/remote/gateways/remote_data_cash_test.rb | 85 +- .../gateways/remote_decidir_plus_test.rb | 334 + test/remote/gateways/remote_decidir_test.rb | 351 ++ test/remote/gateways/remote_deepstack_test.rb | 230 + test/remote/gateways/remote_dibs_test.rb | 10 +- test/remote/gateways/remote_digitzs_test.rb | 9 +- test/remote/gateways/remote_ebanx_test.rb | 153 +- test/remote/gateways/remote_efsnet_test.rb | 10 +- test/remote/gateways/remote_elavon_test.rb | 331 +- test/remote/gateways/remote_element_test.rb | 191 +- test/remote/gateways/remote_epay_test.rb | 85 +- test/remote/gateways/remote_evo_ca_test.rb | 18 +- .../gateways/remote_eway_managed_test.rb | 18 +- .../remote/gateways/remote_eway_rapid_test.rb | 259 +- test/remote/gateways/remote_eway_test.rb | 24 +- test/remote/gateways/remote_exact_test.rb | 10 +- test/remote/gateways/remote_ezic_test.rb | 4 +- test/remote/gateways/remote_fat_zebra_test.rb | 75 +- .../gateways/remote_federated_canada_test.rb | 13 +- .../remote/gateways/remote_finansbank_test.rb | 14 +- .../gateways/remote_first_giving_test.rb | 9 +- test/remote/gateways/remote_first_pay_test.rb | 2 +- .../gateways/remote_firstdata_e4_test.rb | 20 +- .../gateways/remote_firstdata_e4_v27_test.rb | 21 +- test/remote/gateways/remote_forte_test.rb | 61 +- test/remote/gateways/remote_garanti_test.rb | 17 +- .../gateways/remote_global_collect_test.rb | 509 +- .../gateways/remote_global_transport_test.rb | 3 +- test/remote/gateways/remote_hdfc_test.rb | 16 +- test/remote/gateways/remote_hps_test.rb | 366 +- .../gateways/remote_iats_payments_test.rb | 50 +- test/remote/gateways/remote_inspire_test.rb | 31 +- test/remote/gateways/remote_instapay_test.rb | 17 +- test/remote/gateways/remote_ipg_test.rb | 187 + test/remote/gateways/remote_ipp_test.rb | 2 +- test/remote/gateways/remote_iridium_test.rb | 38 +- test/remote/gateways/remote_itransact_test.rb | 15 +- test/remote/gateways/remote_iveri_test.rb | 49 +- test/remote/gateways/remote_ixopay_test.rb | 248 + test/remote/gateways/remote_jetpay_test.rb | 25 +- .../remote_jetpay_v2_certification_test.rb | 72 +- test/remote/gateways/remote_jetpay_v2_test.rb | 31 +- test/remote/gateways/remote_komoju_test.rb | 16 +- test/remote/gateways/remote_kushki_test.rb | 274 +- test/remote/gateways/remote_linkpoint_test.rb | 22 +- .../remote_litle_certification_test.rb | 802 +-- test/remote/gateways/remote_litle_test.rb | 367 +- .../gateways/remote_mercado_pago_test.rb | 189 +- .../remote_merchant_e_solutions_test.rb | 122 +- .../gateways/remote_merchant_one_test.rb | 13 +- .../gateways/remote_merchant_ware_test.rb | 14 +- .../remote_merchant_ware_version_four_test.rb | 22 +- .../gateways/remote_merchant_warrior_test.rb | 90 +- .../remote_mercury_certification_test.rb | 38 +- test/remote/gateways/remote_mercury_test.rb | 34 +- .../gateways/remote_metrics_global_test.rb | 40 +- .../gateways/remote_micropayment_test.rb | 2 +- test/remote/gateways/remote_migs_test.rb | 71 +- test/remote/gateways/remote_mit_test.rb | 129 + .../remote_modern_payments_cim_test.rb | 11 +- .../gateways/remote_modern_payments_test.rb | 13 +- test/remote/gateways/remote_moka_test.rb | 274 + test/remote/gateways/remote_monei_test.rb | 212 +- test/remote/gateways/remote_moneris_test.rb | 397 +- .../remote/gateways/remote_moneris_us_test.rb | 261 - .../gateways/remote_money_movers_test.rb | 12 +- test/remote/gateways/remote_mundipagg_test.rb | 298 +- .../gateways/remote_nab_transact_test.rb | 40 +- .../gateways/remote_ncr_secure_pay_test.rb | 3 +- .../gateways/remote_net_registry_test.rb | 15 +- test/remote/gateways/remote_netaxept_test.rb | 16 +- test/remote/gateways/remote_netbanx_test.rb | 114 +- .../remote/gateways/remote_netbilling_test.rb | 25 +- test/remote/gateways/remote_netpay_test.rb | 86 +- .../gateways/remote_network_merchants_test.rb | 34 +- test/remote/gateways/remote_nmi_test.rb | 265 +- test/remote/gateways/remote_ogone_test.rb | 185 +- test/remote/gateways/remote_omise_test.rb | 9 +- test/remote/gateways/remote_openpay_test.rb | 19 +- test/remote/gateways/remote_opp_test.rb | 23 +- .../gateways/remote_optimal_payment_test.rb | 64 +- test/remote/gateways/remote_orbital_test.rb | 1416 ++++- test/remote/gateways/remote_pagarme_test.rb | 3 +- .../remote/gateways/remote_pago_facil_test.rb | 2 +- test/remote/gateways/remote_pay_arc_test.rb | 262 + test/remote/gateways/remote_pay_conex_test.rb | 15 +- .../gateways/remote_pay_gate_xml_test.rb | 16 +- test/remote/gateways/remote_pay_hub_test.rb | 16 +- .../gateways/remote_pay_junction_test.rb | 47 +- .../gateways/remote_pay_junction_v2_test.rb | 22 +- .../remote/gateways/remote_pay_secure_test.rb | 11 +- test/remote/gateways/remote_pay_trace_test.rb | 429 ++ .../gateways/remote_paybox_direct_3ds_test.rb | 140 + .../gateways/remote_paybox_direct_test.rb | 46 +- test/remote/gateways/remote_payeezy_test.rb | 207 +- test/remote/gateways/remote_payex_test.rb | 13 +- .../gateways/remote_payflow_express_test.rb | 226 +- test/remote/gateways/remote_payflow_test.rb | 198 +- .../remote/gateways/remote_payflow_uk_test.rb | 40 +- .../gateways/remote_payment_express_test.rb | 42 +- test/remote/gateways/remote_paymentez_test.rb | 119 +- test/remote/gateways/remote_paymill_test.rb | 3 +- .../gateways/remote_paypal_express_test.rb | 41 +- test/remote/gateways/remote_paypal_test.rb | 113 +- test/remote/gateways/remote_paysafe_test.rb | 423 ++ test/remote/gateways/remote_payscout_test.rb | 10 +- .../remote/gateways/remote_paystation_test.rb | 15 +- test/remote/gateways/remote_payu_in_test.rb | 48 +- .../remote/gateways/remote_payu_latam_test.rb | 189 +- .../gateways/remote_payway_dot_com_test.rb | 143 + test/remote/gateways/remote_payway_test.rb | 68 +- test/remote/gateways/remote_pin_test.rb | 85 +- test/remote/gateways/remote_plexo_test.rb | 266 + test/remote/gateways/remote_plugnpay_test.rb | 4 +- test/remote/gateways/remote_priority_test.rb | 352 ++ test/remote/gateways/remote_pro_pay_test.rb | 4 +- test/remote/gateways/remote_psigate_test.rb | 9 +- test/remote/gateways/remote_psl_card_test.rb | 48 +- test/remote/gateways/remote_qbms_test.rb | 6 +- test/remote/gateways/remote_quantum_test.rb | 9 +- .../remote/gateways/remote_quickbooks_test.rb | 68 +- test/remote/gateways/remote_quickpay_test.rb | 20 +- .../gateways/remote_quickpay_v10_test.rb | 20 +- .../gateways/remote_quickpay_v4_test.rb | 18 +- .../gateways/remote_quickpay_v5_test.rb | 18 +- .../gateways/remote_quickpay_v6_test.rb | 18 +- .../gateways/remote_quickpay_v7_test.rb | 24 +- test/remote/gateways/remote_qvalent_test.rb | 60 +- test/remote/gateways/remote_rapyd_test.rb | 416 ++ test/remote/gateways/remote_reach_test.rb | 326 + test/remote/gateways/remote_realex_test.rb | 366 +- .../gateways/remote_redsys_sha256_test.rb | 299 +- test/remote/gateways/remote_redsys_test.rb | 119 +- test/remote/gateways/remote_s5_test.rb | 4 +- .../gateways/remote_safe_charge_test.rb | 148 +- test/remote/gateways/remote_sage_pay_test.rb | 241 +- test/remote/gateways/remote_sage_test.rb | 27 +- .../remote/gateways/remote_sallie_mae_test.rb | 6 +- .../remote/gateways/remote_secure_net_test.rb | 8 +- .../gateways/remote_secure_pay_au_test.rb | 45 +- .../gateways/remote_secure_pay_tech_test.rb | 10 +- .../remote/gateways/remote_secure_pay_test.rb | 12 +- .../gateways/remote_securion_pay_test.rb | 50 +- test/remote/gateways/remote_shift4_test.rb | 270 + test/remote/gateways/remote_shift4_v2_test.rb | 88 + test/remote/gateways/remote_simetrik_test.rb | 276 + test/remote/gateways/remote_skipjack_test.rb | 18 +- .../gateways/remote_so_easy_pay_test.rb | 21 +- .../gateways/remote_spreedly_core_test.rb | 46 +- .../remote/gateways/remote_stripe_3ds_test.rb | 177 +- .../remote_stripe_android_pay_test.rb | 12 +- .../gateways/remote_stripe_apple_pay_test.rb | 29 +- .../gateways/remote_stripe_connect_test.rb | 22 +- .../remote/gateways/remote_stripe_emv_test.rb | 8 +- .../remote_stripe_payment_intents_test.rb | 1490 +++++ test/remote/gateways/remote_stripe_test.rb | 191 +- test/remote/gateways/remote_sum_up_test.rb | 156 + .../gateways/remote_swipe_checkout_test.rb | 4 +- test/remote/gateways/remote_tns_test.rb | 38 +- .../gateways/remote_trans_first_test.rb | 11 +- ...te_trans_first_transaction_express_test.rb | 79 +- .../gateways/remote_transact_pro_test.rb | 6 +- test/remote/gateways/remote_transax_test.rb | 18 +- .../gateways/remote_trust_commerce_test.rb | 142 +- .../gateways/remote_usa_epay_advanced_test.rb | 164 +- .../remote_usa_epay_transaction_test.rb | 58 +- test/remote/gateways/remote_vanco_test.rb | 51 +- test/remote/gateways/remote_verifi_test.rb | 10 +- test/remote/gateways/remote_viaklix_test.rb | 8 +- .../gateways/remote_visanet_peru_test.rb | 12 +- test/remote/gateways/remote_vpos_test.rb | 115 + .../gateways/remote_vpos_without_key_test.rb | 125 + test/remote/gateways/remote_webpay_test.rb | 20 +- test/remote/gateways/remote_wepay_test.rb | 4 +- test/remote/gateways/remote_wirecard_test.rb | 10 +- test/remote/gateways/remote_wompi_test.rb | 125 + test/remote/gateways/remote_world_net_test.rb | 8 +- .../remote_worldpay_online_payments_test.rb | 4 +- test/remote/gateways/remote_worldpay_test.rb | 944 ++- .../gateways/remote_worldpay_us_test.rb | 16 +- test/remote/gateways/remote_xpay_test.rb | 15 + .../CyberSourceTransaction_1.153.xsd | 4770 +++++++++++++++ .../CyberSourceTransaction_1.155.xsd | 4857 +++++++++++++++ .../CyberSourceTransaction_1.156.xsd | 4894 +++++++++++++++ .../CyberSourceTransaction_1.164.xsd | 5111 ++++++++++++++++ .../CyberSourceTransaction_1.181.xsd | 5290 ++++++++++++++++ .../CyberSourceTransaction_1.198.xsd | 5349 +++++++++++++++++ .../CyberSourceTransaction_1.201.xsd | 5106 ++++++++++++++++ test/schema/orbital/Request_PTI83.xsd | 1142 ++++ test/support/mercury_helper.rb | 2 +- test/test_helper.rb | 140 +- test/unit/avs_result_test.rb | 16 +- test/unit/base_test.rb | 1 - test/unit/check_test.rb | 84 +- test/unit/connection_test.rb | 27 +- test/unit/country_test.rb | 2 +- test/unit/credit_card_formatting_test.rb | 5 + test/unit/credit_card_methods_test.rb | 324 +- test/unit/credit_card_test.rb | 80 +- test/unit/fixtures_test.rb | 2 +- test/unit/gateways/adyen_test.rb | 1679 +++++- test/unit/gateways/airwallex_test.rb | 552 ++ test/unit/gateways/alelo_test.rb | 380 ++ test/unit/gateways/allied_wallet_test.rb | 6 +- test/unit/gateways/authorize_net_arb_test.rb | 124 +- test/unit/gateways/authorize_net_cim_test.rb | 368 +- test/unit/gateways/authorize_net_test.rb | 710 ++- test/unit/gateways/axcessms_test.rb | 24 +- test/unit/gateways/balanced_test.rb | 999 +-- test/unit/gateways/bambora_apac_test.rb | 211 +- test/unit/gateways/banwire_test.rb | 47 +- .../gateways/barclaycard_smartpay_test.rb | 227 +- .../gateways/barclays_epdq_extra_plus_test.rb | 112 +- test/unit/gateways/be2bill_test.rb | 16 +- test/unit/gateways/beanstream_interac_test.rb | 12 +- test/unit/gateways/beanstream_test.rb | 95 +- test/unit/gateways/blue_pay_test.rb | 123 +- test/unit/gateways/blue_snap_test.rb | 716 ++- test/unit/gateways/bogus_test.rb | 65 +- test/unit/gateways/borgun_test.rb | 118 +- test/unit/gateways/bpoint_test.rb | 29 +- test/unit/gateways/braintree_blue_test.rb | 1216 +++- test/unit/gateways/braintree_orange_test.rb | 36 +- test/unit/gateways/braintree_test.rb | 13 +- .../gateways/braintree_token_nonce_test.rb | 205 + test/unit/gateways/bridge_pay_test.rb | 12 +- test/unit/gateways/cams_test.rb | 76 +- test/unit/gateways/card_connect_test.rb | 128 +- test/unit/gateways/card_save_test.rb | 5 +- test/unit/gateways/card_stream_test.rb | 166 +- test/unit/gateways/cardknox_test.rb | 8 +- test/unit/gateways/cardprocess_test.rb | 2 +- test/unit/gateways/cashnet_test.rb | 287 +- test/unit/gateways/cecabank_rest_json_test.rb | 236 + test/unit/gateways/cecabank_test.rb | 92 +- test/unit/gateways/cenpos_test.rb | 14 +- test/unit/gateways/checkout_test.rb | 23 +- test/unit/gateways/checkout_v2_test.rb | 1238 +++- test/unit/gateways/citrus_pay_test.rb | 46 +- test/unit/gateways/clearhaus_test.rb | 69 +- test/unit/gateways/commerce_hub_test.rb | 806 +++ test/unit/gateways/conekta_test.rb | 60 +- test/unit/gateways/creditcall_test.rb | 12 +- test/unit/gateways/credorax_test.rb | 917 ++- test/unit/gateways/cyber_source_rest_test.rb | 585 ++ test/unit/gateways/cyber_source_test.rb | 1610 ++++- test/unit/gateways/d_local_test.rb | 362 +- test/unit/gateways/data_cash_test.rb | 244 +- test/unit/gateways/decidir_plus_test.rb | 368 ++ test/unit/gateways/decidir_test.rb | 699 +++ test/unit/gateways/deepstack_test.rb | 284 + test/unit/gateways/dibs_test.rb | 8 +- test/unit/gateways/digitzs_test.rb | 10 +- test/unit/gateways/ebanx_test.rb | 84 +- test/unit/gateways/efsnet_test.rb | 107 +- test/unit/gateways/elavon_test.rb | 1004 +++- test/unit/gateways/element_test.rb | 324 +- test/unit/gateways/epay_test.rb | 39 +- test/unit/gateways/evo_ca_test.rb | 22 +- test/unit/gateways/eway_managed_test.rb | 264 +- test/unit/gateways/eway_rapid_test.rb | 433 +- test/unit/gateways/eway_test.rb | 22 +- test/unit/gateways/exact_test.rb | 171 +- test/unit/gateways/ezic_test.rb | 1 - test/unit/gateways/fat_zebra_test.rb | 434 +- test/unit/gateways/federated_canada_test.rb | 30 +- test/unit/gateways/finansbank_test.rb | 398 +- test/unit/gateways/first_pay_test.rb | 12 +- test/unit/gateways/firstdata_e4_test.rb | 1528 ++--- test/unit/gateways/firstdata_e4_v27_test.rb | 1397 ++--- test/unit/gateways/flo2cash_simple_test.rb | 12 +- test/unit/gateways/flo2cash_test.rb | 14 +- test/unit/gateways/forte_test.rb | 117 +- test/unit/gateways/garanti_test.rb | 138 +- test/unit/gateways/gateway_test.rb | 45 +- test/unit/gateways/global_collect_test.rb | 526 +- test/unit/gateways/global_transport_test.rb | 14 +- test/unit/gateways/hdfc_test.rb | 44 +- test/unit/gateways/hps_test.rb | 1529 +++-- test/unit/gateways/iats_payments_test.rb | 460 +- .../in_context_paypal_express_test.rb | 6 +- test/unit/gateways/inspire_test.rb | 28 +- test/unit/gateways/instapay_test.rb | 2 +- test/unit/gateways/ipg_test.rb | 800 +++ test/unit/gateways/ipp_test.rb | 192 +- test/unit/gateways/iridium_test.rb | 23 +- test/unit/gateways/itransact_test.rb | 19 +- test/unit/gateways/iveri_test.rb | 624 +- test/unit/gateways/ixopay_test.rb | 673 +++ test/unit/gateways/jetpay_test.rb | 50 +- test/unit/gateways/jetpay_v2_test.rb | 71 +- test/unit/gateways/komoju_test.rb | 18 +- test/unit/gateways/kushki_test.rb | 231 +- test/unit/gateways/latitude19_test.rb | 18 +- test/unit/gateways/linkpoint_test.rb | 127 +- test/unit/gateways/litle_test.rb | 286 +- test/unit/gateways/maxipago_test.rb | 12 +- test/unit/gateways/mercado_pago_test.rb | 344 +- .../gateways/merchant_e_solutions_test.rb | 74 +- test/unit/gateways/merchant_one_test.rb | 23 +- test/unit/gateways/merchant_partners_test.rb | 18 +- test/unit/gateways/merchant_ware_test.rb | 187 +- .../merchant_ware_version_four_test.rb | 384 +- test/unit/gateways/merchant_warrior_test.rb | 339 +- test/unit/gateways/mercury_test.rb | 166 +- test/unit/gateways/metrics_global_test.rb | 47 +- test/unit/gateways/micropayment_test.rb | 10 +- test/unit/gateways/migs_test.rb | 149 +- test/unit/gateways/mit_test.rb | 268 + .../unit/gateways/modern_payments_cim_test.rb | 128 +- test/unit/gateways/moka_test.rb | 356 ++ test/unit/gateways/monei_test.rb | 519 +- test/unit/gateways/moneris_test.rb | 895 ++- test/unit/gateways/moneris_us_test.rb | 649 -- test/unit/gateways/money_movers_test.rb | 26 +- test/unit/gateways/mundipagg_test.rb | 535 +- test/unit/gateways/nab_transact_test.rb | 117 +- test/unit/gateways/ncr_secure_pay_test.rb | 10 +- test/unit/gateways/net_registry_test.rb | 580 +- test/unit/gateways/netaxept_test.rb | 14 +- test/unit/gateways/netbanx_test.rb | 34 +- test/unit/gateways/netbilling_test.rb | 10 +- test/unit/gateways/netpay_test.rb | 14 +- test/unit/gateways/network_merchants_test.rb | 16 +- test/unit/gateways/nmi_test.rb | 583 +- test/unit/gateways/ogone_test.rb | 138 +- test/unit/gateways/omise_test.rb | 19 +- test/unit/gateways/openpay_test.rb | 472 +- test/unit/gateways/opp_test.rb | 140 +- test/unit/gateways/optimal_payment_test.rb | 420 +- test/unit/gateways/orbital_avs_result_test.rb | 4 +- test/unit/gateways/orbital_test.rb | 1563 ++++- test/unit/gateways/pac_net_raven_test.rb | 78 +- test/unit/gateways/pagarme_test.rb | 21 +- test/unit/gateways/pago_facil_test.rb | 294 +- test/unit/gateways/pay_arc_test.rb | 792 +++ test/unit/gateways/pay_conex_test.rb | 19 +- test/unit/gateways/pay_gate_xml_test.rb | 11 +- test/unit/gateways/pay_hub_test.rb | 10 +- test/unit/gateways/pay_junction_test.rb | 222 +- test/unit/gateways/pay_junction_v2_test.rb | 19 +- test/unit/gateways/pay_secure_test.rb | 37 +- test/unit/gateways/pay_trace_test.rb | 565 ++ test/unit/gateways/paybox_direct_test.rb | 20 +- test/unit/gateways/payeezy_test.rb | 1142 ++-- test/unit/gateways/payex_test.rb | 12 +- test/unit/gateways/payflow_express_test.rb | 207 +- test/unit/gateways/payflow_express_uk_test.rb | 170 +- test/unit/gateways/payflow_test.rb | 969 ++- test/unit/gateways/payflow_uk_test.rb | 6 +- test/unit/gateways/payment_express_test.rb | 572 +- test/unit/gateways/paymentez_test.rb | 172 +- test/unit/gateways/paymill_test.rb | 21 +- .../gateways/paypal/paypal_common_api_test.rb | 105 +- .../gateways/paypal_digital_goods_test.rb | 103 +- test/unit/gateways/paypal_express_test.rb | 1537 ++--- test/unit/gateways/paypal_test.rb | 1526 ++--- test/unit/gateways/paysafe_test.rb | 385 ++ test/unit/gateways/payscout_test.rb | 28 +- test/unit/gateways/paystation_test.rb | 19 +- test/unit/gateways/payu_in_test.rb | 206 +- test/unit/gateways/payu_latam_test.rb | 288 +- test/unit/gateways/payway_dot_com_test.rb | 1478 +++++ test/unit/gateways/payway_test.rb | 25 +- test/unit/gateways/pin_test.rb | 110 +- test/unit/gateways/plexo_test.rb | 884 +++ test/unit/gateways/plugnpay_test.rb | 15 +- test/unit/gateways/priority_test.rb | 1352 +++++ test/unit/gateways/pro_pay_test.rb | 114 +- test/unit/gateways/psigate_test.rb | 168 +- test/unit/gateways/psl_card_test.rb | 13 +- test/unit/gateways/qbms_test.rb | 47 +- test/unit/gateways/quantum_test.rb | 10 +- test/unit/gateways/quickbooks_test.rb | 424 +- test/unit/gateways/quickpay_test.rb | 6 +- test/unit/gateways/quickpay_v10_test.rb | 164 +- test/unit/gateways/quickpay_v4to7_test.rb | 76 +- test/unit/gateways/qvalent_test.rb | 108 +- test/unit/gateways/rapyd_test.rb | 604 ++ test/unit/gateways/reach_test.rb | 258 + test/unit/gateways/realex_test.rb | 811 ++- test/unit/gateways/redsys_sha256_test.rb | 280 +- test/unit/gateways/redsys_test.rb | 201 +- test/unit/gateways/s5_test.rb | 2 +- test/unit/gateways/safe_charge_test.rb | 293 +- test/unit/gateways/sage_pay_test.rb | 326 +- test/unit/gateways/sage_test.rb | 330 +- test/unit/gateways/sallie_mae_test.rb | 12 +- test/unit/gateways/secure_net_test.rb | 106 +- test/unit/gateways/secure_pay_au_test.rb | 24 +- test/unit/gateways/secure_pay_tech_test.rb | 8 +- test/unit/gateways/secure_pay_test.rb | 10 +- test/unit/gateways/securion_pay_test.rb | 117 +- test/unit/gateways/shift4_test.rb | 1072 ++++ test/unit/gateways/shift4_v2_test.rb | 91 + test/unit/gateways/simetrik_test.rb | 970 +++ test/unit/gateways/skip_jack_test.rb | 87 +- test/unit/gateways/so_easy_pay_test.rb | 15 +- test/unit/gateways/spreedly_core_test.rb | 148 +- .../gateways/stripe_payment_intents_test.rb | 2080 +++++++ test/unit/gateways/stripe_test.rb | 733 ++- test/unit/gateways/sum_up_test.rb | 494 ++ test/unit/gateways/swipe_checkout_test.rb | 2 +- test/unit/gateways/telr_test.rb | 10 +- test/unit/gateways/tns_test.rb | 46 +- test/unit/gateways/trans_first_test.rb | 11 +- .../trans_first_transaction_express_test.rb | 202 +- test/unit/gateways/transact_pro_test.rb | 6 +- test/unit/gateways/trexle_test.rb | 7 +- test/unit/gateways/trust_commerce_test.rb | 215 +- test/unit/gateways/usa_epay_advanced_test.rb | 254 +- test/unit/gateways/usa_epay_test.rb | 17 +- .../gateways/usa_epay_transaction_test.rb | 450 +- test/unit/gateways/vanco_test.rb | 73 +- test/unit/gateways/verifi_test.rb | 12 +- test/unit/gateways/viaklix_test.rb | 19 +- test/unit/gateways/visanet_peru_test.rb | 116 +- test/unit/gateways/vpos_test.rb | 227 + test/unit/gateways/webpay_test.rb | 480 +- test/unit/gateways/wepay_test.rb | 5 +- test/unit/gateways/wirecard_test.rb | 598 +- test/unit/gateways/wompi_test.rb | 223 + .../gateways/worldpay_online_payments_test.rb | 4 +- test/unit/gateways/worldpay_test.rb | 1855 +++++- test/unit/gateways/worldpay_us_test.rb | 182 +- test/unit/gateways/xpay_test.rb | 57 + test/unit/multi_response_test.rb | 148 +- test/unit/network_connection_retries_test.rb | 16 +- .../network_tokenization_credit_card_test.rb | 30 +- test/unit/post_data_test.rb | 6 +- test/unit/posts_data_test.rb | 5 +- test/unit/rails_compatibility_test.rb | 2 +- test/unit/response_test.rb | 14 +- test/unit/three_d_secure_eci_mapper_test.rb | 30 + test/unit/transcripts/alelo_purchase | 69 + test/unit/transcripts/alelo_purchase_scrubbed | 69 + 778 files changed, 140464 insertions(+), 29952 deletions(-) create mode 100644 .github/workflows/ruby-ci.yml create mode 100644 .github/workflows/stale.yml delete mode 100644 .travis.yml delete mode 100644 CONTRIBUTING.md create mode 100644 gemfiles/Gemfile.rails60 create mode 100644 lib/active_merchant/billing/gateways/airwallex.rb create mode 100644 lib/active_merchant/billing/gateways/alelo.rb create mode 100644 lib/active_merchant/billing/gateways/braintree/token_nonce.rb create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb create mode 100644 lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb create mode 100644 lib/active_merchant/billing/gateways/commerce_hub.rb create mode 100644 lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb create mode 100644 lib/active_merchant/billing/gateways/cyber_source_rest.rb create mode 100644 lib/active_merchant/billing/gateways/decidir.rb create mode 100644 lib/active_merchant/billing/gateways/decidir_plus.rb create mode 100644 lib/active_merchant/billing/gateways/deepstack.rb create mode 100644 lib/active_merchant/billing/gateways/ipg.rb create mode 100644 lib/active_merchant/billing/gateways/ixopay.rb create mode 100644 lib/active_merchant/billing/gateways/mit.rb create mode 100644 lib/active_merchant/billing/gateways/moka.rb delete mode 100644 lib/active_merchant/billing/gateways/moneris_us.rb create mode 100644 lib/active_merchant/billing/gateways/pay_arc.rb create mode 100644 lib/active_merchant/billing/gateways/pay_trace.rb create mode 100644 lib/active_merchant/billing/gateways/paysafe.rb create mode 100644 lib/active_merchant/billing/gateways/payway_dot_com.rb create mode 100644 lib/active_merchant/billing/gateways/plexo.rb create mode 100644 lib/active_merchant/billing/gateways/priority.rb create mode 100644 lib/active_merchant/billing/gateways/rapyd.rb create mode 100644 lib/active_merchant/billing/gateways/reach.rb create mode 100644 lib/active_merchant/billing/gateways/shift4.rb create mode 100644 lib/active_merchant/billing/gateways/shift4_v2.rb create mode 100644 lib/active_merchant/billing/gateways/simetrik.rb create mode 100644 lib/active_merchant/billing/gateways/stripe_payment_intents.rb create mode 100644 lib/active_merchant/billing/gateways/sum_up.rb create mode 100644 lib/active_merchant/billing/gateways/vpos.rb create mode 100644 lib/active_merchant/billing/gateways/wompi.rb create mode 100644 lib/active_merchant/billing/gateways/xpay.rb create mode 100644 lib/active_merchant/billing/three_d_secure_eci_mapper.rb create mode 100644 test/remote/gateways/remote_airwallex_test.rb create mode 100644 test/remote/gateways/remote_alelo_test.rb create mode 100644 test/remote/gateways/remote_alelo_test_certification.rb create mode 100644 test/remote/gateways/remote_braintree_token_nonce_test.rb create mode 100644 test/remote/gateways/remote_cecabank_rest_json_test.rb create mode 100644 test/remote/gateways/remote_commerce_hub_test.rb create mode 100644 test/remote/gateways/remote_cyber_source_rest_test.rb create mode 100644 test/remote/gateways/remote_decidir_plus_test.rb create mode 100644 test/remote/gateways/remote_decidir_test.rb create mode 100644 test/remote/gateways/remote_deepstack_test.rb create mode 100644 test/remote/gateways/remote_ipg_test.rb create mode 100644 test/remote/gateways/remote_ixopay_test.rb create mode 100644 test/remote/gateways/remote_mit_test.rb create mode 100644 test/remote/gateways/remote_moka_test.rb delete mode 100644 test/remote/gateways/remote_moneris_us_test.rb create mode 100644 test/remote/gateways/remote_pay_arc_test.rb create mode 100644 test/remote/gateways/remote_pay_trace_test.rb create mode 100644 test/remote/gateways/remote_paybox_direct_3ds_test.rb create mode 100644 test/remote/gateways/remote_paysafe_test.rb create mode 100644 test/remote/gateways/remote_payway_dot_com_test.rb create mode 100644 test/remote/gateways/remote_plexo_test.rb create mode 100644 test/remote/gateways/remote_priority_test.rb create mode 100644 test/remote/gateways/remote_rapyd_test.rb create mode 100644 test/remote/gateways/remote_reach_test.rb create mode 100644 test/remote/gateways/remote_shift4_test.rb create mode 100644 test/remote/gateways/remote_shift4_v2_test.rb create mode 100644 test/remote/gateways/remote_simetrik_test.rb create mode 100644 test/remote/gateways/remote_stripe_payment_intents_test.rb create mode 100644 test/remote/gateways/remote_sum_up_test.rb create mode 100644 test/remote/gateways/remote_vpos_test.rb create mode 100644 test/remote/gateways/remote_vpos_without_key_test.rb create mode 100644 test/remote/gateways/remote_wompi_test.rb create mode 100644 test/remote/gateways/remote_xpay_test.rb create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.153.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.155.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.156.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.164.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.181.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.198.xsd create mode 100644 test/schema/cyber_source/CyberSourceTransaction_1.201.xsd create mode 100644 test/schema/orbital/Request_PTI83.xsd create mode 100644 test/unit/gateways/airwallex_test.rb create mode 100644 test/unit/gateways/alelo_test.rb create mode 100644 test/unit/gateways/braintree_token_nonce_test.rb create mode 100644 test/unit/gateways/cecabank_rest_json_test.rb create mode 100644 test/unit/gateways/commerce_hub_test.rb create mode 100644 test/unit/gateways/cyber_source_rest_test.rb create mode 100644 test/unit/gateways/decidir_plus_test.rb create mode 100644 test/unit/gateways/decidir_test.rb create mode 100644 test/unit/gateways/deepstack_test.rb create mode 100644 test/unit/gateways/ipg_test.rb create mode 100644 test/unit/gateways/ixopay_test.rb create mode 100644 test/unit/gateways/mit_test.rb create mode 100644 test/unit/gateways/moka_test.rb delete mode 100644 test/unit/gateways/moneris_us_test.rb create mode 100644 test/unit/gateways/pay_arc_test.rb create mode 100644 test/unit/gateways/pay_trace_test.rb create mode 100644 test/unit/gateways/paysafe_test.rb create mode 100644 test/unit/gateways/payway_dot_com_test.rb create mode 100644 test/unit/gateways/plexo_test.rb create mode 100644 test/unit/gateways/priority_test.rb create mode 100644 test/unit/gateways/rapyd_test.rb create mode 100644 test/unit/gateways/reach_test.rb create mode 100644 test/unit/gateways/shift4_test.rb create mode 100644 test/unit/gateways/shift4_v2_test.rb create mode 100644 test/unit/gateways/simetrik_test.rb create mode 100644 test/unit/gateways/stripe_payment_intents_test.rb create mode 100644 test/unit/gateways/sum_up_test.rb create mode 100644 test/unit/gateways/vpos_test.rb create mode 100644 test/unit/gateways/wompi_test.rb create mode 100644 test/unit/gateways/xpay_test.rb create mode 100644 test/unit/three_d_secure_eci_mapper_test.rb create mode 100644 test/unit/transcripts/alelo_purchase create mode 100644 test/unit/transcripts/alelo_purchase_scrubbed diff --git a/.github/workflows/ruby-ci.yml b/.github/workflows/ruby-ci.yml new file mode 100644 index 00000000000..1275083a680 --- /dev/null +++ b/.github/workflows/ruby-ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + pull_request: + branches: + - '**' + push: + branches: + - master + +jobs: + build: + name: Ruby ${{ matrix.version }} ${{ matrix.gemfile }} + runs-on: ubuntu-latest + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} + strategy: + matrix: + version: + - 2.7 + gemfile: + - gemfiles/Gemfile.rails50 + - gemfiles/Gemfile.rails51 + - gemfiles/Gemfile.rails52 + - gemfiles/Gemfile.rails60 + - gemfiles/Gemfile.rails_master + exclude: + - version: 2.6 + gemfile: gemfiles/Gemfile.rails_master + - version: 2.5 + gemfile: gemfiles/Gemfile.rails_master + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby ${{ matrix.version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.version }} + bundler-cache: true + + - name: Test + run: bundle exec rake test + - name: Linter + run: bundle exec rubocop 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 f1deca38196..f012a5c1777 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.3 + TargetRubyVersion: 2.7 # Active Merchant gateways are not amenable to length restrictions Metrics/ClassLength: @@ -32,3 +32,11 @@ Layout/DotPosition: Layout/CaseIndentation: EnforcedStyle: end + +Layout/IndentFirstHashElement: + EnforcedStyle: consistent + +Naming/PredicateName: + Exclude: + - "lib/active_merchant/billing/gateways/payeezy.rb" + - 'lib/active_merchant/billing/gateways/airwallex.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8025e1f862..359bc075fb3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Include, TreatCommentsAsGroupSeparators. -# Include: **/*.gemspec -Gemspec/OrderedDependencies: - Exclude: - - 'activemerchant.gemspec' - # Offense count: 1828 # Cop supports --auto-correct. # Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. @@ -23,159 +15,6 @@ Gemspec/OrderedDependencies: Layout/AlignHash: Enabled: false -# Offense count: 57 -# Cop supports --auto-correct. -Layout/ClosingHeredocIndentation: - Enabled: false - -# Offense count: 167 -# Cop supports --auto-correct. -Layout/EmptyLineAfterGuardClause: - Enabled: false - -# Offense count: 173 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Enabled: false - -# Offense count: 39 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Layout/EndAlignment: - Enabled: false - -# Offense count: 174 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Enabled: false - -# Offense count: 105 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses -Layout/FirstParameterIndentation: - Enabled: false - -# Offense count: 255 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/IndentHash: - Enabled: false - -# Offense count: 392 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent -Layout/IndentHeredoc: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineArrayBraceLayout: - Exclude: - - 'lib/active_merchant/billing/gateways/optimal_payment.rb' - -# Offense count: 36 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineHashBraceLayout: - Enabled: false - -# Offense count: 232 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineMethodCallBraceLayout: - Enabled: false - -# Offense count: 24 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'lib/active_merchant/billing/credit_card_methods.rb' - - 'lib/active_merchant/billing/gateways/iridium.rb' - - 'lib/active_merchant/billing/gateways/moneris.rb' - - 'lib/active_merchant/billing/gateways/moneris_us.rb' - - 'lib/active_merchant/billing/gateways/orbital.rb' - - 'lib/active_merchant/billing/gateways/redsys.rb' - - 'test/unit/gateways/braintree_blue_test.rb' - - 'test/unit/gateways/skip_jack_test.rb' - -# Offense count: 15 -# Cop supports --auto-correct. -Layout/RescueEnsureAlignment: - Exclude: - - 'lib/active_merchant/billing/gateways/balanced.rb' - - 'lib/active_merchant/billing/gateways/clearhaus.rb' - - 'lib/active_merchant/billing/gateways/culqi.rb' - - 'lib/active_merchant/billing/gateways/eway_managed.rb' - - 'lib/active_merchant/billing/gateways/fat_zebra.rb' - - 'lib/active_merchant/billing/gateways/hps.rb' - - 'lib/active_merchant/billing/gateways/iveri.rb' - - 'lib/active_merchant/billing/gateways/kushki.rb' - - 'lib/active_merchant/billing/gateways/merchant_e_solutions.rb' - - 'lib/active_merchant/billing/gateways/netbanx.rb' - - 'lib/active_merchant/billing/gateways/opp.rb' - - 'lib/active_merchant/billing/gateways/orbital.rb' - - 'lib/active_merchant/billing/gateways/pay_junction_v2.rb' - - 'lib/active_merchant/billing/gateways/quickbooks.rb' - - 'lib/active_merchant/billing/gateways/trans_first_transaction_express.rb' - -# Offense count: 649 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Enabled: false - -# Offense count: 104 -# Cop supports --auto-correct. -Layout/SpaceAroundKeyword: - Enabled: false - -# Offense count: 782 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceAroundOperators: - Enabled: false - -# Offense count: 118 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideArrayLiteralBrackets: - Enabled: false - -# Offense count: 12 -# Cop supports --auto-correct. -Layout/SpaceInsideArrayPercentLiteral: - Exclude: - - 'lib/active_merchant/billing/gateways/migs/migs_codes.rb' - -# Offense count: 1186 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideHashLiteralBraces: - Enabled: false - -# Offense count: 115 -# Cop supports --auto-correct. -Layout/SpaceInsidePercentLiteralDelimiters: - Enabled: false - # Offense count: 150 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: @@ -197,12 +36,6 @@ Lint/RescueException: Exclude: - 'lib/active_merchant/billing/gateways/quantum.rb' -# Offense count: 1502 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Enabled: false - # Offense count: 284 # Cop supports --auto-correct. # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. @@ -249,35 +82,6 @@ Naming/AccessorMethodName: - 'test/remote/gateways/remote_authorize_net_cim_test.rb' - 'test/unit/gateways/authorize_net_cim_test.rb' -# Offense count: 1 -Naming/ConstantName: - Exclude: - - 'test/test_helper.rb' - -# Offense count: 46 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: lowercase, uppercase -Naming/HeredocDelimiterCase: - Exclude: - - 'test/unit/gateways/authorize_net_test.rb' - - 'test/unit/gateways/card_stream_test.rb' - - 'test/unit/gateways/hps_test.rb' - - 'test/unit/gateways/litle_test.rb' - - 'test/unit/gateways/moneris_test.rb' - -# Offense count: 85 -# Configuration parameters: Blacklist. -# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) -Naming/HeredocDelimiterNaming: - Enabled: false - -# Offense count: 1 -# Configuration parameters: EnforcedStyleForLeadingUnderscores. -# SupportedStylesForLeadingUnderscores: disallowed, required, optional -Naming/MemoizedInstanceVariableName: - Exclude: - - 'lib/active_merchant/billing/compatibility.rb' - # Offense count: 15 # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, camelCase @@ -292,21 +96,6 @@ Naming/MethodName: - 'test/remote/gateways/remote_sage_pay_test.rb' - 'test/unit/gateways/sage_pay_test.rb' -# Offense count: 5 -# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros. -# NamePrefix: is_, has_, have_ -# NamePrefixBlacklist: is_, has_, have_ -# NameWhitelist: is_a? -# MethodDefinitionMacros: define_method, define_singleton_method -Naming/PredicateName: - Exclude: - - 'spec/**/*' - - 'lib/active_merchant/billing/gateways/authorize_net.rb' - - 'lib/active_merchant/billing/gateways/payeezy.rb' - - 'lib/active_merchant/billing/gateways/paymill.rb' - - 'lib/active_merchant/billing/gateways/redsys.rb' - - 'lib/active_merchant/billing/gateways/sage_pay.rb' - # Offense count: 14 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at, ip, db @@ -339,42 +128,6 @@ Naming/VariableName: - 'test/unit/gateways/card_stream_test.rb' - 'test/unit/gateways/worldpay_online_payments_test.rb' -# Offense count: 11 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: snake_case, normalcase, non_integer -Naming/VariableNumber: - Exclude: - - 'lib/active_merchant/billing/gateways/merchant_partners.rb' - - 'lib/active_merchant/billing/gateways/mercury.rb' - - 'lib/active_merchant/billing/gateways/orbital.rb' - - 'test/remote/gateways/remote_paypal_test.rb' - - 'test/unit/gateways/merchant_ware_test.rb' - - 'test/unit/gateways/merchant_ware_version_four_test.rb' - - 'test/unit/gateways/orbital_test.rb' - - 'test/unit/gateways/paypal/paypal_common_api_test.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -Performance/RedundantMatch: - Exclude: - - 'lib/active_merchant/billing/gateways/opp.rb' - - 'test/unit/gateways/payu_latam_test.rb' - -# Offense count: 11 -# Cop supports --auto-correct. -Performance/StringReplacement: - Exclude: - - 'lib/active_merchant/billing/compatibility.rb' - - 'lib/active_merchant/billing/gateways/card_connect.rb' - - 'lib/active_merchant/billing/gateways/firstdata_e4.rb' - - 'lib/active_merchant/billing/gateways/merchant_ware.rb' - - 'lib/active_merchant/billing/gateways/merchant_ware_version_four.rb' - - 'lib/active_merchant/billing/gateways/orbital.rb' - - 'lib/active_merchant/billing/gateways/quickbooks.rb' - - 'lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb' - - 'lib/active_merchant/billing/gateways/realex.rb' - - 'test/unit/gateways/nab_transact_test.rb' - # Offense count: 2 # Configuration parameters: EnforcedStyle. # SupportedStyles: inline, group @@ -383,36 +136,6 @@ Style/AccessModifierDeclarations: - 'test/unit/gateways/metrics_global_test.rb' - 'test/unit/gateways/optimal_payment_test.rb' -# Offense count: 11 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: prefer_alias, prefer_alias_method -Style/Alias: - Exclude: - - 'lib/active_merchant/billing/gateways/beanstream.rb' - - 'lib/active_merchant/billing/gateways/braintree_blue.rb' - - 'lib/active_merchant/billing/gateways/inspire.rb' - - 'lib/active_merchant/billing/gateways/migs.rb' - - 'lib/active_merchant/billing/gateways/smart_ps.rb' - - 'lib/active_merchant/billing/gateways/spreedly_core.rb' - - 'lib/active_merchant/post_data.rb' - - 'test/unit/gateways/bpoint_test.rb' - - 'test/unit/gateways/paymentez_test.rb' - -# Offense count: 12 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, conditionals -Style/AndOr: - Exclude: - - 'lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb' - - 'lib/active_merchant/billing/gateways/eway.rb' - - 'lib/active_merchant/billing/gateways/iridium.rb' - - 'lib/active_merchant/billing/gateways/pac_net_raven.rb' - - 'lib/active_merchant/billing/gateways/smart_ps.rb' - - 'lib/active_merchant/billing/gateways/stripe.rb' - - 'lib/active_merchant/billing/gateways/webpay.rb' - # Offense count: 47 # Configuration parameters: AllowedChars. Style/AsciiComments: @@ -431,12 +154,6 @@ Style/AsciiComments: - 'test/remote/gateways/remote_data_cash_test.rb' - 'test/remote/gateways/remote_nab_transact_test.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Style/Attr: - Exclude: - - 'test/unit/gateways/forte_test.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -446,14 +163,6 @@ Style/BarePercentLiterals: - 'test/unit/gateways/eway_rapid_test.rb' - 'test/unit/gateways/orbital_test.rb' -# Offense count: 3 -# Cop supports --auto-correct. -Style/BlockComments: - Exclude: - - 'test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb' - - 'test/remote/gateways/remote_netpay_test.rb' - - 'test/remote/gateways/remote_payu_in_test.rb' - # Offense count: 77 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods. @@ -706,13 +415,6 @@ Style/GlobalVars: Style/GuardClause: Enabled: false -# Offense count: 7482 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -Style/HashSyntax: - Enabled: false - # Offense count: 6 Style/IdenticalConditionalBranches: Exclude: @@ -734,11 +436,6 @@ Style/IfInsideElse: - 'lib/active_merchant/billing/gateways/skip_jack.rb' - 'lib/active_merchant/billing/gateways/worldpay_online_payments.rb' -# Offense count: 128 -# Cop supports --auto-correct. -Style/IfUnlessModifier: - Enabled: false - # Offense count: 1 Style/IfUnlessModifierOfIfUnless: Exclude: @@ -1018,41 +715,6 @@ Style/SingleLineMethods: Exclude: - 'test/unit/gateways/paypal/paypal_common_api_test.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: . -# SupportedStyles: use_perl_names, use_english_names -Style/SpecialGlobalVars: - EnforcedStyle: use_perl_names - -# Offense count: 27 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiteralsInInterpolation: - Exclude: - - 'lib/active_merchant/billing/gateways/banwire.rb' - - 'lib/active_merchant/billing/gateways/cams.rb' - - 'lib/active_merchant/billing/gateways/checkout_v2.rb' - - 'lib/active_merchant/billing/gateways/credorax.rb' - - 'lib/active_merchant/billing/gateways/digitzs.rb' - - 'lib/active_merchant/billing/gateways/ebanx.rb' - - 'lib/active_merchant/billing/gateways/merchant_one.rb' - - 'lib/active_merchant/billing/gateways/micropayment.rb' - - 'lib/active_merchant/billing/gateways/pagarme.rb' - - 'lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb' - - 'lib/active_merchant/billing/gateways/stripe.rb' - - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' - - 'lib/active_merchant/billing/gateways/worldpay.rb' - - 'test/unit/gateways/eway_managed_test.rb' - -# Offense count: 309 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - Enabled: false - # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline. @@ -1063,26 +725,6 @@ Style/TrailingCommaInArrayLiteral: - 'test/unit/gateways/netaxept_test.rb' - 'test/unit/gateways/usa_epay_transaction_test.rb' -# Offense count: 160 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Enabled: false - -# Offense count: 38 -# Cop supports --auto-correct. -# Configuration parameters: AllowNamedUnderscoreVariables. -Style/TrailingUnderscoreVariable: - Enabled: false - -# Offense count: 119 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinSize, WordRegex. -# SupportedStyles: percent, brackets -Style/WordArray: - Enabled: false - # Offense count: 34 # Cop supports --auto-correct. Style/ZeroLengthPredicate: @@ -1093,3 +735,4 @@ Style/ZeroLengthPredicate: # URISchemes: http, https Metrics/LineLength: Max: 2602 + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a43b6cfc204..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: ruby -sudo: false -cache: bundler - -rvm: -- 2.5 -- 2.4 -- 2.3 - -gemfile: -- gemfiles/Gemfile.rails52 -- gemfiles/Gemfile.rails51 -- gemfiles/Gemfile.rails50 -- gemfiles/Gemfile.rails42 -- gemfiles/Gemfile.rails_master - -jobs: - include: - rvm: 2.5 - gemfile: Gemfile - script: bundle exec rubocop --parallel - -matrix: - exclude: - - rvm: 2.3 - gemfile: 'gemfiles/Gemfile.rails_master' - - rvm: 2.4 - gemfile: 'gemfiles/Gemfile.rails_master' - -notifications: - email: - on_success: never - on_failure: always diff --git a/CHANGELOG b/CHANGELOG index c7377ac35a1..c2fe53be078 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,1303 @@ + = ActiveMerchant CHANGELOG == HEAD +* 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 + +== 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 +* Worldpay: Add transaction inquire request [molbrown] #4592 +* Alelo: Adding homologation changes [heavyblade] #4590 +* Adyen: Map standard error codes for `processing_error`, `config_error`, `invalid_amount`, and `incorrect_address` [ajawadmirza] #4593 +* MerchantE: Add support for recurring transactions [naashton] #4594 +* CyberSource: Add support for `discount_management_indicator`, `purchase_tax_amount`, `installment_total_amount`, and `installment_annual_interest_rate` fields. [rachelkirk] #4595 +* Shift4: Remove `customer` from refund and `clerk` from store requests [ajawadmirza] #4596 +* TransFirst Transaction Express: Update xml prefixing to be compatible with Nokogiri 1.13.4 [dsmcclain] #4582 +* Iveri: Adding support for external MPI 3DS2 [heavyblade] #4598 +* Credorax: Pass Network Transaction ID on MIT [jherreraa] #4600 +* iVeri: Remove schema validation on Live requests [curiousepic] #4606 +* Beanstream: Adding Third Party 3DS fields [heavyblade] #4602 +* Borgun: Add support for 3DS preauth [ajawadmirza] #4603 +* Cardstream: Add third party 3ds2 support [sainterman] #4570 +* Accept both formats of Canadian routing numbers [molbrown] #4568 +* DLocal: Add support for `original_order_id` [rachelkirk] #4605 +* CheckoutV2: Add support for `merchant_initiated_transaction_id` [rachelkirk] #4611 +* CardConnect: Add stored credential & pass any valid `ecomind` field [ajawadmirza] #4609 +* Alelo: Trigger access token refresh on 404 [curiousepic] #4614 +* DLocal: Add Network Tokens [gasb150] #4608 +* Redsys: enable NTID generation with zero-value verify [jcreiff] #4615 +* IPG: Add support for passing in `store_id` on transactions [aenand] #4619 +* Adyen: Field support for Level 2 and level 3 information [sainterman] #4617 +* Add alternate alpha2 country code for Kosovo [jcreiff] #4622 +* CyberSource: Add support for several fields [rachelkirk] #4623 +* Reach: adding gateway [cristian] #4618 +* Orbital: integration improvements [molbrown] #4626 +* iVeri: add new url [almalee24] #4630 +* Payeezy: Enable Apple Pay support [naashton] #4631 +* Payeezy: Scrub Cryptogram [naashton] #4633 +* Checkout: Fix for `[:source][:stored]` in stored credentials [marioarranzr] #4629 +* Mundipagg: send authorization_secret_key on all transaction types [edgarv09] #4635 +* CommerceHub: Add new gateway [naashton] #4640 +* CyberSource: Update installment data method [rachelkirk] #4642 +* Element: fix bug with billing address email [jcreiff] #4644 +* Openpay: set URL by merchant country [edgarv09] #4637 +* Alelo: Improving credentials refresh process [heavyblade] #4616 +* Decidir: Add transaction inquire request [almalee24] #4649 +* EBANX: add soft_descriptor field [jcreiff] #4658 +* CommerceHub: Add Apple Pay and Google Pay [gasb150] #4648 +* Global Collect & Alelo: Fixing year dependent failing tests [heavyblade] #4665 +* Moneris: Add Google Pay [sinourain] #4666 +* Global Collect: Add transaction inquire request [almalee24] #4669 +* Stripe PI: Add Level 3 support [almalee24] #4673 +* Braintree: return additional processor response [jcreiff] #4653 +* Payeezy: name from `billing_address` on `purchase` [naashton] #4674 +* Stripe: add reverse_transfer to void transactions [jcreiff] #4668 +* Global Collect: fix bug on transaction inquire request [almalee24] #4676 +* 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 +* Maestro: Adding missing BIN ranges [bradbroge] #4423 +* Simetrik: Fix integer and float types, update scrub method [rachelkirk] #4405 +* Credorax: Convert country codes for `recipient_country_code` field [ajawadmirza] #4408 +* BlueSnap: Correctly parse `refund-transaction-id` [dsmcclain] #4411 +* Worldpay: Add level II and level III data [javierpedrozaing] #4393 +* Worldpay: extract `issuer_response_code` and `issuer_response_description` from gateway response [dsmcclain] #4412 +* Vantiv: Support `duplicate` field read from saleResponse.duplicate attr [mashton] #4413 +* Ogone: Add support for 3dsv2 [gasb150] #4410 +* BlueSnap: Add support for stored credentials [ajawadmirza] #4414 +* Monei: Add support for `lang` field [drkjc] #4421 +* Wompi: Redirect `refund` to `void` [drkjc] #4424 +* Rapyd: 3DS Support [naashton] #4422 +* Adyen: Update API version [jherreraa] #4418 +* Ogone: Updated home gateway URL [gasb150] #4419 +* Credorax: Update url gateway and credit cards [javierpedrozaing] #4417 +* Kushki: Pass extra_taxes with USD [therufs] #4426 +* DLocal: fix bug with `X-Idempotency-Key` header [dsmcclain] #4431 +* DLocal: Mark support for additional countries [gasb150] #4427 +* Rapyd: Additional Fields [naashton] #4434 +* Braintree: Return generated client token [BritneyS] #4416 +* Simetrik: Update `audience` field [simetrik-frank] #4433 +* CyberSource: Add bank account payment method support [heavyblade] #4428 +* Rapyd: Zero Dollar Auth [naashton] #4435 +* Rapyd: Scrub ACH [naashton] #4436 +* VisaNet Peru: Update `purchase_number` [rachelkirk] #4437 +* CardConnect: Add support for 3ds V2 [javierpedrozaing] #4429 +* Rapyd: Support `store` and `unstore` [naashton] #4439 +* Orbital: Update API version to 9.0 [gasb150] #4440 +* Plexo: Add `meta_data` fields and reorder amount object in response [ajawadmirza] #4441 +* Plexo: Change field name from `meta_data` to `metadata` [ajawadmirza] #4443 +* Simetrik: Update `vat` to be in cents [simetrik-frank] #4425 +* Cybersource: Handle Amex cryptograms [heavyblade] #4445 +* Rapyd: Pass fields to `refund` and `store` [naashton] #4449 +* VPOS: Allow reuse of encryption key [therufs] #4450 +* Orbital: Add `payment_action_ind` field and refund through credit card to support tandem implementation [ajawadmirza] #4420 +* Airwallex: Send `referrer_data` on setup transactions [drkjc] #4453 +* Adyen and StripPI: Updated error messaging [mbreenlyles] #4454 +* Airwallex: Update `referrer_data` field [drkjc] #4455 +* Simetrik: Update `order_id` and `description` to be top level fields [simetrik-frank] #4451 +* Plexo: Update `ip`, `description`, and `email` fields request format and scrub method to not filter cardholder name and reference id [ajawadmirza] #4457 +* Plexo: Update `verify` implementation and add `verify_amount` field [ajawadmirza] #4462 +* Vanco: Update `purchase` to complete a purchase transaction with an existing session id [BritneyS] #4461 +* Authorize.net: Allow custom verify_amount and validate it [jherreraa] #4464 +* Shift4: Add gateway adapter [ali-hassan] #4415 +* Rapyd: Correctly add `billing_address` [naashton] #4465 +* Credorax: Update processor response messages [jcreiff] #4466 +* Shift4: add `customer_reference`, `destination_postal_code`, `product_descriptors` fields and core refactoring [ajawadmirza] #4469 +* Paypal Express: Add checkout status to response object [mbreenlyles] #4467 +* Shift4: Scrub security code [naashton] #4470 +* Shift4: Update `cardOnFile` transaction requests [ajawadmirza] #4471 +* Plexo: Update `success_from` definition [ajawadmirza] #4468 +* Rapyd: Un-nest the payment urls [naashton] #4472 +* Paypal Express: Correct naming mistake for accessor [mbreenlyles] #4473 +* GlobalCollect: Enable Google Pay and Apple Pay [gasb150] #4388 +* Shift4: $0 auth [naashton] #4474 +* CyberSource: Updatie API version to 1.198 and fix 3DS test [cristian] #4456 +* Shift4: add `store` method, `present` field in card, and to pass amount in cents [ajawadmirza] #4475 +* Shift4: add `3ds2` implementation [ajawadmirza] #4476 +* Shift4: update `success_from` definition to consider response code [ajawadmirza] #4477 +* Rapyd: Customer Object [naashton] #4478 +* Shift4: Verify Endopint Fix [naashton] #4479 +* CheckoutV2: Scrub cryptogram and credit card number [ajawadmirza] #4488 +* CheckoutV2: Add `3ds.status` field to send status of 3DS flow of all 3DS transactions [BritneyS] #4492 +* CheckoutV2: Add `challenge_indicator`, `exemption`, `authorization_type`, `processing_channel_id`, and `capture_type` fields [ajawadmirza] #4482 +* Add `mada` card type and associated BINs; add support for `mada` in CheckoutV2 gateway [dsmcclain] #4486 +* Authorize.net: Refactor custom verify amount handling [jherreraa] #4485 +* EBANX: Change amount for Colombia [flaaviaa] #4481 +* Worldpay: Update `required_status_message` and `message_from` methods for response. [rachelkirk] #4493 +* CheckoutV2: Add support for transactions through OAuth [ajawadmirza] #4483 +* Vanco: Update unit test to remove remote call to gateway [ajawadmirza] #4497 +* Shift4: remove support for 3ds2 [ajawadmirza] #4503 +* Rapyd: Add support for stored credential [ajawadmirza] #4487 +* MerchantE: Update `store` and add `verify` method [ajawadmirza] #4507 +* Shift4: Add default `numericId`, add `InterfaceVersion`, `InterfaceName`, and `CompanyName` header fields, change date time format and allow merchant time zone [ajawadmirza] #4509 +* BraintreeBlue: Add support for partial capture [aenand] #4515 +* Rapyd: Change key name to `network_transaction_id` [ajawadmirza] #4514 +* CyberSource: Handle unsupported Network Token brands [heavyblade] #4500 +* Ingenico(Global Collect): Add support for `payment_product_id` [rachelkirk] #4521 +* Adyen: Add network transaction id to store call [jcreiff] #4522 +* Worldpay: Add machine cookie to subsequent calls during 3DS challenge [mbreenlyles] #4513* +* Shift4: Scrub `securityCode` fix [naashton] #4524 +* Credorax: Update `OpCode` for credit transactions [dsmcclain] #4279 +* CheckoutV2: Add `credit` method [ajawadmirza] #4490 +* Stripe Payment Intents: Add `options` for retrieve_setup_intent [aenand] #4529 +* CheckoutV2: Send payment id via `incremental_authorization` field [ajawadmirza] #4518 +* Shift4: Add card `present` field, use previous transaction authorization for capture, and hardcode header values [ajawadmirza] #4528 +* Orbital: Remove `DPANInd` field for RC transactions [ajawadmirza] #4502 +* EBANX: Add Spreedly tag to payment body [flaaviaa] #4527 +* Shift4: Add `expiration_date` field for refund transactions [ajawadmirza] #4532 +* Improve handling of AVS and CVV Results in Multiresponses [gasb150] #4516 +* Airwallex: Add `skip_3ds` field for create payment transactions [ajawadmirza] #4534 +* Shift4: Typo correction for `initial_transaction` [ajawadmirza] #4537 +* Rapyd: Pass Customer ID and fix `add_token` method [naashton] #4538 +* Shift4: If no timezone is sent on transactions, the code uses the hours and minutes as a timezone offset [ali-hassan] #4536 +* Priority: Add support for general credit and updating cvv and zip [priorityspreedly] #4517 +* Worldpay: Update actions for generated message in `required_status_message` method [rachelkirk] #4530 +* Adyen: Modify handling of countryCode for ACH [jcreiff] #4543 +* CardConnect: update api end-point urls [heavyblade] #4541 +* Vantiv(Litle): Add support for `fraudFilterOverride` field [rachelkirk] #4544 +* Stripe: Add shipping address [jcreiff] #4539 +* PayuLatam: Add extra1, extra2, extra3 fields [jcreiff] #4550 +* Paysafe: Add fundingTransaction object [jcreiff] #4552 +* MerchantE: Add tests for `moto_ecommerce_ind` field [ajawadmirza] #4554 +* Plexo: Update `purchase` method, add flags for header fields, add new fields `billing_address`, `identification_type`, `identification_value`, and `cardholder_birthdate` [ajawadmirza] #4540 +* Rapyd: Remove `BR`, `MX`, and `US` from supported countries [ajawadmirza] #4558 +* Stripe Payment Intents: fix bug with billing address email [jcreiff] #4556 +* Shift4: Add customer to `purchase` & `store` and remove transaction from `store` [ajawadmirza] #4557 +* MerchantE: only add `moto_commerce_ind` to request if it is present [ajawadmirza] #4560 +* Add BpPlus card type along with custom validation logic [dsmcclain] #4559 +* PayTrace: Support ACH implementation for new endpoints and request body [ajawadmirza] #4545 +* Rapyd: No force capture for ACH [naashton] #4562 +* Shift4: Applied checks on Shift4 Time/Timezone offset [ali-hassan] #4561 +* Alelo: Add gateway [heavyblade] #4555 +* Wompi: Allow partial refund amount on void_sync [jcreiff] #4535 +* Shift4: Timezone Offset [naashton] #4566 +* MerchantE: `recurring_pmt_num` and `recurring_pmt_count` fields [ali-hassan] #4553 +* Orbital: Add South African Rand to supported currencies [molbrown] #4569 +* Orbital: Fix CardSecValInd [molbrown] #4563 +* Shift4: Add `usage_indicator`, `indicator`, `scheduled_indicator`, and `transaction_id` fields [ajawadmirza] #4564 +* Shift4: Retrieve `access_token` once [naashton] #4572 +* Redsys: Update Base64 encryption handling for secret key [jcreiff] #4565 +* Shift4: refuse `postalCode` when its null [ajawadmirza] #4574 +* Plexo: Update param key to `refund_type` [ajawadmirza] #4575 +* Shift4: Update request params for `verify`, `capture`, and `refund` [ajawadmirza] #4577 +* CyberSource: Add support for `sec_code` [rachelkirk] #4581 +* BraintreeBlue: Correctly vault payment method token for PayPal Checkout with Vault [almalee24] #4579 +* BpPlus: Allow spaces in card number [ajawadmirza] #4585 +* Shift4: Decline referral transactions and parse message for internal server errors [ajawadmirza] #4583 +* Litle: Update homepage_url [gasb150] #4491 +* Priority: Update credential handling [therufs] #4571 +* Shift4: Fix authorization and remove `entryMode` from verify and store transactions [ajawadmirza] #4589 + +== Version 1.126.0 (April 15th, 2022) +* Moneris: Add 3DS MPI field support [esmitperez] #4373 +* StripePI: Add ability to change payment_method_type to confirm_intent [aenand] #4300 +* GlobalCollect: Improve support for Naranja and Cabal card types [dsmcclain] #4286 +* Payflow: Add support for stored credentials [ajawadmirza] #4277 +* Orbital: Don't void $0 auths for Verify [javierpedrozaing] #2487 +* StripePI: Enable Apple Pay and Google Pay payment methods [gasb150] #4252 +* PaySafe: Update `unstore` method and authorization for redact [ajawadmirza] #4294 +* CyberSource: Add `national_tax_indicator` fields in authorize and purchase [ajawadmirza] #4299 +* NMI: Update gateway credentials to accept security_key [javierpedrozaing] #4302 +* PaySafe: Fix commit for `unstore` method [ajawadmirza] #4303 +* Ebanx: Add support for `order_number` field [ali-hassan] #4304 +* BlueSnap: Add support for `idempotency_key` field [drkjc] #4305 +* Paymentez: Update `capture` method to verify by otp for pending transactions [ajawadmirza] #4267 +* BlueSnap: Update refund request and endpoint along with merchant transaction support [ajawadmirza] #4307 +* DecidirPlus: Added `authorize`, `capture`, `void`, and `verify` methods [ajawadmirza] #4284 +* Paymentez: Fix `authorize` to call `purchase` for otp flow [ajawadmirza] #4310 +* Orbital: Indicate support for network tokenization [dsmcclain] #4309 +* IPG: remove `uruguay` from supported countries [ajawadmirza] #4311 +* Decidir: Add sub_payments sub-fields to gateway [meagabeth] #4315 +* Priority: Add additional fields to purchase and capture requests [dsmcclain] #4301 +* DecidirPlus: Added `unstore` method [ajawadmirza] #4317 +* Decidir & Decidir Plus: Revise handling of `sub_payment` sub-fields [meagabeth] #4318 +* DecidirPlus: Update `unstore` implementation to get token from params [ajawadmirza] #4320 +* CyberSource: Add option for zero amount verify [gasb150] #4313 +* PayU Latam: Refactor `message_from` method, fix failing remote tests [rachelkirk] #4326 +* Adyen: Add currencies with three decimals places [gasb150] #4322 +* GlobalCollect: Stregthen success criteria for void action [peteroas] #4324 +* Priority Payment Systems - Clean up/refactor gateway file and tests [ali-hassan] #4327 +* SafeCharge: change `verify` to send 0 amount [dsmcclain] #4332 +* DLocal: add support for `force_type` field [dsmcclain] #4336 +* Barclaycard SmartPay: Support more nonstandard currencies [jherreraa] #4335 +* DecidirPlus: `name_override` option on `store` [naashton] #4338 +* Priority: Update `add_purchases_data` to return if `options[:purchases]` is empty [drkjc] #4349 +* Stripe PI: update `shipping` field to `shipping_address` [ajawadmirza] #4347 +* DecidirPlus: Handle `payment_method_id` by `card_brand` [naashton] #4350 +* DecidirPlus: `debit` and `payment_method_id` fields [naashton] #4351 +* Adyen: Include Application ID in adyen authorize and purchase transactions [peteroas] #4343 +* Priority: Add support for `replay_id` field [drkjc] #4352 +* Stripe PI: standardize `shipping_address` fields [dsmcclain] #4355 +* Airwallex: support gateway [therufs] #4342 +* Litle: Translate google_pay as android_pay [javierpedrozaing] #4331 +* Braintree: Add ACH support for store [cristian] #4285 +* Simetrik: Add support for Simetrik gateway [simetrik-frank] #4339 +* EBANX: Change amount for Mexico and Chile [flaaviaa] #4337 +* DecidirPlus: Add `establishment_name`, `aggregate_data`, `sub_payments`, `card_holder_identification_type`, `card_holder_identification_number`, `card_door_number`, and `card_holder_birthday` fields [ajawadmirza] #4361 +* DecidirPlus: Update `error_code_from` to get error reason id [ajawadmirza] #4364 +* Dlocal: Add three_ds mpi support [cristian] #4345 +* Stripe PI: Add `request_three_d_secure` field for `create_setup_intent` [aenand] #4365 +* Adyen: Add `verify_amount` field for verify [ajawadmirza] #4369 +* Stripe PI: Pass options for tokenizing Apple/Google Pay [gasb150] #4368 +* Dlocal: Format 3DS mpi enrollment data correctly [cristian] #4371 +* Airwallex: QA fixes for option handling [therufs] #4367 +* CardConnect: Fixed duplicate(concat) Address sent - card_connect is concat. address1 and 2 causing a AVS error [ahmirza] #4362 +* CyberSource: Remove Pinless Debit Transaction Functionality [peteroas] #4370 +* Litle: Add support for Level 2 and 3 enhanced data [curiousepic] #4360 +* Rapyd: Add gateway support [meagabeth] #4372 +* CyberSource: Update and fix test coverage [peteroas] #4374 +* Airwallex: QA fixes for address and create_setup_intent handling [therufs] #4377 +* Airwallex: add `descriptor` field and update logic for sending `request_id` and `merchant_order_id` [dsmcclain] #4379 +* Visanet Peru: use timestamp instead of random for purchaseNumber [therufs] #4093 +* Orbital: add `verify_amount` field [ajawadmirza] #4376 +* Credorax: add `recipient_street_address`, `recipient_city`, `recipient_province_code`, and `recipient_country_code` fields [ajawadmirza] #4384 +* Airwallex: add support for stored credentials [drkjc] #4382 +* Rapyd: Add metadata and ewallet_id options [naashton] #4387 +* Priority: Add additional fields to request and refactor gateway integration [dsmcclain] #4383 +* Rapyd: Update `type` option to `pm_type` [naashton] #4391 +* Conekta: Fix remote test [javierpedrozaing] #4386 +* NMI: Update post URL [jherreraa] #4380 +* Multiple Gateways: Resolve when/case bug [naashton] #4399 +* Airwallex: Add 3DS MPI support [drkjc] #4395 +* Add Cartes Bancaires card bin ranges [leahriffell] #4398 +* Airwallex: Add support for `original_transaction_id` field [drkjc] #4401 +* Securion Pay: Pass external 3DS data [jherreraa] #4404 +* Airwallex: Update Stored Credentials testing, remove support for `original_transaction_id` field [drkjc] 4407 + +== Version 1.125.0 (January 20, 2022) +* Wompi: support gateway [therufs] #4173 +* Stripe Payment Intents: Add setup_purchase [aenand] #4178 +* Ipg: Add new gateway [ajawadmirza] #4171 +* Worldpay: Adding support for google pay and apple pay [cristian] #4180 +* Worldpay: Adding scrubbing for network token transactions [cristian] #4181 +* SafeCharge: Add sg_NotUseCVV field [ajawadmirza] #4177 +* PayULatam: Correctly map maestro and condensa card types [dsmcclain] #4182 +* StripePaymentIntents: Refactor response for setup_purchase [aenand] #4183 +* Wompi: cast error messages to JSON [therufs] #4186 +* NMI: Omit initial_transaction_id for CIT [aenand] #4189 +* Priority: Support Priority Payment Systems gateway [jessiagee] #4166 +* GlobalCollect: Support for Lodging Data [naashton] #4190 +* IPG: Add support for sub-merchant and recurring type fields [ajawadmirza] # 4188 +* Wompi: Support `installments` option [therufs] #4192 +* Stripe PI: add support for `fulfillment_date` and `event_type` [dsmcclain] #4193 +* Paysafe: Adjust logic for sending 3DS field [meagabeth] #4194 +* Priority: Fix unit test cases [ajawadmirza] #4195 +* EBANX: New Gateway Specific Receiver [spreedly-kledoux] #4198 +* Wompi: Don't send CVV field if no CVV provided [therufs] #4199 +* Worldpay: cleaning order_id according to worldpay rules [cristian] #4197 +* Paysafe: Concatenate credentials for headers [meagabeth] #4201 +* Stripe Payment Intents: Add metadata to setup_purchase [aenand] #4202 +* Priority: Add gateway standard changes [ajawadmirza] #4200 +* IPG: Add support for payment by token [ajawadmirza] #4191 +* Element (Vantiv Express): Add support for general credit [dsmcclain] #4203 +* Worldpay: Update supported countries list, currencies [jherreraa] #4207 +* StripePI: Adding countries available. [gasb150] #4208 +* Orbital: Adding google pay payment tests for Orbital. [ajawadmirza] #4205 +* Bug: Fixing supported countries method when there is inheritance involved [cristian] #4211 +* Mundipagg: Update success method [ajawadmirza] #4210 +* Worldpay: Add support for Visa Direct Fast Funds Credit [dsmcclain] #4212 +* Paysafe: Add support for stored credentials [meagabeth] #4214 +* Worldpay: Adding missing countries to supported countries [cristian] #4213 +* Update institution numbers for Canadian banks [therufs] #4216 +* Worldpay: Set default eCommerce indicator for EMVCO network tokens [shasum] #4215 +* Update handling routing numbers for Canadian banks [therufs] #4217 +* Stripe: API version updated [jherreraa] #4209 +* Mercado Pago: Update verify method [ajawadmirza] #4219 +* DLocal: Set API Version [gasb150] #4222 +* Wompi: Add support for Authorize and Capture [rachelkirk] #4218 +* Priority: Update source and billing address checks [jessiagee] #4220 +* Pin Payments: Add support for `diners_club`, `discover`, and `jcb` cardtypes [montdidier] #4142 +* USA ePay: Add store method [ajawadmirza] #4224 +* IPG: Quick fix to remove warning [ajawadmirza] #4225 +* Remove YAML warning on load_fixtures_method [jherreraa] #4226 +* Worldpay: Add support for tokenizing payment methods with transaction identifiers [dsmcclain] #4227 +* USA ePay: Update implementation to send valid authorization [ajawadmirza] #4231 +* USA ePay: Add store test, update authorize param [jessiagee] #4232 +* Stripe: Update destination test account [jherreraa] #4234 +* Add skip_response option on request check for commit stubs [cristian] #4223 +* Pin Payments: Add support for `void` and New Zealand to supported countries. [montdidier] #4144 +* Wompi: Update authorization in `capture` method. [rachelkirk] #4238 +* IPG: Update authorization to support `store` method token. [ajawadmirza] #4233 +* Paymentez: Update card mappings [ajawadmirza] #4237 +* Priority: Update parsing for error messages [jessiagee] #4245 +* GlobalCollect: Support for Airline Data [naashton] #4187 +* IPG: Add `tpv_error_code` and `tpv_error_msg` fields [ajawadmirza] #4241 +* StripePI: Set restriction for Apple/Google Pay [jherreraa] #4247 +* Cashnet: support multiple itemcodes and amounts [peteroas] #4243 +* IPG: Send default currency in `verify` and two digit `ExpMonth` [ajawadmirza] #4244 +* Stripe: Add remote tests set up to avoid exceed the max external accounts limit [jherreraa] #4239 +* Stripe: Add support for `radar_options: skip_rules` [dsmcclain] #4250 +* CyberSource: Add `user_po`, `taxable`, `national_tax_indicator`, `tax_amount`, and `national_tax` fields [ajawadmirza] #4251 +* Kushki: Add support for `metadata` [rachelkirk] #4253 +* IPG: Add `redact` operation [ajawadmirza] #4254 +* Wompi: Update sandbox and production endpoints [rachelkirk] #4255 +* Orbital: Add `sca_merchant_initiated` operation [ajawadmirza] #4256 +* Cashnet: convert amounts to integers for proper gateway handling [peteroas] #2207 +* PayTrace: Add `unstore` operation [ajawadmirza] #4262 +* Decidir Plus: Add gateway adapter [naashton] #4264 +* CheckoutV2: Add support for Apple Pay and Google Pay tokens [AMHOL] #4235 +* Decidir Plus: Update payment reference [naashton] #4271 +* Paysafe: Update redact method [meagabeth] #4269 +* CyberSource: Add `line_items` field in authorize method [ajawadmirza] #4268 +* CheckoutV2: Support processing channel and marketplace sub entity ID [AMHOL] #4236 +* Elavon: `third_party_token` bug fix [rachelkirk] #4273 +* Decidir Plus: Add `sub_payments` field [naashton] #4274 +* Pin Payments: Add `unstore` support [montdidier] #4276 +* Orbital: Add support for $0 verify [javierpedrozaing] #4275 +* Update inline documentation with all supported cardtypes [ali-hassan] #4283 +* PayWay: Update endpoints, response code [jessiagee] #4281 +* CyberSource: Add `line_items` for purchase [ajawadmirza] #4282 +* Payflow Pro: Add `stored_credential` fields [ajawadmirza] #4277 +* Decidir Plus: Add `fraud_detection` fields [naashton] #4289 + +== Version 1.124.0 (October 28th, 2021) +* Worldpay: Add Support for Submerchant Data on Worldpay [almalee24] #4147 +* dlocal: Add device_id and ip to payer object and add additional_data [aenand] #4116 +* Adyen: Add network tokenization support to Adyen gateway [mymir] #4101 +* Adyen: Add ACH Support [almalee24] #4105 +* Moka: Support 3DS endpoint and update test url [dsmcclain] #4110 +* Paysafe: Adjust profile data [meagabeth] #4112 +* Stripe Payment Intents: Add support for claim_without_transaction_id field [BritneyS] #4111 +* Mit: Add New Gateway [EsporaInfra] #3820 +* Routex: add card type [rachelkirk] #4115 +* Orbital: Scrub Payment Cryptogram [naashton] #4121 +* Paysafe: Add support for airline fields [meagabeth] #4120 +* Stripe and Stripe PI: Add Radar Session Option [tatsianaclifton] #4119 +* PayArc: Fix billing address nil and phone_number issues [dsmcclain] #4114 +* Routex: Update BIN numbers [rachelkirk] #4123 +* UnionPay: Add Stripe's UnionPay test card to UnionPay BIN range #4122 +* GlobalCollect: Support URL override [naashton] #4127 +* PayConex: scrub bank account info from transcripts [mbreenlyles] #4128 +* Moka: Remove additional transaction data from subsequent calls [naashton] #4129 +* Moka: Ensure CvcNumber can be an empty string [jessiagee] #4130 +* Maestro: Allow more card lengths for Luhnless bins [therufs] #4131 +* Paysafe: Update supported countries [meagabeth] #4135 +* Paysafe: Update field mapping for split_pay [meagabeth] #4136 +* SafeCharge: Add handling for non-fractional currencies [dsmcclain] #4137 +* CardStream: Support passing country_code in request [dsmcclain] #4139 +* Adyen: Adjust phone number mapping [aenand] #4138 +* Mit: Change how parameters are converted to JSON [tatsianaclifton] #4140 +* Stripe: Add account_number to scrubbing [aenand] #4145 +* Stripe PI: add name on card to billing_details [dsmcclain] #4146 +* TrustCommerce: Scrub bank account info [mbreenlyles] #4149 +* TransFirst: Scrub account number [aenand] #4152 +* Paysafe: Update supported countries list [meagabeth] #4154 +* dLocal: Update supported countries list [mbreenlyles] #4155 +* SafeCharge: Add support for email field in capture [rachelkirk] #4153 +* Paysafe: Remove invalid code [meagabeth] #4156 +* NMI: Add descriptor fields [ajawadmirza] #4157 +* Authorize.net: Add tests for scrubbing banking account info (in addition to BluePay, BridgePay, Forte, HPS, and Vanco Gateways)[aenand] #4159 +* Moka: Send refund amount with decimal [dsmcclain] #4160 +* GlobalCollect: Append URI to the URL [naashton] #4162 +* Adyen: Add application info fields [aenand] #4163 +* Adyen: Send NTID from stored cred hash [curiousepic] #4164 +* Payflow: use proper case for 3DS 2.x element names [bbraschi] #4113 +* Realex: Add support for stored credentials [dsmcclain] #4170 +* Moka: Add support for InstallmentNumber field [dsmcclain] #4172 +* Payflow: include AuthenticationStatus for 3DS 2.x [bbraschi] #4168 + +== Version 1.123.0 (September 10th, 2021) +* Paysafe: Add gateway integration [meagabeth] #4085 +* Elavon: Support recurring transactions with stored credentials [cdmackeyfree] #4086 +* Orbital: Truncate three_d_secure[:version] [carrigan] #4087 +* Credorax: Determine ISK decimal by datetime [curiousepic] #4088 +* Moka: support new gateway type [dsmcclain] #4089 +* Paymentez: Add more_info field [reblevins] #4091 +* Worldpay: Support $0 auth [therufs] #4092 +* Elavon: Support recurring transactions with token, revert stored credentials recurring [cdmackeyfree] #4089 +* SafeCharge(Nuvei): Add support for product_id [rachelkirk] #4095 +* NMI: Change cardholder_auth 3DS field population [carrigan] #4094 +* Synchrony: add card type [therufs] #4096 +* Maestro: support BINs without Luhn check [therufs] #4097 +* Maestro: support BINs [therufs] #4098 +* Redsys: Route MIT Exemptions to webservice endpoint [curiousepic] #4081 +* Adyen: Update Classic Integration API to v64 and Recurring API to v49 [almalee24] #4090 +* Payeezy: support soft_descriptor and merchant_ref [cdmackeyfree] #4099 +* Elavon: add ssl_token field [cdmackeyfree] #4100 +* Credorax: Remove special logic for ISK [curiousepic] #4102 +* UnionPay: Pull UnionPay's 62* BIN ranges out of Discover's #4103 +* Monei: Update Creation of Billing Details [tatsianaclifton] #4107 +* Monei: Typo Correction on Billing Details [tatsianaclifton] #4108 +* Paysafe: Add support for 3DS [meagabeth] #4109 + +== Version 1.122.0 (August 3rd, 2021) +* Orbital: Correct success logic for refund [tatsianaclifton] #4014 +* usaepay: Added pin gateway setting [DustinHaefele] #4026 +* MercadoPago: Added external_reference, more payer object options, and metadata field [DustinHaefele] #4020 +* Element: Add duplicate_override_flag [almalee24] #4012 +* PayTrace: Support gateway [meagabeth] #3985 +* vPOS: Support credit + refund [therufs] #3998 +* PayArc: Support gateway [senthil-code] #3974 +* NMI: Support cardholder_auth field for 3DS2 [cdmackeyfree] #4002 +* Confiable: Support cardtype [therufs] #4004 +* Maestro: Add BIN [therufs] #4003 +* PayULatam: Ensure phone number is pulled from shipping_address correctly [dsmcclain] #4005 +* SafeCharge: Add challenge_preference for 3DS [klaiv] #3999 +* Adyen: Pass networkTxReference in all transactions [naashton] #4006 +* Adyen: Ensure correct transaction reference is selected [dsmcclain] #4007 +* PayTrace: Support level_3_data fields [meagabeth] #4008 +* BluePay: Add support for Stored Credentials [dsmcclain] #4009 +* Orbital: Add support for SCARecurringPayment [jessiagee] #4010 +* Braintree: Support recurring_first and moto reasons [curiousepic] #4013 +* PayTrace: Adjust capture method [meagabeth] #4015 +* BarclaysEpdqExtraPlus: updated custom_eci test + remote tests [yyapuncich] #4022 +* CyberSource: Add customerID field [deemeyers] #4025 +* CyberSource: Adjust Auth [naashton] #3956 +* Valid Canadian Institution Numbers [naashton] #4024 +* PayTrace: Adjust purchase and capture methods to handle MultiResponse scenarios [meagabeth] #4027 +* Payflow: Add support for MERCHDESCR field [rachelkirk] #4028 +* PayTrace: Support $0 authorize in verify method [meagabeth] #4030 +* PayArc: Add error_code in response [cdm-83] #4021 +* Update bank routing account validation check [jessiagee] #4029 +* Kushki: Add 'contactDetails' fields [mbreenlyles] #4033 +* Adyen: Truncating order_id and remote test [yyapuncich] #4036 +* CyberSource: Allow string content for Ignore AVS/CVV flags [curiousepic] #4043 +* Decidir: Update validation error message handling [arbianchi] #4042 +* Authorize.net: Remove cardholderAuthentication for non-3DS transactions [BritneyS] #4045 +* BlueSnap: Handle 429 errors [britth] #4044 +* Orbital: Update unit test files [meagabeth] #4046 +* Orbital: Strip null characters from responses [britth] #4041 +* Merchant Warrior: Handle invalid XML responses [arbianchi] #4047 +* Braintree: Fix NoMethodError for failed card verification [molbrown] #4048 +* Worldpay: Accepting 3DS1 and 3DS2 authentication data from external MPI [chandan-PS] #4017 +* PayArc: Currency and parameters updates [jessiagee] #4051 +* Elavon: Add support for special characters [mbreenlyles] #4049 +* PayArc: Formatting CC month, adding tax_rate, removing default void reason [jessiagee] #4053 +* Kushki: Add support for fullResponse field [rachelkirk] #4057 +* Element: Add support for `MerchantDescriptor` field [BritneyS] #4058 +* PayArc: Added email and phone to credit and charge [jessiagee] #4056 +* Mundipagg: Added support for 'authentication_secret_key' for 'api_key' overwrite [DustinHaefele] #4059 +* Payflow: Raise an error if store method is called [dsmcclain] #4066 +* Monei: JSON API implementation [jimmyn] #3613 +* Maestro: Update BINs [therufs] #4067 +* Monei: Change domain to monei.com [jimmyn] #4068 +* Spreedly: Support gateway_specific_response_fields in response params [abarrak] #4064 +* Payeezy: Add support for `add_soft_descriptors` [rachelkirk] #4069 +* Stripe Payment Intents: Add support for network_transaction_id field [cdmackeyfree] #4060 +* Worldpay: Support 'CAPTURED' response for authorize transactions [naashton] #4070 +* Ingenico (Global Collect): New idempotence key header [BritneyS] #4073 +* PayTrace: Adjust handling of line_items subfields [meagabeth] #4074 +* Worldpay: Correct Expiration Year Format [tatsianaclifton] #4076 +* Monei: Improve Scrub Regex [tatsianaclifton] #4072 +* Payflow: add THREEDSVERSION and DSTRANSACTIONID when present [bbraschi] #4075 +* CT Payments: update remote tests [cdmackeyfree] #3947 +* Orbital: Ensure full e-check scrubbing [mbreenlyles] #4079 + +== Version 1.121 (June 8th, 2021) +* Braintree: Lift restriction on gem version to allow for backwards compatibility [naashton] #3993 +* Payment Express/Windcave: Send amount on verify calls [cdmackeyfree] #3995 +* Orbital: Use billing_address name as fallback [curiousepic] #3966 +* vPOS: handle shop_process_id correctly [therufs] #3996 +* Checkout v2: Support metadata field [saschakala] #3992 +* Adyen: Support networkTxReference field [naashton] #3997 +* Paypal Express: Enable PayPal express reference transaction request to send merchant session id [janees-e] #3994 + +== Version 1.120.0 (May 28th, 2021) +* Braintree: Bump required braintree gem version to 3.0.1 +* Stripe PI: ensure `setup_future_sage` and `off_session` work when using SetupIntents. +* Orbital: Update commit to accept retry_logic in params [jessiagee] #3890 +* Orbital: Update remote 3DS tests [jessiagee] #3892 +* Mercado Pago: support Creditel card type [therufs] #3893 +* Payeezy: Update error mapping [meagabeth] #3896 +* HPS: Add support for stored_credential [cdmackeyfree] #3894 +* Orbital: Ensure payment_detail sends for ECP [jessiagee] #3899 +* Payeezy: Update `error_code_from` method [meagabeth] #3900 +* Worldpay: Add support for `statementNarrative` field [meagabeth] #3901 +* Mercado Pago: Give ability to pass capture option in authorize txn field [naashton] #3897 +* Orbital: Ensure correct fields sent in refund [jessiagee] #3903 +* WorldPay: remove some defaults in billing address [carrigan] #3902 +* Adyen: Support for General Credit [naashton] #3904 +* Worldpay: reintroduce address1 and city defaults [carrigan] #3905 +* Stripe: ensure potentially nested data is scrubbed #3907 +* Stripe PI: Send Validate on Payment Method Attach [tatsianaclifton] #3909 +* Adyen: Update handling of authorization returned from gateway [meagabeth] #3910 +* Update gateway templates for Rubocop compliance [therufs] #3912 #3895 +* Orbital: Send AVSname for all eCheck transactions [jessiagee] #3911 +* Litle: update support of customerId field [cdmackeyfree] #3913 +* Payment Express: fix signature for `verify` [therufs] #3914 +* Forte: Send xdata fields [dsmcclain] #3915 +* PaywayDotCom: Add New Gateway [DanAtPayway] #3898 +* Orbital: Remove unnecessary requirements [jessiagee] #3917 +* SafeCharge (Nuvei): Add network tokenization support [DStoyanoff] #3847 +* Stripe PI: Enhance testing of SetupIntents API #3908 +* SafeCharge (Nuvei): Fix NT related bug [jimilpatel24] #3921 +* Worldpay: Only override cardholdername for 3ds tests [curiousepic] #3918 +* Orbital: Add support for general credit [meagabeth] #3922 +* Banco Sabadell: Ensure sca_exemption field is used #3923 +* Redsys: Refactor XML character escape logic #3925 +* HPS: Strip zip codes of non-alphanumeric characters [dsmcclain] #3926 +* Orbital: $0 PreNote using authorize for eCheck force_capture [jessiagee] #3927 +* Worldpay: synchronous response changes [naashton] #3928 +* PaywayDotCom: Add more thorough scrubbing [tatsianaclifton] #3929 +* Remove CONTRIBUTING.md and update README.md to reflect new repository wiki [dsmcclain] #3930 +* Qvalent: Add customer_reference_number [fredo-] #3931 +* Orbital: Add 'ND' ECPActionCode to $0 Prenote Check [jessiagee] #3935 +* Checkout: Add support for stored_credential [meagabeth] #3934 +* Credorax: Add support for 3ds_reqchallengeind [dsmcclain] #3936 +* Adyen: cancelOrRefund endpoint when passed as option [naashton] #3937 +* Qvalent: Add customer reference number FIX [fredo-] #3939 +* Orbital: Pass line_items in capture [jessiagee] #3941 +* BraintreeBlue: Add support for $0 auth verification [meagabeth] #3944 +* JCB: Add additional BIN ranges [dsmcclain] #3946 +* vPOS: Support new gateway type [therufs] #3906 +* Braintree: Add support for AVS and CVV results in $0 credit card verification transactions [meagabeth] #3951 +* Braintree: Return cvv_code and avs_code in response [meagabeth] #3952 +* vPOS: Stringify values [therufs] #3954 +* Payeezy: Send level2 fields [dsmcclain] #3953 +* Credorax: adjust logic for sending 3ds shipping address fields [dsmcclain] #3959 +* Orbital: Ensure ECP always sends AVSName [jessiagee] #3963 +* Orbital: Add middle name to EWSMiddleName for ECP [jessiagee] #3962 +* Support Canadian Bank Accounts [naashton] #3964 +* Windcave/Payment Express: Add support for AvsAction and EnableAVSData fields [meagabeth] #3967 +* CyberSource: Update XML tag for merchantDefinedData [meagabeth] #3969 +* Elavon: Send ssl_vendor_id field [dsmcclain] #3972 +* Credorax: Add support for `echo` field [meagabeth] #3973 +* Worldpay: support cancelOrRefund via options [therufs] #3975 +* Payeezy: support general credit [cdmackeyfree] #3977 +* Ripley and Hipercard: Add BIN ranges [naashton] #3978 +* Adyen: Default card holder name for credit cards [shasum] #3980 +* PaywayDotCom: make `source_id` a required field [dsmcclain] # 3981 +* Qvalent: remove `pem_password` from required credentials [dsmcclain] #3982 +* Authorize.net: Fix stored credentials [tatsianaclifton] #3971 +* CyberSource: Add support for multiple new fields [dsmcclain] #3984 +* CASHNet: Update gateway adapter [dsmcclain] #3986 +* Elavon: Send `ssl_vendor_id` field via options on gateway initialization [dsmcclain] #3989 + +== Version 1.119.0 (February 9th, 2021) +* Payment Express: support verify/validate [therufs] #3874 +* GlobalCollect: Truncate address fields [meagabeth] #3878 +* Litle: Truncate address fields [meagabeth] #3877 +* Netbanx: Add-customer-information(name,email,IP)-to-a-transaction [rockyhakjoong] #3754 +* Netbanx: Adjust the avs and cvv return code in shopify [rockyhakjoong] #3833 +* Decidir: Improve error mapping [meagabeth] #3875 +* Worldpay: support `skip_capture` [therufs] #3879 +* Redsys: Add new response code text [britth] #3880 +* Orbital: Update ECP details to use payment source [jessiagee] #3881 +* Alelo: Add additional BIN ranges [meagabeth] #3882 +* HPS: Update Add support for general credit [naashton] #3885 +* Elavon: Fix issue with encoding data sent in the request [naashton] #3865 +* Orbital: Update ECP to use EWS verification [jessiagee] #3886 +* Eway: Add 3ds field when do direct payment [GavinSun9527] #3860 +* Support Creditel cardtype [therufs] #3883 +* Elavon: Remove ampersand char from fields [naashton] #3891 + +== Version 1.118.0 (January 22nd, 2021) +* Worldpay: Add support for challengeWindowSize [carrigan] #3823 +* Adyen: Update capitalization on subMerchantId field [cdmackeyfree] #3824 +* Maestro and Elo: Update BIN ranges [meagabeth] #3822 +* HPS: Truncate invoice numbers that are too long [curiousepic] #3825 +* Pass network_transaction_id attribute in Response [therufs] #3815 +* Elavon: support standardized stored credentials [therufs] #3816 +* Decidir: update fraud_detection field [cdmackeyfree] #3829 +* Paymentez: Add Olimpica cardtype [meagabeth] #3831 +* SafeCharge: 3DS external MPI data refinements [curiousepic] #3821 +* Credorax: Add support for 3DS Adviser [meagabeth] #3834 +* Adyen: Support subMerchant data [mymir][therufs] #3835 +* Decidir: add device_unique_identifier to card data [cdmackeyfree] #3839 +* BraintreeBlue: add support for account_type field [jimilpatel24] #3840 +* Redsys: Add support for stored_credential [meagabeth] #3844 +* Redsys: add_payment method solution [meagabeth] #3845 +* Stripe Payment Intents: Add support for error_on_requires_action option [tatsianaclifton] #3846 +* Add 3DS 2.0 values to paypal [nebdil] #3285 +* Redsys: Update Mpi Fields [tatsianaclifton] #3855 +* Paypal: Update AuthStatus3ds MPI field [curiousepic] #3857 +* Orbital: Update 3DS support for Mastercard [meagabeth] #3850 +* Payeezy: Support standardized stored credentials [therufs] #3861 +* CyberSource: Update `billing_address` override [meagabeth] #3862 +* Paymentez: Add 3DS MPI field support [carrigan] #3856 +* BlueSnap: Add support `fraud-session-id` field [meagabeth] #3863 +* BlueSnap: Update handling of `transaction-fraud-info` fields [meagabeth] #3866 +* Payeezy: Allow no stored credential transaction id [therufs] #3868 +* Orbital: eCheck processing added [ajawadmirza] #3870 +* FirsdataE4V27: Fixes some apple pay transaction issues [pi3r] #3872 + +== Version 1.117.0 (November 13th) +* Checkout V2: Pass attempt_n3d along with 3ds enabled [naashton] #3805 +* GlobalCollect: Add support for Third-party 3DS2 data [molbrown] #3801 +* Authorize.net: Pass stored credentials [therufs] #3804 +* Authorize.net: Don't pass isFirstRecurringPayment [therufs] #3805 +* Litle: Add support for general credit transactions [naashton] #3807 +* Redsys: Add 3DS2 Integration Support [esmitperez] #3794 +* Cybersource: Use firstname/lastname from address instead of the payment method [pi3r] #3798 +* Add MPI functionality for SafeCharge gateway [daniel] #3809 +* SafeCharge: Standardize MPI fields [curiousepic] #3809 +* Credorax: Adds AMEX to supported cards and adds 1A error code [LinTrieu] #3792 +* Stripe PI: Pass external 3DS auth data [curiousepic] #3811 +* Credorax: Allow 3DS1 normalized pass-through, ease version matching [britth] #3812 +* Redsys: Redsys: Harden 3DS v1/v2 check for External MPI [esmitperez] #3814 +* Add card types for Stripe, Worldpay, Checkout.com [LinTrieu] #3810 +* ActiveMerchant::Billing::Response: Include `network_transaction_id` attribute [therufs] #3815 + +== Version 1.116.0 (October 28th) +* Remove Braintree specific version dependency [pi3r] #3800 + +== Version 1.115.0 (October 27th) +* Checkout v2: $0 Auth on gateway [jessiagee] #3762 +* Adyen: Safely add execute_threeds: false [curiousepic] #3756 +* RuboCop: Fix Layout/SpaceAroundEqualsInParameterDefault [leila-alderman] #3720 +* iATS: Allow email to be passed outside of the billing_address context [naashton] #3750 +* Orbital: Don't pass xid for transactions using network tokens [britth] #3757 +* Forte: Add service_fee_amount field [meagabeth] #3751 +* WorldPay: Add support for idempotency_key[cdmackeyfree] #3759 +* Orbital: Handle line_tot key as a string [naashton] #3760 +* RuboCop: Fix Lint/UnusedMethodArgument [leila-alderman] #3721 +* RuboCop: Fix Naming/MemoizedInstanceVariableName [leila-alderman] #3722 +* RuboCop: Fix Style/BlockComments [leila-alderman] #3729 +* Checkout V2: Move to single-transaction Purchases [curiousepic] #3761 +* RuboCop: Fix Naming/ConstantName [leila-alderman] #3723 +* Orbital: Fix schema errors [britth] #3766 +* Checkout V2: Start testing via amount code [curiousepic] #3767 +* CyberSource: Don't include empty `mdd_` fields [arbianchi] #3758 +* RuboCop: Fix Naming/VariableNumber [leila-alderman] #3725 +* Update BIN ranges for Elo cardtype [cdmackeyfree] #3769 +* Orbital: Resolve CardIndicators issue [meagabeth] #3771 +* Adyen: Add subMerchant fields [naashton] #3772 +* PayPal Express: reduce param requirements [shasum] #3773 +* PayU Latam: Support partial refunds [leila-alderman] #3774 +* RuboCop: Fix Style/Alias [leila-alderman] #3727 +* Stripe PI: Allow `on_behalf_of` to be passed alone #3776 +* RuboCop: Fix Performance/RedundantMatch [leila-alderman] #3765 +* RuboCop: Fix Layout/MultilineMethodCallBraceLayout [leila-alderman] #3763 +* NMI: Add standardized 3DS fields [meagabeth] #3775 +* Mundipagg: Add support for SubMerchant fields [meagabeth] #3779 +* Stripe Payment Intents: Add request_three_d_secure option [molbrown] #3787 +* Decidir: Add support for csmdds fields [naashton] #3786 +* RuboCop: Fix Performance/StringReplacement [leila-alderman] #3782 +* RuboCop: Fix Naming/HeredocDelimiterCase & Naming [leila-alderman] #3781 +* BlueSnap: Add address fields to contact info [naashton] #3777 +* RuboCop: Fix Layout/SpaceInsideHashLiteralBraces [leila-alderman] #3780 +* RuboCop: Fix Style/AndOr [leila-alderman] #3783 +* Checkout V2: Support ability to pass attempt_n3d 3ds field [naashton] #3788 +* Elavon: Upgrade to `processxml.do` [therufs] #3784 +* Checkout V2: Support for attempt_n3d 3DS field [naashton] #3790 +* Elavon: Strip ampersands [therufs] #3795 +* Paybox: Add support for 3DS 1.0 values [jcpaybox] #3335 +* Decidir: Add additional fraud_detection options [cdmackeyfree] #3812 + +== Version 1.114.0 +* BlueSnap: Add address1,address2,phone,shipping_* support #3749 +* BlueSnap: Protect against `nil` metadata [carrigan] #3752 +* Cybersource: [CyberSource] Ensure the default address doesn't override `ActionController::Parameters` [pi3r] #3755 + +== Version 1.113.0 +* Orbital: Add cardIndicators field [meagabeth] #3734 +* Openpay: Add Colombia to supported countries [molbrown] #3740 +* Mercado Pago: Update Device Id Header field [cdmackeyfree] #3741 +* RuboCop: Fix Style/TrailingCommaInHashLiteral [leila-alderman] #3718 +* RuboCop: Fix Naming/PredicateName [leila-alderman] #3724 +* RuboCop: Fix Style/Attr [leila-alderman] #3728 +* Payflow: Use application_id to set buttonsource [britth] #3737 +* HPS: Enable refunds using capture transaction [britth] #3738 +* Quickbooks: Omit empty strings in address [leila-alderman] #3743 +* BlueSnap: Add transactionMetaData support #3745 +* Orbital: Fix typo in PC3DtlLineTot field [naashton] #3736 +* Credorax: Send first and last name parameters for CFT transactions [britth] #3748 +* Orbital: Update CardIndicators field to fix bug [meagabeth] #3746 +* CyberSource: Always send default address [leila-alderman] #3747 +* Netbanx: Reject partial refund on pending status [rockyhakjoong] #3735 + +== Version 1.112.0 +* Cybersource: add `maestro` and `diners_club` eci brand mapping [bbraschi] #3708 +* Cybersource: Ensure Partner Solution Id placement conforms to schema [britth] #3715 +* Adyen: Adyen: Pass `subMerchantId` as `additionalData` [naashton] #3714 +* Litle: Omit checkNum when nil [leila-alderman] #3719 +* 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 + +== Version 1.111.0 +* Fat Zebra: standardized 3DS fields and card on file extra data for Visa scheme rules [montdidier] #3409 +* Realex: Change 3DSecure v1 message_version to a valid format [shuhala] #3702 +* Ingenico/ GlobalCollect: Add field for installments [cdmackeyfree] #3707 +* Cybersource: do not send 3DS fields if 'cavv` is missing and `commerceIndicator` is inferred [bbraschi] #3712 + +== Version 1.110.0 +* FirstData e4 v27+: Strip linebreaks from address [curiousepic] #3693 +* Adyen: Change shopper_email to email and shopper_ip to ip [rikterbeek] #3675 +* FirstData e4 v27+ Fix strip_line_breaks method [carrigan] #3695 +* Cybersource: Set authorization on the response even when in fraud review [pi3r] #3701 +* Cybersource: Add fields to override stored creds [leila-alderman] #3689 +* Cybersource: Conditionally find stored credentials [therufs] #3696 #3697 +* Cybersource: Update logic to send cavv as xid for 3DS2 [douglas] #3699 +* Credorax: Default 3ds_browsercolordepth to 32 when passed as 30 [britth] #3700 + +== Version 1.109.0 +* Remove reference to `Billing::Integrations` [pi3r] #3692 +* DLocal: Handle nil address1 [molbrown] #3661 +* Braintree: Add travel and lodging fields [leila-alderman] #3668 +* Stripe: strict_encode64 api key [britth] #3672 +* Stripe PI: Implement verify action [leila-alderman] #3662 +* Stripe, Stripe Payment Intents: Update supported countries [britth] #3684 +* Forte: Use underscore for unused arguments in test [wsmoak] #3605 +* Add Alia card type [therufs] #3673 +* Element: Fix unit tests [leila-alderman] #3676 +* PayU Latam: Fix store method [ccarruitero] #2590 +* Adyen: Allow for executeThreeD to be passed as false [naashton] #3681 +* WorldPay: Fix handling of `state` field for 3DS transactions [chinhle23] #3687 +* Alia: Skip Luhn validation [therufs] #3673 +* Diners Club: support 16 digit card numbers [therufs] #3682 +* Cybersource: Update supported countries [britth] #3683 +* Cybersource: pass reconciliation_id [therufs] #3688 +* RuboCop: Fix Style/SpecialGlobalVars [leila-alderman] #3669 +* RuboCop: Fix Style/StringLiteralsInInterpolation [leila-alderman] #3670 +* RuboCop: Fix Layout/HeredocIndentation [leila-alderman] #3685 +* RuboCop: Fix Gemspec/OrderedDependencies [leila-alderman] #3679 +* RuboCop: Fix Style/TrailingUnderscoreVariable [leila-alderman] #3663 +* RuboCop: Fix Style/WordArray [leila-alderman] #3664 +* RuboCop: Fix Style/SymbolArray [leila-alderman] #3665 +* Mercado-Pago: Notification url GSF [cdmackeyfree] #3678 +* Credorax: Update logic for setting 3ds_homephonecountry [britth] #3691 + +== Version 1.108.0 (Jun 9, 2020) +* Cybersource: Send cavv as xid is xid is missing [pi3r] #3658 +* Forte: Change default sec_code value to PPD [molbrown] #3653 +* Elavon: Add merchant initiated unscheduled field [leila-alderman] #3647 +* Decidir: Add aggregate data fields [leila-alderman] #3648 +* Vantiv: Vantiv(Element): add option to send terminal id in transactions [cdmackeyfree] #3654 +* Update supported Ruby and Rails versions [leila-alderman] #3656 +* CI: Drop unused sudo: false Travis directive [olleolleolle] #3616 +* PayU Latam: Prevent blank country in billing_address [britth] #3657 +* DLocal: Fix address field names [molbrown] #3651 + +== Version 1.107.4 (Jun 2, 2020) +* Elavon: Implement true verify action [leila-alderman] #3610 +* Vantiv Express: Implement true verify [leila-alderman] #3617 +* Litle: Pass expiration data for basis payment method [therufs] #3606 +* Stripe Payment Intents: Error handling and backwards compatibility within refund [britth] #3627 +* HPS: Prevent errors when account_type or account_holder_type are nil [britth] #3628 +* D Local: Handle invalid country code errors [curiousepic] #3626 +* Stripe Payment Intents: Utilize execute_threed flag to determine success [britth] #3625 +* Elavon: Add Level 3 fields [leila-alderman] #3632 +* CyberSource: Stored Credential fixes [curiousepic] #3624 +* CyberSource: Fix invalid and missing field tests [curiousepic] #3634 +* CyberSource: Pass stored credentials with purchase [curiousepic] #3636 +* Mercado Pago: Add payment_method_option_id field [schwarzgeist] #3635 +* Stripe: Provide error when attempting an authorize with ACH [britth] #3633 +* EBANX: Send original order id as merchant_payment_code metadata [miguelxpn] #3637 +* Element: Add card_present_code field [schwarzgeist] #3623 +* Orbital: Add support for Level 3 fields [leila-alderman] #3639 +* Firstdata: Strip newline characters from address [bittercoder] #3643 +* Forte: add sec_code attribute for echeck [wsmoak] #3640 + +== Version 1.107.3 (May 8, 2020) +* Realex: Ignore IPv6 unsupported addresses [elfassy] #3622 +* Cybersource: Set partnerSolutionID after the business rules, fixes 500 error [pi3r] #3621 + +== Version 1.107.2 (May 7, 2020) +* Cybersource: Send a specific card brand commerceIndicator for 3DS [pi3r] #3620 +* Cybersource: Send application_id as partnerSolutionID [pi3r] #3620 +* Iridium: Localize zero-decimal currencies [chinhle23] #3587 +* iVeri: Fix `verify` action [chinhle23] #3588 +* Ixopay: Properly support three-decimal currencies [chinhle23] #3589 +* Kushki: support `auth` and `capture` [therufs] #3591 +* PaymentExpress: Update references to Windcave to reflect rebranding [britth] #3595 +* Decidir: Improve handling of error responses from the gateway [naashton] #3594 +* CyberSource: Added support for MerchantInformation CyberSource-specific fields [apfranzen] #3592 +* ePay: Send unique order ids for remote tests [curiousepic] #3593 +* Checkout V2: Send more informative error messages for 4xx errors [britth] #3601 +* Elavon: Add ssl_dynamic_dba field [apfranzen] #3600 +* iATS Payments: Update gateway to v3 and add support for additional GSFs [naashton] #3599 +* Remove deprecated `rubyforge_project` attribute and tidy up unit test output [fatcatt316] #3598 +* Elavon: Cleanup inadvertant field removal (avs_address) in #3600 [apfranzen] #3602 +* EBANX: Fix transaction amount for verify transaction [miguelxpn] #3603 +* iATS Payments: Update gateway to accept `email`, `phone`, and `country` fields [naashton] #3607 +* Braintree: Fix response for failed refunds when falling back to voids [jasonwebster] #3608 +* Worldpay: Fix response for failed refunds when falling back to voids [jasonwebster] #3609 +* iATS Payments: Add support for Customer Code payment method [molbrown] #3611 +* HPS: Add Google Pay support [MSmedal] #3597 +* Adyen: Parse appropriate message for 3DS2 authorization calls [britth] #3619 +* CyberSource: Add error details response fields [schwarzgeist] #3629 + +== Version 1.107.1 (Apr 1, 2020) +* Add `allowed_push_host` to gemspec [mdeloupy] + +== Version 1.107.0 (Apr 1, 2020) +* Stripe Payment Intents: Early return failed `payment_methods` response [chinhle23] #3570 +* Borgun: Support `passengerItineraryData` [therufs] #3572 +* Ingenico GlobalCollect: support optional `requires_approval` field [fatcatt316] #3571 +* CenPOS: Update failing remote tests [britth] #3575 +* Realex: Update remote tests [britth] #3576 +* FirstData e4 v27: Properly tag stored credential initiation field in request [britth] #3578 +* Orbital: Fix stored credentials [chinhle23] #3579 +* Acapture(Opp): Update gateway credentials [molbrown] #3574 +* Ingenico GlobalCollect: support `requires_approval` field [fatcatt316] #3577 +* CyberSource: Fix `void` for `purchase` transactions [chinhle23] #3581 +* Checkout V2: Begin to add support for using network tokens for transactions. [arbianchi] #3580 +* Opp: Update remote test fixtures [ccarruitero] #3582 +* Optimal Payment: Add support for store [britth] #3585 +* SecurePay Australia : Update test URL (#3586) + +== Version 1.106.0 (Mar 10, 2020) +* PayJunctionV2: Send billing address in `auth` and `purchase` transactions [naashton] #3538 +* Adyen: Fix some remote tests [curiousepic] #3541 +* Redsys: Properly escape cardholder name and description fields in 3DS requests [britth] #3537 +* RuboCop: Fix Style/HashSyntax [leila-alderman] #3540 +* Paypal: Fix OrderTotal elements in `add_payment_details` [chinhle23] #3544 +* Stripe Payment Intents: Add tests for "Idempotency-Key" header [fatcatt316] #3542 +* Paypal: Fix RuboCop Style/HashSyntax violations [chinhle23] #3547 +* Rubocop corrections for space around operators [cdmackeyfree] #3543 +* Fat Zebra: Add `is_billing` in post for `store` call [chinhle23] #3551 +* SafeCharge: Adds four supported countries [carrigan] #3550 +* Ixopay: Support stored credentials [leila-alderman] #3549 +* BlueSnap: Adds localized currency support [carrigan] #3552 +* CheckoutV2: Use status as message for 3DS txns in progress [britth] #3545 +* Stripe Payment Intents: Prevent idempotency key errors for compound actions [britth] #3554 +* Adyen: Add tests for voiding with idempotency keys [jknipp] #3553 +* Fat Zebra: Fix `store` call [chinhle23] #3556 +* Update README to include Adyen [haolime] #3452 +* PayJunctionv2: Fix billing address fields [leila-alderman] #3557 +* Adyen: Fail unexpected 3DS responses [curiousepic] #3546 +* Merchant Warrior: Add support for setting soft descriptors [daBayrus] #3558 +* Adyen: Fix stored credentials [chinhle23] #3560 +* Update BIN ranges for Alelo and Maestro cards [leila-alderman] #3559 +* EBANX: Fix declines if order id is bigger than 40 chars [miguelxpn] #3563 +* Moneris US: Remove gateway [chinhle23] #3561 +* Decidir: Decidir: Improving the response message when encountering errors [naashton] #3564 +* PayBox: Added USERTrust RSA Certification Authority and Sectigo RSA Organization Validation Secure Server CA [baldowl] #3567 + +== Version 1.105.0 (Feb 20, 2020) +* Credorax: Fix `3ds_transtype` setting in post [chinhle23] #3531 +* Bambora Apac: Send void amount in options [leila-alderman] #3532 +* RuboCop: Fix Layout/IndentHash [leila-alderman] #3529 +* Stripe: Add connected account support [Carrigan] #3535 +* Redsys: Update scrub method to account for 3DS error responses [britth] #3534 +* Authorize.Net: Pass `account_type` to `check` payment types [chinhle23] #3530 +* Merchant Warrior: Send void amount in options [leila-alderman] #3525 +* Stripe: Add support for `statement_descriptor_suffix` field [Carrigan] #3528 +* Decidir: Add support for fraud_detection, site_id, and establishment_name [fatcatt316] #3527 +* HPS: support eCheck [therufs] #3500 +* EBANX: Add metadata information in post [miguelxpn] #3522 +* Worldpay: Add `riskData` GSF [fatcatt316] #3514 +* EBANX: Fix `scrub` [chinhle23] #3521 +* Worldpay: Remove unnecessary .tag! methods [leila-alderman] #3519 +* BPoint: Remove amount from void requests [leila-alderman] #3518 +* Authorize.net: Trim supported countries to AU, CA, US [fatcatt316] #3516 +* Credorax: Allow optional 3DS 2 fields [jeremywrowe] #3515 +* Stripe: Remove outdated 'customer options' deprecation [alexdunae] #3401 +* Added support for fraud review in CyberSource gateway [greg-burgoon] #3536 + +== Version 1.104.0 (Jan 29, 2020) +* Adyen: add `recurring_contract_type` GSF [therufs] #3460 +* Credorax: Only pass `3ds_version` parameter when required [britth] #3458 +* EBANX: Include Peru in supported countries [Ruanito] #3443 +* Bluesnap: include fraud data in response message [therufs] #3459 +* Ingenico GlobalCollect: support `airline_data` and related GSFs [therufs] #3461 +* Add UnionPay card type [leila-alderman] #3464 +* Braintree: Fix add_credit_card_to_customer in Store [molbrown] #3466 +* EBANX: Default to not send amount on capture [chinhle23] #3463 +* Latitude19: Convert money format to dollars [molbrown] #3468 +* Adyen: Fix response success for unstore [kheang] #3470 +* CyberSource: add several GSFs [therufs] #3465 +* Adyen: add `recurring_contract_type` GSF to auth [therufs] #3471 +* Stripe Payment Intents: Use localized_amount on capture [molbrown] #3475 +* dLocal: Add support for installments [kdelemme] #3456 +* Merchant Warrior: Add void operation [leila-alderman] #3474 +* Decidir: Update payment method IDs [leila-alderman] #3476 +* Adyen: Add delivery address [leila-alderman] #3477 +* Authorize.net: Correctly parse direct_response field with quotation marks [britth] #3479 +* Decidir: Add debit card payment method IDs [leila-alderman] #3480 +* CyberSource: Add issuer data+MDD to credit & void [leila-alderman] #3481 +* Credorax: add `authorization_type` and `multiple_capture_count` GSFs [therufs] #3478 +* CardStream: use localized_amount to correctly support zero-decimal currencies [britth] #3473 +* EBANX: Add additional data in post [Ruanito] #3482 +* Credorax: Omit phone when nil [leila-alderman] #3490 +* TransFirst TrExp: Remove hyphens from zip [leila-alderman] #3483 +* Mundipagg: Return acquirer code as the error code [leila-alderman] #3492 +* Braintree Blue: Remove customer hash when using a payment_method_nonce #3495 +* Credorax: Update non-standard currencies list [chinhle23] #3499 +* Redsys: Update production URL [britth] #3505 +* Moneris: include AVS and CoF fields when storing vault records [alexdunae] #3446 +* Moneris: Add support for temporary vault storage [alexdunae] #3446 +* Clearhaus: Update currencies without fractions list [chinhle23] #3506 +* Merchant Warrior: Add recurringFlag to purchase & authorize [carrigan] #3504 +* CardConnect: Remove domain port validation [leila-alderman] #3494 +* Paymentez: Correct refund and void message parsing [carrigan] #3509 +* Mercado Pago: Add taxes and net_amount gateway specific fields [carrigan] #3512 +* Moneris: use dedicated card_verification methods [alexdunae] #3428 +* Authorize.net: Trim down supported countries [fatcatt316] #3511 +* Stripe: Add support for `statement_descriptor_suffix` field [carrigan] #3528 +* Stripe: Add connected account support [carrigan] #3535 + +== Version 1.103.0 (Dec 2, 2019) +* Quickbooks: Mark transactions that returned `AuthorizationFailed` as failures [britth] #3447 +* Credorax: Add referral CFT transactions [leila-alderman] #3432 +* DLocal: Updates for version 2.1 [molbrown] #3449 +* CyberSource: Send MDD on capture [leila-alderman] #3453 +* Ixopay: Include extra_data gateway specific field [therufs] #3450 +* CyberSource: Fix XML error on capture [leila-alderman] #3454 +* Adyen: Add gateway specific field for splits [leila-alderman] #3448 +* Adyen: Add `unstore` and `storeToken` actions with '/Recurring' endpoint [deedeelavinder][davidsantoso] #3438 +* Barclaycard Smartpay: Add functionality to set 3DS exemptions via API [britth] #3457 +* Use null@cybersource.com when option[:email] is an empty string [pi3r] #3462 + +== Version 1.102.0 (Nov 14, 2019) +* Quickbooks: Make token refresh optional with allow_refresh flag [britth] #3419 +* Paymentez: Update supported countries [curiousepic] #3425 +* Ixopay: Add new gateway [jasonxp] #3426 +* Ixopay: Add support for currency option to refund method #3433 +* Ixopay: Remove default callback URL #3436 +* Ixopay: Refactor capture #3431 +* Update supported countries list. Add currencies without fractions / with 3 decimal places #3424 +* RuboCop: Fix Layout/EndAlignment [leila-alderman] #3427 +* RuboCop: Fix Layout/ExtraSpacing [leila-alderman] #3429 +* RuboCop: Fix Layout/MultilineOperationIndentation [leila-alderman] #3439 +* Worldpay: Update logic to set cardholderName for 3DS transactions [britth] #3444 +* Adopt new enrolled key for 3DS1 transactions. enrolled contains the 3… #3442 + +== Version 1.101.0 (Nov 4, 2019) +* Add UYI to list of currencies without fractions [curiousepic] #3416 +* Quickbooks: Add OAuth 2.0 support and void action [britth] #3397 +* Credorax: Stop always sending r1 parameter [molbrown] #3415 +* Rubocop: Layout/RescueEnsureAlignment fix [leila-alderman] #3411 +* CyberSource: Send issuer data on capture [leila-alderman] #3404 +* Rubocop: Style/IfUnlessModifier [nfarve] #3390 +* Redsys: Updates to parse method for non-3DS responses [britth] #3391 +* Netbanx: Add 3DS2 Support [Jujhar] #3394 + +== Version 1.100.0 (Oct 16, 2019) +* Stripe: Restore non-auto capture behaviour for card present transactions [PatrickFang] #3258 +* Revert "Revert "Worldpay: Switch to Nokogiri"" [curiousepic] #3373 +* Adyen: Fix `authorise3d` message for refusals [jeremywrowe] #3374 +* Redsys: Set authorization field for 3DS transactions [britth] #3377 +* Adyen: Add capture_delay_hours GSF [therufs] #3376 +* Credorax: Add support for stored credentials [chinhle23] #3375 +* BlueSnap: Add remote tests for Cabal and Naranja [leila-alderman] #3382 +* WorldPay: Add Cabal and Naranja remote tests [leila-alderman] #3378 +* Rubocop: Indentions [nfarve] #3383 +* Worldpay: Handle parse errors gracefully [curiousepic] #3380 +* BluePay: Add ability to pass doc_type in refunds and credits [britth] #3386 +* Stripe Payment Intents: Fix fallback for Store [waaux] #3343 +* Update Securionpay supported countries [hossamhossny] #2472 +* Visanet Peru: Add amount argument to Capture [curiousepic] #3389 +* Rubocop: Layout/MultilineHashBraceLayout [nfarve] #3385 +* CardConnect: Always include additional_data in purchase [therufs] #3387 +* CardConnect: Add user_fields GSF [therufs] #3388 +* Moneris: Add support for stored credentials [chinhle23] #3384 + +== Version 1.99.0 (Sep 26, 2019) +* Adyen: Add functionality to set 3DS exemptions via API [britth] #3331 +* Adyen: Send "NA" instead of "N/A" [leila-alderman] #3339 +* Stripe Payment Intents: Set application fee or transfer amount on capture [britth] #3340 +* TNS: Support Europe endpoint [curiousepic] #3346 +* Redsys: Add 3DS support to gateway [britth] #3336 +* Worldpay: Allow multiple refunds per authorization [jknipp] #3349 +* MercadoPago: Add remote and unit tests for Naranja card [hdeters] #3345 +* CyberSource: Pass commerce indicator if present [curiousepic] #3350 +* Worldpay: Add 3DS2 Support [nfarve] #3344 +* Credorax: Add 3DS 2.0 [nfarve] #3342 +* TNS: Update verison and support pay mode [curiousepic] #3355 +* Stripe: Add supported countries [therufs] #3358 +* Stripe Payment Intents: Add supported countries [therufs] #3359 +* Mundipagg: Append error messages to the message response field [jasonxp] #3353 +* Redsys: Add ability to pass sca_exemption and moto fields to request exemptions [britth] #3354 +* Credorax: Add A Mandatory 3DS field [nfarve] #3360 +* CyberSource: Support 3DS2 pass-through fields [curiousepic] #3363 +* Credorax: Add support for MOTO flagging [britth] #3366 +* Credorax: Enable selecting a processor [leila-alderman] #3302 +* Adyen: Add Cabal card [leila-alderman] #3361 +* Decidir: Add remote tests for Cabal and Naranja [leila-alderman] #3337 +* Payflow: Pass correct field in Status for 3DS in Payflow [nebdil] #3362 +* CyberSource: Use 3DS hash for enrolled field [curiousepic] #3371 + +== Version 1.98.0 (Sep 9, 2019) +* Stripe Payment Intents: Add new gateway [britth] #3290 +* Stripe: Send cardholder name and address when creating sources for 3DS 1.0 [jknipp] #3300 +* Checkout_v2: Support for native 3DS2.0 [nfarve] #3303 +* Adds new Maestro BINs [tanyajajodia] #3305 +* eWAY Rapid: If no address is available, default to the name associated with the payment method when setting the Customer fields [jasonxp] #3306 +* eWAY Rapid: Fix a bug in which the email was not set in Customer fields if no address was provided [jasonxp] #3306 +* eWAY Rapid: Support both `phone` and `phone_number` fields under the `shipping_address` option [jasonxp] #3306 +* PayU Latam: Add support for the `merchant_buyer_id` field in the `options` and `buyer` hashes [jasonxp] #3308 +* Update Braintree Gem [curiousepic] #3311 +* Fat Zebra: Send metadata for purchase and authorize [montdidier] #3101 +* TrustCommerce: Add support for custom fields [jasonxp] #3313 +* Stripe Payment Intents: Support option fields `transfer_destination` and `transfer_amount` and remove `transfer_data` hash [britth] #3317 +* Barclaycard Smartpay: Add support for `shopperStatement` gateway-specific field [jasonxp] #3319 +* Stripe Payment Intents: Add support for billing_details on payment methods [britth] #3320 +* BlueSnap: add standardized 3DS 2 auth fields [bayprogrammer] #3318 +* Barclaycard Smartpay: Add app based 3DS requests for auth and purchase [britth] #3327 +* Stripe Payment Intents, Checkout V2: Add support for `MOTO` flagging [britth] #3323 +* Braintree Blue: Adding 3DS2 passthru support [molbrown] #3328 +* Global Collect: Add Cabal card [leila-alderman] #3310 +* WorldPay: Add Cabal card [leila-alderman] #3316 +* Decidir: Add Cabal card [leila-alderman] #3322 +* PayU Latam: Add Cabal card [leila-alderman] #3324 +* dLocal: Add Cabal card [leila-alderman] #3325 +* BlueSnap: Add Cabal card [leila-alderman] #3326 +* Adyen: added 3DS support through external [rikterbeek] #3294 +* Worldpay: Add support for MOTO flagging [britth] #3329 +* ePay: 3DS support [AllaWLie] #3321 +* Checkout.com: added options[:metadata][:manual_entry] support for MOTO transactions [filipebarcos] #3330 + +== Version 1.97.0 (Aug 15, 2019) +* CyberSource: Add issuer `additionalData` gateway-specific field [jasonxp] #3296 +* PayU Latam: Add Naranja card type [hdeters] #3299 +* Adyen: Add app based 3DS requests for auth and purchase [jeremywrowe] #3298 +* MercadoPago: Add Cabal card type [leila-alderman] #3295 +* MONEI: Add external MPI 3DS 1 support [jimmyn] #3292 +* Bambora formerly Beanstream: Pass card owner when storing tokenized cards [alexdunae] #3006 +* Realex: Prevent error calculating `refund_hash` or `credit_hash` when the secret is nil [jasonxp] #3291 +* Orbital: Add external MPI support for 3DS1 [pi3r] #3261 +* Paymill: Add currency and amount to store requests [jasonxp] #3289 +* Realex: Re-implement credit as general credit [leila-alderman] #3280 +* Braintree Blue: Support for stored credentials [hdeters] #3286 +* CardConnect: Move domain from gateway specific to gateway field [hdeters] #3283 + +== Version 1.96.0 (Jul 26, 2019) +* Bluesnap: Omit state codes for unsupported countries [therufs] #3229 +* Adyen: Pass updateShopperStatement, industryUsage [curiousepic] #3233 +* TransFirst Transaction Express: Fix blank address2 values [britth] #3231 +* WorldPay: Add support for store method [bayprogrammer] #3232 +* Adyen: Support for additional AVS code mapping [jknipp] #3236 +* Adyen: Update message for AVS result code 'A' to generically cover postal code mismatches [jknipp] #3237 +* CyberSource: Update CyberSource SOAP documentation link [vince-smith] #3204 +* USAePay: Handle additional error codes and add default error code [estelendur] #3167 +* Braintree: Add `skip_avs` and `skip_cvv` gateway specific fields [leila-alderman] #3241 +* NAB Transact: Update periodic test url [mengqing] #3177 +* NMI: Add level 3 gateway-specific fields tax, shipping, and ponumber [jasonxp] #3239 +* Checkout V2: Update stored card flag [curiousepic] #3247 +* NMI: Add support for stored credentials [bayprogrammer] #3243 +* Spreedly: Consolidate API requests and support bank accounts [lancecarlson] #3105 +* BPoint: Hook up merchant_reference and CRN fields [curiousepic] #3249 +* Checkout V2: Stop sending phone number to Checkout V2 integration [filipebarcos] #3248 +* Barclaycard Smartpay: Add support for 3DS2 [britth] #3251 +* Adyen: Add support for non-fractional currencies [molbrown] #3257 +* Decidir: Add new gateway [jknipp] #3254 +* Checkout V2: Reapply Update stored card flag [curiousepic] +* CyberSource: Update supported countries [molbrown] #3260 +* Credorax: Update supported countries [molbrown] #3260 +* Kushki: Update supported countries [molbrown] #3260 +* Paypal: Update supported countries [molbrown] #3260 +* BlueSnap: Send amount in capture requests [jknipp] #3262 +* Mundipagg: Add Alelo card support [jasonxp] #3255 +* Adyen: Remove temporary amount modification for non-fractional currencies [molbrown] #3263 +* Adyen: Set blank state to N/A [therufs] #3252 +* MiGS: Add tx_source gateway specific field [leila-alderman] #3264 +* NMI: Correct password scrubber to scrub symbols [hdeters] #3267 +* Global Collect: Only add name if present [curiousepic] #3268 +* HPS: Add Apple Pay raw cryptogram support [slogsdon] #3209 +* CardConnect: Fix parsing of level 3 fields [hdeters] #3273 +* TrustCommerce: Support void after purchase [jknipp] #3265 +* Payflow: Support arbitrary level 2 + level 3 fields [therufs] #3272 +* BlueSnap: Default to not send amount on capture [molbrown] #3270 +* Spreedly: extra fields, remove extraneous check [montdidier] #3102 #3281 +* Cecabank: Update encryption to SHA2 [leila-alderman] #3278 +* Checkout V2: Fix 3DS 1&2 integration [nicolas-maalouf-cko] #3240 +* Credorax: add 3DS2 MPI auth data support [bayprogrammer] #3274 +* Add Kosovo to the list of countries [AnotherJoSmith] #3226 +* Realex: Adds 3DS 1&2 support through external MPI [filipebarcos] #3284 +* PayPal: Adds 3DS 1 support through external MPI [nebdil] #3279 + +== Version 1.95.0 (May 23, 2019) +* Adyen: Constantize version to fix subdomains [curiousepic] #3228 +* Qvalent: Adds support for standard stored credential framework [molbrown] #3227 +* Cybersource: Send tokenization data when card is :master [pi3r] #3230 + +== Version 1.94.0 (May 21, 2019) +* Mundipagg: Fix number lengths for both VR and Sodexo [dtykocki] #3195 +* Stripe: Support show and list webhook endpoints [jknipp] #3196 +* CardConnect: Add frontendid parameter to requests [gcatlin] #3198 +* Adyen: Correct formatting of Billing Address [nfarve] #3200 +* Stripe: Stripe: Show payment source [jknipp] #3202 +* Checkout V2: Checkout V2: Correct success criteria [curiousepic] #3205 +* Adyen: Add normalized hash of 3DS 2.0 data fields from web browsers [davidsantoso] #3207 +* Stripe: Do not attempt application fee refund if refund was not successful [jasonwebster] #3206 +* Elavon: Send transaction_currency if currency is provided [gcatlin] #3201 +* Elavon: Multi-currency support [jknipp] #3210 +* Adyen: Support preAuths and Synchronous Adjusts [curiousepic] #3212 +* WorldPay: Support Unknown Card Type [tanyajajodia] #3213 +* Mundipagg: Make gateway_affiliation_id an option [curiousepic] #3219 +* CyberSource: Adds Elo Card Type [tanyajajodia] #3220 +* CyberSource: Support standalone credit for cards [curiousepic] #3225 + +== Version 1.93.0 (April 18, 2019) +* Stripe: Do not consider a refund unsuccessful if only refunding the fee failed [jasonwebster] #3188 +* Stripe: Fix webhook creation for connected account [jknipp] #3193 +* Adyen: Upgrade to v40 API version [davidsantoso] #3192 + +== Version 1.92.0 (April 8, 2019) * BluePay: Send customer IP address when provided [jknipp] #3149 * PaymentExpress: Use ip field for client_info field [jknipp] #3150 * Bambora Asia-Pacific: Adds Store [molbrown] #3147 @@ -19,6 +1316,10 @@ * Adyen: Pass phone, statement, device_fingerprint [curiousepic] #3178 * Adyen: Fix adding phone from billing address [curiousepic] #3179 * Fix partial or missing address exceptions [molbrown] #3180 +* Adyen: Update to support normalized stored credential fields [molbrown] #3182 +* VisaNet Peru: Always include DSC_COD_ACCION [bayprogrammer] #3174 +* Adyen: Support adjust action [curiousepic] #3190 +* CyberSource: Add support for stored credentials [therufs] #3185 == Version 1.91.0 (February 22, 2019) * WorldPay: Pull CVC and AVS Result from Response [nfarve] #3106 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c9ce8e449ea..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,42 +0,0 @@ -# Contributing guidelines - -We gladly accept bugfixes, but are not actively looking to add new gateways. Please follow the guidelines here to ensure your work is accepted. - -## New Gateways - -We're not taking on many new gateways at the moment. The team maintaining ActiveMerchant is small and with the limited resources available, we generally prefer not to support a gateway than to support a gateway poorly. - -Please see the [ActiveMerchant Guide to Contributing a new Gateway](https://github.com/activemerchant/active_merchant/wiki/contributing) for information on creating a new gateway. You can place your gateway code in your application's `lib/active_merchant/billing` folder to use it. - -We would like to work with the community to figure out how gateways can release and maintain their integrations outside of the the ActiveMerchant repository. Please join [the discussion](https://github.com/activemerchant/active_merchant/issues/2923) if you're interested or have ideas. - -Gateway placement within Shopify is available by invitation only at this time. - -## Issues & Bugfixes - -### Reporting issues - -When filing a new Issue: - -- Please make clear in the subject what gateway the issue is about. -- Include the version of ActiveMerchant, Ruby, ActiveSupport, and Nokogiri you are using. - -### Pull request guidelines - -When submitting a pull request to resolve an issue: - -1. [Fork it](http://github.com/activemerchant/active_merchant/fork) and clone your new repo -2. Create a branch (`git checkout -b my_awesome_feature`) -3. Commit your changes (`git add my/awesome/file.rb; git commit -m "Added my awesome feature"`) -4. Push your changes to your fork (`git push origin my_awesome_feature`) -5. Open a [Pull Request](https://github.com/activemerchant/active_merchant/pulls) - -## Version/Release Management - -Contributors don't need to worry about versions, this is something Committers do at important milestones: - -1. Check the [semantic versioning page](http://semver.org) for info on how to version the new release. -2. Update the `ActiveMerchant::VERSION` constant in **lib/active_merchant/version.rb**. -3. Add a `CHANGELOG` entry for the new release with the date -4. Tag the release commit on GitHub: `bundle exec rake tag_release` -5. Release the gem to rubygems using ShipIt diff --git a/Gemfile b/Gemfile index 3c766be75de..5f3d4397223 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,14 @@ source 'https://rubygems.org' gemspec -gem 'jruby-openssl', :platforms => :jruby -gem 'rubocop', '~> 0.60.0', require: false +gem 'jruby-openssl', platforms: :jruby +gem 'rubocop', '~> 0.72.0', require: false group :test, :remote_test do # gateway-specific dependencies, keeping these gems out of the gemspec - gem 'braintree', '>= 2.93.0' + gem 'braintree', '>= 4.14.0' + gem 'jose', '~> 1.1.3' + gem 'jwe' + gem 'mechanize' + gem 'timecop' end diff --git a/README.md b/README.md index c026e93ae86..c3f26f99bae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Active Merchant -[![Build Status](https://travis-ci.org/activemerchant/active_merchant.png?branch=master)](https://travis-ci.org/activemerchant/active_merchant) -[![Code Climate](https://codeclimate.com/github/activemerchant/active_merchant.png)](https://codeclimate.com/github/activemerchant/active_merchant) +[![Build Status](https://github.com/activemerchant/active_merchant/workflows/CI/badge.svg?branch=master)](https://github.com/activemerchant/active_merchant/actions?query=workflow%3ACI) +[![Code Climate](https://codeclimate.com/github/activemerchant/active_merchant.svg)](https://codeclimate.com/github/activemerchant/active_merchant) Active Merchant is an extraction from the ecommerce system [Shopify](http://www.shopify.com). Shopify's requirements for a simple and unified API to access dozens of different payment @@ -17,7 +17,7 @@ from an ever-growing set of contributors. See [GettingStarted.md](GettingStarted.md) if you want to learn more about using Active Merchant in your applications. -If you'd like to contribute to Active Merchant, please start with our [contribution guide](CONTRIBUTING.md). +If you'd like to contribute to Active Merchant, please start with our [Contribution Guide](https://github.com/activemerchant/active_merchant/wiki/Contributing). ## Installation @@ -81,15 +81,21 @@ if credit_card.validate.empty? end ``` +## Contributing + For more in-depth documentation and tutorials, see [GettingStarted.md](GettingStarted.md) and the [API documentation](http://www.rubydoc.info/github/activemerchant/active_merchant/). +Emerging ActiveMerchant 3DS conventions are documented in the [Contributing](https://github.com/activemerchant/active_merchant/wiki/Contributing#3ds-options) +guide and [Standardized 3DS Fields](https://github.com/activemerchant/active_merchant/wiki/Standardized-3DS-Fields) guide of the wiki. + ## Supported Payment Gateways The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](https://github.com/activemerchant/active_merchant/wiki/Gateway-Feature-Matrix). +* [Adyen](https://www.adyen.com/) - US, AT, AU, BE, BG, BR, CH, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GI, GR, HK, HU, IE, IS, IT, LI, LT, LU, LV, MC, MT, MX, NL, NO, PL, PT, RO, SE, SG, SK, SI * [Authorize.Net CIM](http://www.authorize.net/) - US -* [Authorize.Net](http://www.authorize.net/) - AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA +* [Authorize.Net](http://www.authorize.net/) - AU, CA, US * [Axcess MS](http://www.axcessms.com/) - AD, AT, BE, BG, BR, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HR, HU, IE, IL, IM, IS, IT, LI, LT, LU, LV, MC, MT, MX, NL, NO, PL, PT, RO, RU, SE, SI, SK, TR, US, VA * [Balanced](https://www.balancedpayments.com/) - US * [Bambora Asia-Pacific](http://www.bambora.com/) - AU, NZ @@ -109,7 +115,7 @@ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wiki * [Cecabank](http://www.ceca.es/es/) - ES * [Cenpos](https://www.cenpos.com/) - AD, AI, AG, AR, AU, AT, BS, BB, BE, BZ, BM, BR, BN, BG, CA, HR, CY, CZ, DK, DM, EE, FI, FR, DE, GR, GD, GY, HK, HU, IS, IN, IL, IT, JP, LV, LI, LT, LU, MY, MT, MX, MC, MS, NL, PA, PL, PT, KN, LC, MF, VC, SM, SG, SK, SI, ZA, ES, SR, SE, CH, TR, GB, US, UY * [CAMS: Central Account Management System](https://www.centralams.com/) - US -* [Checkout.com](https://www.checkout.com/) - AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, MU, NL, NO, PL, PT, RO, SE, SI, SK, US +* [Checkout.com](https://www.checkout.com/) - 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 * [Clearhaus](https://www.clearhaus.com) - AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GL, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK * [Commercegate](http://www.commercegate.com/) - AD, AT, AX, BE, BG, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GG, GI, GR, HR, HU, IE, IM, IS, IT, JE, LI, LT, LU, LV, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, VA * [Conekta](https://conekta.io) - MX @@ -154,9 +160,8 @@ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wiki * [Metrics Global](http://www.metricsglobal.com) - US * [MasterCard Internet Gateway Service (MiGS)](http://mastercard.com/mastercardsps) - AU, AE, BD, BN, EG, HK, ID, IN, JO, KW, LB, LK, MU, MV, MY, NZ, OM, PH, QA, SA, SG, TT, VN * [Modern Payments](http://www.modpay.com) - US -* [MONEI](http://www.monei.net/) - AD, AT, BE, BG, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, US, VA +* [MONEI](http://www.monei.com/) - AD, AT, BE, BG, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, US, VA * [Moneris](http://www.moneris.com/) - CA -* [Moneris (US)](http://www.monerisusa.com/) - US * [MoneyMovers](http://mmoa.us/) - US * [NAB Transact](http://transact.nab.com.au) - AU * [NELiX TransaX](https://www.nelixtransax.com/) - US @@ -180,7 +185,7 @@ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wiki * [Paybox Direct](http://www.paybox.com/) - FR * [Payeezy](https://developer.payeezy.com/) - CA, US * [Payex](http://payex.com/) - DK, FI, NO, SE -* [PaymentExpress](http://www.paymentexpress.com/) - AU, CA, DE, ES, FR, GB, HK, IE, MY, NL, NZ, SG, US, ZA +* [Windcave (formerly PaymentExpress)](https://www.windcave.com/) - AU, CA, DE, ES, FR, GB, HK, IE, MY, NL, NZ, SG, US, ZA * [PAYMILL](https://paymill.com) - AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, VA * [PayPal Express Checkout](https://www.paypal.com/webapps/mpp/express-checkout) - US, CA, SG, AU * [PayPal Express Checkout (UK)](https://www.paypal.com/uk/webapps/mpp/express-checkout) - GB @@ -212,7 +217,7 @@ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wiki * [SecureNet](http://www.securenet.com/) - US * [SecurePay](http://www.securepay.com/) - US, CA, GB, AU * [SecurePayTech](http://www.securepaytech.com/) - NZ -* [SecurionPay](https://securionpay.com/) - AD, AE, AF, AG, AI, AL, AM, AO, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, BR, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MF, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, ST, SV, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, YE, YT, ZA, ZM, ZW +* [SecurionPay](https://securionpay.com/) - AD, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GI, GL, GR, GS, GT, HR, HU, IE, IS, IT, LI, LR, LT, LU, LV, MC, MT, MU, MV, MW, NL, NO, PL, RO, SE, SI * [SkipJack](http://www.skipjack.com/) - US, CA * [SoEasyPay](http://www.soeasypay.com/) - US, CA, AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, GB, IS, NO, CH * [Spreedly](https://spreedly.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA @@ -222,6 +227,7 @@ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wiki * [Transact Pro](https://www.transactpro.lv/business/online-payments-acceptance) - US * [TransFirst](http://www.transfirst.com/) - US * [Transnational](http://www.tnbci.com/) - US +* [Trexle](https://trexle.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA * [TrustCommerce](http://www.trustcommerce.com/) - US * [USA ePay](http://www.usaepay.com/) - US * [Vanco Payment Solutions](http://vancopayments.com/) - US @@ -240,4 +246,4 @@ Functionality or APIs that are deprecated will be marked as such. Deprecated fun ## Ruby and Rails compatibility policies -Because Active Merchant is a payment library, it needs to take security seriously. For this reason, Active Merchant guarantees compatibility only with actively supported versions of Ruby and Rails. At the time of this writing, that means that Ruby 2.3+ and Rails 4.2+ are supported. +Because Active Merchant is a payment library, it needs to take security seriously. For this reason, Active Merchant guarantees compatibility only with actively supported versions of Ruby and Rails. At the time of this writing, that means that Ruby 2.5+ and Rails 5.0+ are supported. diff --git a/Rakefile b/Rakefile index 5e92782a541..0334d839734 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,4 @@ -$:.unshift File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'active_merchant/version' begin @@ -24,8 +24,8 @@ task :tag_release do end desc 'Run the unit test suite' -task :default => 'test:units' -task :test => 'test:units' +task default: 'test:units' +task test: 'test:units' RuboCop::RakeTask.new @@ -33,11 +33,11 @@ namespace :test do Rake::TestTask.new(:units) do |t| t.pattern = 'test/unit/**/*_test.rb' t.libs << 'test' - t.verbose = true + t.verbose = false end desc 'Run all tests that do not require network access' - task :local => ['test:units', 'rubocop'] + task local: ['test:units', 'rubocop'] Rake::TestTask.new(:remote) do |t| t.pattern = 'test/remote/**/*_test.rb' diff --git a/activemerchant.gemspec b/activemerchant.gemspec index bb9ea4e14f8..a1e8ed4f5b6 100644 --- a/activemerchant.gemspec +++ b/activemerchant.gemspec @@ -1,4 +1,4 @@ -$:.push File.expand_path('../lib', __FILE__) +$LOAD_PATH.push File.expand_path('../lib', __FILE__) require 'active_merchant/version' Gem::Specification.new do |s| @@ -12,22 +12,26 @@ Gem::Specification.new do |s| s.author = 'Tobias Luetke' s.email = 'tobi@leetsoft.com' s.homepage = 'http://activemerchant.org/' - s.rubyforge_project = 'activemerchant' - s.required_ruby_version = '>= 2.3' + s.required_ruby_version = '>= 2.7' s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'CONTRIBUTORS', 'lib/**/*', 'vendor/**/*'] s.require_path = 'lib' + s.metadata['allowed_push_host'] = 'https://rubygems.org' + s.has_rdoc = true if Gem::VERSION < '1.7.0' s.add_dependency('activesupport', '>= 4.2') - s.add_dependency('i18n', '>= 0.6.9') s.add_dependency('builder', '>= 2.1.2', '< 4.0.0') + s.add_dependency('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') + s.add_development_dependency('pry-byebug') s.add_development_dependency('rake') s.add_development_dependency('test-unit', '~> 3') - s.add_development_dependency('mocha', '~> 1') s.add_development_dependency('thor') end diff --git a/circle.yml b/circle.yml index 5d2a53e5abb..949fa18bb15 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: ruby: - version: '2.3.0' + version: '2.7.0' dependencies: cache_directories: diff --git a/gemfiles/Gemfile.rails60 b/gemfiles/Gemfile.rails60 new file mode 100644 index 00000000000..c9289b875eb --- /dev/null +++ b/gemfiles/Gemfile.rails60 @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', '~> 6.0.0' diff --git a/generators/gateway/templates/gateway.rb b/generators/gateway/templates/gateway.rb index f9f986176f8..671d5c22129 100644 --- a/generators/gateway/templates/gateway.rb +++ b/generators/gateway/templates/gateway.rb @@ -6,19 +6,19 @@ class <%= class_name %>Gateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.example.net/' self.display_name = 'New Gateway' STANDARD_ERROR_CODE_MAPPING = {} - def initialize(options={}) + def initialize(options = {}) requires!(options, :some_credential, :another_credential) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -28,7 +28,7 @@ def purchase(money, payment, options={}) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -38,19 +38,19 @@ def authorize(money, payment, options={}) commit('authonly', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) commit('capture', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -67,19 +67,16 @@ def scrub(transcript) private - def add_customer_data(post, options) - end + def add_customer_data(post, options); end - def add_address(post, creditcard, options) - end + def add_address(post, creditcard, options); end def add_invoice(post, money, options) post[:amount] = amount(money) post[:currency] = (options[:currency] || currency(money)) end - def add_payment(post, payment) - end + def add_payment(post, payment); end def parse(body) {} @@ -94,24 +91,20 @@ def commit(action, parameters) message_from(response), response, authorization: authorization_from(response), - avs_result: AVSResult.new(code: response["some_avs_response_key"]), - cvv_result: CVVResult.new(response["some_cvv_response_key"]), + avs_result: AVSResult.new(code: response['some_avs_response_key']), + cvv_result: CVVResult.new(response['some_cvv_response_key']), test: test?, error_code: error_code_from(response) ) end - def success_from(response) - end + def success_from(response); end - def message_from(response) - end + def message_from(response); end - def authorization_from(response) - end + def authorization_from(response); end - def post_data(action, parameters = {}) - end + def post_data(action, parameters = {}); end def error_code_from(response) unless success_from(response) diff --git a/generators/gateway/templates/gateway_test.rb b/generators/gateway/templates/gateway_test.rb index 03f699b9641..38d9f0817e1 100644 --- a/generators/gateway/templates/gateway_test.rb +++ b/generators/gateway/templates/gateway_test.rb @@ -31,38 +31,27 @@ def test_failed_purchase assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end - def test_successful_authorize - end + def test_successful_authorize; end - def test_failed_authorize - end + def test_failed_authorize; end - def test_successful_capture - end + def test_successful_capture; end - def test_failed_capture - end + def test_failed_capture; end - def test_successful_refund - end + def test_successful_refund; end - def test_failed_refund - end + def test_failed_refund; end - def test_successful_void - end + def test_successful_void; end - def test_failed_void - end + def test_failed_void; end - def test_successful_verify - end + def test_successful_verify; end - def test_successful_verify_with_failed_void - end + def test_successful_verify_with_failed_void; end - def test_failed_verify - end + def test_failed_verify; end def test_scrub assert @gateway.supports_scrubbing? @@ -72,19 +61,19 @@ def test_scrub private def pre_scrubbed - %q( + ' Run the remote tests for this gateway, and then put the contents of transcript.log here. - ) + ' end def post_scrubbed - %q( + ' Put the scrubbed contents of transcript.log here after implementing your scrubbing function. Things to scrub: - Credit card number - CVV - Sensitive authentication details - ) + ' end def successful_purchase_response @@ -98,30 +87,21 @@ def successful_purchase_response ) end - def failed_purchase_response - end + def failed_purchase_response; end - def successful_authorize_response - end + def successful_authorize_response; end - def failed_authorize_response - end + def failed_authorize_response; end - def successful_capture_response - end + def successful_capture_response; end - def failed_capture_response - end + def failed_capture_response; end - def successful_refund_response - end + def successful_refund_response; end - def failed_refund_response - end + def failed_refund_response; end - def successful_void_response - end + def successful_void_response; end - def failed_void_response - end + def failed_void_response; end end diff --git a/generators/gateway/templates/remote_gateway_test.rb b/generators/gateway/templates/remote_gateway_test.rb index 08262a97c93..dbe34a57225 100644 --- a/generators/gateway/templates/remote_gateway_test.rb +++ b/generators/gateway/templates/remote_gateway_test.rb @@ -22,8 +22,8 @@ def test_successful_purchase def test_successful_purchase_with_more_options options = { order_id: '1', - ip: "127.0.0.1", - email: "joe@example.com" + ip: '127.0.0.1', + email: 'joe@example.com' } response = @gateway.purchase(@amount, @credit_card, options) @@ -56,7 +56,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -79,7 +79,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -143,5 +143,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/lib/active_merchant.rb b/lib/active_merchant.rb index 06ab2f0d19d..5634caae807 100644 --- a/lib/active_merchant.rb +++ b/lib/active_merchant.rb @@ -50,9 +50,9 @@ require 'active_merchant/country' module ActiveMerchant - def self.deprecated(message, caller=Kernel.caller[1]) + def self.deprecated(message, caller = Kernel.caller[1]) warning = caller + ': ' + message - if(respond_to?(:logger) && logger.present?) + if respond_to?(:logger) && logger.present? logger.warn(warning) else warn(warning) diff --git a/lib/active_merchant/billing.rb b/lib/active_merchant/billing.rb index ea3108597c8..55838882995 100644 --- a/lib/active_merchant/billing.rb +++ b/lib/active_merchant/billing.rb @@ -13,3 +13,4 @@ require 'active_merchant/billing/response' require 'active_merchant/billing/gateways' require 'active_merchant/billing/gateway' +require 'active_merchant/billing/three_d_secure_eci_mapper' diff --git a/lib/active_merchant/billing/avs_result.rb b/lib/active_merchant/billing/avs_result.rb index b21017eaa2f..c4bcf939dda 100644 --- a/lib/active_merchant/billing/avs_result.rb +++ b/lib/active_merchant/billing/avs_result.rb @@ -3,14 +3,13 @@ module ActiveMerchant module Billing # Implements the Address Verification System - # https://www.wellsfargo.com/downloads/pdf/biz/merchant/visa_avs.pdf + # https://www.cybersource.com/developers/other_resources/quick_references/avs_results/. # http://en.wikipedia.org/wiki/Address_Verification_System - # http://apps.cybersource.com/library/documentation/dev_guides/CC_Svcs_IG/html/app_avs_cvn_codes.htm#app_AVS_CVN_codes_7891_48375 - # http://imgserver.skipjack.com/imgServer/5293710/AVS%20and%20CVV2.pdf # http://www.emsecommerce.net/avs_cvv2_response_codes.htm + # https://www.cardfellow.com/blog/address-verification-service-avs/ class AVSResult MESSAGES = { - 'A' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', + 'A' => 'Street address matches, but postal code does not match.', 'B' => 'Street address matches, but postal code not verified.', 'C' => 'Street address and postal code do not match.', 'D' => 'Street address and postal code match.', @@ -23,7 +22,7 @@ class AVSResult 'K' => 'Card member\'s name matches but billing address and billing postal code do not match.', 'L' => 'Card member\'s name and billing postal code match, but billing address does not match.', 'M' => 'Street address and postal code match.', - 'N' => 'Street address and postal code do not match.', + 'N' => 'Street address and postal code do not match. For American Express: Card member\'s name, street address and postal code do not match.', 'O' => 'Card member\'s name and billing address match, but billing postal code does not match.', 'P' => 'Postal code matches, but street address not verified.', 'Q' => 'Card member\'s name, billing address, and postal code match. Shipping information verified but chargeback protection not guaranteed.', @@ -40,10 +39,10 @@ class AVSResult # Map vendor's AVS result code to a postal match code POSTAL_MATCH_CODE = { - 'Y' => %w( D H F H J L M P Q V W X Y Z ), - 'N' => %w( A C K N O ), - 'X' => %w( G S ), - nil => %w( B E I R T U ) + 'Y' => %w(D H F H J L M P Q V W X Y Z), + 'N' => %w(A C K N O), + 'X' => %w(G S), + nil => %w(B E I R T U) }.inject({}) do |map, (type, codes)| codes.each { |code| map[code] = type } map @@ -51,10 +50,10 @@ class AVSResult # Map vendor's AVS result code to a street match code STREET_MATCH_CODE = { - 'Y' => %w( A B D H J M O Q T V X Y ), - 'N' => %w( C K L N W Z ), - 'X' => %w( G S ), - nil => %w( E F I P R U ) + 'Y' => %w(A B D H J M O Q T V X Y), + 'N' => %w(C K L N W Z), + 'X' => %w(G S), + nil => %w(E F I P R U) }.inject({}) do |map, (type, codes)| codes.each { |code| map[code] = type } map @@ -89,8 +88,7 @@ def to_hash { 'code' => code, 'message' => message, 'street_match' => street_match, - 'postal_match' => postal_match - } + 'postal_match' => postal_match } end end end diff --git a/lib/active_merchant/billing/base.rb b/lib/active_merchant/billing/base.rb index 5392189e46b..269cf394512 100644 --- a/lib/active_merchant/billing/base.rb +++ b/lib/active_merchant/billing/base.rb @@ -39,19 +39,6 @@ def self.gateway(name) end end - # Return the matching integration module - # You can then get the notification from the module - # * bogus: Bogus - Does nothing (for testing) - # * chronopay: Chronopay - # * paypal: Paypal - # - # chronopay = ActiveMerchant::Billing::Base.integration('chronopay') - # notification = chronopay.notification(raw_post) - # - def self.integration(name) - Billing::Integrations.const_get(name.to_s.downcase.camelize) - end - # A check to see if we're in test mode def self.test? mode == :test diff --git a/lib/active_merchant/billing/check.rb b/lib/active_merchant/billing/check.rb index 6dcba4b0267..1d6feb931f2 100644 --- a/lib/active_merchant/billing/check.rb +++ b/lib/active_merchant/billing/check.rb @@ -7,12 +7,22 @@ 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 + # Canadian Institution Numbers + # Partial list found here: https://en.wikipedia.org/wiki/Routing_number_(Canada) + CAN_INSTITUTION_NUMBERS = %w( + 001 002 003 004 006 010 016 030 039 117 127 177 219 245 260 269 270 308 + 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 618 842 + ) + def name @name ||= "#{first_name} #{last_name}".strip end @@ -29,19 +39,15 @@ def name=(value) def validate errors = [] - [:name, :routing_number, :account_number].each do |attr| + %i[name routing_number account_number].each do |attr| errors << [attr, 'cannot be empty'] if empty?(self.send(attr)) end errors << [:routing_number, 'is invalid'] unless valid_routing_number? - if(!empty?(account_holder_type) && !%w[business personal].include?(account_holder_type.to_s)) - errors << [:account_holder_type, 'must be personal or business'] - end + errors << [:account_holder_type, 'must be personal or business'] if !empty?(account_holder_type) && !%w[business personal].include?(account_holder_type.to_s) - if(!empty?(account_type) && !%w[checking savings].include?(account_type.to_s)) - errors << [:account_type, 'must be checking or savings'] - end + errors << [:account_type, 'must be checking or savings'] if !empty?(account_type) && !%w[checking savings].include?(account_type.to_s) errors_hash(errors) end @@ -54,25 +60,51 @@ def credit_card? false end + def valid_routing_number? + digits = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).cover?(d) } + case digits.size + when 9 + return checksum(digits) == 0 || CAN_INSTITUTION_NUMBERS.include?(routing_number[1..3]) + when 8 + return CAN_INSTITUTION_NUMBERS.include?(routing_number[5..7]) + end + + false + end + # Routing numbers may be validated by calculating a checksum and dividing it by 10. The # formula is: # (3(d1 + d4 + d7) + 7(d2 + d5 + d8) + 1(d3 + d6 + d9))mod 10 = 0 # See http://en.wikipedia.org/wiki/Routing_transit_number#Internal_checksums - def valid_routing_number? + def checksum(digits) + ((3 * (digits[0] + digits[3] + digits[6])) + + (7 * (digits[1] + digits[4] + digits[7])) + + (digits[2] + digits[5] + digits[8])) % 10 + end + + # Always return MICR-formatted routing number for Canadian routing numbers, US routing numbers unchanged + def micr_format_routing_number digits = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).cover?(d) } case digits.size when 9 - checksum = ((3 * (digits[0] + digits[3] + digits[6])) + - (7 * (digits[1] + digits[4] + digits[7])) + - (digits[2] + digits[5] + digits[8])) % 10 - case checksum - when 0 - true + if checksum(digits) == 0 + return routing_number else - false + return routing_number[4..8] + routing_number[1..3] end - else - false + when 8 + return routing_number + end + end + + # Always return electronic-formatted routing number for Canadian routing numbers, US routing numbers unchanged + def electronic_format_routing_number + digits = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).cover?(d) } + case digits.size + when 9 + return routing_number + when 8 + return '0' + routing_number[5..7] + routing_number[0..4] end end end diff --git a/lib/active_merchant/billing/compatibility.rb b/lib/active_merchant/billing/compatibility.rb index 11103dcee68..319ec8a4350 100644 --- a/lib/active_merchant/billing/compatibility.rb +++ b/lib/active_merchant/billing/compatibility.rb @@ -28,7 +28,7 @@ def self.deprecated def self.humanize(lower_case_and_underscored_word) result = lower_case_and_underscored_word.to_s.dup result.gsub!(/_id$/, '') - result.gsub!(/_/, ' ') + result.tr!('_', ' ') result.gsub(/([a-z\d]*)/i, &:downcase).gsub(/^\w/) { $&.upcase } end end @@ -56,7 +56,7 @@ def valid? private def internal_errors - @errors ||= Errors.new + @internal_errors ||= Errors.new end class Errors < Hash @@ -75,7 +75,7 @@ def []=(key, value) end def empty? - all? { |k, v| v&.empty? } + all? { |_k, v| v&.empty? } end def on(field) @@ -98,7 +98,8 @@ def full_messages result = [] self.each do |key, messages| - next unless(messages && !messages.empty?) + next unless messages && !messages.empty? + if key == 'base' result << messages.first.to_s else diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb index a14bc726c6a..6982139dad3 100644 --- a/lib/active_merchant/billing/credit_card.rb +++ b/lib/active_merchant/billing/credit_card.rb @@ -18,7 +18,28 @@ module Billing #:nodoc: # * Dankort # * Maestro # * Forbrugsforeningen + # * Sodexo + # * Vr + # * Carnet + # * Synchrony + # * Routex # * Elo + # * Alelo + # * Cabal + # * Naranja + # * UnionPay + # * Alia + # * Olimpica + # * Creditel + # * Confiable + # * Mada + # * BpPlus + # * Passcard + # * Edenred + # * Anda + # * Creditos directos (Tarjeta D) + # * Panal + # * Verve # # 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 @@ -48,6 +69,8 @@ module Billing #:nodoc: class CreditCard < Model include CreditCardMethods + BRANDS_WITH_SPACES_IN_NUMBER = %w(bp_plus) + class << self # Inherited, but can be overridden w/o changing parent's value attr_accessor :require_verification_value @@ -63,7 +86,7 @@ class << self attr_reader :number def number=(value) - @number = (empty?(value) ? value : value.to_s.gsub(/[^\d]/, '')) + @number = (empty?(value) ? value : filter_number(value)) end # Returns or sets the expiry month for the card. @@ -89,7 +112,28 @@ def number=(value) # * +'dankort'+ # * +'maestro'+ # * +'forbrugsforeningen'+ + # * +'sodexo'+ + # * +'vr'+ + # * +'carnet'+ + # * +'synchrony'+ + # * +'routex'+ # * +'elo'+ + # * +'alelo'+ + # * +'cabal'+ + # * +'naranja'+ + # * +'union_pay'+ + # * +'alia'+ + # * +'olimpica'+ + # * +'creditel'+ + # * +'confiable'+ + # * +'mada'+ + # * +'bp_plus'+ + # * +'passcard'+ + # * +'edenred'+ + # * +'anda'+ + # * +'tarjeta-d'+ + # * +'panal'+ + # * +'verve'+ # # Or, if you wish to test your implementation, +'bogus'+. # @@ -180,7 +224,7 @@ def requires_verification_value? 'contactless' => 'Data was read by a Contactless EMV kernel. Issuer script results are not available.', 'contactless_magstripe' => 'Contactless data was read with a non-EMV protocol.', 'contact' => 'Data was read using the EMV protocol. Issuer script results may follow.', - 'contact_quickchip' => 'Data was read by the Quickchip EMV kernel. Issuer script results are not available.', + 'contact_quickchip' => 'Data was read by the Quickchip EMV kernel. Issuer script results are not available.' } # Returns the ciphertext of the card's encrypted PIN. @@ -311,8 +355,21 @@ def emv? icc_data.present? end + def allow_spaces_in_card?(number = nil) + BRANDS_WITH_SPACES_IN_NUMBER.include?(self.class.brand?(self.number || number)) + end + private + def filter_number(value) + regex = if allow_spaces_in_card?(value) + /[^\d ]/ + else + /[^\d]/ + end + value.to_s.gsub(regex, '') + end + def validate_essential_attributes #:nodoc: errors = [] @@ -321,7 +378,7 @@ def validate_essential_attributes #:nodoc: errors << [:last_name, 'cannot be empty'] if last_name.blank? end - if(empty?(month) || empty?(year)) + if empty?(month) || empty?(year) errors << [:month, 'is required'] if empty?(month) errors << [:year, 'is required'] if empty?(year) else @@ -330,7 +387,7 @@ def validate_essential_attributes #:nodoc: if expired? errors << [:year, 'expired'] else - errors << [:year, 'is not a valid year'] if !valid_expiry_year?(year) + errors << [:year, 'is not a valid year'] if !valid_expiry_year?(year) end end @@ -341,7 +398,7 @@ def validate_card_brand_and_number #:nodoc: errors = [] if !empty?(brand) - errors << [:brand, 'is invalid'] if !CreditCard.card_companies.include?(brand) + errors << [:brand, 'is invalid'] if !CreditCard.card_companies.include?(brand) end if empty?(number) @@ -361,9 +418,7 @@ def validate_verification_value #:nodoc: errors = [] if verification_value? - unless valid_card_verification_value?(verification_value, brand) - errors << [:verification_value, "should be #{card_verification_value_length(brand)} digits"] - end + errors << [:verification_value, "should be #{card_verification_value_length(brand)} digits"] unless valid_card_verification_value?(verification_value, brand) elsif requires_verification_value? && !valid_card_verification_value?(verification_value, brand) errors << [:verification_value, 'is required'] end diff --git a/lib/active_merchant/billing/credit_card_formatting.rb b/lib/active_merchant/billing/credit_card_formatting.rb index 2a55bae60ad..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" @@ -15,6 +19,7 @@ def format(number, option) case option when :two_digits then sprintf('%.2i', number.to_i)[-2..-1] when :four_digits then sprintf('%.4i', number.to_i)[-4..-1] + when :four_digits_year then number.to_s.length == 2 ? '20' + number.to_s : format(number, :four_digits) else number end end diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb index 47ad881355f..bd9fe0c9197 100644 --- a/lib/active_merchant/billing/credit_card_methods.rb +++ b/lib/active_merchant/billing/credit_card_methods.rb @@ -1,3 +1,5 @@ +require 'set' + module ActiveMerchant #:nodoc: module Billing #:nodoc: # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object. @@ -6,23 +8,51 @@ module CreditCardMethods 'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ }, 'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) }, 'elo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ELO_RANGES) }, - 'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ }, + 'cabal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 8), CABAL_RANGES) }, + 'alelo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ALELO_RANGES) }, + 'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}$/ }, 'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ }, - 'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ }, - 'jcb' => ->(num) { num =~ /^35(28|29|[3-8]\d)\d{12}$/ }, + 'naranja' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), NARANJA_RANGES) }, + 'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11,16}$/ }, + 'jcb' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 4), JCB_RANGES) }, 'dankort' => ->(num) { num =~ /^5019\d{12}$/ }, - 'maestro' => ->(num) { (12..19).cover?(num&.size) && in_bin_range?(num.slice(0, 6), MAESTRO_RANGES) }, + 'maestro' => lambda { |num| + (12..19).cover?(num&.size) && ( + in_bin_range?(num.slice(0, 6), MAESTRO_RANGES) || + MAESTRO_BINS.any? { |bin| num.slice(0, bin.size) == bin } + ) + }, + '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)\d{8}$/ }, - 'vr' => ->(num) { num =~ /^(627416|637036)\d{8}$/ }, + 'sodexo' => ->(num) { num =~ /^(606071|603389|606070|606069|606068|600818|505864|505865)\d{10}$/ }, + '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) }, 'carnet' => lambda { |num| num&.size == 16 && ( in_bin_range?(num.slice(0, 6), CARNET_RANGES) || CARNET_BINS.any? { |bin| num.slice(0, bin.size) == bin } ) - } + }, + 'cartes_bancaires' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), CARTES_BANCAIRES_RANGES) }, + 'olimpica' => ->(num) { num =~ /^636853\d{10}$/ }, + '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}$/ }, + '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}$/ }, + '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) } } + SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ } + # http://www.barclaycard.co.uk/business/files/bin_rules.pdf ELECTRON_RANGES = [ [400115], @@ -48,35 +78,216 @@ module CreditCardMethods ] CARNET_BINS = Set.new( - [ - '286900', '502275', '606333', '627535', '636318', '636379', '639388', - '639484', '639559', '50633601', '50633606', '58877274', '62753500', - '60462203', '60462204', '588772' + %w[ + 286900 502275 606333 627535 636318 636379 639388 + 639484 639559 50633601 50633606 58877274 62753500 + 60462203 60462204 588772 ] ) + CARTES_BANCAIRES_RANGES = [ + (507589..507590), + (507593..507595), + [507597], + [560408], + [581752], + (585402..585405), + (585501..585505), + (585577..585582) + ] + # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73 MASTERCARD_RANGES = [ (222100..272099), (510000..559999), + [605272], + [606282], + [637095], + [637568], + (637599..637600), + [637609], ] - # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73 + MAESTRO_BINS = Set.new( + %w[ 500057 + 501018 501043 501045 501047 501049 501051 501072 501075 501083 501087 501089 501095 + 501500 501623 + 501879 502113 502120 502121 502301 + 503175 503337 503645 503670 + 504310 504338 504363 504533 504587 504620 504639 504656 504738 504781 504910 + 507001 507002 507004 507082 507090 + 560014 560565 561033 + 572402 572610 572626 + 576904 + 578614 + 581149 + 585274 585697 + 586509 + 588729 588792 + 589244 589407 589471 589605 589633 589647 589671 589916 + 590043 590206 590263 590265 590278 590361 590362 590379 590393 590590 + 591235 591420 591481 591620 591770 591948 591994 + 592024 592161 592184 592186 592201 592384 592393 592528 592566 592704 592735 592879 592884 + 593074 593264 593272 593355 593496 593556 593589 593666 593709 593825 593963 593994 + 594184 594409 594468 594475 594581 594665 594691 594710 594874 594968 + 595355 595364 595532 595547 595561 595568 595743 595929 + 596245 596289 596399 596405 596590 596608 596645 596646 596791 596808 596815 596846 + 597077 597094 597143 597370 597410 597765 597855 597862 + 598053 598054 598395 598585 598793 598794 598815 598835 598838 598880 598889 + 599000 599069 599089 599148 599191 599310 599741 599742 599867 + 601070 601452 601628 601638 + 602648 + 603326 603450 603689 + 604983 + 606126 + 608710 + 627339 627453 627454 627973 + 636117 636380 636422 636502 636639 + 637046 637529 637568 637600 637756 + 639130 639229 639350 + 690032] + ) + + # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 79 MAESTRO_RANGES = [ + (500032..500033), + (501015..501016), + (501020..501021), + (501023..501029), + (501038..501041), + (501053..501058), + (501060..501063), + (501066..501067), + (501091..501092), + (501104..501105), + (501107..501108), + (501104..501105), + (501107..501108), + (501800..501899), + (502000..502099), + (503800..503899), + (561200..561269), + (561271..561299), + (561320..561356), + (581700..581751), + (581753..581800), + (589300..589399), + (589998..591259), + (591261..596770), + (596772..598744), + (598746..599999), + (600297..600314), + (600316..600335), + (600337..600362), + (600364..600382), + (601232..601254), + (601256..601276), + (601640..601652), + (601689..601700), + (602011..602048), + [602050], + (630400..630499), (639000..639099), (670000..679999), ] # https://dev.elo.com.br/apis/tabela-de-bins, download csv from left sidebar ELO_RANGES = [ - 506707..506708, 506715..506715, 506718..506722, 506724..506724, 506726..506736, 506739..506739, 506741..506743, - 506745..506747, 506753..506753, 506774..506776, 506778..506778, 509000..509001, 509003..509003, 509007..509007, - 509020..509022, 509035..509035, 509039..509042, 509045..509045, 509048..509048, 509051..509071, 509073..509074, - 509077..509080, 509084..509084, 509091..509094, 509098..509098, 509100..509100, 509104..509104, 509106..509109, - 627780..627780, 636368..636368, 650031..650033, 650035..650045, 650047..650047, 650406..650410, 650434..650436, - 650439..650439, 650485..650504, 650506..650530, 650577..650580, 650582..650591, 650721..650727, 650901..650922, - 650928..650928, 650938..650939, 650946..650948, 650954..650955, 650962..650963, 650967..650967, 650971..650971, - 651652..651667, 651675..651678, 655000..655010, 655012..655015, 655051..655052, 655056..655057 + 506707..506708, 506715..506715, 506717..506722, 506724..506736, 506739..506743, + 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, 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. + # The BINs beginning with the digit 4 overlap with Visa's range of valid card numbers. + # By placing the 'alelo' entry in CARD_COMPANY_DETECTORS below the 'visa' entry, we + # identify these cards as Visa. This works because transactions with such cards will + # run on Visa rails. + ALELO_RANGES = [ + 402588..402588, 404347..404347, 405876..405876, 405882..405882, 405884..405884, + 405886..405886, 430471..430471, 438061..438061, 438064..438064, 470063..470066, + 496067..496067, 506699..506704, 506706..506706, 506713..506714, 506716..506716, + 506749..506750, 506752..506752, 506754..506756, 506758..506767, 506770..506771, + 506773..506773, 509015..509019, 509880..509882, 509884..509885, 509887..509887, + 509987..509992 + ] + + CABAL_RANGES = [ + 60420100..60440099, + 58965700..58965799, + 60352200..60352299, + 65027200..65027299, + 65008700..65008700, + 65090000..65090099 + ] + + MADA_RANGES = [ + 504300..504300, 506968..506968, 508160..508160, 585265..585265, 588848..588848, + 588850..588850, 588982..588983, 589005..589005, 589206..589206, 604906..604906, + 605141..605141, 636120..636120, 968201..968209, 968211..968211 + ] + + NARANJA_RANGES = [ + 589562..589562 + ] + + # 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, + 81000000..81099999, 81100000..81319999, 81320000..81519999, 81520000..81639999, 81640000..81719999 + ] + + JCB_RANGES = [ + 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) @@ -86,7 +297,7 @@ def self.included(base) def self.in_bin_range?(number, ranges) bin = number.to_i ranges.any? do |range| - range.cover?(bin) + range.include?(bin) end end @@ -153,8 +364,8 @@ module ClassMethods def valid_number?(number) valid_test_mode_card_number?(number) || valid_card_number_length?(number) && - valid_card_number_characters?(number) && - valid_checksum?(number) + valid_card_number_characters?(brand?(number), number) && + valid_by_algorithm?(brand?(number), number) end def card_companies @@ -194,6 +405,7 @@ def first_digits(number) def last_digits(number) return '' if number.nil? + number.length <= 4 ? number : number.slice(-4..-1) end @@ -215,11 +427,14 @@ def matching_type?(number, brand) def valid_card_number_length?(number) #:nodoc: return false if number.nil? + number.length >= 12 end - def valid_card_number_characters?(number) #:nodoc: + def valid_card_number_characters?(brand, number) #:nodoc: return false if number.nil? + return number =~ /\A[0-9 ]+\Z/ if brand == 'bp_plus' + !number.match(/\D/) end @@ -228,7 +443,28 @@ def valid_test_mode_card_number?(number) #:nodoc: %w[1 2 3 success failure error].include?(number) end - ODD_LUHN_VALUE = { + def sodexo_no_luhn?(numbers) + SODEXO_NO_LUHN.call(numbers) + end + + def valid_by_algorithm?(brand, numbers) #:nodoc: + case brand + when 'naranja' + valid_naranja_algo?(numbers) + when 'creditel' + valid_creditel_algo?(numbers) + when 'alia', 'confiable', 'maestro_no_luhn', 'anda', 'tarjeta-d', 'hipercard' + true + when 'sodexo' + sodexo_no_luhn?(numbers) ? true : valid_luhn?(numbers) + when 'bp_plus', 'passcard', 'edenred' + valid_luhn_non_zero_check_digit?(numbers) + else + valid_luhn?(numbers) + end + end + + BYTES_TO_DIGITS = { 48 => 0, 49 => 1, 50 => 2, @@ -242,7 +478,7 @@ def valid_test_mode_card_number?(number) #:nodoc: nil => 0 }.freeze - EVEN_LUHN_VALUE = { + BYTES_TO_DIGITS_DOUBLED = { 48 => 0, # 0 * 2 49 => 2, # 1 * 2 50 => 4, # 2 * 2 @@ -252,28 +488,67 @@ def valid_test_mode_card_number?(number) #:nodoc: 54 => 3, # 6 * 2 - 9 55 => 5, # etc ... 56 => 7, - 57 => 9, + 57 => 9 }.freeze # Checks the validity of a card number by use of the Luhn Algorithm. # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details. # This implementation is from the luhn_checksum gem, https://github.com/zendesk/luhn_checksum. - def valid_checksum?(numbers) #:nodoc: + def valid_luhn?(numbers) #:nodoc: sum = 0 odd = true - numbers.reverse.bytes.each do |number| + numbers.reverse.bytes.each do |bytes| if odd odd = false - sum += ODD_LUHN_VALUE[number] + sum += BYTES_TO_DIGITS[bytes] else odd = true - sum += EVEN_LUHN_VALUE[number] + sum += BYTES_TO_DIGITS_DOUBLED[bytes] end end sum % 10 == 0 end + + def valid_luhn_with_check_digit?(numbers, check_digit) + sum = 0 + + doubler = true + + numbers.reverse.bytes.each do |bytes| + doubler ? sum += BYTES_TO_DIGITS_DOUBLED[bytes] : sum += BYTES_TO_DIGITS[bytes] + doubler = !doubler + end + + (10 - (sum % 10)) % 10 == check_digit.to_i + end + + def valid_luhn_non_zero_check_digit?(numbers) + return valid_luhn?(numbers.delete(' ')) if numbers[5] == ' ' + + check_digit = numbers[-1] + luhn_payload = numbers.delete(' ').chop + valid_luhn_with_check_digit?(luhn_payload, check_digit) + end + + # Checks the validity of a card number by use of specific algorithms + def valid_naranja_algo?(numbers) #:nodoc: + num_array = numbers.to_s.chars.map(&:to_i) + multipliers = [4, 3, 2, 7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2] + num_sum = num_array[0..14].zip(multipliers).map { |a, b| a * b }.reduce(:+) + intermediate = 11 - (num_sum % 11) + final_num = intermediate > 9 ? 0 : intermediate + final_num == num_array[15] + end + + def valid_creditel_algo?(numbers) #:nodoc: + num_array = numbers.to_s.chars.map(&:to_i) + multipliers = [5, 4, 3, 2, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 9] + num_sum = num_array[0..14].zip(multipliers).map { |a, b| a * b }.reduce(:+) + final_num = num_sum % 10 + final_num == num_array[15] + end end end end diff --git a/lib/active_merchant/billing/cvv_result.rb b/lib/active_merchant/billing/cvv_result.rb index 2fbbbb83c4b..e96eaf0889f 100644 --- a/lib/active_merchant/billing/cvv_result.rb +++ b/lib/active_merchant/billing/cvv_result.rb @@ -4,7 +4,6 @@ module Billing # http://www.bbbonline.org/eExport/doc/MerchantGuide_cvv2.pdf # Check additional codes from cybersource website class CVVResult - MESSAGES = { 'D' => 'CVV check flagged transaction as suspicious', 'I' => 'CVV failed data validation check', diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb index 29fcc6bbaf8..2cbeca869a1 100644 --- a/lib/active_merchant/billing/gateway.rb +++ b/lib/active_merchant/billing/gateway.rb @@ -80,22 +80,23 @@ class Gateway # as network tokenization. STANDARD_ERROR_CODE = { - :incorrect_number => 'incorrect_number', - :invalid_number => 'invalid_number', - :invalid_expiry_date => 'invalid_expiry_date', - :invalid_cvc => 'invalid_cvc', - :expired_card => 'expired_card', - :incorrect_cvc => 'incorrect_cvc', - :incorrect_zip => 'incorrect_zip', - :incorrect_address => 'incorrect_address', - :incorrect_pin => 'incorrect_pin', - :card_declined => 'card_declined', - :processing_error => 'processing_error', - :call_issuer => 'call_issuer', - :pickup_card => 'pick_up_card', - :config_error => 'config_error', - :test_mode_live_card => 'test_mode_live_card', - :unsupported_feature => 'unsupported_feature', + incorrect_number: 'incorrect_number', + invalid_number: 'invalid_number', + invalid_expiry_date: 'invalid_expiry_date', + invalid_cvc: 'invalid_cvc', + expired_card: 'expired_card', + incorrect_cvc: 'incorrect_cvc', + incorrect_zip: 'incorrect_zip', + incorrect_address: 'incorrect_address', + incorrect_pin: 'incorrect_pin', + card_declined: 'card_declined', + processing_error: 'processing_error', + call_issuer: 'call_issuer', + pickup_card: 'pick_up_card', + config_error: 'config_error', + test_mode_live_card: 'test_mode_live_card', + unsupported_feature: 'unsupported_feature', + invalid_amount: 'invalid_amount' } cattr_reader :implementations @@ -123,8 +124,9 @@ def generate_unique_id class_attribute :supported_cardtypes self.supported_cardtypes = [] + # This default list of currencies without fractions are from https://en.wikipedia.org/wiki/ISO_4217 class_attribute :currencies_without_fractions, :currencies_with_three_decimal_places - self.currencies_without_fractions = %w(BIF BYR CLP CVE DJF GNF ISK JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) + self.currencies_without_fractions = %w(BIF BYR CLP CVE DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF) self.currencies_with_three_decimal_places = %w() class_attribute :homepage_url @@ -155,15 +157,13 @@ def self.card_brand(source) def self.supported_countries=(country_codes) country_codes.each do |country_code| - unless ActiveMerchant::Country.find(country_code) - raise ActiveMerchant::InvalidCountryCodeError, "No country could be found for the country #{country_code}" - end + raise ActiveMerchant::InvalidCountryCodeError, "No country could be found for the country #{country_code}" unless ActiveMerchant::Country.find(country_code) end @supported_countries = country_codes.dup end def self.supported_countries - @supported_countries ||= [] + @supported_countries ||= (self.superclass.supported_countries || []) end def supported_countries @@ -200,6 +200,16 @@ def supports_network_tokenization? false end + def add_fields_to_post_if_present(post, options, fields) + fields.each do |field| + add_field_to_post_if_present(post, options, field) + end + end + + def add_field_to_post_if_present(post, options, field) + post[field] = options[field] if options[field] + end + protected # :nodoc: all def normalize(field) @@ -214,11 +224,11 @@ def normalize(field) def user_agent @@ua ||= JSON.dump({ - :bindings_version => ActiveMerchant::VERSION, - :lang => 'ruby', - :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", - :platform => RUBY_PLATFORM, - :publisher => 'active_merchant' + bindings_version: ActiveMerchant::VERSION, + lang: 'ruby', + lang_version: "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + platform: RUBY_PLATFORM, + publisher: 'active_merchant' }) end @@ -240,16 +250,16 @@ def name def amount(money) return nil if money.nil? - cents = if money.respond_to?(:cents) - ActiveMerchant.deprecated 'Support for Money objects is deprecated and will be removed from a future release of ActiveMerchant. Please use an Integer value in cents' - money.cents - else - money - end - if money.is_a?(String) - raise ArgumentError, 'money amount must be a positive Integer in cents.' - end + cents = + if money.respond_to?(:cents) + ActiveMerchant.deprecated 'Support for Money objects is deprecated and will be removed from a future release of ActiveMerchant. Please use an Integer value in cents' + money.cents + else + money + end + + raise ArgumentError, 'money amount must be a positive Integer in cents.' if money.is_a?(String) if self.money_format == :cents cents.to_s @@ -270,6 +280,7 @@ def localized_amount(money, currency) amount = amount(money) return amount unless non_fractional_currency?(currency) || three_decimal_currency?(currency) + if non_fractional_currency?(currency) if self.money_format == :cents sprintf('%.0f', amount.to_f / 100) @@ -291,6 +302,7 @@ def currency(money) def truncate(value, max_size) return nil unless value + value.to_s[0, max_size] end @@ -303,13 +315,22 @@ 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) raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first) valid_options = param[1..-1] - raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:words_connector => 'or')}") unless valid_options.include?(hash[param.first]) + raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(words_connector: 'or')}") unless valid_options.include?(hash[param.first]) else raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param) end diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb index b72ae0c5396..11a003f1f9f 100644 --- a/lib/active_merchant/billing/gateways/adyen.rb +++ b/lib/active_merchant/billing/gateways/adyen.rb @@ -1,38 +1,46 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class AdyenGateway < Gateway - # we recommend setting up merchant-specific endpoints. # https://docs.adyen.com/developers/api-manual#apiendpoints - self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/v18' - self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/v18' + self.test_url = 'https://pal-test.adyen.com/pal/servlet/' + self.live_url = 'https://pal-live.adyen.com/pal/servlet/' - self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US'] + self.supported_countries = %w(AT AU BE BG BR CH CY CZ DE DK EE ES FI FR GB GI GR HK HU IE IS IT LI LT LU LV MC MT MX NL NO PL PT RO SE SG SK SI US) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo] + self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) + self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LYD OMR TND) + self.supported_cardtypes = %i[visa master american_express diners_club jcb dankort maestro discover elo naranja cabal unionpay] self.money_format = :cents self.homepage_url = 'https://www.adyen.com/' self.display_name = 'Adyen' + PAYMENT_API_VERSION = 'v68' + RECURRING_API_VERSION = 'v68' + STANDARD_ERROR_CODE_MAPPING = { + '0' => STANDARD_ERROR_CODE[:processing_error], + '10' => STANDARD_ERROR_CODE[:config_error], + '100' => STANDARD_ERROR_CODE[:invalid_amount], '101' => STANDARD_ERROR_CODE[:incorrect_number], '103' => STANDARD_ERROR_CODE[:invalid_cvc], + '104' => STANDARD_ERROR_CODE[:incorrect_address], '131' => STANDARD_ERROR_CODE[:incorrect_address], '132' => STANDARD_ERROR_CODE[:incorrect_address], '133' => STANDARD_ERROR_CODE[:incorrect_address], '134' => STANDARD_ERROR_CODE[:incorrect_address], - '135' => STANDARD_ERROR_CODE[:incorrect_address], + '135' => STANDARD_ERROR_CODE[:incorrect_address] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password, :merchant_account) @username, @password, @merchant_account = options.values_at(:username, :password, :merchant_account) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) if options[:execute_threed] || options[:threed_dynamic] authorize(money, payment, options) else @@ -43,53 +51,127 @@ def purchase(money, payment, options={}) end end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) requires!(options, :order_id) post = init_post(options) add_invoice(post, money, options) - add_payment(post, payment) + add_payment(post, payment, options) add_extra_data(post, payment, options) - add_shopper_interaction(post, payment, options) + add_stored_credentials(post, payment, options) add_address(post, options) add_installments(post, options) if options[:installments] add_3ds(post, options) + add_3ds_authenticated_data(post, options) + add_splits(post, options) + add_recurring_contract(post, options) + add_network_transaction_reference(post, 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) commit('authorise', post, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = init_post(options) add_invoice_for_modification(post, money, 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 - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = init_post(options) add_invoice_for_modification(post, money, options) - add_original_reference(post, authorization, options) + add_reference(post, authorization, options) + add_splits(post, options) + add_network_transaction_reference(post, options) commit('refund', post, options) end - def void(authorization, options={}) + def credit(money, payment, options = {}) + 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 + + def void(authorization, options = {}) + post = init_post(options) + endpoint = options[:cancel_or_refund] ? 'cancelOrRefund' : 'cancel' + add_reference(post, authorization, options) + add_network_transaction_reference(post, options) + commit(endpoint, post, options) + end + + def adjust(money, authorization, options = {}) post = init_post(options) + add_invoice_for_modification(post, money, options) add_reference(post, authorization, options) - commit('cancel', post, options) + add_extra_data(post, nil, options) + commit('adjustAuthorisation', post, options) end - def store(credit_card, options={}) + def store(credit_card, options = {}) requires!(options, :order_id) post = init_post(options) add_invoice(post, 0, options) - add_payment(post, credit_card) + add_payment(post, credit_card, options) add_extra_data(post, credit_card, options) - add_recurring_contract(post, options) + add_stored_credentials(post, credit_card, options) add_address(post, options) - commit('authorise', post, options) + add_network_transaction_reference(post, options) + options[:recurring_contract_type] ||= 'RECURRING' + add_recurring_contract(post, options) + + action = options[:tokenize_only] ? 'storeToken' : 'authorise' + + initial_response = commit(action, post, options) + + if initial_response.success? && card_not_stored?(initial_response) + unsupported_failure_response(initial_response) + else + initial_response + end + end + + def unstore(options = {}) + requires!(options, :shopper_reference, :recurring_detail_reference) + post = {} + + add_shopper_reference(post, options) + add_merchant_account(post, options) + post[:recurringDetailReference] = options[:recurring_detail_reference] + + commit('disable', post, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) + amount = options[:verify_amount]&.to_i || 0 MultiResponse.run(:use_first_response) do |r| - r.process { authorize(0, credit_card, options) } + r.process { authorize(amount, credit_card, options) } options[:idempotency_key] = nil r.process(:ignore_result) { void(r.authorization, options) } end @@ -99,12 +181,19 @@ def supports_scrubbing? true end + def supports_network_tokenization? + true + end + def scrub(transcript) transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). - gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]'). - gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]'). - gsub(%r(("cavv\\?":\\?")[^"]*)i, '\1[FILTERED]') + gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cvc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cavv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("bankLocationId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("iban\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("bankAccountNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]') end private @@ -129,10 +218,14 @@ def scrub(transcript) '16' => 'N', # Postal code doesn't match, address unknown '17' => 'U', # Postal code doesn't match, address not checked '18' => 'I', # Neither postal code nor address were checked + '19' => 'L', # Name and postal code matches. '20' => 'V', # Name, address and postal code matches. + '21' => 'O', # Name and address matches. + '22' => 'K', # Name matches. '23' => 'F', # Postal code matches, name doesn't match. '24' => 'H', # Both postal code and address matches, name doesn't match. - '25' => 'T' # Address matches, name doesn't match. + '25' => 'T', # Address matches, name doesn't match. + '26' => 'N' # Neither postal code, address nor name matches. } CVC_MAPPING = { @@ -148,26 +241,185 @@ 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] if options.dig(:billing_address, :phone) - post[:shopperEmail] = options[:shopper_email] if options[:shopper_email] - post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip] - post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference] - post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement] - post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset] + post[:telephoneNumber] = (options[:billing_address][:phone_number] if options.dig(:billing_address, :phone_number)) || (options[:billing_address][:phone] if options.dig(:billing_address, :phone)) || '' 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] post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard) - post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint] - add_risk_data(post, options) + post[:additionalData][:authorisationType] = options[:authorisation_type] if options[:authorisation_type] + post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data] + post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage] + post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test? + post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement] + end + + def extract_and_transform(mapper, from) + mapper.each_with_object({}) do |key_map, hsh| + key, item_key = key_map[0], key_map[1] + hsh[key] = from[item_key.to_sym] + end + end + + def add_level_2_data(post, options) + return unless options[:level_2_data].present? + + mapper = { + "enhancedSchemeData.totalTaxAmount": 'total_tax_amount', + "enhancedSchemeData.customerReference": 'customer_reference' + } + post[:additionalData].merge!(extract_and_transform(mapper, options[:level_2_data])) + end + + def add_level_3_data(post, options) + return unless options[:level_3_data].present? + + mapper = { "enhancedSchemeData.freightAmount": 'freight_amount', + "enhancedSchemeData.destinationStateProvinceCode": 'destination_state_province_code', + "enhancedSchemeData.shipFromPostalCode": 'ship_from_postal_code', + "enhancedSchemeData.orderDate": 'order_date', + "enhancedSchemeData.destinationPostalCode": 'destination_postal_code', + "enhancedSchemeData.destinationCountryCode": 'destination_country_code', + "enhancedSchemeData.dutyAmount": 'duty_amount' } + + post[:additionalData].merge!(extract_and_transform(mapper, options[:level_3_data])) + + item_detail_keys = %w[description product_code quantity unit_of_measure unit_price discount_amount total_amount commodity_code] + if options[:level_3_data][:items].present? + options[:level_3_data][:items].last(9).each.with_index(1) do |item, index| + mapper = item_detail_keys.each_with_object({}) do |key, hsh| + hsh["enhancedSchemeData.itemDetailLine#{index}.#{key.camelize(:lower)}"] = key + end + post[:additionalData].merge!(extract_and_transform(mapper, item)) + end + end + post[:additionalData].compact! + end + + 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) + post[:additionalData][:subMerchantID] = options[:sub_merchant_id] if options[:sub_merchant_id] + post[:additionalData][:subMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name] + post[:additionalData][:subMerchantStreet] = options[:sub_merchant_street] if options[:sub_merchant_street] + post[:additionalData][:subMerchantCity] = options[:sub_merchant_city] if options[:sub_merchant_city] + post[:additionalData][:subMerchantState] = options[:sub_merchant_state] if options[:sub_merchant_state] + post[:additionalData][:subMerchantPostalCode] = options[:sub_merchant_postal_code] if options[:sub_merchant_postal_code] + post[:additionalData][:subMerchantCountry] = options[:sub_merchant_country] if options[:sub_merchant_country] + post[:additionalData][:subMerchantTaxId] = options[:sub_merchant_tax_id] if options[:sub_merchant_tax_id] + post[:additionalData][:subMerchantMCC] = options[:sub_merchant_mcc] if options[:sub_merchant_mcc] + post[:additionalData] = post[:additionalData].merge(options[:sub_merchant_data]) if options[:sub_merchant_data] end def add_risk_data(post, options) @@ -177,8 +429,44 @@ def add_risk_data(post, options) end end - def add_shopper_interaction(post, payment, options={}) - if (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard) + def add_splits(post, options) + return unless split_data = options[:splits] + + splits = [] + split_data.each do |split| + amount = { + value: split['amount']['value'] + } + amount[:currency] = split['amount']['currency'] if split['amount']['currency'] + + split_hash = { + amount: amount, + type: split['type'], + reference: split['reference'] + } + split_hash['account'] = split['account'] if split['account'] + splits.push(split_hash) + end + post[:splits] = splits + end + + def add_stored_credentials(post, payment, options) + add_shopper_interaction(post, payment, options) + add_recurring_processing_model(post, options) + end + + def add_merchant_account(post, options) + post[:merchantAccount] = options[:merchant_account] || @merchant_account + end + + def add_shopper_reference(post, options) + post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference] + end + + def add_shopper_interaction(post, payment, options = {}) + if (options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder') || + (payment.respond_to?(:verification_value) && payment.verification_value && options.dig(:stored_credential, :initial_transaction).nil?) || + payment.is_a?(NetworkTokenizationCreditCard) shopper_interaction = 'Ecommerce' else shopper_interaction = 'ContAuth' @@ -187,47 +475,99 @@ def add_shopper_interaction(post, payment, options={}) post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction end + def add_recurring_processing_model(post, options) + return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model] + + if options.dig(:stored_credential, :reason_type) == 'unscheduled' + if options.dig(:stored_credential, :initiator) == 'merchant' + recurring_processing_model = 'UnscheduledCardOnFile' + else + recurring_processing_model = 'CardOnFile' + end + else + recurring_processing_model = 'Subscription' + end + + post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model + end + def add_address(post, options) - return unless post[:card]&.kind_of?(Hash) + if address = options[:shipping_address] + post[:deliveryAddress] = {} + 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] + end + return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash) + if (address = options[:billing_address] || options[:address]) && address[:country] - post[:card][:billingAddress] = {} - post[:card][:billingAddress][:street] = address[:address1] || 'N/A' - post[:card][:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A' - post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip] - post[:card][:billingAddress][:city] = address[:city] || 'N/A' - post[:card][:billingAddress][:stateOrProvince] = address[:state] || 'N/A' - post[:card][: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] = address[:country] if address[:country] + post[:telephoneNumber] = address[:phone_number] || address[:phone] || '' + end + + def get_state(address) + address[:state] && !address[:state].blank? ? address[:state] : 'NA' + end + def add_invoice(post, money, options) + currency = options[:currency] || currency(money) amount = { - value: amount(money), - currency: options[:currency] || currency(money) + value: localized_amount(money, currency), + currency: currency } + post[:amount] = amount - post[:recurringProcessingModel] = options[:recurring_processing_model] if options[:recurring_processing_model] end def add_invoice_for_modification(post, money, options) + currency = options[:currency] || currency(money) amount = { - value: amount(money), - currency: options[:currency] || currency(money) + value: localized_amount(money, currency), + currency: currency } post[:modificationAmount] = amount end - def add_payment(post, payment) + def add_payment(post, payment, options, action = nil) if payment.is_a?(String) _, _, recurring_detail_reference = payment.split('#') post[:selectedRecurringDetailReference] = recurring_detail_reference - add_recurring_contract(post, options) + options[:recurring_contract_type] ||= 'RECURRING' + 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 + def add_bank_account(post, bank_account, options, action) + bank = { + bankAccountNumber: bank_account.account_number, + ownerName: bank_account.name, + countryCode: options[:billing_address].try(:[], :country) + } + + action == 'refundWithData' ? bank[:iban] = bank_account.routing_number : bank[:bankLocationId] = bank_account.routing_number + + requires!(bank, :bankAccountNumber, :ownerName, :countryCode) + post[:bankAccount] = bank + end + def add_card(post, credit_card) card = { expiryMonth: credit_card.month, @@ -237,28 +577,44 @@ def add_card(post, credit_card) cvc: credit_card.verification_value } - card.delete_if { |k, v| v.blank? } - card[:holderName] ||= 'Not Provided' if credit_card.is_a?(NetworkTokenizationCreditCard) + card.delete_if { |_k, v| v.blank? } + card[:holderName] ||= 'Not Provided' requires!(card, :expiryMonth, :expiryYear, :holderName, :number) 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] + options end - def add_reference(post, authorization, options = {}) - _, psp_reference, _ = authorization.split('#') - post[:originalReference] = single_reference(authorization) || psp_reference + def add_network_transaction_reference(post, options) + return unless ntid = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id) + + post[:additionalData] = {} unless post[:additionalData] + post[:additionalData][:networkTxReference] = ntid end - def add_original_reference(post, authorization, options = {}) - original_psp_reference, _, _ = authorization.split('#') - post[:originalReference] = single_reference(authorization) || original_psp_reference + def add_reference(post, authorization, options = {}) + original_reference = authorization.split('#').reject(&:empty?).first + 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 @@ -266,16 +622,41 @@ def add_mpi_data_for_network_tokenization_card(post, payment) post[:mpiData][:eci] = payment.eci || '07' end - def single_reference(authorization) - authorization if !authorization.include?('#') + def add_recurring_contract(post, options = {}) + return unless options[:recurring_contract_type] + + 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_recurring_contract(post, options = {}) - recurring = { - contract: 'RECURRING' + def add_application_info(post, options) + post[:applicationInfo] ||= {} + add_external_platform(post, options) + add_merchant_application(post, options) + end + + def add_external_platform(post, options) + options.update(externalPlatform: application_id) if application_id + + return unless options[:externalPlatform] + + post[:applicationInfo][:externalPlatform] = { + name: options[:externalPlatform][:name], + version: options[:externalPlatform][:version] } + end + + def add_merchant_application(post, options) + return unless options[:merchantApplication] - post[:recurring] = recurring + post[:applicationInfo][:merchantApplication] = { + name: options[:merchantApplication][:name], + version: options[:merchantApplication][:version] + } end def add_installments(post, options) @@ -285,33 +666,108 @@ def add_installments(post, options) end def add_3ds(post, options) - return unless options[:execute_threed] || options[:threed_dynamic] - post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } - post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed] + if three_ds_2_options = options[:three_ds_2] + device_channel = three_ds_2_options[:channel] + if device_channel == 'app' + post[:threeDS2RequestData] = { deviceChannel: device_channel } + else + add_browser_info(three_ds_2_options[:browser_info], post) + post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] } + end + + if options.has_key?(:execute_threed) + post[:additionalData][:executeThreeD] = options[:execute_threed] + post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption] + end + else + return unless !options[:execute_threed].nil? || !options[:threed_dynamic].nil? + + post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } if options[:execute_threed] || options[:threed_dynamic] + post[:additionalData] ||= {} + post[:additionalData][:executeThreeD] = options[:execute_threed] if !options[:execute_threed].nil? + end + end + + def add_3ds_authenticated_data(post, options) + if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid] + add_3ds1_authenticated_data(post, options) + elsif options[:three_d_secure] + add_3ds2_authenticated_data(post, options) + end + end + + def add_3ds1_authenticated_data(post, options) + three_d_secure_options = options[:three_d_secure] + post[:mpiData] = { + cavv: three_d_secure_options[:cavv], + cavvAlgorithm: three_d_secure_options[:cavv_algorithm], + eci: three_d_secure_options[:eci], + xid: three_d_secure_options[:xid], + directoryResponse: three_d_secure_options[:enrolled], + authenticationResponse: three_d_secure_options[:authentication_response_status] + } + end + + def add_3ds2_authenticated_data(post, options) + three_d_secure_options = options[:three_d_secure] + # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes. + if three_d_secure_options[:authentication_response_status].nil? + authentication_response = three_d_secure_options[:directory_response_status] + else + authentication_response = three_d_secure_options[:authentication_response_status] + end + post[:mpiData] = { + threeDSVersion: three_d_secure_options[:version], + eci: three_d_secure_options[:eci], + cavv: three_d_secure_options[:cavv], + dsTransID: three_d_secure_options[:ds_transaction_id], + directoryResponse: three_d_secure_options[:directory_response_status], + authenticationResponse: authentication_response + } + end + + def 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 parse(body) return {} if body.blank? + JSON.parse(body) end def commit(action, parameters, options) begin - raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options)) + raw_response = ssl_post(url(action), post_data(action, parameters), request_headers(options)) response = parse(raw_response) rescue ResponseError => e raw_response = e.response.body response = parse(raw_response) end - success = success_from(action, response) + + 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?, error_code: success ? nil : error_code_from(response), - avs_result: AVSResult.new(:code => avs_code_from(response)), + network_transaction_id: network_transaction_id_from(response), + avs_result: AVSResult.new(code: avs_code_from(response)), cvv_result: CVVResult.new(cvv_result_from(response)) ) end @@ -324,13 +780,24 @@ def cvv_result_from(response) CVC_MAPPING[response['additionalData']['cvcResult'][0]] if response.dig('additionalData', 'cvcResult') end - def url + def endpoint(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) if test? - test_url + "#{test_url}#{endpoint(action)}" elsif @options[:subdomain] - "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment/v18" + "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/#{endpoint(action)}" else - live_url + "#{live_url}#{endpoint(action)}" end end @@ -347,40 +814,75 @@ def request_headers(options) headers end - def success_from(action, response) + def success_from(action, response, options) + if %w[RedirectShopper ChallengeShopper].include?(response.dig('resultCode')) && !options[:execute_threed] && !options[:threed_dynamic] + response['refusalReason'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.' + return false + end case action.to_s when 'authorise', 'authorise3d' - ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode']) - when 'capture', 'refund', 'cancel' + %w[Authorised Received RedirectShopper].include?(response['resultCode']) + when 'capture', 'refund', 'cancel', 'cancelOrRefund' response['response'] == "[#{action}-received]" + when 'adjustAuthorisation' + response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]' + when 'storeToken' + response['result'] == 'Success' + when 'disable' + 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 action.to_s == 'authorise' - response['response'] || response['message'] + 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 + + 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 - response['refusalReason'] || response['resultCode'] || response['message'] + response['refusalReason'] || response['resultCode'] || response['message'] || response['result'] end end def authorization_from(action, parameters, response) return nil if response['pspReference'].nil? + recurring = response['additionalData']['recurring.recurringDetailReference'] if response['additionalData'] + recurring = response['recurringDetailReference'] if action == 'storeToken' + "#{parameters[:originalReference]}##{response['pspReference']}##{recurring}" end def init_post(options = {}) post = {} - post[:merchantAccount] = options[:merchant_account] || @merchant_account - post[:reference] = options[:order_id] if options[:order_id] + add_merchant_account(post, options) + post[:reference] = options[:order_id][0..79] if options[:order_id] post end @@ -389,7 +891,43 @@ def post_data(action, parameters = {}) end def error_code_from(response) - STANDARD_ERROR_CODE_MAPPING[response['errorCode']] + STANDARD_ERROR_CODE_MAPPING[response['errorCode']] || response['errorCode'] + end + + def network_transaction_id_from(response) + response.dig('additionalData', 'networkTxReference') + end + + def add_browser_info(browser_info, post) + return unless browser_info + + post[:browserInfo] = { + acceptHeader: browser_info[:accept_header], + colorDepth: browser_info[:depth], + javaEnabled: browser_info[:java], + language: browser_info[:language], + screenHeight: browser_info[:height], + screenWidth: browser_info[:width], + timeZoneOffset: browser_info[:timezone], + userAgent: browser_info[:user_agent] + } + end + + def unsupported_failure_response(initial_response) + Response.new( + false, + 'Recurring transactions are not supported for this card type.', + initial_response.params, + authorization: initial_response.authorization, + test: initial_response.test, + error_code: initial_response.error_code, + avs_result: initial_response.avs_result, + cvv_result: initial_response.cvv_result[:code] + ) + end + + def card_not_stored?(response) + response.authorization ? response.authorization.split('#')[2].nil? : true end end end diff --git a/lib/active_merchant/billing/gateways/airwallex.rb b/lib/active_merchant/billing/gateways/airwallex.rb new file mode 100644 index 00000000000..d2a20c2cc1a --- /dev/null +++ b/lib/active_merchant/billing/gateways/airwallex.rb @@ -0,0 +1,384 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class AirwallexGateway < Gateway + self.test_url = 'https://api-demo.airwallex.com/api/v1' + self.live_url = 'https://pci-api.airwallex.com/api/v1' + + # per https://www.airwallex.com/docs/online-payments__overview, cards are accepted in all EU countries + self.supported_countries = %w[AT AU BE BG CY CZ DE DK EE GR ES FI FR GB HK HR HU IE IT LT LU LV MT NL PL PT RO SE SG SI SK] + self.default_currency = 'AUD' + self.supported_cardtypes = %i[visa master] + + self.homepage_url = 'https://airwallex.com/' + self.display_name = 'Airwallex' + + ENDPOINTS = { + login: '/authentication/login', + setup: '/pa/payment_intents/create', + sale: '/pa/payment_intents/%{id}/confirm', + capture: '/pa/payment_intents/%{id}/capture', + refund: '/pa/refunds/create', + void: '/pa/payment_intents/%{id}/cancel' + } + + # Provided by Airwallex for testing purposes + TEST_NETWORK_TRANSACTION_IDS = { + visa: '123456789012345', + master: 'MCC123ABC0101' + } + + def initialize(options = {}) + requires!(options, :client_id, :client_api_key) + @client_id = options[:client_id] + @client_api_key = options[:client_api_key] + super + @access_token = options[:access_token] || setup_access_token + end + + def purchase(money, card, options = {}) + payment_intent_id = create_payment_intent(money, options) + post = { + 'request_id' => request_id(options), + 'merchant_order_id' => merchant_order_id(options) + } + add_card(post, card, options) + add_descriptor(post, options) + add_stored_credential(post, options) + add_return_url(post, options) + post['payment_method_options'] = { 'card' => { 'auto_capture' => false } } if authorization_only?(options) + + add_three_ds(post, options) + commit(:sale, post, payment_intent_id) + end + + def authorize(money, payment, options = {}) + # authorize is just a purchase w/o an auto capture + purchase(money, payment, options.merge({ auto_capture: false })) + end + + def capture(money, authorization, options = {}) + raise ArgumentError, 'An authorization value must be provided.' if authorization.blank? + + post = { + 'request_id' => request_id(options), + 'merchant_order_id' => merchant_order_id(options), + 'amount' => amount(money) + } + add_descriptor(post, options) + + commit(:capture, post, authorization) + end + + def refund(money, authorization, options = {}) + raise ArgumentError, 'An authorization value must be provided.' if authorization.blank? + + post = {} + post[:amount] = amount(money) + post[:payment_intent_id] = authorization + post[:request_id] = request_id(options) + post[:merchant_order_id] = merchant_order_id(options) + + commit(:refund, post) + end + + def void(authorization, options = {}) + raise ArgumentError, 'An authorization value must be provided.' if authorization.blank? + + post = {} + post[:request_id] = request_id(options) + post[:merchant_order_id] = merchant_order_id(options) + add_descriptor(post, options) + + commit(:void, post, authorization) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]'). + gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]') + end + + private + + def request_id(options) + options[:request_id] || generate_uuid + end + + def merchant_order_id(options) + options[:merchant_order_id] || options[:order_id] || generate_uuid + end + + def add_return_url(post, options) + post[:return_url] = options[:return_url] if options[:return_url] + end + + def generate_uuid + SecureRandom.uuid + end + + def setup_access_token + token_headers = { + 'Content-Type' => 'application/json', + 'x-client-id' => @client_id, + 'x-api-key' => @client_api_key + } + + 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) + endpoint = ENDPOINTS[action].to_s + endpoint = id.present? ? endpoint % { id: id } : endpoint + base_url + endpoint + end + + def add_referrer_data(post) + post[:referrer_data] = { type: 'spreedly' } + end + + def create_payment_intent(money, options = {}) + post = {} + add_invoice(post, money, options) + add_order(post, options) + post[:request_id] = "#{request_id(options)}_setup" + post[:merchant_order_id] = merchant_order_id(options) + add_referrer_data(post) + add_descriptor(post, options) + post['payment_method_options'] = { 'card' => { 'risk_control' => { 'three_ds_action' => 'SKIP_3DS' } } } if options[:skip_3ds] + + response = commit(:setup, post) + raise ArgumentError.new(response.message) unless response.success? + + response.params['id'] + end + + def add_billing(post, card, options = {}) + return unless has_name_info?(card) + + billing = post['payment_method']['card']['billing'] || {} + billing['email'] = options[:email] if options[:email] + billing['phone'] = options[:phone] if options[:phone] + billing['first_name'] = card.first_name + billing['last_name'] = card.last_name + billing_address = options[:billing_address] + billing['address'] = build_address(billing_address) if has_required_address_info?(billing_address) + + post['payment_method']['card']['billing'] = billing + end + + def has_name_info?(card) + # These fields are required if billing data is sent. + card.first_name && card.last_name + end + + def has_required_address_info?(address) + # These fields are required if address data is sent. + return unless address + + address[:address1] && address[:country] + end + + def build_address(address) + return unless address + + address_data = {} # names r hard + address_data[:country_code] = address[:country] + address_data[:street] = address[:address1] + address_data[:city] = address[:city] if address[:city] # required per doc, not in practice + address_data[:postcode] = address[:zip] if address[:zip] + address_data[:state] = address[:state] if address[:state] + address_data + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + end + + def add_card(post, card, options = {}) + post['payment_method'] = { + 'type' => 'card', + 'card' => { + 'expiry_month' => format(card.month, :two_digits), + 'expiry_year' => card.year.to_s, + 'number' => card.number.to_s, + 'name' => card.name, + 'cvc' => card.verification_value, + 'brand' => card.brand + } + } + add_billing(post, card, options) + end + + def add_order(post, options) + return unless shipping_address = options[:shipping_address] + + physical_address = build_shipping_address(shipping_address) + first_name, last_name = split_names(shipping_address[:name]) + shipping = {} + shipping[:first_name] = first_name if first_name + shipping[:last_name] = last_name if last_name + shipping[:phone_number] = shipping_address[:phone_number] if shipping_address[:phone_number] + shipping[:address] = physical_address + post[:order] = { shipping: shipping } + end + + def build_shipping_address(shipping_address) + address = {} + address[:city] = shipping_address[:city] + address[:country_code] = shipping_address[:country] + address[:postcode] = shipping_address[:zip] + address[:state] = shipping_address[:state] + address[:street] = shipping_address[:address1] + address + end + + def add_stored_credential(post, options) + return unless stored_credential = options[:stored_credential] + + external_recurring_data = post[:external_recurring_data] = {} + + case stored_credential.dig(:reason_type) + when 'recurring', 'installment' + external_recurring_data[:merchant_trigger_reason] = 'scheduled' + when 'unscheduled' + external_recurring_data[:merchant_trigger_reason] = 'unscheduled' + end + + external_recurring_data[:original_transaction_id] = test_mit?(options) ? test_network_transaction_id(post) : stored_credential.dig(:network_transaction_id) + external_recurring_data[:triggered_by] = stored_credential.dig(:initiator) == 'cardholder' ? 'customer' : 'merchant' + end + + def test_network_transaction_id(post) + case post['payment_method']['card']['brand'] + when 'visa' + TEST_NETWORK_TRANSACTION_IDS[:visa] + when 'master' + TEST_NETWORK_TRANSACTION_IDS[:master] + end + end + + def test_mit?(options) + test? && options.dig(:stored_credential, :initiator) == 'merchant' + end + + def add_three_ds(post, options) + return unless three_d_secure = options[:three_d_secure] + + pm_options = post.dig('payment_method_options', 'card') + + external_three_ds = { + '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 } } + end + + def format_three_ds_version(three_d_secure) + version = three_d_secure[:version].split('.') + + version.push('0') until version.length == 3 + version.join('.') + end + + 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] + } + else + { + 'cavv': three_d_secure[:cavv], + 'xid': three_d_secure[:xid] + } + end + end + + def authorization_only?(options = {}) + options.include?(:auto_capture) && options[:auto_capture] == false + end + + def add_descriptor(post, options) + post[:descriptor] = options[:description] if options[:description] + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, post, id = nil) + url = build_request_url(action, id) + + post_headers = { 'Authorization' => "Bearer #{@access_token}", 'Content-Type' => 'application/json' } + response = parse(ssl_post(url, post_data(post), post_headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response.dig('latest_payment_attempt', 'authentication_data', 'avs_result')), + cvv_result: CVVResult.new(response.dig('latest_payment_attempt', 'authentication_data', 'cvc_code')), + test: test?, + error_code: error_code_from(response) + ) + end + + def handle_response(response) + case response.code.to_i + when 200...300, 400, 404 + response.body + else + raise ResponseError.new(response) + end + end + + def post_data(post) + post.to_json + end + + def success_from(response) + %w(REQUIRES_PAYMENT_METHOD SUCCEEDED RECEIVED REQUIRES_CAPTURE CANCELLED).include?(response['status']) + end + + def message_from(response) + response.dig('latest_payment_attempt', 'status') || response['status'] || response['message'] + end + + def authorization_from(response) + response.dig('latest_payment_attempt', 'payment_intent_id') + end + + def error_code_from(response) + response['provider_original_response_code'] || response['code'] unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/alelo.rb b/lib/active_merchant/billing/gateways/alelo.rb new file mode 100644 index 00000000000..381b5859372 --- /dev/null +++ b/lib/active_merchant/billing/gateways/alelo.rb @@ -0,0 +1,274 @@ +require 'jose' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class AleloGateway < Gateway + class_attribute :prelive_url + + self.test_url = 'https://sandbox-api.alelo.com.br/alelo/sandbox/' + self.live_url = 'https://api.alelo.com.br/alelo/prd/' + self.prelive_url = 'https://api.homologacaoalelo.com.br/alelo/uat/' + + self.supported_countries = ['BR'] + self.default_currency = 'BRL' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://www.alelo.com.br' + self.display_name = 'Alelo' + + def initialize(options = {}) + requires!(options, :client_id, :client_secret) + super + end + + def purchase(money, payment, options = {}) + post = {} + add_order(post, options) + add_amount(post, money) + add_payment(post, payment) + add_geolocation(post, options) + add_extra_data(post, options) + + commit('capture/transaction', post, options) + end + + def refund(money, authorization, options = {}) + request_id = authorization.split('#').first + options[:http] = { method: :put, prevent_encrypt: true } + commit('capture/transaction/refund', { requestId: request_id }, options, :put) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + force_utf8(transcript.encode). + gsub(%r((Authorization: Bearer )[\w -]+), '\1[FILTERED]'). + gsub(%r((client_id=|Client-Id:)[\w -]+), '\1[FILTERED]\2'). + gsub(%r((client_secret=|Client-Secret:)[\w -]+), '\1[FILTERED]\2'). + gsub(%r((access_token\":\")[^\"]*), '\1[FILTERED]'). + gsub(%r((publicKey\":\")[^\"]*), '\1[FILTERED]') + end + + private + + def force_utf8(string) + return nil unless string + + # binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') + string.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') + end + + def add_amount(post, money) + post[:amount] = amount(money).to_f + end + + def add_order(post, options) + post[:requestId] = options[:order_id] + end + + def add_extra_data(post, options) + post.merge!({ + establishmentCode: options[:establishment_code], + playerIdentification: options[:player_identification], + captureType: '3', # send fixed value 3 to ecommerce + subMerchantCode: options[:sub_merchant_mcc], + externalTraceNumber: options[:external_trace_number] + }.compact) + end + + def add_geolocation(post, options) + return if options[:geo_latitude].blank? || options[:geo_longitude].blank? + + post.merge!(geolocation: { + latitude: options[:geo_latitude], + longitude: options[:geo_longitude] + }) + end + + def add_payment(post, payment) + post.merge!({ + cardNumber: payment.number, + cardholderName: payment.name, + expirationMonth: payment.month, + expirationYear: format(payment.year, :two_digits).to_i, + securityCode: payment.verification_value + }) + end + + def fetch_access_token + params = { + grant_type: 'client_credentials', + client_id: @options[:client_id], + client_secret: @options[:client_secret], + scope: '/capture' + } + + headers = { + 'Accept' => 'application/json', + 'Content-Type' => 'application/x-www-form-urlencoded' + } + + 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) + response = parse(ssl_get(url('capture/key'), request_headers(access_token))) + Response.new(true, response[:publicKey], response) + end + + def ensure_credentials(try_again = true) + multiresp = MultiResponse.new + access_token = @options[:access_token] + key = @options[:encryption_key] + uuid = @options[:encryption_uuid] + + if access_token.blank? + multiresp.process { fetch_access_token } + access_token = multiresp.message + key = nil + uuid = nil + end + + if key.blank? + multiresp.process { remote_encryption_key(access_token) } + key = multiresp.message + uuid = multiresp.params['uuid'] + end + + { + key: key, + uuid: uuid, + access_token: access_token, + multiresp: multiresp.responses.present? ? multiresp : nil + } + rescue ActiveMerchant::OAuthResponseError => e + raise e + rescue ResponseError => e + # retry to generate a new access_token when the provided one is expired + raise e unless retry?(try_again, e, :access_token) + + @options.delete(:access_token) + @options.delete(:encryption_key) + ensure_credentials false + end + + def encrypt_payload(body, credentials, options) + key = OpenSSL::PKey::RSA.new(Base64.decode64(credentials[:key])) + jwk = JOSE::JWK.from_key(key) + alg_enc = { 'alg' => 'RSA-OAEP-256', 'enc' => 'A128CBC-HS256' } + + token = JOSE::JWE.block_encrypt(jwk, body.to_json, alg_enc).compact + + encrypted_body = { + token: token, + uuid: credentials[:uuid] + } + + encrypted_body.to_json + end + + def parse(body) + JSON.parse(body, symbolize_names: true) + end + + def post_data(params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def commit(action, body, options, try_again = true) + credentials = ensure_credentials + payload = encrypt_payload(body, credentials, options) + + if options.dig :http, :method + payload = body.to_json if options.dig :http, :prevent_encrypt + response = parse ssl_request(options[:http][:method], url(action), payload, request_headers(credentials[:access_token])) + else + response = parse ssl_post(url(action), payload, request_headers(credentials[:access_token])) + end + + resp = Response.new( + success_from(action, response), + message_from(response), + response, + authorization: authorization_from(response, options), + test: test? + ) + + return resp unless credentials[:multiresp].present? + + multiresp = credentials[:multiresp] + resp.params.merge!({ + 'access_token' => credentials[:access_token], + 'encryption_key' => credentials[:key], + 'encryption_uuid' => credentials[:uuid] + }) + multiresp.process { resp } + + multiresp + rescue ActiveMerchant::OAuthResponseError => e + raise OAuthResponseError.new(e) + rescue ActiveMerchant::ResponseError => e + # Retry on a possible expired encryption key + if retry?(try_again, e, :encryption_key) + @options.delete(:encryption_key) + commit(action, body, options, false) + else + res = parse(e.response.body) + Response.new(false, res[:messageUser] || res[:error], res, test: test?) + 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' + response[:status] == 'ESTORNADA' + when 'capture/transaction' + response[:status] == 'CONFIRMADA' + else + false + end + end + + def message_from(response) + response[:messages] || response[:messageUser] + end + + def authorization_from(response, options) + [response[:requestId]].join('#') + end + + def url(action) + return prelive_url if @options[:url_override] == 'prelive' + + "#{test? ? test_url : live_url}#{action}" + end + + def request_headers(access_token) + { + 'Accept' => 'application/json', + 'X-IBM-Client-Id' => @options[:client_id], + 'X-IBM-Client-Secret' => @options[:client_secret], + 'Content-Type' => 'application/json', + 'Authorization' => "Bearer #{access_token}" + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/allied_wallet.rb b/lib/active_merchant/billing/gateways/allied_wallet.rb index 8cdbd6f4eaa..68159fcf540 100644 --- a/lib/active_merchant/billing/gateways/allied_wallet.rb +++ b/lib/active_merchant/billing/gateways/allied_wallet.rb @@ -9,15 +9,15 @@ class AlliedWalletGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover, - :diners_club, :jcb, :maestro] + self.supported_cardtypes = %i[visa master american_express discover + diners_club jcb maestro] - def initialize(options={}) + def initialize(options = {}) requires!(options, :site_id, :merchant_id, :token) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -26,7 +26,7 @@ def purchase(amount, payment_method, options={}) commit(:purchase, post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -35,7 +35,7 @@ def authorize(amount, payment_method, options={}) commit(:authorize, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization, :capture) @@ -44,14 +44,14 @@ def capture(amount, authorization, options={}) commit(:capture, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization, :void) commit(:void, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization, :refund) @@ -61,7 +61,7 @@ def refund(amount, authorization, options={}) commit(:refund, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -113,7 +113,7 @@ def add_customer_data(post, options) post[:city] = billing_address[:city] post[:state] = billing_address[:state] post[:countryId] = billing_address[:country] - post[:postalCode] = billing_address[:zip] + post[:postalCode] = billing_address[:zip] post[:phone] = billing_address[:phone] end end @@ -141,7 +141,8 @@ def commit(action, post) raw_response = ssl_post(url(action), post.to_json, headers) response = parse(raw_response) rescue ResponseError => e - raise unless(e.response.code.to_s =~ /4\d\d/) + raise unless e.response.code.to_s =~ /4\d\d/ + response = parse(e.response.body) end @@ -151,8 +152,8 @@ def commit(action, post) message_from(succeeded, response), response, authorization: response['id'], - :avs_result => AVSResult.new(code: response['avs_response']), - :cvv_result => CVVResult.new(response['cvv2_response']), + avs_result: AVSResult.new(code: response['avs_response']), + cvv_result: CVVResult.new(response['cvv2_response']), test: test? ) rescue JSON::ParserError @@ -199,7 +200,6 @@ def message_from(succeeded, response) response['message'] || 'Unable to read error message' end end - end end end diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb index ccc8794a05a..f83ac599e38 100644 --- a/lib/active_merchant/billing/gateways/authorize_net.rb +++ b/lib/active_merchant/billing/gateways/authorize_net.rb @@ -8,10 +8,10 @@ class AuthorizeNetGateway < Gateway self.test_url = 'https://apitest.authorize.net/xml/v1/request.api' self.live_url = 'https://api2.authorize.net/xml/v1/request.api' - self.supported_countries = %w(AD AT AU BE BG CA CH CY CZ DE DK EE ES FI FR GB GI GR HU IE IL IS IT LI LT LU LV MC MT NL NO PL PT RO SE SI SK SM TR US VA) + self.supported_countries = %w(AU CA US) self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro] self.homepage_url = 'http://www.authorize.net/' self.display_name = 'Authorize.Net' @@ -54,25 +54,25 @@ class AuthorizeNetGateway < Gateway '37' => STANDARD_ERROR_CODE[:invalid_expiry_date], '378' => STANDARD_ERROR_CODE[:invalid_cvc], '38' => STANDARD_ERROR_CODE[:expired_card], - '384' => STANDARD_ERROR_CODE[:config_error], + '384' => STANDARD_ERROR_CODE[:config_error] } MARKET_TYPE = { - :moto => '1', - :retail => '2' + moto: '1', + retail: '2' } DEVICE_TYPE = { - :unknown => '1', - :unattended_terminal => '2', - :self_service_terminal => '3', - :electronic_cash_register => '4', - :personal_computer_terminal => '5', - :airpay => '6', - :wireless_pos => '7', - :website => '8', - :dial_terminal => '9', - :virtual_terminal => '10' + unknown: '1', + unattended_terminal: '2', + self_service_terminal: '3', + electronic_cash_register: '4', + personal_computer_terminal: '5', + airpay: '6', + wireless_pos: '7', + website: '8', + dial_terminal: '9', + virtual_terminal: '10' } class_attribute :duplicate_window @@ -85,16 +85,14 @@ 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' - def initialize(options={}) + def initialize(options = {}) requires!(options, :login, :password) super end @@ -111,7 +109,7 @@ def purchase(amount, payment, options = {}) end end - def authorize(amount, payment, options={}) + def authorize(amount, payment, options = {}) if payment.is_a?(String) commit(:cim_authorize, options) do |xml| add_cim_auth_purchase(xml, 'profileTransAuthOnly', amount, payment, options) @@ -123,7 +121,7 @@ def authorize(amount, payment, options={}) end end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) if auth_was_for_cim?(authorization) cim_capture(amount, authorization, options) else @@ -131,12 +129,13 @@ def capture(amount, authorization, options={}) end end - def refund(amount, authorization, options={}) - response = if auth_was_for_cim?(authorization) - cim_refund(amount, authorization, options) - else - normal_refund(amount, authorization, options) - end + def refund(amount, authorization, options = {}) + response = + if auth_was_for_cim?(authorization) + cim_refund(amount, authorization, options) + else + normal_refund(amount, authorization, options) + end return response if response.success? return response unless options[:force_full_refund_if_unsettled] @@ -148,7 +147,7 @@ def refund(amount, authorization, options={}) end end - def void(authorization, options={}) + def void(authorization, options = {}) if auth_was_for_cim?(authorization) cim_void(authorization, options) else @@ -156,10 +155,8 @@ def void(authorization, options={}) end end - def credit(amount, payment, options={}) - if payment.is_a?(String) - raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' - end + def credit(amount, payment, options = {}) + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if payment.is_a?(String) commit(:credit) do |xml| add_order_id(xml, options) @@ -167,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) @@ -177,11 +174,29 @@ def credit(amount, payment, options={}) end end - def verify(credit_card, options = {}) + def verify(payment_method, options = {}) + amount = amount_for_verify(options) + MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } + r.process { authorize(amount, payment_method, options) } + r.process(:ignore_result) { void(r.authorization, options) } unless amount == 0 end + rescue ArgumentError => e + Response.new(false, e.message) + end + + def amount_for_verify(options) + return 100 unless options[:verify_amount].present? + + amount = options[:verify_amount] + raise ArgumentError.new 'verify_amount value must be an integer' unless amount.is_a?(Integer) && !amount.negative? || amount.is_a?(String) && amount.match?(/^\d+$/) && !amount.to_i.negative? + raise ArgumentError.new 'Billing address including zip code is required for a 0 amount verify' if amount.to_i.zero? && !validate_billing_address_values?(options) + + amount.to_i + end + + def validate_billing_address_values?(options) + options.dig(:billing_address, :zip).present? && options.dig(:billing_address, :address1).present? end def store(credit_card, options = {}) @@ -193,7 +208,7 @@ def store(credit_card, options = {}) end def unstore(authorization) - customer_profile_id, _, _ = split_authorization(authorization) + customer_profile_id, = split_authorization(authorization) delete_customer_profile(customer_profile_id) end @@ -222,13 +237,13 @@ def scrub(transcript) def supports_network_tokenization? card = Billing::NetworkTokenizationCreditCard.new({ - :number => '4111111111111111', - :month => 12, - :year => 20, - :first_name => 'John', - :last_name => 'Smith', - :brand => 'visa', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + number: '4111111111111111', + month: 12, + year: 20, + first_name: 'John', + last_name: 'Smith', + brand: 'visa', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' }) request = post_data(:authorize) do |xml| @@ -246,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) @@ -258,6 +273,8 @@ def add_auth_purchase(xml, transaction_type, amount, payment, options) add_settings(xml, payment, options) add_user_fields(xml, amount, options) add_ship_from_address(xml, options) + add_processing_options(xml, options) + add_subsequent_auth_information(xml, options) end end @@ -269,7 +286,7 @@ 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_tax_exempt_status(xml, options) end @@ -312,7 +329,7 @@ def normal_capture(amount, authorization, options) end def cim_refund(amount, authorization, options) - transaction_id, card_number, _ = split_authorization(authorization) + transaction_id, card_number, = split_authorization(authorization) commit(:cim_refund, options) do |xml| add_order_id(xml, options) @@ -332,7 +349,7 @@ def cim_refund(amount, authorization, options) end def normal_refund(amount, authorization, options) - transaction_id, card_number, _ = split_authorization(authorization) + transaction_id, card_number, = split_authorization(authorization) commit(:refund) do |xml| xml.transactionRequest do @@ -344,7 +361,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 @@ -389,26 +406,34 @@ def normal_void(authorization, options) end end - def add_payment_source(xml, source, options, action = nil) - return unless source - if source.is_a?(String) - add_token_payment_method(xml, source, options) - elsif card_brand(source) == 'check' - add_check(xml, source) - elsif card_brand(source) == 'apple_pay' - add_apple_pay_payment_token(xml, source) + def add_payment_method(xml, payment_method, options, action = nil) + return unless payment_method + + 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.class == NetworkTokenizationCreditCard && action != :credit && options[:turn_on_nt_flow] + end + def camel_case_lower(key) String(key).split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join end def add_settings(xml, source, options) xml.transactionSettings do - if options[:recurring] + if options[:recurring] || subsequent_recurring_transaction?(options) xml.setting do xml.settingName('recurringBilling') xml.settingValue('true') @@ -479,12 +504,8 @@ def add_credit_card(xml, credit_card, action) xml.creditCard do xml.cardNumber(truncate(credit_card.number, 16)) xml.expirationDate(format(credit_card.month, :two_digits) + '/' + format(credit_card.year, :four_digits)) - if credit_card.valid_card_verification_value?(credit_card.verification_value, credit_card.brand) - xml.cardCode(credit_card.verification_value) - end - if credit_card.is_a?(NetworkTokenizationCreditCard) && action != :credit - xml.cryptogram(credit_card.payment_cryptogram) - end + 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 @@ -492,7 +513,7 @@ def add_credit_card(xml, credit_card, action) def add_swipe_data(xml, credit_card) TRACKS.each do |key, regex| - if regex.match(credit_card.track_data) + if regex.match?(credit_card.track_data) @valid_track_data = true xml.payment do xml.trackData do @@ -504,24 +525,28 @@ def add_swipe_data(xml, credit_card) end def add_token_payment_method(xml, token, options) - customer_profile_id, customer_payment_profile_id, _ = split_authorization(token) + customer_profile_id, customer_payment_profile_id, = split_authorization(token) customer_profile_id = options[:customer_profile_id] if options[:customer_profile_id] customer_payment_profile_id = options[:customer_payment_profile_id] if options[:customer_payment_profile_id] xml.customerProfileId(customer_profile_id) xml.customerPaymentProfileId(customer_payment_profile_id) end - 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 xml.marketType(options[:market_type] || MARKET_TYPE[:retail]) @@ -547,6 +572,7 @@ def valid_track_data def add_check(xml, check) xml.payment do xml.bankAccount do + xml.accountType(check.account_type) xml.routingNumber(check.routing_number) xml.accountNumber(check.account_number) xml.nameOnAccount(truncate(check.name, 22)) @@ -567,12 +593,16 @@ def add_customer_data(xml, payment_source, options) xml.customerIP(options[:ip]) unless empty?(options[:ip]) - xml.cardholderAuthentication do - three_d_secure = options.fetch(:three_d_secure, {}) - xml.authenticationIndicator( - options[:authentication_indicator] || three_d_secure[:eci]) - xml.cardholderAuthenticationValue( - options[:cardholder_authentication_value] || three_d_secure[:cavv]) + if !empty?(options.fetch(:three_d_secure, {})) || options[:authentication_indicator] || options[:cardholder_authentication_value] + xml.cardholderAuthentication do + three_d_secure = options.fetch(:three_d_secure, {}) + xml.authenticationIndicator( + options[:authentication_indicator] || three_d_secure[:eci] + ) + xml.cardholderAuthenticationValue( + options[:cardholder_authentication_value] || three_d_secure[:cavv] + ) + end end end @@ -583,6 +613,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) @@ -592,21 +623,22 @@ 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 - def add_shipping_address(xml, options, root_node='shipTo') + def add_shipping_address(xml, options, root_node = 'shipTo') address = options[:shipping_address] || options[:address] return unless address xml.send(root_node) do - first_name, last_name = if address[:name] - split_names(address[:name]) - else - [address[:first_name], address[:last_name]] - end + first_name, last_name = + if address[:name] + split_names(address[:name]) + else + [address[:first_name], address[:last_name]] + end full_address = "#{address[:address1]} #{address[:address2]}".strip xml.firstName(truncate(first_name, 50)) unless empty?(first_name) @@ -620,7 +652,7 @@ def add_shipping_address(xml, options, root_node='shipTo') end end - def add_ship_from_address(xml, options, root_node='shipFrom') + def add_ship_from_address(xml, options, root_node = 'shipFrom') address = options[:ship_from_address] return unless address @@ -701,18 +733,37 @@ def add_extra_options_for_cim(xml, options) xml.extraOptions("x_delim_char=#{options[:delimiter]}") if options[:delimiter] end + def add_processing_options(xml, options) + return unless options[:stored_credential] + + xml.processingOptions do + if options[:stored_credential][:initial_transaction] && options[:stored_credential][:reason_type] == 'recurring' + xml.isFirstRecurringPayment 'true' + elsif options[:stored_credential][:initial_transaction] + xml.isFirstSubsequentAuth 'true' + elsif options[:stored_credential][:initiator] == 'cardholder' + xml.isStoredCredentials 'true' + else + xml.isSubsequentAuth 'true' + end + end + end + + def add_subsequent_auth_information(xml, options) + return unless options.dig(:stored_credential, :initiator) == 'merchant' + + xml.subsequentAuthInformation do + xml.reason options[:stored_credential_reason_type_override] if options[:stored_credential_reason_type_override] + xml.originalNetworkTransId options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + end + end + def create_customer_payment_profile(credit_card, options) commit(:cim_store_update, options) do |xml| xml.customerProfileId options[:customer_profile_id] xml.paymentProfile do add_billing_address(xml, credit_card, options) - xml.payment do - xml.creditCard do - xml.cardNumber(truncate(credit_card.number, 16)) - xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits)) - xml.cardCode(credit_card.verification_value) if credit_card.verification_value - end - end + add_credit_card(xml, credit_card, :cim_store_update) end end end @@ -728,13 +779,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 @@ -756,13 +801,17 @@ def names_from(payment_source, address, options) end def state_from(address, options) - if ['US', 'CA'].include?(address[:country]) + if %w[US CA].include?(address[:country]) address[:state] || 'NC' else address[:state] || 'n/a' end end + def subsequent_recurring_transaction?(options) + options.dig(:stored_credential, :reason_type) == 'recurring' && !options.dig(:stored_credential, :initial_transaction) + end + def headers { 'Content-Type' => 'text/xml' } end @@ -772,7 +821,7 @@ def url end def parse(action, raw_response, options = {}) - if is_cim_action?(action) || action == :verify_credentials + if cim_action?(action) || action == :verify_credentials parse_cim(raw_response, options) else parse_normal(action, raw_response) @@ -803,7 +852,7 @@ def commit(action, options = {}, &payload) end end - def is_cim_action?(action) + def cim_action?(action) action.to_s.start_with?('cim') end @@ -825,7 +874,7 @@ def root_for(action) 'deleteCustomerProfileRequest' elsif action == :verify_credentials 'authenticateTestRequest' - elsif is_cim_action?(action) + elsif cim_action?(action) 'createCustomerProfileTransactionRequest' else 'createTransactionRequest' @@ -843,19 +892,19 @@ def parse_normal(action, body) doc = Nokogiri::XML(body) doc.remove_namespaces! - response = {action: action} + response = { action: action } - response[:response_code] = if(element = doc.at_xpath('//transactionResponse/responseCode')) - (empty?(element.content) ? nil : element.content.to_i) - end + response[:response_code] = if (element = doc.at_xpath('//transactionResponse/responseCode')) + empty?(element.content) ? nil : element.content.to_i + end - if(element = doc.at_xpath('//errors/error')) + if (element = doc.at_xpath('//errors/error')) response[:response_reason_code] = element.at_xpath('errorCode').content[/0*(\d+)$/, 1] response[:response_reason_text] = element.at_xpath('errorText').content.chomp('.') - elsif(element = doc.at_xpath('//transactionResponse/messages/message')) + elsif (element = doc.at_xpath('//transactionResponse/messages/message')) response[:response_reason_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] response[:response_reason_text] = element.at_xpath('description').content.chomp('.') - elsif(element = doc.at_xpath('//messages/message')) + elsif (element = doc.at_xpath('//messages/message')) response[:response_reason_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] response[:response_reason_text] = element.at_xpath('text').content.chomp('.') else @@ -863,37 +912,50 @@ def parse_normal(action, body) response[:response_reason_text] = '' end - response[:avs_result_code] = if(element = doc.at_xpath('//avsResultCode')) - (empty?(element.content) ? nil : element.content) - end + response[:avs_result_code] = + if (element = doc.at_xpath('//avsResultCode')) + empty?(element.content) ? nil : element.content + end - response[:transaction_id] = if(element = doc.at_xpath('//transId')) - (empty?(element.content) ? nil : element.content) - end + response[:transaction_id] = + if element = doc.at_xpath('//transId') + empty?(element.content) ? nil : element.content + end - response[:card_code] = if(element = doc.at_xpath('//cvvResultCode')) - (empty?(element.content) ? nil : element.content) - end + response[:card_code] = + if element = doc.at_xpath('//cvvResultCode') + empty?(element.content) ? nil : element.content + end - response[:authorization_code] = if(element = doc.at_xpath('//authCode')) - (empty?(element.content) ? nil : element.content) - end + response[:authorization_code] = + if element = doc.at_xpath('//authCode') + empty?(element.content) ? nil : element.content + end - response[:cardholder_authentication_code] = if(element = doc.at_xpath('//cavvResultCode')) - (empty?(element.content) ? nil : element.content) - end + response[:cardholder_authentication_code] = + if element = doc.at_xpath('//cavvResultCode') + empty?(element.content) ? nil : element.content + end - response[:account_number] = if(element = doc.at_xpath('//accountNumber')) - (empty?(element.content) ? nil : element.content[-4..-1]) - end + response[:account_number] = + if element = doc.at_xpath('//accountNumber') + empty?(element.content) ? nil : element.content[-4..-1] + end - response[:test_request] = if(element = doc.at_xpath('//testRequest')) - (empty?(element.content) ? nil : element.content) - end + response[:test_request] = + if element = doc.at_xpath('//testRequest') + empty?(element.content) ? nil : element.content + end - response[:full_response_code] = if(element = doc.at_xpath('//messages/message/code')) - (empty?(element.content) ? nil : element.content) - end + response[:full_response_code] = + if element = doc.at_xpath('//messages/message/code') + empty?(element.content) ? nil : element.content + end + + response[:network_trans_id] = + if element = doc.at_xpath('//networkTransId') + empty?(element.content) ? nil : element.content + end response end @@ -903,35 +965,41 @@ def parse_cim(body, options) doc = Nokogiri::XML(body).remove_namespaces! - if (element = doc.at_xpath('//messages/message')) + if element = doc.at_xpath('//messages/message') response[:message_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] response[:message_text] = element.at_xpath('text').content.chomp('.') end - response[:result_code] = if(element = doc.at_xpath('//messages/resultCode')) - (empty?(element.content) ? nil : element.content) - end + response[:result_code] = + if element = doc.at_xpath('//messages/resultCode') + empty?(element.content) ? nil : element.content + end - response[:test_request] = if(element = doc.at_xpath('//testRequest')) - (empty?(element.content) ? nil : element.content) - end + response[:test_request] = + if element = doc.at_xpath('//testRequest') + empty?(element.content) ? nil : element.content + end - response[:customer_profile_id] = if(element = doc.at_xpath('//customerProfileId')) - (empty?(element.content) ? nil : element.content) - end + response[:customer_profile_id] = + if element = doc.at_xpath('//customerProfileId') + empty?(element.content) ? nil : element.content + end - response[:customer_payment_profile_id] = if(element = doc.at_xpath('//customerPaymentProfileIdList/numericString')) - (empty?(element.content) ? nil : element.content) - end + response[:customer_payment_profile_id] = + if element = doc.at_xpath('//customerPaymentProfileIdList/numericString') + empty?(element.content) ? nil : element.content + end - response[:customer_payment_profile_id] = if(element = doc.at_xpath('//customerPaymentProfileIdList/numericString') || - doc.at_xpath('//customerPaymentProfileId')) - (empty?(element.content) ? nil : element.content) - end + response[:customer_payment_profile_id] = + if element = doc.at_xpath('//customerPaymentProfileIdList/numericString') || + doc.at_xpath('//customerPaymentProfileId') + empty?(element.content) ? nil : element.content + end - response[:direct_response] = if(element = doc.at_xpath('//directResponse')) - (empty?(element.content) ? nil : element.content) - end + response[:direct_response] = + if element = doc.at_xpath('//directResponse') + empty?(element.content) ? nil : element.content + end response.merge!(parse_direct_response_elements(response, options)) @@ -950,7 +1018,7 @@ def message_from(action, response, avs_result, cvv_result) if response[:response_code] == DECLINED if CARD_CODE_ERRORS.include?(cvv_result.code) return cvv_result.message - elsif(AVS_REASON_CODES.include?(response[:response_reason_code]) && AVS_ERRORS.include?(avs_result.code)) + elsif AVS_REASON_CODES.include?(response[:response_reason_code]) && AVS_ERRORS.include?(avs_result.code) return avs_result.message end end @@ -975,7 +1043,7 @@ def cim?(action) end def transaction_id_from(authorization) - transaction_id, _, _ = split_authorization(authorization) + transaction_id, = split_authorization(authorization) transaction_id end @@ -993,11 +1061,11 @@ def map_error_code(response_code, response_reason_code) def auth_was_for_cim?(authorization) _, _, action = split_authorization(authorization) - action && is_cim_action?(action) + action && cim_action?(action) end def parse_direct_response_elements(response, options) - params = response[:direct_response] + params = response[:direct_response]&.tr('"', '') return {} unless params parts = params.split(options[:delimiter] || ',') @@ -1046,10 +1114,9 @@ def parse_direct_response_elements(response, options) card_type: parts[51] || '', split_tender_id: parts[52] || '', requested_amount: parts[53] || '', - balance_on_card: parts[54] || '', + balance_on_card: parts[54] || '' } end - end end end diff --git a/lib/active_merchant/billing/gateways/authorize_net_arb.rb b/lib/active_merchant/billing/gateways/authorize_net_arb.rb index 406cc55e50e..85a5d6c4b10 100644 --- a/lib/active_merchant/billing/gateways/authorize_net_arb.rb +++ b/lib/active_merchant/billing/gateways/authorize_net_arb.rb @@ -31,18 +31,18 @@ class AuthorizeNetArbGateway < Gateway self.default_currency = 'USD' - self.supported_countries = ['US', 'CA', 'GB'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_countries = %w[US CA GB] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.authorize.net/' self.display_name = 'Authorize.Net' AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' RECURRING_ACTIONS = { - :create => 'ARBCreateSubscription', - :update => 'ARBUpdateSubscription', - :cancel => 'ARBCancelSubscription', - :status => 'ARBGetSubscriptionStatus' + create: 'ARBCreateSubscription', + update: 'ARBUpdateSubscription', + cancel: 'ARBCancelSubscription', + status: 'ARBGetSubscriptionStatus' } # Creates a new AuthorizeNetArbGateway @@ -82,9 +82,9 @@ def initialize(options = {}) # +:interval => { :unit => :months, :length => 3 }+ (REQUIRED) # * :duration -- A hash containing keys for the :start_date the subscription begins (also the date the # initial billing occurs) and the total number of billing :occurrences or payments for the subscription. (REQUIRED) - def recurring(money, creditcard, options={}) + def recurring(money, creditcard, options = {}) requires!(options, :interval, :duration, :billing_address) - requires!(options[:interval], :length, [:unit, :days, :months]) + requires!(options[:interval], :length, %i[unit days months]) requires!(options[:duration], :start_date, :occurrences) requires!(options[:billing_address], :first_name, :last_name) @@ -110,7 +110,7 @@ def recurring(money, creditcard, options={}) # # * :subscription_id -- A string containing the :subscription_id of the recurring payment already in place # for a given credit card. (REQUIRED) - def update_recurring(options={}) + def update_recurring(options = {}) requires!(options, :subscription_id) request = build_recurring_request(:update, options) recurring_commit(:update, request) @@ -126,7 +126,7 @@ def update_recurring(options={}) # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place # for a given credit card. (REQUIRED) def cancel_recurring(subscription_id) - request = build_recurring_request(:cancel, :subscription_id => subscription_id) + request = build_recurring_request(:cancel, subscription_id: subscription_id) recurring_commit(:cancel, request) end @@ -139,7 +139,7 @@ def cancel_recurring(subscription_id) # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place # for a given credit card. (REQUIRED) def status_recurring(subscription_id) - request = build_recurring_request(:status, :subscription_id => subscription_id) + request = build_recurring_request(:status, subscription_id: subscription_id) recurring_commit(:status, request) end @@ -147,13 +147,11 @@ def status_recurring(subscription_id) # Builds recurring billing request def build_recurring_request(action, options = {}) - unless RECURRING_ACTIONS.include?(action) - raise StandardError, "Invalid Automated Recurring Billing Action: #{action}" - end + raise StandardError, "Invalid Automated Recurring Billing Action: #{action}" unless RECURRING_ACTIONS.include?(action) - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') + xml.tag!("#{RECURRING_ACTIONS[action]}Request", xmlns: AUTHORIZE_NET_ARB_NAMESPACE) do add_merchant_authentication(xml) # Merchant-assigned reference ID for the request xml.tag!('refId', options[:ref_id]) if options[:ref_id] @@ -232,6 +230,7 @@ def add_subscription(xml, options) def add_interval(xml, options) interval = options[:interval] return unless interval + xml.tag!('interval') do # The measurement of time, in association with the Interval Unit, # that is used to define the frequency of the billing occurrences @@ -246,6 +245,7 @@ def add_interval(xml, options) def add_duration(xml, options) duration = options[:duration] return unless duration + # The date the subscription begins # (also the date the initial billing occurs) xml.tag!('startDate', duration[:start_date]) if duration[:start_date] @@ -255,6 +255,7 @@ def add_duration(xml, options) def add_payment_schedule(xml, options) return unless options[:interval] || options[:duration] + xml.tag!('paymentSchedule') do # Contains information about the interval of time between payments add_interval(xml, options) @@ -269,6 +270,7 @@ def add_payment_schedule(xml, options) # Adds customer's credit card or bank account payment information def add_payment(xml, options) return unless options[:credit_card] || options[:bank_account] + xml.tag!('payment') do # Contains the customer’s credit card information add_credit_card(xml, options) @@ -283,6 +285,7 @@ def add_payment(xml, options) def add_credit_card(xml, options) credit_card = options[:credit_card] return unless credit_card + xml.tag!('creditCard') do # The credit card number used for payment of the subscription xml.tag!('cardNumber', credit_card.number) @@ -297,6 +300,7 @@ def add_credit_card(xml, options) def add_bank_account(xml, options) bank_account = options[:bank_account] return unless bank_account + xml.tag!('bankAccount') do # The type of bank account used for payment of the subscription xml.tag!('accountType', bank_account[:account_type]) @@ -319,6 +323,7 @@ def add_bank_account(xml, options) def add_order(xml, options) order = options[:order] return unless order + xml.tag!('order') do # Merchant-assigned invoice number for the subscription (optional) xml.tag!('invoiceNumber', order[:invoice_number]) @@ -331,6 +336,7 @@ def add_order(xml, options) def add_customer(xml, options) customer = options[:customer] return unless customer + xml.tag!('customer') do xml.tag!('type', customer[:type]) if customer[:type] xml.tag!('id', customer[:id]) if customer[:id] @@ -346,6 +352,7 @@ def add_customer(xml, options) def add_drivers_license(xml, options) return unless customer = options[:customer] return unless drivers_license = customer[:drivers_license] + xml.tag!('driversLicense') do # The customer's driver's license number xml.tag!('number', drivers_license[:number]) @@ -359,6 +366,7 @@ def add_drivers_license(xml, options) # Adds address information def add_address(xml, container_name, address) return if address.blank? + xml.tag!(container_name) do xml.tag!('firstName', address[:first_name]) xml.tag!('lastName', address[:last_name]) @@ -385,9 +393,12 @@ def recurring_commit(action, request) test_mode = test? || message =~ /Test Mode/ success = response[:result_code] == 'Ok' - Response.new(success, message, response, - :test => test_mode, - :authorization => response[:subscription_id] + Response.new( + success, + message, + response, + test: test_mode, + authorization: response[:subscription_id] ) end diff --git a/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/lib/active_merchant/billing/gateways/authorize_net_cim.rb index fac2913ff37..09eff729308 100644 --- a/lib/active_merchant/billing/gateways/authorize_net_cim.rb +++ b/lib/active_merchant/billing/gateways/authorize_net_cim.rb @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + module ActiveMerchant #:nodoc: module Billing #:nodoc: # ==== Customer Information Manager (CIM) @@ -33,55 +34,55 @@ class AuthorizeNetCimGateway < Gateway AUTHORIZE_NET_CIM_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' CIM_ACTIONS = { - :create_customer_profile => 'createCustomerProfile', - :create_customer_payment_profile => 'createCustomerPaymentProfile', - :create_customer_shipping_address => 'createCustomerShippingAddress', - :get_customer_profile => 'getCustomerProfile', - :get_customer_profile_ids => 'getCustomerProfileIds', - :get_customer_payment_profile => 'getCustomerPaymentProfile', - :get_customer_shipping_address => 'getCustomerShippingAddress', - :delete_customer_profile => 'deleteCustomerProfile', - :delete_customer_payment_profile => 'deleteCustomerPaymentProfile', - :delete_customer_shipping_address => 'deleteCustomerShippingAddress', - :update_customer_profile => 'updateCustomerProfile', - :update_customer_payment_profile => 'updateCustomerPaymentProfile', - :update_customer_shipping_address => 'updateCustomerShippingAddress', - :create_customer_profile_transaction => 'createCustomerProfileTransaction', - :validate_customer_payment_profile => 'validateCustomerPaymentProfile' + create_customer_profile: 'createCustomerProfile', + create_customer_payment_profile: 'createCustomerPaymentProfile', + create_customer_shipping_address: 'createCustomerShippingAddress', + get_customer_profile: 'getCustomerProfile', + get_customer_profile_ids: 'getCustomerProfileIds', + get_customer_payment_profile: 'getCustomerPaymentProfile', + get_customer_shipping_address: 'getCustomerShippingAddress', + delete_customer_profile: 'deleteCustomerProfile', + delete_customer_payment_profile: 'deleteCustomerPaymentProfile', + delete_customer_shipping_address: 'deleteCustomerShippingAddress', + update_customer_profile: 'updateCustomerProfile', + update_customer_payment_profile: 'updateCustomerPaymentProfile', + update_customer_shipping_address: 'updateCustomerShippingAddress', + create_customer_profile_transaction: 'createCustomerProfileTransaction', + validate_customer_payment_profile: 'validateCustomerPaymentProfile' } CIM_TRANSACTION_TYPES = { - :auth_capture => 'profileTransAuthCapture', - :auth_only => 'profileTransAuthOnly', - :capture_only => 'profileTransCaptureOnly', - :prior_auth_capture => 'profileTransPriorAuthCapture', - :refund => 'profileTransRefund', - :void => 'profileTransVoid' + auth_capture: 'profileTransAuthCapture', + auth_only: 'profileTransAuthOnly', + capture_only: 'profileTransCaptureOnly', + prior_auth_capture: 'profileTransPriorAuthCapture', + refund: 'profileTransRefund', + void: 'profileTransVoid' } CIM_VALIDATION_MODES = { - :none => 'none', - :test => 'testMode', - :live => 'liveMode', - :old => 'oldLiveMode' + none: 'none', + test: 'testMode', + live: 'liveMode', + old: 'oldLiveMode' } BANK_ACCOUNT_TYPES = { - :checking => 'checking', - :savings => 'savings', - :business_checking => 'businessChecking' + checking: 'checking', + savings: 'savings', + business_checking: 'businessChecking' } ECHECK_TYPES = { - :ccd => 'CCD', - :ppd => 'PPD', - :web => 'WEB' + ccd: 'CCD', + ppd: 'PPD', + web: 'WEB' } self.homepage_url = 'http://www.authorize.net/' self.display_name = 'Authorize.Net CIM' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # Creates a new AuthorizeNetCimGateway # @@ -485,13 +486,11 @@ def expdate(credit_card) end def build_request(action, options = {}) - unless CIM_ACTIONS.include?(action) - raise StandardError, "Invalid Customer Information Manager Action: #{action}" - end + raise StandardError, "Invalid Customer Information Manager Action: #{action}" unless CIM_ACTIONS.include?(action) - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - xml.tag!("#{CIM_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_CIM_NAMESPACE) do + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') + xml.tag!("#{CIM_ACTIONS[action]}Request", xmlns: AUTHORIZE_NET_CIM_NAMESPACE) do add_merchant_authentication(xml) # Merchant-assigned reference ID for the request xml.tag!('refId', options[:ref_id]) if options[:ref_id] @@ -564,6 +563,8 @@ def build_delete_customer_shipping_address_request(xml, options) def build_get_customer_profile_request(xml, options) xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('unmaskExpirationDate', options[:unmask_expiration_date]) if options[:unmask_expiration_date] + xml.tag!('includeIssuerInfo', options[:include_issuer_info]) if options[:include_issuer_info] xml.target! end @@ -575,6 +576,7 @@ def build_get_customer_payment_profile_request(xml, options) xml.tag!('customerProfileId', options[:customer_profile_id]) xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id]) xml.tag!('unmaskExpirationDate', options[:unmask_expiration_date]) if options[:unmask_expiration_date] + xml.tag!('includeIssuerInfo', options[:include_issuer_info]) if options[:include_issuer_info] xml.target! end @@ -657,9 +659,7 @@ def add_profile(xml, profile, update = false) end def add_transaction(xml, transaction) - unless CIM_TRANSACTION_TYPES.include?(transaction[:type]) - raise StandardError, "Invalid Customer Information Manager Transaction Type: #{transaction[:type]}" - end + raise StandardError, "Invalid Customer Information Manager Transaction Type: #{transaction[:type]}" unless CIM_TRANSACTION_TYPES.include?(transaction[:type]) xml.tag!('transaction') do xml.tag!(CIM_TRANSACTION_TYPES[transaction[:type]]) do @@ -695,12 +695,10 @@ def add_transaction(xml, transaction) add_order(xml, transaction[:order]) if transaction[:order].present? end - if [:auth_capture, :auth_only, :capture_only].include?(transaction[:type]) + if %i[auth_capture auth_only capture_only].include?(transaction[:type]) xml.tag!('recurringBilling', transaction[:recurring_billing]) if transaction.has_key?(:recurring_billing) end - unless [:void, :refund, :prior_auth_capture].include?(transaction[:type]) - tag_unless_blank(xml, 'cardCode', transaction[:card_code]) - end + tag_unless_blank(xml, 'cardCode', transaction[:card_code]) unless %i[void refund prior_auth_capture].include?(transaction[:type]) end end end @@ -797,6 +795,7 @@ def add_address(xml, address) # when the payment method is credit card. def add_credit_card(xml, credit_card) return unless credit_card + xml.tag!('creditCard') do # The credit card number used for payment of the subscription xml.tag!('cardNumber', full_or_masked_card_number(credit_card.number)) @@ -857,7 +856,7 @@ def commit(action, request) response_params = parse(action, xml) - message_element= response_params['messages']['message'] + message_element = response_params['messages']['message'] first_error = message_element.is_a?(Array) ? message_element.first : message_element message = first_error['text'] test_mode = @options[:test_requests] || message =~ /Test Mode/ @@ -883,7 +882,7 @@ def format_extra_options(options) def parse_direct_response(params) delimiter = @options[:delimiter] || ',' - direct_response = {'raw' => params} + direct_response = { 'raw' => params } direct_response_fields = params.split(delimiter) direct_response.merge( { @@ -933,7 +932,7 @@ def parse_direct_response(params) 'card_type' => direct_response_fields[51] || '', 'split_tender_id' => direct_response_fields[52] || '', 'requested_amount' => direct_response_fields[53] || '', - 'balance_on_card' => direct_response_fields[54] || '', + 'balance_on_card' => direct_response_fields[54] || '' } ) end @@ -942,9 +941,7 @@ def parse(action, xml) xml = REXML::Document.new(xml) root = REXML::XPath.first(xml, "//#{CIM_ACTIONS[action]}Response") || REXML::XPath.first(xml, '//ErrorResponse') - if root - response = parse_element(root) - end + response = parse_element(root) if root response end diff --git a/lib/active_merchant/billing/gateways/axcessms.rb b/lib/active_merchant/billing/gateways/axcessms.rb index 7f9207ff6a2..eff4b112086 100644 --- a/lib/active_merchant/billing/gateways/axcessms.rb +++ b/lib/active_merchant/billing/gateways/axcessms.rb @@ -8,7 +8,7 @@ class AxcessmsGateway < Gateway GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT MX NL NO PL PT RO RU SE SI SK TR US VA) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro] + self.supported_cardtypes = %i[visa master american_express discover jcb maestro] self.homepage_url = 'http://www.axcessms.com/' self.display_name = 'Axcess MS' @@ -23,33 +23,33 @@ class AxcessmsGateway < Gateway PAYMENT_CODE_REFUND = 'CC.RF' PAYMENT_CODE_REBILL = 'CC.RB' - def initialize(options={}) + def initialize(options = {}) requires!(options, :sender, :login, :password, :channel) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) payment_code = payment.respond_to?(:number) ? PAYMENT_CODE_DEBIT : PAYMENT_CODE_REBILL commit(payment_code, money, payment, options) end - def authorize(money, authorization, options={}) + def authorize(money, authorization, options = {}) commit(PAYMENT_CODE_PREAUTHORIZATION, money, authorization, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) commit(PAYMENT_CODE_CAPTURE, money, authorization, options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit(PAYMENT_CODE_REFUND, money, authorization, options) end - def void(authorization, options={}) + def void(authorization, options = {}) commit(PAYMENT_CODE_REVERSAL, nil, authorization, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -71,9 +71,12 @@ def commit(paymentcode, money, payment, options) message = "#{response[:reason]} - #{response[:return]}" authorization = response[:unique_id] - Response.new(success, message, response, - :authorization => authorization, - :test => (response[:mode] != 'LIVE') + Response.new( + success, + message, + response, + authorization: authorization, + test: (response[:mode] != 'LIVE') ) end @@ -93,9 +96,7 @@ def parse(body) end def parse_element(response, node) - if node.has_attributes? - node.attributes.each { |name, value| response["#{node.name}_#{name}".underscore.to_sym] = value } - end + node.attributes.each { |name, value| response["#{node.name}_#{name}".underscore.to_sym] = value } if node.has_attributes? if node.has_elements? node.elements.each { |element| parse_element(response, element) } @@ -105,7 +106,7 @@ def parse_element(response, node) end def build_request(payment_code, money, payment, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'Request', 'version' => API_VERSION do xml.tag! 'Header' do @@ -168,6 +169,7 @@ def add_payment(xml, payment) def add_address(xml, address) raise ArgumentError.new('Address is required') unless address + xml.tag! 'Address' do xml.tag! 'Street', "#{address[:address1]} #{address[:address2]}".strip xml.tag! 'City', address[:city] diff --git a/lib/active_merchant/billing/gateways/balanced.rb b/lib/active_merchant/billing/gateways/balanced.rb index f9b7accff02..a3ecf3792e7 100644 --- a/lib/active_merchant/billing/gateways/balanced.rb +++ b/lib/active_merchant/billing/gateways/balanced.rb @@ -22,7 +22,7 @@ class BalancedGateway < Gateway self.live_url = 'https://api.balancedpayments.com' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.balancedpayments.com/' self.display_name = 'Balanced' self.money_format = :cents @@ -44,12 +44,13 @@ def purchase(money, payment_method, options = {}) add_common_params(post, options) MultiResponse.run do |r| - identifier = if(payment_method.respond_to?(:number)) - r.process { store(payment_method, options) } - r.authorization - else - payment_method - end + identifier = + if payment_method.respond_to?(:number) + r.process { store(payment_method, options) } + r.authorization + else + payment_method + end r.process { commit('debits', "cards/#{card_identifier_from(identifier)}/debits", post) } end end @@ -61,12 +62,13 @@ def authorize(money, payment_method, options = {}) add_common_params(post, options) MultiResponse.run do |r| - identifier = if(payment_method.respond_to?(:number)) - r.process { store(payment_method, options) } - r.authorization - else - payment_method - end + identifier = + if payment_method.respond_to?(:number) + r.process { store(payment_method, options) } + r.authorization + else + payment_method + end r.process { commit('card_holds', "cards/#{card_identifier_from(identifier)}/card_holds", post) } end end @@ -97,7 +99,7 @@ def refund(money, identifier, options = {}) commit('refunds', "debits/#{reference_identifier_from(identifier)}/refunds", post) end - def store(credit_card, options={}) + def store(credit_card, options = {}) post = {} post[:number] = credit_card.number @@ -137,7 +139,7 @@ def add_amount(post, money) def add_address(post, options) address = (options[:billing_address] || options[:address]) - if(address && address[:zip].present?) + if address && address[:zip].present? post[:address] = {} post[:address][:line1] = address[:address1] if address[:address1] post[:address][:line2] = address[:address2] if address[:address2] @@ -154,18 +156,22 @@ def add_common_params(post, options) post[:meta] = options[:meta] end - def commit(entity_name, path, post, method=:post) - raw_response = begin - parse(ssl_request( - method, - live_url + "/#{path}", - post_data(post), - headers - )) - rescue ResponseError => e - raise unless(e.response.code.to_s =~ /4\d\d/) - parse(e.response.body) - end + def commit(entity_name, path, post, method = :post) + raw_response = + begin + parse( + ssl_request( + method, + live_url + "/#{path}", + post_data(post), + headers + ) + ) + rescue ResponseError => e + raise unless e.response.code.to_s =~ /4\d\d/ + + parse(e.response.body) + end Response.new( success_from(entity_name, raw_response), @@ -178,13 +184,13 @@ def commit(entity_name, path, post, method=:post) def success_from(entity_name, raw_response) entity = (raw_response[entity_name] || []).first - if(!entity) + if !entity false - elsif((entity_name == 'refunds') && entity.include?('status')) + elsif (entity_name == 'refunds') && entity.include?('status') %w(succeeded pending).include?(entity['status']) - elsif(entity.include?('status')) + elsif entity.include?('status') (entity['status'] == 'succeeded') - elsif(entity_name == 'cards') + elsif entity_name == 'cards' !!entity['id'] else false @@ -192,7 +198,7 @@ def success_from(entity_name, raw_response) end def message_from(raw_response) - if(raw_response['errors']) + if raw_response['errors'] error = raw_response['errors'].first (error['additional'] || error['message'] || error['description']) else @@ -222,6 +228,7 @@ def post_data(params) params.map do |key, value| next if value.blank? + if value.is_a?(Hash) h = {} value.each do |k, v| @@ -245,10 +252,10 @@ def headers ) { - 'Authorization' => 'Basic ' + Base64.encode64(@options[:login].to_s + ':').strip, - 'User-Agent' => "Balanced/v1.1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", - 'Accept' => 'application/vnd.api+json;revision=1.1', - 'X-Balanced-User-Agent' => @@ua, + 'Authorization' => 'Basic ' + Base64.encode64(@options[:login].to_s + ':').strip, + 'User-Agent' => "Balanced/v1.1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'Accept' => 'application/vnd.api+json;revision=1.1', + 'X-Balanced-User-Agent' => @@ua } end end diff --git a/lib/active_merchant/billing/gateways/bambora_apac.rb b/lib/active_merchant/billing/gateways/bambora_apac.rb index 6cfe3a82bdc..596007e1a1b 100644 --- a/lib/active_merchant/billing/gateways/bambora_apac.rb +++ b/lib/active_merchant/billing/gateways/bambora_apac.rb @@ -6,8 +6,8 @@ class BamboraApacGateway < Gateway self.live_url = 'https://www.bambora.co.nz/interface/api' self.test_url = 'https://demo.bambora.co.nz/interface/api' - self.supported_countries = ['AU', 'NZ'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_countries = %w[AU NZ] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.homepage_url = 'http://www.bambora.com/' self.display_name = 'Bambora Asia-Pacific' @@ -18,19 +18,19 @@ class BamboraApacGateway < Gateway '05' => STANDARD_ERROR_CODE[:card_declined], '06' => STANDARD_ERROR_CODE[:processing_error], '14' => STANDARD_ERROR_CODE[:invalid_number], - '54' => STANDARD_ERROR_CODE[:expired_card], + '54' => STANDARD_ERROR_CODE[:expired_card] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) commit('SubmitSinglePayment') do |xml| xml.Transaction do xml.CustRef options[:order_id] - add_amount(xml, money) + xml.Amount amount(money) xml.TrnType '1' add_payment(xml, payment) add_credentials(xml, options) @@ -39,11 +39,11 @@ def purchase(money, payment, options={}) end end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) commit('SubmitSinglePayment') do |xml| xml.Transaction do xml.CustRef options[:order_id] - add_amount(xml, money) + xml.Amount amount(money) xml.TrnType '2' add_payment(xml, payment) add_credentials(xml, options) @@ -52,37 +52,37 @@ def authorize(money, payment, options={}) end end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) commit('SubmitSingleCapture') do |xml| xml.Capture do xml.Receipt authorization - add_amount(xml, money) + xml.Amount amount(money) add_credentials(xml, options) end end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit('SubmitSingleRefund') do |xml| xml.Refund do xml.Receipt authorization - add_amount(xml, money) + xml.Amount amount(money) add_credentials(xml, options) end end end - def void(money, authorization, options={}) + def void(authorization, options = {}) commit('SubmitSingleVoid') do |xml| xml.Void do xml.Receipt authorization - add_amount(xml, money) + xml.Amount amount(options[:amount]) add_credentials(xml, options) end end end - def store(payment, options={}) + def store(payment, options = {}) commit('TokeniseCreditCard') do |xml| xml.TokeniseCreditCard do xml.CardNumber payment.number @@ -116,10 +116,6 @@ def add_credentials(xml, options) end end - def add_amount(xml, money) - xml.Amount amount(money) - end - def add_payment(xml, payment) if payment.is_a?(String) add_token(xml, payment) @@ -136,7 +132,7 @@ def add_token(xml, payment) end def add_credit_card(xml, payment) - xml.CreditCard :Registered => 'False' do + xml.CreditCard Registered: 'False' do xml.CardNumber payment.number xml.ExpM format(payment.month, :two_digits) xml.ExpY format(payment.year, :four_digits) diff --git a/lib/active_merchant/billing/gateways/bank_frick.rb b/lib/active_merchant/billing/gateways/bank_frick.rb index 8a35089978c..3bdd3cb2a0d 100644 --- a/lib/active_merchant/billing/gateways/bank_frick.rb +++ b/lib/active_merchant/billing/gateways/bank_frick.rb @@ -9,9 +9,9 @@ class BankFrickGateway < Gateway self.test_url = 'https://test.ctpe.io/payment/ctpe' self.live_url = 'https://ctpe.io/payment/ctpe' - self.supported_countries = ['LI', 'US'] + self.supported_countries = %w[LI US] self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.bankfrickacquiring.com/' self.display_name = 'Bank Frick' @@ -24,15 +24,15 @@ class BankFrickGateway < Gateway 'authonly' => 'CC.PA', 'capture' => 'CC.CP', 'refund' => 'CC.RF', - 'void' => 'CC.RV', + 'void' => 'CC.RV' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :sender, :channel, :userid, :userpwd) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -42,7 +42,7 @@ def purchase(money, payment, options={}) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -52,7 +52,7 @@ def authorize(money, payment, options={}) commit('authonly', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} post[:authorization] = authorization add_invoice(post, money, options) @@ -60,7 +60,7 @@ def capture(money, authorization, options={}) commit('capture', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} post[:authorization] = authorization add_invoice(post, money, options) @@ -68,14 +68,14 @@ def refund(money, authorization, options={}) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} post[:authorization] = authorization commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -97,7 +97,7 @@ def add_address(post, creditcard, options) post[:zip] = address[:zip].to_s post[:city] = address[:city].to_s post[:country] = address[:country].to_s - post[:state] = address[:state].blank? ? 'n/a' : address[:state] + post[:state] = address[:state].blank? ? 'n/a' : address[:state] end end @@ -119,7 +119,7 @@ def add_payment(post, payment) end def parse(body) - results = {} + results = {} xml = Nokogiri::XML(body) resp = xml.xpath('//Response/Transaction/Identification') resp.children.each do |element| @@ -166,7 +166,7 @@ def post_data(action, parameters = {}) end def build_xml_request(action, data) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.Request(version: '1.0') do xml.Header do xml.Security(sender: @options[:sender], type: 'MERCHANT') diff --git a/lib/active_merchant/billing/gateways/banwire.rb b/lib/active_merchant/billing/gateways/banwire.rb index 99d3e683e34..d4e784361d2 100644 --- a/lib/active_merchant/billing/gateways/banwire.rb +++ b/lib/active_merchant/billing/gateways/banwire.rb @@ -4,7 +4,7 @@ class BanwireGateway < Gateway URL = 'https://banwire.com/api.pago_pro' self.supported_countries = ['MX'] - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.homepage_url = 'http://www.banwire.com/' self.display_name = 'Banwire' @@ -63,7 +63,7 @@ def add_creditcard(post, creditcard) post[:card_num] = creditcard.number post[:card_name] = creditcard.name post[:card_type] = card_brand(creditcard) - post[:card_exp] = "#{sprintf("%02d", creditcard.month)}/#{creditcard.year.to_s[-2, 2]}" + post[:card_exp] = "#{sprintf('%02d', creditcard.month)}/#{creditcard.year.to_s[-2, 2]}" post[:card_ccv2] = creditcard.verification_value end @@ -74,7 +74,7 @@ def add_amount(post, money, options) def card_brand(card) brand = super - ({'master' => 'mastercard', 'american_express' => 'amex'}[brand] || brand) + ({ 'master' => 'mastercard', 'american_express' => 'amex' }[brand] || brand) end def parse(body) @@ -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']) + test: test?, + authorization: response['code_auth'] + ) end def success?(response) diff --git a/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb b/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb index 84a59f82081..734e96e3c86 100644 --- a/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +++ b/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb @@ -4,16 +4,17 @@ class BarclaycardSmartpayGateway < Gateway self.test_url = 'https://pal-test.barclaycardsmartpay.com/pal/servlet' self.live_url = 'https://pal-live.barclaycardsmartpay.com/pal/servlet' - self.supported_countries = ['AL', 'AD', 'AM', 'AT', 'AZ', 'BY', 'BE', 'BA', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'KZ', 'LV', 'LI', 'LT', 'LU', 'MK', 'MT', 'MD', 'MC', 'ME', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM', 'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'TR', 'UA', 'GB', 'VA'] + self.supported_countries = %w[AL AD AM AT AZ BY BE BA BG HR CY CZ DK EE FI FR DE GR HU IS IE IT KZ LV LI LT LU MK MT MD MC ME NL NO PL PT RO RU SM RS SK SI ES SE CH TR UA GB VA] self.default_currency = 'EUR' - self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND) + self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND IQD JOD LYD) self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro] + self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) self.homepage_url = 'https://www.barclaycardsmartpay.com/' self.display_name = 'Barclaycard Smartpay' - API_VERSION = 'v30' + API_VERSION = 'v40' def initialize(options = {}) requires!(options, :company, :merchant, :password) @@ -37,7 +38,9 @@ def authorize(money, creditcard, options = {}) post[:card] = credit_card_hash(creditcard) post[:billingAddress] = billing_address_hash(options) if options[:billing_address] post[:deliveryAddress] = shipping_address_hash(options) if options[:shipping_address] - add_3ds(post, options) if options[:execute_threed] + post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement] + + add_3ds(post, options) commit('authorise', post) end @@ -69,21 +72,23 @@ def credit(money, creditcard, options = {}) post[:shopperName] = options[:shopper_name] if options[:shopper_name] if options[:third_party_payout] - post[:recurring] = options[:recurring_contract] || {contract: 'PAYOUT'} + post[:recurring] = options[:recurring_contract] || { contract: 'PAYOUT' } MultiResponse.run do |r| r.process { commit( 'storeDetailAndSubmitThirdParty', post, @options[:store_payout_account], - @options[:store_payout_password]) + @options[:store_payout_password] + ) } r.process { commit( 'confirmThirdParty', modification_request(r.authorization, @options), @options[:review_payout_account], - @options[:review_payout_password]) + @options[:review_payout_password] + ) } end else @@ -106,7 +111,7 @@ def verify(creditcard, options = {}) def store(creditcard, options = {}) post = store_request(options) post[:card] = credit_card_hash(creditcard) - post[:recurring] = {:contract => 'RECURRING'} + post[:recurring] = { contract: 'RECURRING' } commit('store', post) end @@ -127,25 +132,25 @@ def scrub(transcript) # Smartpay may return AVS codes not covered by standard AVSResult codes. # Smartpay's descriptions noted below. AVS_MAPPING = { - '0' => 'R', # Unknown - '1' => 'A', # Address matches, postal code doesn't - '2' => 'N', # Neither postal code nor address match - '3' => 'R', # AVS unavailable - '4' => 'E', # AVS not supported for this card type - '5' => 'U', # No AVS data provided - '6' => 'Z', # Postal code matches, address doesn't match - '7' => 'D', # Both postal code and address match - '8' => 'U', # Address not checked, postal code unknown - '9' => 'B', # Address matches, postal code unknown - '10' => 'N', # Address doesn't match, postal code unknown - '11' => 'U', # Postal code not checked, address unknown - '12' => 'B', # Address matches, postal code not checked - '13' => 'U', # Address doesn't match, postal code not checked - '14' => 'P', # Postal code matches, address unknown - '15' => 'P', # Postal code matches, address not checked - '16' => 'N', # Postal code doesn't match, address unknown - '17' => 'U', # Postal code doesn't match, address not checked - '18' => 'I' # Neither postal code nor address were checked + '0' => 'R', # Unknown + '1' => 'A', # Address matches, postal code doesn't + '2' => 'N', # Neither postal code nor address match + '3' => 'R', # AVS unavailable + '4' => 'E', # AVS not supported for this card type + '5' => 'U', # No AVS data provided + '6' => 'Z', # Postal code matches, address doesn't match + '7' => 'D', # Both postal code and address match + '8' => 'U', # Address not checked, postal code unknown + '9' => 'B', # Address matches, postal code unknown + '10' => 'N', # Address doesn't match, postal code unknown + '11' => 'U', # Postal code not checked, address unknown + '12' => 'B', # Address matches, postal code not checked + '13' => 'U', # Address doesn't match, postal code not checked + '14' => 'P', # Postal code matches, address unknown + '15' => 'P', # Postal code matches, address not checked + '16' => 'N', # Postal code doesn't match, address unknown + '17' => 'U', # Postal code doesn't match, address not checked + '18' => 'I' # Neither postal code nor address were checked } def commit(action, post, account = 'ws', password = @options[:password]) @@ -159,20 +164,20 @@ def commit(action, post, account = 'ws', password = @options[:password]) message_from(response), response, test: test?, - avs_result: AVSResult.new(:code => parse_avs_code(response)), + avs_result: AVSResult.new(code: parse_avs_code(response)), authorization: response['recurringDetailReference'] || authorization_from(post, response) ) rescue ResponseError => e case e.response.code when '401' - return Response.new(false, 'Invalid credentials', {}, :test => test?) + return Response.new(false, 'Invalid credentials', {}, test: test?) when '403' - return Response.new(false, 'Not allowed', {}, :test => test?) + return Response.new(false, 'Not allowed', {}, test: test?) when '422', '500' if e.response.body.split(/\W+/).any? { |word| %w(validation configuration security).include?(word) } error_message = e.response.body[/#{Regexp.escape('message=')}(.*?)#{Regexp.escape('&')}/m, 1].tr('+', ' ') error_code = e.response.body[/#{Regexp.escape('errorCode=')}(.*?)#{Regexp.escape('&')}/m, 1] - return Response.new(false, error_code + ': ' + error_message, {}, :test => test?) + return Response.new(false, error_code + ': ' + error_message, {}, test: test?) end end raise @@ -182,11 +187,12 @@ def authorization_from(parameters, response) authorization = [parameters[:originalReference], response['pspReference']].compact return nil if authorization.empty? + return authorization.join('#') end def parse_avs_code(response) - AVS_MAPPING[response['avsResult'][0..1].strip] if response['avsResult'] + AVS_MAPPING[response['additionalData']['avsResult'][0..1].strip] if response.dig('additionalData', 'avsResult') end def flatten_hash(hash, prefix = nil) @@ -210,12 +216,18 @@ def headers(account, password) end def parse(response) - Hash[ - response.split('&').map do |x| - key, val = x.split('=', 2) - [key.split('.').last, CGI.unescape(val)] + parsed_response = {} + params = CGI.parse(response) + params.each do |key, value| + parsed_key = key.split('.', 2) + if parsed_key.size > 1 + parsed_response[parsed_key[0]] ||= {} + parsed_response[parsed_key[0]][parsed_key[1]] = value[0] + else + parsed_response[parsed_key[0]] = value[0] end - ] + end + parsed_response end def post_data(data) @@ -230,6 +242,7 @@ def message_from(response) return response['resultCode'] if response.has_key?('resultCode') # Payment request return response['response'] if response['response'] # Modification request return response['result'] if response.has_key?('result') # Store/Recurring request + 'Failure' # Negative fallback in case of error end @@ -343,8 +356,41 @@ def store_request(options) end def add_3ds(post, options) - post[:additionalData] = { executeThreeD: 'true' } - post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } + if three_ds_2_options = options[:three_ds_2] + device_channel = three_ds_2_options[:channel] + if device_channel == 'app' + post[:threeDS2RequestData] = { deviceChannel: device_channel } + else + add_browser_info(three_ds_2_options[:browser_info], post) + post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] } + end + + if options.has_key?(:execute_threed) + post[:additionalData] ||= {} + post[:additionalData][:executeThreeD] = options[:execute_threed] + post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption] + end + else + return unless options[:execute_threed] || options[:threed_dynamic] + + post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } + post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed] + end + end + + def add_browser_info(browser_info, post) + return unless browser_info + + post[:browserInfo] = { + acceptHeader: browser_info[:accept_header], + colorDepth: browser_info[:depth], + javaEnabled: browser_info[:java], + language: browser_info[:language], + screenHeight: browser_info[:height], + screenWidth: browser_info[:width], + timeZoneOffset: browser_info[:timezone], + userAgent: browser_info[:user_agent] + } end end end diff --git a/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb b/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb index 6f020860b2a..b0e42176e5e 100644 --- a/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb +++ b/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb @@ -8,7 +8,7 @@ class BarclaysEpdqExtraPlusGateway < OgoneGateway self.homepage_url = 'http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/' self.supported_countries = ['GB'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb maestro] self.default_currency = 'GBP' end end diff --git a/lib/active_merchant/billing/gateways/be2bill.rb b/lib/active_merchant/billing/gateways/be2bill.rb index f309272466f..f00ae7db8a9 100644 --- a/lib/active_merchant/billing/gateways/be2bill.rb +++ b/lib/active_merchant/billing/gateways/be2bill.rb @@ -9,7 +9,7 @@ class Be2billGateway < Gateway self.display_name = 'Be2Bill' self.homepage_url = 'http://www.be2bill.com/' self.supported_countries = ['FR'] - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.default_currency = 'EUR' self.money_format = :cents @@ -92,8 +92,8 @@ def commit(action, money, parameters) successful?(response), message_from(response), response, - :authorization => response['TRANSACTIONID'], - :test => test? + authorization: response['TRANSACTIONID'], + test: test? ) end @@ -111,8 +111,8 @@ def message_from(response) def post_data(action, parameters = {}) { - :method => action, - :params => parameters.merge(HASH: signature(parameters, action)) + method: action, + params: parameters.merge(HASH: signature(parameters, action)) }.to_query end diff --git a/lib/active_merchant/billing/gateways/beanstream.rb b/lib/active_merchant/billing/gateways/beanstream.rb index aedcac80462..96509d7388b 100644 --- a/lib/active_merchant/billing/gateways/beanstream.rb +++ b/lib/active_merchant/billing/gateways/beanstream.rb @@ -76,6 +76,7 @@ def authorize(money, source, options = {}) add_transaction_type(post, :authorization) add_customer_ip(post, options) add_recurring_payment(post, options) + add_three_ds(post, options) commit(post) end @@ -88,20 +89,24 @@ def purchase(money, source, options = {}) add_transaction_type(post, purchase_action(source)) add_customer_ip(post, options) add_recurring_payment(post, options) + add_three_ds(post, options) commit(post) end def void(authorization, options = {}) reference, amount, type = split_auth(authorization) - - post = {} - add_reference(post, reference) - add_original_amount(post, amount) - add_transaction_type(post, void_action(type)) - commit(post) + if type == TRANSACTIONS[:authorization] + capture(0, authorization, options) + else + post = {} + add_reference(post, reference) + add_original_amount(post, amount) + add_transaction_type(post, void_action(type)) + commit(post) + end end - def verify(source, options={}) + def verify(source, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, source, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -153,6 +158,8 @@ def interac # To match the other stored-value gateways, like TrustCommerce, # store and unstore need to be defined + # + # When passing a single-use token the :name option is required def store(payment_method, options = {}) post = {} add_address(post, options) @@ -170,10 +177,10 @@ def store(payment_method, options = {}) # can't actually delete a secure profile with the supplicated API. This function sets the status of the profile to closed (C). # Closed profiles will have to removed manually. def delete(vault_id) - update(vault_id, false, {:status => 'C'}) + update(vault_id, false, { status: 'C' }) end - alias_method :unstore, :delete + alias unstore delete # Update the values (such as CC expiration) stored at # the gateway. The CC number must be supplied in the @@ -210,6 +217,22 @@ def scrub(transcript) def build_response(*args) Response.new(*args) end + + def add_three_ds(post, options) + return unless three_d_secure = options[:three_d_secure] + + post[:SecureXID] = (three_d_secure[:ds_transaction_id] || three_d_secure[:xid]) if three_d_secure.slice(:ds_transaction_id, :xid).values.any? + post[:SecureECI] = formatted_three_ds_eci(three_d_secure[:eci]) if three_d_secure[:eci].present? + post[:SecureCAVV] = three_d_secure[:cavv] if three_d_secure[:cavv].present? + end + + def formatted_three_ds_eci(val) + case val + when '05', '02' then 5 + when '06', '01' then 6 + else val.to_i + end + end end end end diff --git a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb index 5d0640262fe..87e5c89ba5e 100644 --- a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +++ b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb @@ -9,20 +9,20 @@ module BeanstreamCore SP_SERVICE_VERSION = '1.1' TRANSACTIONS = { - :authorization => 'PA', - :purchase => 'P', - :capture => 'PAC', - :refund => 'R', - :void => 'VP', - :check_purchase => 'D', - :check_refund => 'C', - :void_purchase => 'VP', - :void_refund => 'VR' + authorization: 'PA', + purchase: 'P', + capture: 'PAC', + refund: 'R', + void: 'VP', + check_purchase: 'D', + check_refund: 'C', + void_purchase: 'VP', + void_refund: 'VR' } PROFILE_OPERATIONS = { - :new => 'N', - :modify => 'M' + new: 'N', + modify: 'M' } CVD_CODES = { @@ -41,24 +41,24 @@ module BeanstreamCore } PERIODS = { - :days => 'D', - :weeks => 'W', - :months => 'M', - :years => 'Y' + days: 'D', + weeks: 'W', + months: 'M', + years: 'Y' } PERIODICITIES = { - :daily => [:days, 1], - :weekly => [:weeks, 1], - :biweekly => [:weeks, 2], - :monthly => [:months, 1], - :bimonthly => [:months, 2], - :yearly => [:years, 1] + daily: [:days, 1], + weekly: [:weeks, 1], + biweekly: [:weeks, 2], + monthly: [:months, 1], + bimonthly: [:months, 2], + yearly: [:years, 1] } RECURRING_OPERATION = { - :update => 'M', - :cancel => 'C' + update: 'M', + cancel: 'C' } STATES = { @@ -131,10 +131,10 @@ def self.included(base) base.default_currency = 'CAD' # The countries the gateway supports merchants from as 2 digit ISO country codes - base.supported_countries = ['CA', 'US'] + base.supported_countries = %w[CA US] # The card types supported by the payment gateway - base.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + base.supported_cardtypes = %i[visa master american_express discover diners_club jcb] # The homepage URL of the gateway base.homepage_url = 'http://www.beanstream.com/' @@ -155,7 +155,7 @@ def initialize(options = {}) end def capture(money, authorization, options = {}) - reference, _, _ = split_auth(authorization) + reference, = split_auth(authorization) post = {} add_amount(post, money) add_reference(post, reference) @@ -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] @@ -256,9 +256,10 @@ def state_for(address) end def prepare_address_for_non_american_countries(options) - [ options[:billing_address], options[:shipping_address] ].compact.each do |address| + [options[:billing_address], options[:shipping_address]].compact.each do |address| next if empty?(address[:country]) - unless ['US', 'CA'].include?(address[:country]) + + unless %w[US CA].include?(address[:country]) address[:state] = '--' address[:zip] = '000000' unless address[:zip] end @@ -315,6 +316,9 @@ def add_secure_profile_variables(post, options = {}) post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new) post[:customerCode] = options[:billing_id] || options[:vault_id] || false post[:status] = options[:status] + + billing_address = options[:billing_address] || options[:address] + post[:trnCardOwner] = billing_address ? billing_address[:name] : nil end def add_recurring_amount(post, money) @@ -363,6 +367,7 @@ def interval(options) if interval.respond_to? :parts parts = interval.parts raise ArgumentError.new("Cannot recur with mixed interval (#{interval}). Use only one of: days, weeks, months or years") if parts.length > 1 + parts.first elsif interval.kind_of? Hash requires!(interval, :unit) @@ -405,14 +410,17 @@ def recurring_commit(params) recurring_post(post_data(params, false)) end - def post(data, use_profile_api=nil) + def post(data, use_profile_api = nil) response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : self.live_url), data)) response[:customer_vault_id] = response[:customerCode] if response[:customerCode] - build_response(success?(response), message_from(response), response, - :test => test? || response[:authCode] == 'TEST', - :authorization => authorization_from(response), - :cvv_result => CVD_CODES[response[:cvdId]], - :avs_result => { :code => AVS_CODES.include?(response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] } + 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] } ) end @@ -438,7 +446,7 @@ def recurring_success?(response) end def add_source(post, source) - if source.is_a?(String) or source.is_a?(Integer) + if source.is_a?(String) || source.is_a?(Integer) post[:customerCode] = source else card_brand(source) == 'check' ? add_check(post, source) : add_credit_card(post, source) @@ -463,7 +471,7 @@ def post_data(params, use_profile_api) params[:vbvEnabled] = '0' params[:scEnabled] = '0' - params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + params.reject { |_k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/beanstream_interac.rb b/lib/active_merchant/billing/gateways/beanstream_interac.rb index 37ca7595a31..5459e0aa8b2 100644 --- a/lib/active_merchant/billing/gateways/beanstream_interac.rb +++ b/lib/active_merchant/billing/gateways/beanstream_interac.rb @@ -18,7 +18,7 @@ class BeanstreamInteracGateway < Gateway # post back is for until the response of the confirmation is # received, which contains the order number. def self.confirm(transaction) - gateway = new(:login => '') + gateway = new(login: '') gateway.confirm(transaction) end diff --git a/lib/active_merchant/billing/gateways/blue_pay.rb b/lib/active_merchant/billing/gateways/blue_pay.rb index e275b649dfe..b1e60343f17 100644 --- a/lib/active_merchant/billing/gateways/blue_pay.rb +++ b/lib/active_merchant/billing/gateways/blue_pay.rb @@ -10,15 +10,15 @@ class BluePayGateway < Gateway self.ignore_http_status = true - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) + CARD_CODE_ERRORS = %w(N S) + AVS_ERRORS = %w(A E N R W Z) AVS_REASON_CODES = %w(27 45) FIELD_MAP = { 'TRANS_ID' => :transaction_id, 'STATUS' => :response_code, 'AVS' => :avs_result_code, - 'CVV2'=> :card_code, + 'CVV2' => :card_code, 'AUTH_CODE' => :authorization, 'MESSAGE' => :message, 'REBID' => :rebid, @@ -29,7 +29,7 @@ class BluePayGateway < Gateway REBILL_FIELD_MAP = { 'REBILL_ID' => :rebill_id, - 'ACCOUNT_ID'=> :account_id, + 'ACCOUNT_ID' => :account_id, 'USER_ID' => :user_id, 'TEMPLATE_ID' => :template_id, 'STATUS' => :status, @@ -44,8 +44,8 @@ class BluePayGateway < Gateway 'CUST_TOKEN' => :cust_token } - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.bluepay.com/' self.display_name = 'BluePay' self.money_format = :dollars @@ -84,7 +84,8 @@ def authorize(money, payment_object, options = {}) add_customer_data(post, options) add_rebill(post, options) if options[:rebill] add_duplicate_override(post, options) - post[:TRANS_TYPE] = 'AUTH' + add_stored_credential(post, options) + post[:TRANS_TYPE] = 'AUTH' commit('AUTH_ONLY', money, post, options) end @@ -107,7 +108,8 @@ def purchase(money, payment_object, options = {}) add_customer_data(post, options) add_rebill(post, options) if options[:rebill] add_duplicate_override(post, options) - post[:TRANS_TYPE] = 'SALE' + add_stored_credential(post, options) + post[:TRANS_TYPE] = 'SALE' commit('AUTH_CAPTURE', money, post, options) end @@ -155,7 +157,7 @@ def void(identification, options = {}) # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay. # * options -- A hash of parameters. def refund(money, identification, options = {}) - if(identification && !identification.kind_of?(String)) + if identification && !identification.kind_of?(String) ActiveMerchant.deprecated 'refund should only be used to refund a referenced transaction' return credit(money, identification, options) end @@ -164,6 +166,7 @@ def refund(money, identification, options = {}) post[:PAYMENT_ACCOUNT] = '' post[:MASTER_ID] = identification post[:TRANS_TYPE] = 'REFUND' + post[:DOC_TYPE] = options[:doc_type] if options[:doc_type] post[:NAME1] = options[:first_name] || '' post[:NAME2] = options[:last_name] if options[:last_name] post[:ZIP] = options[:zip] if options[:zip] @@ -183,6 +186,7 @@ def credit(money, payment_object, options = {}) post[:PAYMENT_ACCOUNT] = '' add_payment_method(post, payment_object) post[:TRANS_TYPE] = 'CREDIT' + post[:DOC_TYPE] = options[:doc_type] if options[:doc_type] post[:NAME1] = options[:first_name] || '' post[:NAME2] = options[:last_name] if options[:last_name] @@ -315,7 +319,7 @@ def scrub(transcript) private def commit(action, money, fields, options = {}) - fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill') + fields[:AMOUNT] = amount(money) unless fields[:TRANS_TYPE] == 'VOID' || action == 'rebill' fields[:MODE] = (test? ? 'TEST' : 'LIVE') fields[:ACCOUNT_ID] = @options[:login] fields[:CUSTOMER_IP] = options[:ip] if options[:ip] @@ -330,7 +334,7 @@ def commit(action, money, fields, options = {}) parse(ssl_post(url, post_data(action, fields))) end - def parse_recurring(response_fields, opts={}) # expected status? + def parse_recurring(response_fields, opts = {}) # expected status? parsed = {} response_fields.each do |k, v| mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k @@ -340,18 +344,20 @@ def parse_recurring(response_fields, opts={}) # expected status? success = parsed[:status] != 'error' message = parsed[:status] - Response.new(success, message, parsed, - :test => test?, - :authorization => parsed[:rebill_id]) + Response.new( + success, + message, + parsed, + test: test?, + authorization: parsed[:rebill_id] + ) end def parse(body) # The bp20api has max one value per form field. response_fields = Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }] - if response_fields.include? 'REBILL_ID' - return parse_recurring(response_fields) - end + return parse_recurring(response_fields) if response_fields.include? 'REBILL_ID' parsed = {} response_fields.each do |k, v| @@ -362,17 +368,20 @@ def parse(body) # normalize message message = message_from(parsed) success = parsed[:response_code] == '1' - Response.new(success, message, parsed, - :test => test?, - :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]), - :avs_result => { :code => parsed[:avs_result_code] }, - :cvv_result => parsed[:card_code] + Response.new( + success, + message, + parsed, + test: test?, + authorization: (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]), + avs_result: { code: parsed[:avs_result_code] }, + cvv_result: parsed[:card_code] ) end def message_from(parsed) message = parsed[:message] - if(parsed[:response_code].to_i == 2) + if parsed[:response_code].to_i == 2 if CARD_CODE_ERRORS.include?(parsed[:card_code]) message = CVVResult.messages[parsed[:card_code]] elsif AVS_ERRORS.include?(parsed[:avs_result_code]) @@ -382,10 +391,10 @@ def message_from(parsed) end elsif message == 'Missing ACCOUNT_ID' message = 'The merchant login ID or password is invalid' - elsif message =~ /Approved/ + elsif /Approved/.match?(message) message = 'This transaction has been approved' - elsif message =~ /Expired/ - message = 'The credit card has expired' + elsif /Expired/.match?(message) + message = 'The credit card has expired' end message end @@ -462,6 +471,33 @@ def add_rebill(post, options) post[:REB_CYCLES] = options[:rebill_cycles] end + def add_stored_credential(post, options) + post[:cof] = initiator(options) + post[:cofscheduled] = scheduled(options) + end + + def initiator(options) + return unless initiator = options.dig(:stored_credential, :initiator) + + case initiator + when 'merchant' + 'M' + when 'cardholder' + 'C' + end + end + + def scheduled(options) + return unless reason_type = options.dig(:stored_credential, :reason_type) + + case reason_type + when 'recurring', 'installment' + 'Y' + when 'unscheduled' + 'N' + end + end + def post_data(action, parameters = {}) post = {} post[:version] = '1' @@ -512,9 +548,8 @@ def calc_rebill_tps(post) end def handle_response(response) - if ignore_http_status || (200...300).cover?(response.code.to_i) - return response.body - end + return response.body if ignore_http_status || (200...300).cover?(response.code.to_i) + raise ResponseError.new(response) end end diff --git a/lib/active_merchant/billing/gateways/blue_snap.rb b/lib/active_merchant/billing/gateways/blue_snap.rb index 0b0eabb2c45..5abac55c16b 100644 --- a/lib/active_merchant/billing/gateways/blue_snap.rb +++ b/lib/active_merchant/billing/gateways/blue_snap.rb @@ -8,7 +8,9 @@ class BlueSnapGateway < Gateway self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal] + self.currencies_without_fractions = %w(BYR CLP ILS JPY KRW VND XOF) + self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR TND) self.homepage_url = 'https://home.bluesnap.com/' self.display_name = 'BlueSnap' @@ -56,7 +58,7 @@ class BlueSnapGateway < Gateway 'line1: N, zip: M, name: N' => 'W', 'line1: N, zip: N, name: U' => 'N', 'line1: N, zip: N, name: M' => 'K', - 'line1: N, zip: N, name: N' => 'N', + 'line1: N, zip: N, name: N' => 'N' } BANK_ACCOUNT_TYPE_MAPPING = { @@ -66,15 +68,19 @@ class BlueSnapGateway < Gateway 'business_savings' => 'CORPORATE_SAVINGS' } - def initialize(options={}) + SHOPPER_INITIATOR = %w(CUSTOMER CARDHOLDER) + + STATE_CODE_COUNTRIES = %w(US CA) + + def initialize(options = {}) requires!(options, :api_username, :api_password) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) payment_method_details = PaymentMethodDetails.new(payment_method) - commit(:purchase, :post, payment_method_details) do |doc| + commit(:purchase, options, :post, payment_method_details) do |doc| if payment_method_details.alt_transaction? add_alt_transaction_purchase(doc, money, payment_method_details, options) else @@ -83,42 +89,44 @@ def purchase(money, payment_method, options={}) end end - def authorize(money, payment_method, options={}) - commit(:authorize) do |doc| + def authorize(money, payment_method, options = {}) + commit(:authorize, options) do |doc| add_auth_purchase(doc, money, payment_method, options) end end - def capture(money, authorization, options={}) - commit(:capture, :put) do |doc| + def capture(money, authorization, options = {}) + commit(:capture, options, :put) do |doc| add_authorization(doc, authorization) add_order(doc, options) + add_amount(doc, money, options) if options[:include_capture_amount] == true end end - def refund(money, authorization, options={}) - commit(:refund, :put) do |doc| - add_authorization(doc, authorization) - add_amount(doc, money, options) - add_order(doc, options) + def refund(money, authorization, options = {}) + options[:endpoint] = options[:merchant_transaction_id] ? "/refund/merchant/#{options[:merchant_transaction_id]}" : "/refund/#{authorization}" + commit(:refund, options, :post) do |doc| + add_amount(doc, money, options) if money + %i[reason cancel_subscription tax_amount].each { |field| send_when_present(doc, field, options) } + add_metadata(doc, options) end end - def void(authorization, options={}) - commit(:void, :put) do |doc| + def void(authorization, options = {}) + commit(:void, options, :put) do |doc| add_authorization(doc, authorization) add_order(doc, options) end end - def verify(payment_method, options={}) + def verify(payment_method, options = {}) authorize(0, payment_method, options) end def store(payment_method, options = {}) payment_method_details = PaymentMethodDetails.new(payment_method) - commit(:store, :post, payment_method_details) do |doc| + commit(:store, options, :post, payment_method_details) do |doc| add_personal_info(doc, payment_method, options) add_echeck_company(doc, payment_method) if payment_method_details.check? doc.send('payment-sources') do @@ -144,7 +152,7 @@ def store_echeck(doc, payment_method) def verify_credentials begin - ssl_get(url.to_s, headers) + ssl_get(url.to_s, headers(options)) rescue ResponseError => e return false if e.response.code.to_i == 401 end @@ -172,7 +180,8 @@ def add_auth_purchase(doc, money, payment_method, options) add_order(doc, options) doc.send('store-card', options[:store_card] || false) add_amount(doc, money, options) - add_fraud_info(doc, options) + add_fraud_info(doc, payment_method, options) + add_stored_credentials(doc, options) if payment_method.is_a?(String) doc.send('vaulted-shopper-id', payment_method) @@ -184,9 +193,23 @@ def add_auth_purchase(doc, money, payment_method, options) end end + def add_stored_credentials(doc, options) + return unless stored_credential = options[:stored_credential] + + initiator = stored_credential[:initiator]&.upcase + initiator = 'SHOPPER' if SHOPPER_INITIATOR.include?(initiator) + doc.send('transaction-initiator', initiator) if stored_credential[:initiator] + if stored_credential[:network_transaction_id] + doc.send('network-transaction-info') do + doc.send('original-network-transaction-id', stored_credential[:network_transaction_id]) + end + end + end + def add_amount(doc, money, options) - doc.amount(amount(money)) - doc.currency(options[:currency] || currency(money)) + currency = options[:currency] || currency(money) + doc.amount(localized_amount(money, currency)) + doc.currency(currency) end def add_personal_info(doc, payment_method, options) @@ -194,6 +217,7 @@ def add_personal_info(doc, payment_method, options) doc.send('last-name', payment_method.last_name) doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number] doc.email(options[:email]) if options[:email] + doc.phone(options[:phone_number]) if options[:phone_number] add_address(doc, options) end @@ -206,12 +230,29 @@ def add_credit_card(doc, card) end end - def add_description(doc, description) + def add_metadata(doc, options) + transaction_meta_data = options[:transaction_meta_data] || [] + return if transaction_meta_data.empty? && !options[:description] + doc.send('transaction-meta-data') do - doc.send('meta-data') do - doc.send('meta-key', 'description') - doc.send('meta-value', truncate(description, 500)) - doc.send('meta-description', 'Description') + # ensure backwards compatibility for calls expecting :description + # to become meta-data fields. + if options[:description] + doc.send('meta-data') do + doc.send('meta-key', 'description') + doc.send('meta-value', truncate(options[:description], 500)) + doc.send('meta-description', 'Description') + end + end + + # https://developers.bluesnap.com/v8976-XML/docs/meta-data + transaction_meta_data.each do |entry| + doc.send('meta-data') do + doc.send('meta-key', truncate(entry[:meta_key], 40)) + doc.send('meta-value', truncate(entry[:meta_value], 500)) + doc.send('meta-description', truncate(entry[:meta_description], 40)) + doc.send('is-visible', truncate(entry[:meta_is_visible], 5)) + end end end end @@ -219,7 +260,9 @@ def add_description(doc, description) def add_order(doc, options) doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id] doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor] - add_description(doc, options[:description]) if options[:description] + 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) end @@ -228,14 +271,32 @@ def add_address(doc, options) return unless address doc.country(address[:country]) if address[:country] - doc.state(address[:state]) if address[:state] - doc.address(address[:address]) if address[:address] + doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country]) + doc.address(address[:address1]) if address[:address1] + doc.address2(address[:address2]) if address[:address2] doc.city(address[:city]) if address[:city] doc.zip(address[:zip]) if address[:zip] end + def add_3ds(doc, three_d_secure_options) + eci = three_d_secure_options[:eci] + cavv = three_d_secure_options[:cavv] + xid = three_d_secure_options[:xid] + ds_transaction_id = three_d_secure_options[:ds_transaction_id] + version = three_d_secure_options[:version] + + doc.send('three-d-secure') do + doc.eci(eci) if eci + doc.cavv(cavv) if cavv + doc.xid(xid) if xid + doc.send('three-d-secure-version', version) if version + doc.send('ds-transaction-id', ds_transaction_id) if ds_transaction_id + end + end + def add_level_3_data(doc, options) return unless options[:customer_reference_number] + doc.send('level-3-data') do send_when_present(doc, :customer_reference_number, options) send_when_present(doc, :sales_tax_amount, options) @@ -253,6 +314,7 @@ def add_level_3_data(doc, options) def send_when_present(doc, options_key, options, xml_element_name = nil) return unless options[options_key] + xml_element_name ||= options_key.to_s doc.send(xml_element_name.dasherize, options[options_key]) @@ -273,26 +335,49 @@ def add_authorization(doc, authorization) doc.send('transaction-id', authorization) end - def add_fraud_info(doc, options) + def add_fraud_info(doc, payment_method, options) doc.send('transaction-fraud-info') do doc.send('shopper-ip-address', options[:ip]) if options[:ip] + if fraud_info = options[:transaction_fraud_info] + doc.send('fraud-session-id', fraud_info[:fraud_session_id]) if fraud_info[:fraud_session_id] + end + unless payment_method.is_a? String + doc.send('shipping-contact-info') do + add_shipping_contact_info(doc, payment_method, options) + end + end + end + end + + def add_shipping_contact_info(doc, payment_method, options) + if address = options[:shipping_address] + # https://developers.bluesnap.com/v8976-XML/docs/shipping-contact-info + doc.send('first-name', payment_method.first_name) + doc.send('last-name', payment_method.last_name) + + doc.country(address[:country]) if address[:country] + doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country]) + doc.address1(address[:address1]) if address[:address1] + doc.address2(address[:address2]) if address[:address2] + doc.city(address[:city]) if address[:city] + doc.zip(address[:zip]) if address[:zip] end end def add_alt_transaction_purchase(doc, money, payment_method_details, options) doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id] doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor] + 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 doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id - if payment_method_details.check? - add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) - end + add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) if payment_method_details.check? - add_fraud_info(doc, options) - add_description(doc, options) + add_fraud_info(doc, payment_method_details.payment_method, options) + add_stored_credentials(doc, options) + add_metadata(doc, options) end def add_echeck_transaction(doc, check, options, vaulted_shopper) @@ -322,17 +407,25 @@ def add_echeck(doc, check) def parse(response) return bad_authentication_response if response.code.to_i == 401 - return forbidden_response(response.body) if response.code.to_i == 403 + return generic_error_response(response.body) if [403, 405, 429].include?(response.code.to_i) parsed = {} doc = Nokogiri::XML(response.body) doc.root.xpath('*').each do |node| + name = node.name.downcase if node.elements.empty? - parsed[node.name.downcase] = node.text + parsed[name] = node.text + elsif name == 'transaction-meta-data' + metadata = [] + node.elements.each { |m| + metadata.push parse_metadata_entry(m) + } + + parsed['transaction-meta-data'] = metadata else - node.elements.each do |childnode| + node.elements.each { |childnode| parse_element(parsed, childnode) - end + } end end @@ -340,6 +433,18 @@ def parse(response) parsed end + def parse_metadata_entry(node) + entry = {} + + node.elements.each { |e| + entry = entry.merge({ + e.name => e.text + }) + } + + entry + end + def parse_element(parsed, node) if !node.elements.empty? node.elements.each { |e| parse_element(parsed, e) } @@ -348,21 +453,21 @@ def parse_element(parsed, node) end end - def api_request(action, request, verb, payment_method_details) - ssl_request(verb, url(action, payment_method_details), request, headers) + def api_request(action, request, verb, payment_method_details, options) + ssl_request(verb, url(action, options, payment_method_details), request, headers(options)) rescue ResponseError => e e.response end - def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new()) + def commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new()) request = build_xml_request(action, payment_method_details) { |doc| yield(doc) } - response = api_request(action, request, verb, payment_method_details) + response = api_request(action, request, verb, payment_method_details, options) parsed = parse(response) succeeded = success_from(action, response) Response.new( succeeded, - message_from(succeeded, parsed), + message_from(succeeded, response), parsed, authorization: authorization_from(action, parsed, payment_method_details), avs_result: avs_result(parsed), @@ -372,9 +477,10 @@ def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.n ) end - def url(action = nil, payment_method_details = PaymentMethodDetails.new()) + def url(action = nil, options = {}, payment_method_details = PaymentMethodDetails.new()) base = test? ? test_url : live_url resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url + resource += options[:endpoint] if action == :refund "#{base}/#{resource}" end @@ -394,17 +500,45 @@ def success_from(action, response) (200...300).cover?(response.code.to_i) end - def message_from(succeeded, parsed_response) + def message_from(succeeded, response) return 'Success' if succeeded - parsed_response['description'] + + parsed = parse(response) + if parsed.dig('error-name') == 'FRAUD_DETECTED' + fraud_codes_from(response) + else + parsed['description'] + end + end + + def fraud_codes_from(response) + event_summary = {} + doc = Nokogiri::XML(response.body) + fraud_events = doc.xpath('//xmlns:fraud-events', 'xmlns' => 'http://ws.plimus.com') + fraud_events.children.each do |child| + if child.children.children.any? + event_summary[child.name] = event_summary[child.name] || [] + event = {} + child.children.each do |chi| + event[chi.name] = chi.text + end + event_summary[child.name] << event + else + event_summary[child.name] = child.text + end + end + event_summary.to_json end def authorization_from(action, parsed_response, payment_method_details) - action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id'] + return vaulted_shopper_id(parsed_response, payment_method_details) if action == :store + + parsed_response['refund-transaction-id'] || parsed_response['transaction-id'] end def vaulted_shopper_id(parsed_response, payment_method_details) return nil unless parsed_response['content-location-header'] + vaulted_shopper_id = parsed_response['content-location-header'].split('/').last vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction? vaulted_shopper_id @@ -421,20 +555,28 @@ def root_attributes end def root_element(action, payment_method_details) - action == :store ? 'vaulted-shopper' : payment_method_details.root_element + return 'refund' if action == :refund + return 'vaulted-shopper' if action == :store + + payment_method_details.root_element end - def headers - { + def headers(options) + idempotency_key = options[:idempotency_key] if options[:idempotency_key] + + headers = { 'Content-Type' => 'application/xml', - 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip), + 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip) } + + headers['Idempotency-Key'] = idempotency_key if idempotency_key + headers end def build_xml_request(action, payment_method_details) builder = Nokogiri::XML::Builder.new builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc| - doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction? + doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction? && action != :refund yield(doc) end builder.doc.root.to_xml @@ -453,7 +595,7 @@ def bad_authentication_response { 'description' => 'Unable to authenticate. Please check your credentials.' } end - def forbidden_response(body) + def generic_error_response(body) { 'description' => body } end end diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb index 8cafd0eeba5..30b8be9838a 100644 --- a/lib/active_merchant/billing/gateways/bogus.rb +++ b/lib/active_merchant/billing/gateways/bogus.rb @@ -47,9 +47,9 @@ def credit(money, paysource, options = {}) money = amount(money) case normalize(paysource) when /1$/ - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + Response.new(true, SUCCESS_MESSAGE, { paid_amount: money }, test: true) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { paid_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else raise Error, error_message(paysource) end @@ -61,9 +61,9 @@ def refund(money, reference, options = {}) when /1$/ raise Error, REFUND_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { paid_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + Response.new(true, SUCCESS_MESSAGE, { paid_amount: money }, test: true) end end @@ -73,9 +73,9 @@ def capture(money, reference, options = {}) when /1$/ raise Error, CAPTURE_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { paid_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + Response.new(true, SUCCESS_MESSAGE, { paid_amount: money }, test: true) end end @@ -84,18 +84,22 @@ def void(reference, options = {}) when /1$/ raise Error, VOID_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { authorization: reference, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else - Response.new(true, SUCCESS_MESSAGE, {:authorization => reference}, :test => true) + Response.new(true, SUCCESS_MESSAGE, { authorization: reference }, test: true) end end + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + def store(paysource, options = {}) case normalize(paysource) when /1$/ - Response.new(true, SUCCESS_MESSAGE, {:billingid => '1'}, :test => true, :authorization => AUTHORIZATION) + Response.new(true, SUCCESS_MESSAGE, { billingid: '1' }, test: true, authorization: AUTHORIZATION) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { billingid: nil, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else raise Error, error_message(paysource) end @@ -104,9 +108,9 @@ def store(paysource, options = {}) def unstore(reference, options = {}) case reference when /1$/ - Response.new(true, SUCCESS_MESSAGE, {}, :test => true) + Response.new(true, SUCCESS_MESSAGE, {}, test: true) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else raise Error, UNSTORE_ERROR_MESSAGE end @@ -118,9 +122,9 @@ def authorize_emv(money, paysource, options = {}) money = amount(money) case money when /00$/ - Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS) + Response.new(true, SUCCESS_MESSAGE, { authorized_amount: money }, test: true, authorization: AUTHORIZATION, emv_authorization: AUTHORIZATION_EMV_SUCCESS) when /05$/ - Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE) + Response.new(false, FAILURE_MESSAGE, { authorized_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error], emv_authorization: AUTHORIZATION_EMV_DECLINE) else raise Error, error_message(paysource) end @@ -130,9 +134,9 @@ def authorize_swipe(money, paysource, options = {}) money = amount(money) case normalize(paysource) when /1$/, AUTHORIZATION - Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION) + Response.new(true, SUCCESS_MESSAGE, { authorized_amount: money }, test: true, authorization: AUTHORIZATION) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { authorized_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else raise Error, error_message(paysource) end @@ -142,9 +146,9 @@ def purchase_emv(money, paysource, options = {}) money = amount(money) case money when /00$/ - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS) + Response.new(true, SUCCESS_MESSAGE, { paid_amount: money }, test: true, authorization: AUTHORIZATION, emv_authorization: AUTHORIZATION_EMV_SUCCESS) when /05$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE) + Response.new(false, FAILURE_MESSAGE, { paid_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error], emv_authorization: AUTHORIZATION_EMV_DECLINE) else raise Error, error_message(paysource) end @@ -154,9 +158,9 @@ def purchase_swipe(money, paysource, options = {}) money = amount(money) case normalize(paysource) when /1$/, AUTHORIZATION - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION) + Response.new(true, SUCCESS_MESSAGE, { paid_amount: money }, test: true, authorization: AUTHORIZATION) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + Response.new(false, FAILURE_MESSAGE, { paid_amount: money, error: FAILURE_MESSAGE }, test: true, error_code: STANDARD_ERROR_CODE[:processing_error]) else raise Error, error_message(paysource) end diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb index b949144fcfb..778c6bc64eb 100644 --- a/lib/active_merchant/billing/gateways/borgun.rb +++ b/lib/active_merchant/billing/gateways/borgun.rb @@ -9,35 +9,51 @@ class BorgunGateway < Gateway self.test_url = 'https://gatewaytest.borgun.is/ws/Heimir.pub.ws:Authorization' self.live_url = 'https://gateway01.borgun.is/ws/Heimir.pub.ws:Authorization' - self.supported_countries = ['IS', 'GB', 'HU', 'CZ', 'DE', 'DK', 'SE' ] + self.supported_countries = %w[IS GB HU CZ DE DK SE] self.default_currency = 'ISK' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb] self.homepage_url = 'https://www.borgun.is/' - def initialize(options={}) + def initialize(options = {}) requires!(options, :processor, :merchant_id, :username, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} - post[:TransType] = '1' + action = '' + if options[:apply_3d_secure] == '1' + add_3ds_preauth_fields(post, options) + action = '3ds_preauth' + else + post[:TransType] = '1' + add_3ds_fields(post, options) + action = 'sale' + end add_invoice(post, money, options) add_payment_method(post, payment) - commit('sale', post) + commit(action, post, options) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} - post[:TransType] = '5' + action = '' + if options[:apply_3d_secure] == '1' + add_3ds_preauth_fields(post, options) + action = '3ds_preauth' + else + post[:TransType] = '5' + add_3ds_fields(post, options) + action = 'authonly' + end add_invoice(post, money, options) add_payment_method(post, payment) - commit('authonly', post) + commit(action, post, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} post[:TransType] = '1' add_invoice(post, money, options) @@ -45,7 +61,7 @@ def capture(money, authorization, options={}) commit('capture', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} post[:TransType] = '3' add_invoice(post, money, options) @@ -53,7 +69,7 @@ def refund(money, authorization, options={}) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} # TransType, TrAmount, and currency must match original values from auth or purchase. _, _, _, _, _, transtype, tramount, currency = split_authorization(authorization) @@ -76,14 +92,32 @@ def scrub(transcript) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } CURRENCY_CODES['ISK'] = '352' CURRENCY_CODES['EUR'] = '978' CURRENCY_CODES['USD'] = '840' + CURRENCY_CODES['GBP'] = '826' + + def add_3ds_fields(post, options) + post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id] + post[:ThreeDS_PARes] = options[:three_ds_pares] if options[:three_ds_pares] + post[:ThreeDS_CRes] = options[:three_ds_cres] if options[:three_ds_cres] + end + + def add_3ds_preauth_fields(post, options) + post[:SaleDescription] = options[:sale_description] || '' + 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)] + # 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 @@ -96,18 +130,18 @@ def add_payment_method(post, payment_method) end def add_reference(post, authorization) - dateandtime, _batch, transaction, rrn, authcode, _, _, _ = split_authorization(authorization) + dateandtime, _batch, transaction, rrn, authcode, = split_authorization(authorization) post[:DateAndTime] = dateandtime post[:Transaction] = transaction post[:RRN] = rrn post[:AuthCode] = authcode end - def parse(xml) + def parse(xml, options = nil) response = {} doc = Nokogiri::XML(CGI.unescapeHTML(xml)) - body = doc.xpath('//getAuthorizationReply') + body = options[:apply_3d_secure] == '1' ? doc.xpath('//get3DSAuthenticationReply') : doc.xpath('//getAuthorizationReply') body = doc.xpath('//cancelAuthorizationReply') if body.length == 0 body.children.each do |node| if node.text? @@ -121,43 +155,42 @@ def parse(xml) end end end - response end - def commit(action, post) + def commit(action, post, options = {}) post[:Version] = '1000' post[:Processor] = @options[:processor] post[:MerchantID] = @options[:merchant_id] - request = build_request(action, post) + request = build_request(action, post, options) raw = ssl_post(url(action), request, headers) - pairs = parse(raw) + pairs = parse(raw, options) success = success_from(pairs) Response.new( success, message_from(success, pairs), pairs, - authorization: authorization_from(pairs), + authorization: authorization_from(pairs, options), test: test? ) end def success_from(response) - (response[:actioncode] == '000') + (response[:actioncode] == '000') || (response[:status_resultcode] == '0') end 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], @@ -167,6 +200,8 @@ def authorization_from(response) response[:tramount], response[:trcurrency] ].join('|') + + authorization == '|||||||' ? nil : authorization end def split_authorization(authorization) @@ -176,36 +211,55 @@ def split_authorization(authorization) def headers { - 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:password].to_s), + 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:password].to_s) } end - def build_request(action, post) + def build_request(action, post, options = {}) mode = action == 'void' ? 'cancel' : 'get' - xml = Builder::XmlMarkup.new :indent => 18 - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - xml.tag!("#{mode}Authorization") do + transaction_type = action == '3ds_preauth' ? '3DSAuthentication' : 'Authorization' + xml = Builder::XmlMarkup.new indent: 18 + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') + xml.tag!("#{mode}#{transaction_type}") do post.each do |field, value| xml.tag!(field, value) end + build_airline_xml(xml, options[:passenger_itinerary_data]) if options[:passenger_itinerary_data] end inner = CGI.escapeHTML(xml.target!) - envelope(mode).sub(/{{ :body }}/, inner) + envelope(mode, action).sub(/{{ :body }}/, inner) + end + + def build_airline_xml(xml, airline_data) + xml.tag!('PassengerItineraryData') do + xml.tag!('A1') do + airline_data.each do |field, value| + xml.tag!(field, value) + end + end + end end - def envelope(mode) - <<-EOS + def envelope(mode, action) + if action == '3ds_preauth' + transaction_action = "#{mode}3DSAuthentication" + request_action = "#{mode}Auth3DSReqXml" + else + transaction_action = "#{mode}AuthorizationInput" + request_action = "#{mode}AuthReqXml" + end + <<-XML - - <#{mode}AuthReqXml> + + <#{request_action}> {{ :body }} - - + + - EOS + XML end def url(action) diff --git a/lib/active_merchant/billing/gateways/bpoint.rb b/lib/active_merchant/billing/gateways/bpoint.rb index dc48ffa47a1..d8fa40783f9 100644 --- a/lib/active_merchant/billing/gateways/bpoint.rb +++ b/lib/active_merchant/billing/gateways/bpoint.rb @@ -7,17 +7,17 @@ class BpointGateway < Gateway self.supported_countries = ['AU'] self.default_currency = 'AUD' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] self.homepage_url = 'https://www.bpoint.com.au/bpoint' self.display_name = 'BPoint' - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password, :merchant_number) super end - def store(credit_card, options={}) + def store(credit_card, options = {}) options[:crn1] ||= 'DEFAULT' request_body = soap_request do |xml| add_token(xml, credit_card, options) @@ -25,7 +25,7 @@ def store(credit_card, options={}) commit(request_body) end - def purchase(amount, credit_card, options={}) + def purchase(amount, credit_card, options = {}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_purchase(payment_xml, amount, credit_card, options) @@ -34,7 +34,7 @@ def purchase(amount, credit_card, options={}) commit(request_body) end - def authorize(amount, credit_card, options={}) + def authorize(amount, credit_card, options = {}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_authorize(payment_xml, amount, credit_card, options) @@ -43,7 +43,7 @@ def authorize(amount, credit_card, options={}) commit(request_body) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_capture(payment_xml, amount, authorization, options) @@ -52,7 +52,7 @@ def capture(amount, authorization, options={}) commit(request_body) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_refund(payment_xml, amount, authorization, options) @@ -61,19 +61,19 @@ def refund(amount, authorization, options={}) commit(request_body) end - def void(amount, authorization, options={}) + def void(authorization, options = {}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| - add_void(payment_xml, amount, authorization, options) + add_void(payment_xml, authorization, options) end end commit(request_body) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(100, r.authorization, options) } + r.process(:ignore_result) { void(r.authorization, options.merge(amount: 100)) } end end @@ -91,7 +91,7 @@ def scrub(transcript) private def soap_request - Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml| xml.send('soap12:Envelope', soap_envelope_attributes) { xml.send('soap12:Body') { yield(xml) if block_given? @@ -154,7 +154,9 @@ def add_refund(xml, amount, transaction_number, options) transaction_number_xml(xml, transaction_number) end - def add_void(xml, amount, transaction_number, options) + def add_void(xml, transaction_number, options) + # The amount parameter is required for void requests on BPoint. + amount = options[:amount] payment_xml(xml, 'REVERSAL', amount, options) transaction_number_xml(xml, transaction_number) end @@ -163,10 +165,10 @@ def payment_xml(xml, payment_type, amount, options) xml.send('PaymentType', payment_type) xml.send('TxnType', 'WEB_SHOP') xml.send('BillerCode', options.fetch(:biller_code, '')) - xml.send('MerchantReference', '') - xml.send('CRN1', '') - xml.send('CRN2', '') - xml.send('CRN3', '') + xml.send('MerchantReference', options[:order_id]) if options[:order_id] + xml.send('CRN1', options[:crn1]) if options[:crn1] + xml.send('CRN2', options[:crn2]) if options[:crn2] + xml.send('CRN3', options[:crn3]) if options[:crn3] xml.send('Amount', amount) end @@ -240,7 +242,6 @@ def options end class ProcessPaymentResponse < BPointResponse - private def authorization_key @@ -257,7 +258,6 @@ def message end class AddTokenResponse < BPointResponse - private def authorization_key diff --git a/lib/active_merchant/billing/gateways/braintree.rb b/lib/active_merchant/billing/gateways/braintree.rb index bfe682c31db..c3008feb70a 100644 --- a/lib/active_merchant/billing/gateways/braintree.rb +++ b/lib/active_merchant/billing/gateways/braintree.rb @@ -7,7 +7,7 @@ class BraintreeGateway < Gateway self.abstract_class = true - def self.new(options={}) + def self.new(options = {}) if options.has_key?(:login) BraintreeOrangeGateway.new(options) else diff --git a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb index 7343584f7aa..165d8faaa90 100644 --- a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +++ b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb @@ -1,7 +1,7 @@ module BraintreeCommon def self.included(base) base.supported_countries = %w(US CA AD AT BE BG HR CY CZ DK EE FI FR GI DE GR GG HU IS IM IE IT JE LV LI LT LU MT MC NL NO PL PT RO SM SK SI ES SE CH TR GB SG HK MY AU NZ) - base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] + base.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro] base.homepage_url = 'http://www.braintreepaymentsolutions.com' base.display_name = 'Braintree' base.default_currency = 'USD' @@ -14,9 +14,15 @@ def supports_scrubbing def scrub(transcript) return '' if transcript.blank? + transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r((&?ccnumber=)\d*(&?)), '\1[FILTERED]\2'). - gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2') + gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2'). + gsub(%r(()[^<]+()), '\1[FILTERED]\2'). + gsub(%r(()[^<]+()), '\1[FILTERED]\2'). + gsub(%r(()[^<]{100,}()), '\1[FILTERED]\2'). + gsub(%r(()[^<]+()), '\1[FILTERED]\2') end end diff --git a/lib/active_merchant/billing/gateways/braintree/token_nonce.rb b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb new file mode 100644 index 00000000000..dc9a3e0bc90 --- /dev/null +++ b/lib/active_merchant/billing/gateways/braintree/token_nonce.rb @@ -0,0 +1,158 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TokenNonce #:nodoc: + include PostsData + # This class emulates the behavior of the front-end js library to + # create token nonce for a bank account base on the docs: + # https://developer.paypal.com/braintree/docs/guides/ach/client-side + + attr_reader :braintree_gateway, :options + + def initialize(gateway, options = {}) + @braintree_gateway = gateway + @options = options + end + + def url + sandbox = @braintree_gateway.config.environment == :sandbox + "https://payments#{'.sandbox' if sandbox}.braintree-api.com/graphql" + end + + def create_token_nonce_for_payment_method(payment_method) + headers = { + 'Accept' => 'application/json', + 'Authorization' => "Bearer #{client_token}", + 'Content-Type' => 'application/json', + 'Braintree-Version' => '2018-05-10' + } + resp = ssl_post(url, build_nonce_request(payment_method), headers) + json_response = JSON.parse(resp) + + message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present? + token = token_from(payment_method, json_response) + + return token, message + end + + def client_token + base64_token = @braintree_gateway.client_token.generate + JSON.parse(Base64.decode64(base64_token))['authorizationFingerprint'] + end + + private + + def graphql_bank_query + <<-GRAPHQL + mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) { + tokenizeUsBankAccount(input: $input) { + paymentMethod { + id + details { + ... on UsBankAccountDetails { + last4 + } + } + } + } + } + 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? + + address = options[:billing_address] + + { + streetAddress: address[:address1], + extendedAddress: address[:address2], + city: address[:city], + state: address[:state], + zipCode: address[:zip] + }.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], + routingNumber: payment_method.routing_number, + accountNumber: payment_method.account_number, + accountType: payment_method.account_type.upcase, + billingAddress: billing_address_from_options + } + } + + if payment_method.account_holder_type == 'personal' + input[:usBankAccount][:individualOwner] = { + firstName: payment_method.first_name, + lastName: payment_method.last_name + } + else + input[:usBankAccount][:businessOwner] = { + businessName: payment_method.name + } + end + + 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 +end diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index bff4f0b0e12..91a27f00ec1 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -1,4 +1,6 @@ require 'active_merchant/billing/gateways/braintree/braintree_common' +require 'active_merchant/billing/gateways/braintree/token_nonce' +require 'active_support/core_ext/array/extract_options' begin require 'braintree' @@ -6,9 +8,7 @@ raise 'Could not load the braintree gem. Use `gem install braintree` to install it.' end -unless Braintree::Version::Major == 2 && Braintree::Version::Minor >= 78 - raise "Need braintree gem >= 2.78.0. Run `gem install braintree --version '~>2.78'` to get the correct version." -end +raise 'Need braintree gem >= 2.0.0.' unless Braintree::Version::Major >= 2 && Braintree::Version::Minor >= 0 module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -47,6 +47,8 @@ class BraintreeBlueGateway < Gateway cannot_refund_if_unsettled: 91506 } + DIRECT_BANK_ERROR = 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.'.freeze + def initialize(options = {}) requires!(options, :merchant_id, :public_key, :private_key) @merchant_account_id = options[:merchant_account_id] @@ -62,33 +64,50 @@ def initialize(options = {}) end @configuration = Braintree::Configuration.new( - :merchant_id => options[:merchant_id], - :public_key => options[:public_key], - :private_key => options[:private_key], - :environment => (options[:environment] || (test? ? :sandbox : :production)).to_sym, - :custom_user_agent => "ActiveMerchant #{ActiveMerchant::VERSION}", - :logger => options[:logger] || logger + merchant_id: options[:merchant_id], + public_key: options[:public_key], + private_key: options[:private_key], + environment: (options[:environment] || (test? ? :sandbox : :production)).to_sym, + custom_user_agent: "ActiveMerchant #{ActiveMerchant::VERSION}", + logger: options[:logger] || logger ) @braintree_gateway = Braintree::Gateway.new(@configuration) end + def setup_purchase + commit do + Response.new(true, 'Client token created', { client_token: @braintree_gateway.client_token.generate }) + end + end + def authorize(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(:sale, money, credit_card_or_vault_id, options) end def capture(money, authorization, options = {}) - commit do - result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s) - response_from_result(result) + if options[:partial_capture] == true + commit do + result = @braintree_gateway.transaction.submit_for_partial_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s) + response_from_result(result) + end + else + commit do + result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s) + response_from_result(result) + end end end def purchase(money, credit_card_or_vault_id, options = {}) - authorize(money, credit_card_or_vault_id, options.merge(:submit_for_settlement => true)) + authorize(money, credit_card_or_vault_id, options.merge(submit_for_settlement: true)) 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 @@ -100,10 +119,13 @@ def refund(*args) commit do response = response_from_result(@braintree_gateway.transaction.refund(transaction_id, money)) - return response if response.success? - return response unless options[:force_full_refund_if_unsettled] - void(transaction_id) if response.message =~ /#{ERROR_CODES[:cannot_refund_if_unsettled]}/ + if !response.success? && options[:force_full_refund_if_unsettled] && + response.message =~ /#{ERROR_CODES[:cannot_refund_if_unsettled]}/ + void(transaction_id) + else + response + end end end @@ -113,28 +135,46 @@ def void(authorization, options = {}) end end - def verify(credit_card, options = {}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } + def verify(creditcard, options = {}) + if options[:allow_card_verification] == true + options.delete(:allow_card_verification) + exp_month = creditcard.month.to_s + exp_year = creditcard.year.to_s + expiration = "#{exp_month}/#{exp_year}" + payload = { + credit_card: { + number: creditcard.number, + expiration_date: expiration, + cvv: creditcard.verification_value, + billing_address: { + postal_code: options[:billing_address][:zip] + } + } + } + commit do + result = @braintree_gateway.verification.create(payload) + response = Response.new(result.success?, message_from_transaction_result(result), response_options(result)) + response.cvv_result['message'] = '' + response.cvv_result['code'] = response.params['cvv_result'] if response.params['cvv_result'] + response.avs_result['code'] = response.params['avs_result'][:code] if response.params.dig('avs_result', :code) + response + end + + else + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end end end - def store(creditcard, options = {}) - if options[:customer].present? - MultiResponse.new.tap do |r| - customer_exists_response = nil - r.process { customer_exists_response = check_customer_exists(options[:customer]) } - r.process do - if customer_exists_response.params['exists'] - add_credit_card_to_customer(creditcard, options) - else - add_customer_with_credit_card(creditcard, options) - end - end - end - else - add_customer_with_credit_card(creditcard, options) + def store(payment_method, options = {}) + return Response.new(false, bank_account_errors(payment_method, options)) if payment_method.is_a?(Check) && bank_account_errors(payment_method, options).present? + + MultiResponse.run do |r| + r.process { check_customer_exists(options[:customer]) } + process_by = payment_method.is_a?(Check) ? :store_bank_account : :store_credit_card + send process_by, payment_method, options, r end end @@ -146,33 +186,35 @@ def update(vault_id, creditcard, options = {}) options[:update_existing_token] = braintree_credit_card.token credit_card_params = merge_credit_card_options({ - :credit_card => { - :cardholder_name => creditcard.name, - :number => creditcard.number, - :cvv => creditcard.verification_value, - :expiration_month => creditcard.month.to_s.rjust(2, '0'), - :expiration_year => creditcard.year.to_s + credit_card: { + cardholder_name: creditcard.name, + number: creditcard.number, + cvv: creditcard.verification_value, + expiration_month: creditcard.month.to_s.rjust(2, '0'), + expiration_year: creditcard.year.to_s } }, options)[:credit_card] - 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 + result = @braintree_gateway.customer.update( + vault_id, + first_name: creditcard.first_name, + last_name: creditcard.last_name, + email: scrub_email(options[:email]), + 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?) + 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?) ) end end def unstore(customer_vault_id, options = {}) commit do - if(!customer_vault_id && options[:credit_card_token]) + if !customer_vault_id && options[:credit_card_token] @braintree_gateway.credit_card.delete(options[:credit_card_token]) else @braintree_gateway.customer.delete(customer_vault_id) @@ -180,7 +222,7 @@ def unstore(customer_vault_id, options = {}) Response.new(true, 'OK') end end - alias_method :delete, :unstore + alias delete unstore def supports_network_tokenization? true @@ -201,13 +243,13 @@ def verify_credentials private def check_customer_exists(customer_vault_id) + return Response.new true, 'Customer not found', { exists: false } if customer_vault_id.blank? + commit do - begin - @braintree_gateway.customer.find(customer_vault_id) - ActiveMerchant::Billing::Response.new(true, 'Customer found', {exists: true}, authorization: customer_vault_id) - rescue Braintree::NotFoundError - ActiveMerchant::Billing::Response.new(true, 'Customer not found', {exists: false}) - end + @braintree_gateway.customer.find(customer_vault_id) + ActiveMerchant::Billing::Response.new(true, 'Customer found', { exists: true }, authorization: customer_vault_id) + rescue Braintree::NotFoundError + ActiveMerchant::Billing::Response.new(true, 'Customer not found', { exists: false }) end end @@ -217,33 +259,34 @@ def add_customer_with_credit_card(creditcard, options) credit_card_params = { payment_method_nonce: options[:payment_method_nonce] } else credit_card_params = { - :credit_card => { - :cardholder_name => creditcard.name, - :number => creditcard.number, - :cvv => creditcard.verification_value, - :expiration_month => creditcard.month.to_s.rjust(2, '0'), - :expiration_year => creditcard.year.to_s, - :token => options[:credit_card_token] + credit_card: { + cardholder_name: creditcard.name, + number: creditcard.number, + cvv: creditcard.verification_value, + expiration_month: creditcard.month.to_s.rjust(2, '0'), + expiration_year: creditcard.year.to_s, + token: options[:credit_card_token] } } end parameters = { - :first_name => creditcard.first_name, - :last_name => creditcard.last_name, - :email => scrub_email(options[:email]), - :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] && - options[:billing_address][:phone]), - :id => options[:customer], - :device_data => options[:device_data], + first_name: creditcard.first_name, + last_name: creditcard.last_name, + email: scrub_email(options[:email]), + 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?) + 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 @@ -258,11 +301,11 @@ def add_credit_card_to_customer(credit_card, options) cvv: credit_card.verification_value, expiration_month: credit_card.month.to_s.rjust(2, '0'), expiration_year: credit_card.year.to_s, - device_data: options[:device_data], + device_data: options[:device_data] } if options[:billing_address] address = map_address(options[:billing_address]) - parameters[:credit_card][:billing_address] = address unless address.all? { |_k, v| empty?(v) } + parameters[:billing_address] = address unless address.all? { |_k, v| empty?(v) } end result = @braintree_gateway.credit_card.create(parameters) @@ -289,22 +332,20 @@ def scrub_email(email) def scrub_zip(zip) return nil unless zip.present? - return nil if( + return nil if zip.gsub(/[^a-z0-9]/i, '').length > 9 || zip =~ /[^a-z0-9\- ]/i - ) + zip end def merge_credit_card_options(parameters, options) valid_options = {} options.each do |key, value| - valid_options[key] = value if [:update_existing_token, :verify_card, :verification_merchant_account_id].include?(key) + valid_options[key] = value if %i[update_existing_token verify_card verification_merchant_account_id].include?(key) end - if valid_options.include?(:verify_card) && @merchant_account_id - valid_options[:verification_merchant_account_id] ||= @merchant_account_id - end + valid_options[:verification_merchant_account_id] ||= @merchant_account_id if valid_options.include?(:verify_card) && @merchant_account_id parameters[:credit_card] ||= {} parameters[:credit_card][:options] = valid_options @@ -315,22 +356,24 @@ 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], - :extended_address => address[:address2], - :company => address[:company], - :locality => address[:city], - :region => address[:state], - :postal_code => scrub_zip(address[:zip]), + street_address: address[:address1], + extended_address: address[:address2], + company: address[:company], + locality: address[:city], + region: address[:state], + postal_code: scrub_zip(address[:zip]) } mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) if address[:country] || address[:country_code_alpha2] mapped[:country_name] = address[:country_name] if address[:country_name] mapped[:country_code_alpha3] = address[:country_code_alpha3] if address[:country_code_alpha3] - unless address[:country].blank? - mapped[:country_code_alpha3] ||= Country.find(address[:country]).code(:alpha3).value - end + mapped[:country_code_alpha3] ||= Country.find(address[:country]).code(:alpha3).value unless address[:country].blank? mapped[:country_code_numeric] = address[:country_code_numeric] if address[:country_code_numeric] mapped @@ -338,8 +381,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) @@ -375,7 +418,11 @@ def response_params(result) def response_options(result) options = {} - if result.transaction + if result.credit_card_verification + options[:authorization] = result.credit_card_verification.id + options[:avs_result] = { code: avs_code_from(result.credit_card_verification) } + options[:cvv_result] = result.credit_card_verification.cvv_response_code + elsif result.transaction options[:authorization] = result.transaction.id options[:avs_result] = { code: avs_code_from(result.transaction) } options[:cvv_result] = result.transaction.cvv_response_code @@ -419,7 +466,9 @@ def avs_mapping 'street: A, zip: N' => 'C', 'street: A, zip: U' => 'I', 'street: A, zip: I' => 'I', - 'street: A, zip: A' => 'I' + 'street: A, zip: A' => 'I', + + 'street: B, zip: B' => 'B' } end @@ -443,17 +492,28 @@ def response_code_from_result(result) end end + def additional_processor_response_from_result(result) + result.transaction&.additional_processor_response + end + def create_transaction(transaction_type, money, credit_card_or_vault_id, options) transaction_params = create_transaction_parameters(money, credit_card_or_vault_id, options) - commit do result = @braintree_gateway.transaction.send(transaction_type, transaction_params) + make_default_payment_method_token(result) if options.dig(:paypal, :paypal_flow_type) == 'checkout_with_vault' && result.success? response = Response.new(result.success?, message_from_transaction_result(result), response_params(result), response_options(result)) response.cvv_result['message'] = '' response end end + def make_default_payment_method_token(result) + @braintree_gateway.customer.update( + result.transaction.customer_details.id, + default_payment_method_token: result.transaction.paypal_details.implicitly_vaulted_payment_method_token + ) + end + def extract_refund_args(args) options = args.extract_options! @@ -467,7 +527,7 @@ def extract_refund_args(args) end end - def customer_hash(customer, include_credit_cards=false) + def customer_hash(customer, include_credit_cards = false) hash = { 'email' => customer.email, 'phone' => customer.phone, @@ -494,7 +554,8 @@ def customer_hash(customer, include_credit_cards=false) def transaction_hash(result) unless result.success? - return { 'processor_response_code' => response_code_from_result(result) } + return { 'processor_response_code' => response_code_from_result(result), + 'additional_processor_response' => additional_processor_response_from_result(result) } end transaction = result.transaction @@ -513,7 +574,7 @@ def transaction_hash(result) customer_details = { 'id' => transaction.customer_details.id, 'email' => transaction.customer_details.email, - 'phone' => transaction.customer_details.phone, + 'phone' => transaction.customer_details.phone } billing_details = { @@ -523,7 +584,7 @@ def transaction_hash(result) 'locality' => transaction.billing_details.locality, 'region' => transaction.billing_details.region, 'postal_code' => transaction.billing_details.postal_code, - 'country_name' => transaction.billing_details.country_name, + 'country_name' => transaction.billing_details.country_name } shipping_details = { @@ -533,14 +594,17 @@ def transaction_hash(result) 'locality' => transaction.shipping_details.locality, 'region' => transaction.shipping_details.region, 'postal_code' => transaction.shipping_details.postal_code, - 'country_name' => transaction.shipping_details.country_name, + '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 + 'token' => transaction.credit_card_details.token, + 'debit' => transaction.credit_card_details.debit, + 'prepaid' => transaction.credit_card_details.prepaid, + 'issuing_bank' => transaction.credit_card_details.issuing_bank } if transaction.risk_data @@ -554,140 +618,394 @@ 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, - 'processor_response_code' => response_code_from_result(result) + '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), + 'processor_authorization_code' => transaction.processor_authorization_code, + 'recurring' => transaction.recurring, + 'payment_receipt' => payment_receipt } end def create_transaction_parameters(money, credit_card_or_vault_id, options) parameters = { - :amount => localized_amount(money, options[:currency] || default_currency).to_s, - :order_id => options[:order_id], - :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]) + amount: localized_amount(money, options[:currency] || default_currency).to_s, + order_id: options[:order_id], + customer: { + id: options[:store] == true ? '' : options[:store], + email: scrub_email(options[:email]), + phone: phone_from(options) }, - :options => { - :store_in_vault => options[:store] ? true : false, - :submit_for_settlement => options[:submit_for_settlement], - :hold_in_escrow => options[:hold_in_escrow], + options: { + store_in_vault: options[:store] ? true : false, + submit_for_settlement: options[:submit_for_settlement], + hold_in_escrow: options[:hold_in_escrow] } } - if options[:skip_advanced_fraud_checking] - parameters[:options][:skip_advanced_fraud_checking] = options[:skip_advanced_fraud_checking] - end - parameters[:custom_fields] = options[:custom_fields] parameters[:device_data] = options[:device_data] if options[:device_data] parameters[:service_fee_amount] = options[:service_fee_amount] if options[:service_fee_amount] - if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id) - parameters[:merchant_account_id] = merchant_account_id - end - if options[:transaction_source] - parameters[:transaction_source] = options[:transaction_source] - elsif options[:recurring] - parameters[:recurring] = true - end + add_account_type(parameters, options) if options[:account_type] + add_skip_options(parameters, options) + add_merchant_account_id(parameters, options) + add_profile_id(parameters, options) add_payment_method(parameters, credit_card_or_vault_id, options) + add_stored_credential_data(parameters, credit_card_or_vault_id, options) + add_addresses(parameters, 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) + add_transaction_source(parameters, options) + + add_level_2_data(parameters, options) + add_level_3_data(parameters, 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] + end + + parameters + end + + def add_account_type(parameters, options) + parameters[:options][:credit_card] = {} + parameters[:options][:credit_card][:account_type] = options[:account_type] + end + + def add_skip_options(parameters, options) + parameters[:options][:skip_advanced_fraud_checking] = options[:skip_advanced_fraud_checking] if options[:skip_advanced_fraud_checking] + parameters[:options][:skip_avs] = options[:skip_avs] if options[:skip_avs] + parameters[:options][:skip_cvv] = options[:skip_cvv] if options[:skip_cvv] + end + + def add_merchant_account_id(parameters, options) + return unless merchant_account_id = (options[:merchant_account_id] || @merchant_account_id) + + parameters[:merchant_account_id] = merchant_account_id + end + + def add_profile_id(parameters, options) + return unless profile_id = options[:venmo_profile_id] + + parameters[:options][:venmo] = {} + parameters[:options][:venmo][:profile_id] = profile_id + end + def add_transaction_source(parameters, options) + parameters[:transaction_source] = options[:transaction_source] if options[:transaction_source] + parameters[:transaction_source] = 'recurring' if options[:recurring] + end + + def add_addresses(parameters, options) parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address] parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address] + end + def add_channel(parameters, options) channel = @options[:channel] || application_id parameters[:channel] = channel if channel + end - if options[:descriptor_name] || options[:descriptor_phone] || options[:descriptor_url] - parameters[:descriptor] = { - name: options[:descriptor_name], - phone: options[:descriptor_phone], - url: options[:descriptor_url] - } - end + def add_descriptor(parameters, options) + return unless options[:descriptor_name] || options[:descriptor_phone] || options[:descriptor_url] - if options[:three_d_secure] - parameters[:three_d_secure_pass_thru] = { - cavv: options[:three_d_secure][:cavv], - eci_flag: options[:three_d_secure][:eci], - xid: options[:three_d_secure][:xid], - } - end + parameters[:descriptor] = { + name: options[:descriptor_name], + phone: options[:descriptor_phone], + url: options[:descriptor_url] + } + end + def add_risk_data(parameters, options) + return unless options[:risk_data] + + parameters[:risk_data] = { + customer_browser: options[:risk_data][:customer_browser], + customer_ip: options[:risk_data][:customer_ip] + } + 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] parameters[:purchase_order_number] = options[:purchase_order_number] if options[:purchase_order_number] + end + def add_level_3_data(parameters, options) parameters[:shipping_amount] = options[:shipping_amount] if options[:shipping_amount] parameters[:discount_amount] = options[:discount_amount] if options[:discount_amount] parameters[:ships_from_postal_code] = options[:ships_from_postal_code] if options[:ships_from_postal_code] parameters[:line_items] = options[:line_items] if options[:line_items] + end - parameters + def add_travel_data(parameters, options) + parameters[:industry] = { + industry_type: Braintree::Transaction::IndustryType::TravelAndCruise, + data: {} + } + + parameters[:industry][:data][:travel_package] = options[:travel_data][:travel_package] if options[:travel_data][:travel_package] + parameters[:industry][:data][:departure_date] = options[:travel_data][:departure_date] if options[:travel_data][:departure_date] + parameters[:industry][:data][:lodging_check_in_date] = options[:travel_data][:lodging_check_in_date] if options[:travel_data][:lodging_check_in_date] + parameters[:industry][:data][:lodging_check_out_date] = options[:travel_data][:lodging_check_out_date] if options[:travel_data][:lodging_check_out_date] + parameters[:industry][:data][:lodging_name] = options[:travel_data][:lodging_name] if options[:travel_data][:lodging_name] 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 + def add_lodging_data(parameters, options) + parameters[:industry] = { + industry_type: Braintree::Transaction::IndustryType::Lodging, + data: {} + } + + parameters[:industry][:data][:folio_number] = options[:lodging_data][:folio_number] if options[:lodging_data][:folio_number] + parameters[:industry][:data][:check_in_date] = options[:lodging_data][:check_in_date] if options[:lodging_data][:check_in_date] + parameters[:industry][:data][:check_out_date] = options[:lodging_data][:check_out_date] if options[:lodging_data][:check_out_date] + parameters[:industry][:data][:room_rate] = options[:lodging_data][:room_rate] if options[:lodging_data][:room_rate] + end + + def add_3ds_info(parameters, three_d_secure_opts) + return if empty?(three_d_secure_opts) + + pass_thru = {} + + pass_thru[:three_d_secure_version] = three_d_secure_opts[:version] if three_d_secure_opts[:version] + pass_thru[:eci_flag] = three_d_secure_opts[:eci] if three_d_secure_opts[:eci] + pass_thru[:cavv_algorithm] = three_d_secure_opts[:cavv_algorithm] if three_d_secure_opts[:cavv_algorithm] + pass_thru[:cavv] = three_d_secure_opts[:cavv] if three_d_secure_opts[:cavv] + pass_thru[:directory_response] = three_d_secure_opts[:directory_response_status] if three_d_secure_opts[:directory_response_status] + pass_thru[:authentication_response] = three_d_secure_opts[:authentication_response_status] if three_d_secure_opts[:authentication_response_status] + + parameters[:three_d_secure_pass_thru] = pass_thru.merge(xid_or_ds_trans_id(three_d_secure_opts)) + end + + def xid_or_ds_trans_id(three_d_secure_opts) + if three_d_secure_opts[:version].to_f >= 2 + { ds_transaction_id: three_d_secure_opts[:ds_transaction_id] } + else + { xid: three_d_secure_opts[:xid] } + end + end + + def add_stored_credential_data(parameters, credit_card_or_vault_id, options) + return unless (stored_credential = options[:stored_credential]) + + parameters[:external_vault] = {} + if stored_credential[:initial_transaction] + parameters[:external_vault][:status] = 'will_vault' + else + parameters[:external_vault][:status] = 'vaulted' + parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id] + end + if stored_credential[:initiator] == 'merchant' + if stored_credential[:reason_type] == 'installment' + parameters[:transaction_source] = 'recurring' else - parameters[:customer_id] = credit_card_or_vault_id + parameters[:transaction_source] = stored_credential[:reason_type] end + elsif %w(recurring_first moto).include?(stored_credential[:reason_type]) + parameters[:transaction_source] = stored_credential[:reason_type] + else + parameters[:transaction_source] = '' + end + end + + def add_payment_method(parameters, credit_card_or_vault_id, options) + if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer) + 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 + 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 - parameters[:android_pay_card] = { - :number => credit_card_or_vault_id.number, - :cryptogram => credit_card_or_vault_id.payment_cryptogram, - :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'), - :expiration_year => credit_card_or_vault_id.year.to_s, - :google_transaction_id => credit_card_or_vault_id.transaction_id, - :source_card_type => credit_card_or_vault_id.brand, - :source_card_last_four => credit_card_or_vault_id.last_digits, - :eci_indicator => credit_card_or_vault_id.eci - } + 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 + elsif options[:billing_address].blank? + 'billing_address is required parameter to store and verify Bank accounts.' + elsif options[:ach_mandate].blank? + 'ach_mandate is a required parameter to process bank acccount transactions see (https://developer.paypal.com/braintree/docs/guides/ach/client-side#show-required-authorization-language)' + end + end + + def add_bank_account_to_customer(payment_method, options) + bank_account_nonce, error_message = TokenNonce.new(@braintree_gateway, options).create_token_nonce_for_payment_method payment_method + return Response.new(false, error_message) unless bank_account_nonce.present? + + result = @braintree_gateway.payment_method.create( + customer_id: options[:customer], + payment_method_nonce: bank_account_nonce, + options: { + us_bank_account_verification_method: 'network_check' + } + ) + + verified = result.success? && result.payment_method&.verified + message = message_from_result(result) + message = not_verified_reason(result.payment_method) unless verified + + Response.new( + verified, + message, + { + customer_vault_id: options[:customer], + bank_account_token: result.payment_method&.token, + verified: verified + }, + authorization: result.payment_method&.token + ) + end + + def not_verified_reason(bank_account) + return unless bank_account.verifications.present? + + verification = bank_account.verifications.first + "verification_status: [#{verification.status}], processor_response: [#{verification.processor_response_code}-#{verification.processor_response_text}]" + end + + def store_bank_account(payment_method, options, multi_response) + multi_response.process { create_customer_from_bank_account payment_method, options } unless multi_response.params['exists'] + multi_response.process { add_bank_account_to_customer payment_method, options } + end + + def store_credit_card(payment_method, options, multi_response) + process_by = multi_response.params['exists'] ? :add_credit_card_to_customer : :add_customer_with_credit_card + multi_response.process { send process_by, payment_method, options } + end + + def create_customer_from_bank_account(payment_method, options) + parameters = { + id: options[:customer], + first_name: payment_method.first_name, + last_name: payment_method.last_name, + email: scrub_email(options[:email]), + phone: phone_from(options), + device_data: options[:device_data] + }.compact + + result = @braintree_gateway.customer.create(parameters) + customer_id = result.customer.id if result.success? + options[:customer] = customer_id + + Response.new( + result.success?, + message_from_result(result), + { customer_vault_id: customer_id, 'exists': true } + ) + end end end end diff --git a/lib/active_merchant/billing/gateways/bridge_pay.rb b/lib/active_merchant/billing/gateways/bridge_pay.rb index d8cf6218265..7c306842099 100644 --- a/lib/active_merchant/billing/gateways/bridge_pay.rb +++ b/lib/active_merchant/billing/gateways/bridge_pay.rb @@ -9,16 +9,16 @@ class BridgePayGateway < Gateway self.test_url = 'https://gatewaystage.itstgate.com/SmartPayments/transact3.asmx' self.live_url = 'https://gateway.itstgate.com/SmartPayments/transact3.asmx' - self.supported_countries = ['CA', 'US'] + self.supported_countries = %w[CA US] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] - def initialize(options={}) + def initialize(options = {}) requires!(options, :user_name, :password) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = initialize_required_fields('Sale') # Allow the same amount in multiple transactions. @@ -30,7 +30,7 @@ def purchase(amount, payment_method, options={}) commit(post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = initialize_required_fields('Auth') add_invoice(post, amount, options) @@ -40,7 +40,7 @@ def authorize(amount, payment_method, options={}) commit(post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = initialize_required_fields('Force') add_invoice(post, amount, options) @@ -50,7 +50,7 @@ def capture(amount, authorization, options={}) commit(post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = initialize_required_fields('Return') add_invoice(post, amount, options) @@ -59,7 +59,7 @@ def refund(amount, authorization, options={}) commit(post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = initialize_required_fields('Void') add_reference(post, authorization) @@ -74,10 +74,10 @@ def verify(creditcard, options = {}) end end - def store(creditcard, options={}) + def store(creditcard, options = {}) post = initialize_required_fields('') post[:transaction] = 'Create' - post[:CardNumber] = creditcard.number + post[:CardNumber] = creditcard.number post[:CustomerPaymentInfoKey] = '' post[:token] = '' add_payment_method(post, creditcard) @@ -147,7 +147,7 @@ def initialize_required_fields(transaction_type) end def add_customer_data(post, options) - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:Street] = billing_address[:address1] post[:Zip] = billing_address[:zip] end @@ -235,8 +235,8 @@ def add_reference(post, authorization) def post_data(post) { - :UserName => @options[:user_name], - :Password => @options[:password] + UserName: @options[:user_name], + Password: @options[:password] }.merge(post).collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end end diff --git a/lib/active_merchant/billing/gateways/cams.rb b/lib/active_merchant/billing/gateways/cams.rb index 4fd30dd0489..75eb07cde8d 100644 --- a/lib/active_merchant/billing/gateways/cams.rb +++ b/lib/active_merchant/billing/gateways/cams.rb @@ -5,7 +5,7 @@ class CamsGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.centralams.com/' self.display_name = 'CAMS: Central Account Management System' @@ -67,12 +67,12 @@ class CamsGateway < Gateway '894' => STANDARD_ERROR_CODE[:processing_error] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) @@ -86,7 +86,7 @@ def purchase(money, payment, options={}) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -95,7 +95,7 @@ def authorize(money, payment, options={}) commit('auth', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, money, options) @@ -103,20 +103,20 @@ def capture(money, authorization, options={}) commit('capture', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, money, options) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) post = {} add_invoice(post, 0, options) add_payment(post, credit_card) @@ -138,7 +138,7 @@ def scrub(transcript) private - def add_address(post, creditcard, options={}) + def add_address(post, creditcard, options = {}) post[:firstname] = creditcard.first_name post[:lastname] = creditcard.last_name @@ -167,7 +167,7 @@ def add_invoice(post, money, options) def add_payment(post, payment) post[:ccnumber] = payment.number - post[:ccexp] = "#{payment.month.to_s.rjust(2, "0")}#{payment.year.to_s[-2..-1]}" + post[:ccexp] = "#{payment.month.to_s.rjust(2, '0')}#{payment.year.to_s[-2..-1]}" post[:cvv] = payment.verification_value end diff --git a/lib/active_merchant/billing/gateways/card_connect.rb b/lib/active_merchant/billing/gateways/card_connect.rb index 62903dec0ff..6a803aeb322 100644 --- a/lib/active_merchant/billing/gateways/card_connect.rb +++ b/lib/active_merchant/billing/gateways/card_connect.rb @@ -1,12 +1,12 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class CardConnectGateway < Gateway - self.test_url = 'https://fts.cardconnect.com:6443/cardconnect/rest/' - self.live_url = 'https://fts.cardconnect.com:8443/cardconnect/rest/' + self.test_url = 'https://fts-uat.cardconnect.com/cardconnect/rest/' + self.live_url = 'https://fts.cardconnect.com/cardconnect/rest/' self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://cardconnect.com/' self.display_name = 'Card Connect' @@ -61,6 +61,8 @@ class CardConnectGateway < Gateway '60' => STANDARD_ERROR_CODE[:pickup_card] } + SCHEDULED_PAYMENT_TYPES = %w(recurring installment) + def initialize(options = {}) requires!(options, :merchant_id, :username, :password) require_valid_domain!(options, :domain) @@ -68,8 +70,8 @@ def initialize(options = {}) end def require_valid_domain!(options, param) - if options.key?(param) - raise ArgumentError.new('not a valid cardconnect domain') unless /\Dcardconnect.com:\d{1,}\D/ =~ options[param] + if options[param] + raise ArgumentError.new('not a valid cardconnect domain') unless /https:\/\/\D*cardconnect.com/ =~ options[param] end end @@ -87,7 +89,9 @@ def purchase(money, payment, options = {}) add_currency(post, money, options) add_address(post, options) add_customer_data(post, options) - add_3DS(post, options) + add_three_ds_mpi_data(post, options) + add_additional_data(post, options) + add_stored_credential(post, options) post[:capture] = 'Y' commit('auth', post) end @@ -101,7 +105,9 @@ def authorize(money, payment, options = {}) add_payment(post, payment) add_address(post, options) add_customer_data(post, options) - add_3DS(post, options) + add_three_ds_mpi_data(post, options) + add_additional_data(post, options) + add_stored_credential(post, options) commit('auth', post) end @@ -140,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? @@ -167,7 +176,7 @@ def add_customer_data(post, options) def add_address(post, options) if address = options[:billing_address] || options[:address] post[:address] = address[:address1] if address[:address1] - post[:address].concat(" #{address[:address2]}") if address[:address2] + post[:address2] = address[:address2] if address[:address2] post[:city] = address[:city] if address[:city] post[:region] = address[:state] if address[:state] post[:country] = address[:country] if address[:country] @@ -186,7 +195,11 @@ def add_currency(post, money, options) def add_invoice(post, options) post[:orderid] = options[:order_id] - post[:ecomind] = (options[:recurring] ? 'R' : 'E') + post[:ecomind] = if options[:ecomind] + options[:ecomind].capitalize + else + (options[:recurring] ? 'R' : 'E') + end end def add_payment(post, payment) @@ -231,16 +244,28 @@ def add_additional_data(post, options) post[:items] = options[:items].map do |item| updated = {} item.each_pair do |k, v| - updated.merge!(k.to_s.gsub(/_/, '') => v) + updated.merge!(k.to_s.delete('_') => v) end + updated end end + post[:userfields] = options[:user_fields] if options[:user_fields] + end + + def add_three_ds_mpi_data(post, options) + return unless three_d_secure = options[:three_d_secure] + + post[:secureflag] = three_d_secure[:eci] + post[:securevalue] = three_d_secure[:cavv] + post[:securedstid] = three_d_secure[:ds_transaction_id] end - def add_3DS(post, options) - post[:secureflag] = options[:secure_flag] if options[:secure_flag] - post[:securevalue] = options[:secure_value] if options[:secure_value] - post[:securexid] = options[:secure_xid] if options[:secure_xid] + def add_stored_credential(post, options) + return unless stored_credential = options[:stored_credential] + + post[:cof] = stored_credential[:initiator] == 'merchant' ? 'M' : 'C' + post[:cofscheduled] = SCHEDULED_PAYMENT_TYPES.include?(stored_credential[:reason_type]) ? 'Y' : 'N' + post[:cofpermission] = stored_credential[:initial_transaction] ? 'Y' : 'N' end def headers @@ -267,6 +292,7 @@ def url(action, path) end def commit(action, parameters, verb: :put, path: '') + parameters[:frontendid] = application_id parameters[:merchid] = @options[:merchant_id] url = url(action, path) response = parse(ssl_request(verb, url, post_data(parameters), headers)) @@ -282,7 +308,8 @@ def commit(action, parameters, verb: :put, path: '') error_code: error_code_from(response) ) rescue ResponseError => e - return Response.new(false, 'Unable to authenticate. Please check your credentials.', {}, :test => test?) if e.response.code == '401' + return Response.new(false, 'Unable to authenticate. Please check your credentials.', {}, test: test?) if e.response.code == '401' + raise end diff --git a/lib/active_merchant/billing/gateways/card_save.rb b/lib/active_merchant/billing/gateways/card_save.rb index 7d5920be05b..096cd4fcb1c 100644 --- a/lib/active_merchant/billing/gateways/card_save.rb +++ b/lib/active_merchant/billing/gateways/card_save.rb @@ -6,17 +6,16 @@ class CardSaveGateway < IridiumGateway self.money_format = :cents self.default_currency = 'GBP' - self.supported_cardtypes = [ :visa, :maestro, :master, :american_express, :jcb ] - self.supported_countries = [ 'GB' ] + self.supported_cardtypes = %i[visa maestro master american_express jcb] + self.supported_countries = ['GB'] self.homepage_url = 'http://www.cardsave.net/' self.display_name = 'CardSave' - def initialize(options={}) + def initialize(options = {}) super @test_url = 'https://gw1.cardsaveonlinepayments.com:4430/' @live_url = 'https://gw1.cardsaveonlinepayments.com:4430/' end - end end end diff --git a/lib/active_merchant/billing/gateways/card_stream.rb b/lib/active_merchant/billing/gateways/card_stream.rb index f5ac54fdb83..76c8f318519 100644 --- a/lib/active_merchant/billing/gateways/card_stream.rb +++ b/lib/active_merchant/billing/gateways/card_stream.rb @@ -1,14 +1,14 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class CardStreamGateway < Gateway - THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE = 'Specifying the :threeDSRequired initialization option is deprecated. Please use the `:threeds_required => true` *transaction* option instead.' self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/' self.money_format = :cents self.default_currency = 'GBP' - self.supported_countries = ['GB', 'US', 'CH', 'SE', 'SG', 'NO', 'JP', 'IS', 'HK', 'NL', 'CZ', 'CA', 'AU'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] + self.currencies_without_fractions = %w(CVE ISK JPY UGX) + self.supported_countries = %w[GB US CH SE SG NO JP IS HK NL CZ CA AU] + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb maestro] self.homepage_url = 'http://www.cardstream.com/' self.display_name = 'CardStream' @@ -150,30 +150,20 @@ def initialize(options = {}) def authorize(money, credit_card_or_reference, options = {}) post = {} - add_pair(post, :captureDelay, -1) - add_amount(post, money, options) - add_invoice(post, credit_card_or_reference, money, options) - add_credit_card_or_reference(post, credit_card_or_reference) - add_customer_data(post, options) - add_remote_address(post, options) + add_auth_purchase(post, -1, money, credit_card_or_reference, options) commit('SALE', post) end def purchase(money, credit_card_or_reference, options = {}) post = {} - add_pair(post, :captureDelay, 0) - add_amount(post, money, options) - add_invoice(post, credit_card_or_reference, money, options) - add_credit_card_or_reference(post, credit_card_or_reference) - add_customer_data(post, options) - add_remote_address(post, options) + add_auth_purchase(post, 0, money, credit_card_or_reference, options) commit('SALE', post) end def capture(money, authorization, options = {}) post = {} add_pair(post, :xref, authorization) - add_pair(post, :amount, amount(money), :required => true) + add_pair(post, :amount, localized_amount(money, options[:currency] || currency(money)), required: true) add_remote_address(post, options) commit('CAPTURE', post) @@ -184,6 +174,7 @@ def refund(money, authorization, options = {}) add_pair(post, :xref, authorization) add_amount(post, money, options) add_remote_address(post, options) + add_country_code(post, options) response = commit('REFUND_SALE', post) return response if response.success? @@ -203,7 +194,7 @@ def void(authorization, options = {}) commit('CANCEL', post) end - def verify(creditcard, options={}) + def verify(creditcard, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, creditcard, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -223,9 +214,21 @@ def scrub(transcript) private + def add_auth_purchase(post, pair_value, money, credit_card_or_reference, options) + add_pair(post, :captureDelay, pair_value) + add_amount(post, money, options) + add_invoice(post, credit_card_or_reference, money, options) + add_credit_card_or_reference(post, credit_card_or_reference) + add_customer_data(post, options) + add_remote_address(post, options) + add_country_code(post, options) + add_threeds_fields(post, options) + end + def add_amount(post, money, options) - add_pair(post, :amount, amount(money), :required => true) - add_pair(post, :currencyCode, currency_code(options[:currency] || currency(money))) + currency = options[:currency] || currency(money) + add_pair(post, :amount, localized_amount(money, currency), required: true) + add_pair(post, :currencyCode, currency_code(currency)) end def add_customer_data(post, options) @@ -241,15 +244,15 @@ def add_customer_data(post, options) end def add_invoice(post, credit_card_or_reference, money, options) - add_pair(post, :transactionUnique, options[:order_id], :required => true) - add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true) + add_pair(post, :transactionUnique, options[:order_id], required: true) + add_pair(post, :orderRef, options[:description] || options[:order_id], required: true) add_pair(post, :statementNarrative1, options[:merchant_name]) if options[:merchant_name] add_pair(post, :statementNarrative2, options[:dynamic_descriptor]) if options[:dynamic_descriptor] if credit_card_or_reference.respond_to?(:number) - if ['american_express', 'diners_club'].include?(card_brand(credit_card_or_reference).to_s) + 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, amount(money)) + add_pair(post, :item1GrossValue, localized_amount(money, options[:currency] || currency(money))) end end @@ -266,14 +269,14 @@ def add_credit_card_or_reference(post, credit_card_or_reference) end def add_reference(post, reference) - add_pair(post, :xref, reference, :required => true) + add_pair(post, :xref, reference, required: true) end def add_credit_card(post, credit_card) - add_pair(post, :customerName, credit_card.name, :required => true) - add_pair(post, :cardNumber, credit_card.number, :required => true) - add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true) - add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true) + add_pair(post, :customerName, credit_card.name, required: true) + add_pair(post, :cardNumber, credit_card.number, required: true) + add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), required: true) + add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), required: true) add_pair(post, :cardCVV, credit_card.verification_value) end @@ -281,10 +284,28 @@ def add_threeds_required(post, options) add_pair(post, :threeDSRequired, options[:threeds_required] || @threeds_required ? 'Y' : 'N') end - def add_remote_address(post, options={}) + def add_threeds_fields(post, options) + return unless three_d_secure = options[:three_d_secure] + + add_pair(post, :threeDSEnrolled, formatted_enrollment(three_d_secure[:enrolled])) + if three_d_secure[:enrolled] == 'true' + add_pair(post, :threeDSAuthenticated, three_d_secure[:authentication_response_status]) + if three_d_secure[:authentication_response_status] == 'Y' + post[:threeDSECI] = three_d_secure[:eci] + post[:threeDSCAVV] = three_d_secure[:cavv] + post[:threeDSXID] = three_d_secure[:xid] || three_d_secure[:ds_transaction_id] + end + end + end + + def add_remote_address(post, options = {}) add_pair(post, :remoteAddress, options[:ip] || '1.1.1.1') end + def add_country_code(post, options) + post[:countryCode] = options[:country_code] || self.supported_countries[0] + end + def normalize_line_endings(str) str.gsub(/%0D%0A|%0A%0D|%0D/, '%0A') end @@ -308,10 +329,9 @@ def parse(body) end def commit(action, parameters) - parameters.update(:countryCode => self.supported_countries[0]) unless ['CAPTURE', 'CANCEL'].include?(action) parameters.update( - :merchantID => @options[:login], - :action => action + merchantID: @options[:login], + action: action ) # adds a signature to the post hash/array add_hmac(parameters) @@ -322,10 +342,10 @@ def commit(action, parameters) response[:responseCode] == '0', response[:responseCode] == '0' ? 'APPROVED' : response[:responseMessage], response, - :test => test?, - :authorization => response[:xref], - :cvv_result => CVV_CODE[response[:avscv2ResponseCode].to_s[0, 1]], - :avs_result => avs_from(response) + test: test?, + authorization: response[:xref], + cvv_result: CVV_CODE[response[:avscv2ResponseCode].to_s[0, 1]], + avs_result: avs_from(response) ) end @@ -341,12 +361,12 @@ def avs_from(response) 'A' else 'I' - end + end AVSResult.new({ - :code => code, - :postal_match => postal_match, - :street_match => street_match + code: code, + postal_match: postal_match, + street_match: street_match }) end @@ -362,6 +382,13 @@ def add_pair(post, key, value, options = {}) post[key] = value if !value.blank? || options[:required] 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 end end end diff --git a/lib/active_merchant/billing/gateways/cardknox.rb b/lib/active_merchant/billing/gateways/cardknox.rb index 938e6ade478..8acabd4d6cd 100644 --- a/lib/active_merchant/billing/gateways/cardknox.rb +++ b/lib/active_merchant/billing/gateways/cardknox.rb @@ -3,9 +3,9 @@ module Billing #:nodoc: class CardknoxGateway < Gateway self.live_url = 'https://x1.cardknox.com/gateway' - self.supported_countries = ['US', 'CA', 'GB'] + self.supported_countries = %w[US CA GB] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'https://www.cardknox.com/' self.display_name = 'Cardknox' @@ -27,7 +27,7 @@ class CardknoxGateway < Gateway } } - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key) super end @@ -37,7 +37,7 @@ def initialize(options={}) # - check # - cardknox token, which is returned in the the authorization string "ref_num;token;command" - def purchase(amount, source, options={}) + def purchase(amount, source, options = {}) post = {} add_amount(post, amount, options) add_invoice(post, options) @@ -48,7 +48,7 @@ def purchase(amount, source, options={}) commit(:purchase, source_type(source), post) end - def authorize(amount, source, options={}) + def authorize(amount, source, options = {}) post = {} add_amount(post, amount) add_invoice(post, options) @@ -66,7 +66,7 @@ def capture(amount, authorization, options = {}) commit(:capture, source_type(authorization), post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_reference(post, authorization) add_amount(post, amount) @@ -79,7 +79,7 @@ def void(authorization, options = {}) commit(:void, source_type(authorization), post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -116,7 +116,7 @@ def split_authorization(authorization) end def add_reference(post, reference) - reference, _, _ = split_authorization(reference) + reference, = split_authorization(reference) post[:Refnum] = reference end @@ -186,7 +186,7 @@ def add_address_for_type(type, post, source, address) post[address_key(prefix, 'FirstName')] = address[:first_name] post[address_key(prefix, 'LastName')] = address[:last_name] end - post[address_key(prefix, 'MiddleName')] = address[:middle_name] + post[address_key(prefix, 'MiddleName')] = address[:middle_name] post[address_key(prefix, 'Company')] = address[:company] post[address_key(prefix, 'Street')] = address[:address1] @@ -247,7 +247,7 @@ def add_check(post, check) end def add_cardknox_token(post, authorization) - _, token, _ = split_authorization(authorization) + _, token, = split_authorization(authorization) post[:Token] = token end @@ -276,7 +276,7 @@ def parse(body) amount: fields['xAuthAmount'], masked_card_num: fields['xMaskedCardNumber'], masked_account_number: fields['MaskedAccountNumber'] - }.delete_if { |k, v| v.nil? } + }.delete_if { |_k, v| v.nil? } end def commit(action, source_type, parameters) @@ -312,7 +312,7 @@ def post_data(command, parameters = {}) Version: '4.5.4', SoftwareName: 'Active Merchant', SoftwareVersion: ActiveMerchant::VERSION.to_s, - Command: command, + Command: command } seed = SecureRandom.hex(32).upcase @@ -320,7 +320,7 @@ def post_data(command, parameters = {}) initial_parameters[:Hash] = "s/#{seed}/#{hash}/n" unless @options[:pin].blank? parameters = initial_parameters.merge(parameters) - parameters.reject { |k, v| v.blank? }.collect { |key, value| "x#{key}=#{CGI.escape(value.to_s)}" }.join('&') + parameters.reject { |_k, v| v.blank? }.collect { |key, value| "x#{key}=#{CGI.escape(value.to_s)}" }.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/cardprocess.rb b/lib/active_merchant/billing/gateways/cardprocess.rb index c91121c0641..78a18105277 100644 --- a/lib/active_merchant/billing/gateways/cardprocess.rb +++ b/lib/active_merchant/billing/gateways/cardprocess.rb @@ -8,7 +8,7 @@ class CardprocessGateway < Gateway MT HU NL AT PL PT RO SI SK FI SE GB IS LI NO CH ME MK AL RS TR BA ] self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.homepage_url = 'https://vr-pay-ecommerce.docs.oppwa.com/' self.display_name = 'CardProcess VR-Pay' @@ -26,7 +26,7 @@ class CardprocessGateway < Gateway # * :user_id -- The CardProcess user ID # * :password -- The CardProcess password # * :entity_id -- The CardProcess channel or entity ID for any transactions - def initialize(options={}) + def initialize(options = {}) requires!(options, :user_id, :password, :entity_id) super # This variable exists purely to allow remote tests to force error codes; @@ -123,6 +123,7 @@ def add_address(post, _card, options) def add_invoice(post, money, options) return if money.nil? + post[:amount] = amount(money) post[:currency] = (options[:currency] || currency(money)) post[:merchantInvoiceId] = options[:merchant_invoice_id] if options[:merchant_invoice_id] @@ -132,6 +133,7 @@ def add_invoice(post, money, options) def add_payment(post, payment) return if payment.is_a?(String) + post[:paymentBrand] = payment.brand.upcase if payment.brand post[:card] ||= {} post[:card][:number] = payment.number diff --git a/lib/active_merchant/billing/gateways/cashnet.rb b/lib/active_merchant/billing/gateways/cashnet.rb index 48a7d1308c5..340210415c3 100644 --- a/lib/active_merchant/billing/gateways/cashnet.rb +++ b/lib/active_merchant/billing/gateways/cashnet.rb @@ -7,8 +7,8 @@ class CashnetGateway < Gateway self.test_url = 'https://train.cashnet.com/' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] - self.homepage_url = 'http://www.higherone.com/' + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] + self.homepage_url = 'https://transactcampus.com' self.display_name = 'Cashnet' self.money_format = :dollars self.max_retries = 0 @@ -41,7 +41,7 @@ def initialize(options = {}) def purchase(money, payment_object, options = {}) post = {} add_creditcard(post, payment_object) - add_invoice(post, options) + add_invoice(post, money, options) add_address(post, options) add_customer_data(post, options) commit('SALE', money, post) @@ -49,8 +49,8 @@ def purchase(money, payment_object, options = {}) def refund(money, identification, options = {}) post = {} - post[:origtx] = identification - add_invoice(post, options) + post[:origtx] = identification + add_invoice(post, money, options) add_customer_data(post, options) commit('REFUND', money, post) end @@ -69,14 +69,13 @@ def scrub(transcript) private def commit(action, money, fields) - fields[:amount] = amount(money) url = (test? ? test_url : live_url) + CGI.escape(@options[:merchant_gateway_name]) raw_response = ssl_post(url, post_data(action, fields)) parsed_response = parse(raw_response) return unparsable_response(raw_response) unless parsed_response - success = (parsed_response[:result] == '0') + success = success?(parsed_response) Response.new( success, CASHNET_CODES[parsed_response[:result]], @@ -86,8 +85,13 @@ def commit(action, money, fields) ) end + def success?(response) + response[:result] == '0' + end + def post_data(action, parameters = {}) post = {} + post[:command] = action post[:merchant] = @options[:merchant] post[:operator] = @options[:operator] @@ -106,9 +110,19 @@ def add_creditcard(post, creditcard) post[:lname] = creditcard.last_name end - def add_invoice(post, options) - post[:order_number] = options[:order_id] if options[:order_id].present? - post[:itemcode] = (options[:item_code] || @options[:default_item_code]) + def add_invoice(post, money, options) + post[:order_number] = options[:order_id] if options[:order_id].present? + + if options[:item_codes].present? + codes_and_amounts = options[:item_codes].transform_keys { |key| key.to_s.delete('_') } + codes_and_amounts.each do |key, value| + post[key] = value if key.start_with?('itemcode') + post[key] = amount(value.to_i) if key.start_with?('amount') + end + else + post[:itemcode] = (options[:item_code] || @options[:default_item_code]) + post[:amount] = amount(money.to_i) + end end def add_address(post, options) @@ -121,8 +135,8 @@ def add_address(post, options) end def add_customer_data(post, options) - post[:email_g] = options[:email] - post[:custcode] = options[:custcode] unless empty?(options[:custcode]) + post[:email_g] = options[:email] + post[:custcode] = options[:custcode] unless empty?(options[:custcode]) end def expdate(creditcard) @@ -145,6 +159,7 @@ def handle_response(response) elsif response.code.to_i == 302 return ssl_get(URI.parse(response['location'])) end + raise ResponseError.new(response) end @@ -190,6 +205,7 @@ def unparsable_response(raw_response) '215' => 'Old PIN does not validate ', '221' => 'Invalid credit card processor type specified in location or payment code', '222' => 'Credit card processor error', + '230' => 'Host Error (USE VOID OR REVERSAL TO REFUND UNSETTLED TRANSACTIONS)', '280' => 'SmartPay transaction not posted', '301' => 'Original transaction not found for this customer', '302' => 'Amount to refund exceeds original payment amount or is missing', diff --git a/lib/active_merchant/billing/gateways/cc5.rb b/lib/active_merchant/billing/gateways/cc5.rb index 3d25ec7d3f1..5d248ca7914 100644 --- a/lib/active_merchant/billing/gateways/cc5.rb +++ b/lib/active_merchant/billing/gateways/cc5.rb @@ -48,9 +48,9 @@ def credit(money, creditcard, options = {}) protected def build_sale_request(type, money, creditcard, options = {}) - requires!(options, :order_id) + requires!(options, :order_id) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'CC5Request' do add_login_tags(xml) @@ -62,7 +62,7 @@ def build_sale_request(type, money, creditcard, options = {}) add_amount_tags(money, options, xml) xml.tag! 'Email', options[:email] if options[:email] - if(address = (options[:billing_address] || options[:address])) + if (address = (options[:billing_address] || options[:address])) xml.tag! 'BillTo' do add_address(xml, address) end @@ -76,7 +76,7 @@ def build_sale_request(type, money, creditcard, options = {}) end def build_capture_request(money, authorization, options = {}) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'CC5Request' do add_login_tags(xml) @@ -87,7 +87,7 @@ def build_capture_request(money, authorization, options = {}) end def build_void_request(authorization, options = {}) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'CC5Request' do add_login_tags(xml) @@ -97,7 +97,7 @@ def build_void_request(authorization, options = {}) end def build_authorization_credit_request(money, authorization, options = {}) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'CC5Request' do add_login_tags(xml) @@ -108,7 +108,7 @@ def build_authorization_credit_request(money, authorization, options = {}) end def build_creditcard_credit_request(money, creditcard, options = {}) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'CC5Request' do add_login_tags(xml) @@ -157,8 +157,8 @@ def commit(request) success, (success ? 'Approved' : "Declined (Reason: #{response[:proc_return_code]} - #{response[:err_msg]})"), response, - :test => test?, - :authorization => response[:order_id] + test: test?, + authorization: response[:order_id] ) end diff --git a/lib/active_merchant/billing/gateways/cecabank.rb b/lib/active_merchant/billing/gateways/cecabank.rb index ba73965d48a..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 = 'http://tpv.ceca.es:8000' - self.live_url = 'https://pgw.ceca.es' - - self.supported_countries = ['ES'] - self.supported_cardtypes = [:visa, :master, :american_express] - self.homepage_url = 'http://www.ceca.es/es/' - self.display_name = 'Cecabank' - self.default_currency = 'EUR' - self.money_format = :cents - - #### CECA's MAGIC NUMBERS - CECA_NOTIFICATIONS_URL = 'NONE' - CECA_ENCRIPTION = 'SHA1' - 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 = 'tpvanularparcialmente' # use partial refund's URL to avoid time frame limitations and decision logic on client side - CECA_ACTION_PURCHASE = 'tpv' - 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' - if(root.elements['OPERACION/numeroAutorizacion']) - response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text - end - else - response[:authorization] = root.attributes['numeroOperacion'] - end - end - - return response - rescue REXML::ParseException => e - response[:success] = false - response[:message] = 'Unable to parse the response.' - response[:error_message] = e.message - response - end - - def commit(action, parameters) - parameters.merge!( - 'Cifrado' => CECA_ENCRIPTION, - 'Firma' => generate_signature(action, parameters), - 'Exponente' => CECA_DECIMALS, - 'MerchantID' => options[:merchant_id], - 'AcquirerBIN' => options[:acquirer_bin], - 'TerminalID' => options[:terminal_id] - ) - url = (test? ? self.test_url : self.live_url) + "/cgi-bin/#{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::SHA1.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..86c27e3db58 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb @@ -0,0 +1,273 @@ +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, + nil: :NONE + }.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, _network_transaction_id = 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, _network_transaction_id = 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, _network_transaction_id = identification.split('#') + options[:operation_number] = operation_number + handle_cancellation(:refund, money, authorization, options) + end + + def scrub(transcript) + before_message = transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"') + request_data = JSON.parse(before_message) + params = decode_params(request_data['parametros']). + 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]') + 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 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 + 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] ||= {} + + params[:pan] = creditcard.number + params[:caducidad] = strftime_yyyymm(creditcard) + params[:cvv2] = creditcard.verification_value + params[:csc] = creditcard.verification_value if CreditCard.brand?(creditcard.number) == 'american_express' + 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 + + params[:mmppTxId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id] + 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[options[:exemption_type]&.to_sym] + three_d_response = { + exemption_type: options[:exemption_type], + three_ds_version: three_d_secure[:version], + authentication_value: three_d_secure[:cavv], + 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] + } + + three_d_response.merge!({ amount: post[:parametros][:importe] }) + + params[:ThreeDsResponse] = three_d_response.to_json + end + + def commit(action, post, method = :post) + auth_options = { + operation_number: post[:parametros][:numOperacion], + amount: post[:parametros][:importe] + } + + add_encryption(post) + add_merchant_data(post) + + params_encoded = encode_post_parameters(post) + add_signature(post, params_encoded, options) + + response = parse(ssl_request(method, 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].to_json) + end + + def encode_params(params) + Base64.strict_encode64(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/cenpos.rb b/lib/active_merchant/billing/gateways/cenpos.rb index 9ba37a7c00b..00ae73fafb4 100644 --- a/lib/active_merchant/billing/gateways/cenpos.rb +++ b/lib/active_merchant/billing/gateways/cenpos.rb @@ -11,14 +11,14 @@ class CenposGateway < Gateway self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY) self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :password, :user_id) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -27,7 +27,7 @@ def purchase(amount, payment_method, options={}) commit('Sale', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -36,7 +36,7 @@ def authorize(amount, payment_method, options={}) commit('Auth', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -45,7 +45,7 @@ def capture(amount, authorization, options={}) commit('SpecialForce', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_void_required_elements(post) add_reference(post, authorization) @@ -56,7 +56,7 @@ def void(authorization, options={}) commit('Void', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -65,7 +65,7 @@ def refund(amount, authorization, options={}) commit('SpecialReturn', post) end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -73,7 +73,7 @@ def credit(amount, payment_method, options={}) commit('Credit', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -112,7 +112,7 @@ def add_payment_method(post, payment_method) end def add_customer_data(post, options) - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:CustomerEmailAddress] = billing_address[:email] post[:CustomerPhone] = billing_address[:phone] post[:CustomerBillingAddress] = billing_address[:address1] @@ -156,7 +156,7 @@ def commit(action, post) xml = ssl_post(self.live_url, data, headers) raw = parse(xml) rescue ActiveMerchant::ResponseError => e - if(e.response.code == '500' && e.response.body.start_with?(' 'identity', - 'Content-Type' => 'text/xml;charset=UTF-8', - 'SOAPAction' => 'http://tempuri.org/Transactional/ProcessCreditCard' + 'Content-Type' => 'text/xml;charset=UTF-8', + 'SOAPAction' => 'http://tempuri.org/Transactional/ProcessCreditCard' } end def build_request(post) - xml = Builder::XmlMarkup.new :indent => 8 + xml = Builder::XmlMarkup.new indent: 8 xml.tag!('acr:MerchantId', post.delete(:MerchantId)) xml.tag!('acr:Password', post.delete(:Password)) xml.tag!('acr:UserId', post.delete(:UserId)) @@ -198,18 +198,18 @@ def build_request(post) end def envelope(body) - <<-EOS - - - - - - #{body} - - - - - EOS + <<~XML + + + + + + #{body} + + + + + XML end def parse(xml) @@ -250,11 +250,11 @@ def message_from(succeeded, response) '257' => STANDARD_ERROR_CODE[:invalid_cvc], '333' => STANDARD_ERROR_CODE[:expired_card], '1' => STANDARD_ERROR_CODE[:card_declined], - '99' => STANDARD_ERROR_CODE[:processing_error], + '99' => STANDARD_ERROR_CODE[:processing_error] } def authorization_from(request, response) - [ response[:reference_number], request[:CardLastFourDigits], request[:Amount] ].join('|') + [response[:reference_number], request[:CardLastFourDigits], request[:Amount]].join('|') end def split_authorization(authorization) @@ -277,6 +277,7 @@ def avs_result_from_xml(xml) def cvv_result_code(xml) cvv = validation_result_element(xml, 'CVV') return nil unless cvv + validation_result_matches?(*validation_result_element_text(cvv.parent)) ? 'M' : 'N' end diff --git a/lib/active_merchant/billing/gateways/checkout.rb b/lib/active_merchant/billing/gateways/checkout.rb index bd6149b246d..2773b1b0878 100644 --- a/lib/active_merchant/billing/gateways/checkout.rb +++ b/lib/active_merchant/billing/gateways/checkout.rb @@ -7,8 +7,8 @@ class CheckoutGateway < Gateway self.default_currency = 'USD' self.money_format = :cents - self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_countries = %w[AD AT BE BG CH CY CZ DE DK EE ES FO FI FR GB GI GL GR HR HU IE IS IL IT LI LT LU LV MC MT NL NO PL PT RO SE SI SM SK SJ TR VA] + self.supported_cardtypes = %i[visa master american_express diners_club] self.homepage_url = 'https://www.checkout.com/' self.display_name = 'Checkout.com' @@ -83,7 +83,7 @@ def refund(amount, authorization, options = {}) end end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -107,9 +107,7 @@ def add_payment_method(xml, payment_method) xml.bill_cc_ payment_method.number xml.bill_expmonth_ format(payment_method.month, :two_digits) xml.bill_expyear_ format(payment_method.year, :four_digits) - if payment_method.verification_value? - xml.bill_cvv2_ payment_method.verification_value - end + xml.bill_cvv2_ payment_method.verification_value if payment_method.verification_value? end def add_billing_info(xml, options) @@ -152,7 +150,7 @@ def add_other_fields(xml, options) end def add_reference(xml, authorization) - transid, trackid, _, _, _ = split_authorization(authorization) + transid, trackid, = split_authorization(authorization) xml.transid transid add_track_id(xml, trackid) end @@ -161,7 +159,7 @@ def add_track_id(xml, trackid) xml.trackid(trackid) if trackid end - def commit(action, amount=nil, options={}, &builder) + def commit(action, amount = nil, options = {}, &builder) response = parse_xml(ssl_post(live_url, build_xml(action, &builder))) Response.new( (response[:responsecode] == '0'), @@ -200,7 +198,7 @@ def parse_xml(xml) response end - def authorization_from(response, action, amount, options) + def authorization_from(response, action, amount, options) currency = options[:currency] || currency(amount) [response[:tranid], response[:trackid], action, amount, currency].join('|') end diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb index 6289c2a0811..bed352e9a3b 100644 --- a/lib/active_merchant/billing/gateways/checkout_v2.rb +++ b/lib/active_merchant/billing/gateways/checkout_v2.rb @@ -1,70 +1,95 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class CheckoutV2Gateway < Gateway - self.display_name = 'Checkout.com V2 Gateway' + self.display_name = 'Checkout.com Unified Payments' self.homepage_url = 'https://www.checkout.com/' - self.live_url = 'https://api2.checkout.com/v2' - self.test_url = 'https://sandbox.checkout.com/api2/v2' + self.live_url = 'https://api.checkout.com' + self.test_url = 'https://api.sandbox.checkout.com' - self.supported_countries = ['AD', 'AE', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'] + self.supported_countries = %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] self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club maestro discover jcb mada bp_plus] + self.currencies_without_fractions = %w(BIF DJF GNF ISK KMF XAF CLF XPF JPY PYG RWF KRW VUV VND XOF) + self.currencies_with_three_decimal_places = %w(BHD LYD JOD KWD OMR TND) + + LIVE_ACCESS_TOKEN_URL = 'https://access.checkout.com/connect/token' + TEST_ACCESS_TOKEN_URL = 'https://access.sandbox.checkout.com/connect/token' + + def initialize(options = {}) + @options = options + @access_token = options[:access_token] || nil + + if options.has_key?(:secret_key) + requires!(options, :secret_key) + else + requires!(options, :client_id, :client_secret) + @access_token ||= setup_access_token + end - def initialize(options={}) - requires!(options, :secret_key) super end - def purchase(amount, payment_method, options={}) - multi = MultiResponse.run do |r| - r.process { authorize(amount, payment_method, options) } - r.process { capture(amount, r.authorization, options) } - end + def purchase(amount, payment_method, options = {}) + post = {} + build_auth_or_purchase(post, amount, payment_method, options) - merged_params = multi.responses.map(&:params).reduce({}, :merge) - succeeded = success_from(merged_params) + commit(:purchase, post, options) + end - response(:purchase, succeeded, merged_params) + 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, options[:incremental_authorization]) : commit(:authorize, post, options) end - def authorize(amount, payment_method, options={}) + def capture(amount, authorization, options = {}) post = {} - post[:autoCapture] = 'n' + post[:capture_type] = options[:capture_type] || 'Final' add_invoice(post, amount, options) - add_payment_method(post, payment_method) add_customer_data(post, options) - add_transaction_data(post, options) + add_shipping_address(post, options) + add_metadata(post, options) - commit(:authorize, post) + commit(:capture, post, options, authorization) end - def capture(amount, authorization, options={}) + def credit(amount, payment, options = {}) post = {} + add_processing_channel(post, options) add_invoice(post, amount, options) - add_customer_data(post, 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(:capture, post, authorization) + commit(:credit, post, options) end - def void(authorization, options={}) + def void(authorization, _options = {}) post = {} - commit(:void, post, authorization) + add_metadata(post, options) + + commit(:void, post, options, authorization) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, 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={}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } - end + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + + def verify_payment(authorization, option = {}) + commit(:verify_payment, nil, options, authorization, :get) end def supports_scrubbing? @@ -73,96 +98,422 @@ def supports_scrubbing? def scrub(transcript) transcript. - gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]'). - gsub(%r(("number\\":\\")\d+), '\1[FILTERED]'). - gsub(%r(("cvv\\":\\")\d+), '\1[FILTERED]') + gsub(/(Authorization: )[^\\]*/i, '\1[FILTERED]'). + gsub(/("number\\":\\")\d+/, '\1[FILTERED]'). + gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]'). + gsub(/("cryptogram\\":\\")\w+/, '\1[FILTERED]'). + gsub(/(source\\":\{.*\\"token\\":\\")\d+/, '\1[FILTERED]'). + gsub(/("token\\":\\")\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 + def build_auth_or_purchase(post, amount, payment_method, options) + add_invoice(post, amount, 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) + end + def add_invoice(post, money, options) - post[:value] = localized_amount(money, options[:currency]) - post[:trackId] = options[:order_id] + post[:amount] = localized_amount(money, options[:currency]) + post[:reference] = options[:order_id] post[:currency] = options[:currency] || currency(money) - post[:descriptor] = {} - post[:descriptor][:name] = options[:descriptor_name] if options[:descriptor_name] - post[:descriptor][:city] = options[:descriptor_city] if options[:descriptor_city] + if options[:descriptor_name] || options[:descriptor_city] + post[:billing_descriptor] = {} + post[:billing_descriptor][:name] = options[:descriptor_name] if options[:descriptor_name] + post[:billing_descriptor][:city] = options[:descriptor_city] if options[:descriptor_city] + end + post[:metadata] = {} + post[:metadata][:udf5] = application_id || 'ActiveMerchant' + end + + def add_authorization_type(post, options) + post[:authorization_type] = options[:authorization_type] if options[:authorization_type] end - def add_payment_method(post, payment_method) - post[:card] = {} - post[:card][:name] = payment_method.name - post[:card][:number] = payment_method.number - post[:card][:cvv] = payment_method.verification_value - post[:card][:expiryYear] = format(payment_method.year, :four_digits) - post[:card][:expiryMonth] = format(payment_method.month, :two_digits) + def add_metadata(post, options, payment_method = nil) + post[:metadata] = {} unless post[:metadata] + post[:metadata].merge!(options[:metadata]) if options[:metadata] + post[:metadata][:udf1] = 'mada' if payment_method.try(:brand) == 'mada' + end + + def add_payment_method(post, payment_method, options, key = :source) + # the key = :destination when this method is called in def credit + post[key] = {} + case payment_method + when NetworkTokenizationCreditCard + token_type = token_type_from(payment_method) + cryptogram = payment_method.payment_cryptogram + eci = payment_method.eci || options[:eci] + eci ||= '05' if token_type == 'vts' + + post[key][:type] = 'network_token' + post[key][:token] = payment_method.number + post[key][:token_type] = token_type + post[key][:cryptogram] = cryptogram if cryptogram + post[key][:eci] = eci if eci + 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 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] + + 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 + 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 + end + + def add_source(post, options) + post[:source] = {} + post[:source][:type] = options[:source_type] if options[:source_type] + post[:source][:id] = options[:source_id] if options[:source_id] end def add_customer_data(post, options) - post[:email] = options[:email] || 'unspecified@example.com' - post[:customerIp] = options[:ip] if options[:ip] + post[:customer] = {} + post[:customer][:email] = options[:email] || nil + post[:payment_ip] = options[:ip] if options[:ip] address = options[:billing_address] - if(address && post[:card]) - post[:card][:billingDetails] = {} - post[:card][:billingDetails][:addressLine1] = address[:address1] - post[:card][:billingDetails][:addressLine2] = address[:address2] - post[:card][:billingDetails][:city] = address[:city] - post[:card][:billingDetails][:state] = address[:state] - post[:card][:billingDetails][:country] = address[:country] - post[:card][:billingDetails][:postcode] = address[:zip] - post[:card][:billingDetails][:phone] = { number: address[:phone] } unless address[:phone].blank? + if address && post[:source] + post[:source][:billing_address] = {} + post[:source][:billing_address][:address_line1] = address[:address1] unless address[:address1].blank? + post[:source][:billing_address][:address_line2] = address[:address2] unless address[:address2].blank? + post[:source][:billing_address][:city] = address[:city] unless address[:city].blank? + post[:source][:billing_address][:state] = address[:state] unless address[:state].blank? + post[:source][:billing_address][:country] = address[:country] unless address[:country].blank? + post[:source][:billing_address][:zip] = address[:zip] unless address[:zip].blank? + end + end + + # 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 + post[:payment_type] = 'MOTO' if options[:transaction_indicator] == 3 || options.dig(:metadata, :manual_entry) + post[:previous_payment_id] = options[:previous_charge_id] if options[:previous_charge_id] + end + + def merchant_initiated_override(post, options) + post[:merchant_initiated] = true + post[:source][:stored] = true + post[:previous_payment_id] = options[:merchant_initiated_transaction_id] + end + + def add_stored_credentials_using_normalized_fields(post, options) + if options[:stored_credential][:initiator] == 'cardholder' + post[:merchant_initiated] = false + else + post[:source][:stored] = true + post[:previous_payment_id] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + post[:merchant_initiated] = true + end + end + + def add_stored_credential_options(post, options = {}) + return unless options[:stored_credential] + + post[:payment_type] = 'Recurring' if %w(recurring installment).include? options[:stored_credential][:reason_type] + + if options[:merchant_initiated_transaction_id] + merchant_initiated_override(post, options) + else + add_stored_credentials_using_normalized_fields(post, options) + end + end + + def add_3ds(post, options) + if options[:three_d_secure] || options[:execute_threed] + post[:'3ds'] = {} + post[:'3ds'][:enabled] = true + post[:success_url] = options[:callback_url] if options[:callback_url] + post[:failure_url] = options[:callback_url] if options[:callback_url] + post[:'3ds'][:attempt_n3d] = options[:attempt_n3d] if options[:attempt_n3d] + post[:'3ds'][:challenge_indicator] = options[:challenge_indicator] if options[:challenge_indicator] + post[:'3ds'][:exemption] = options[:exemption] if options[:exemption] end + + if options[:three_d_secure] + post[:'3ds'][:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + post[:'3ds'][:cryptogram] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + post[:'3ds'][:version] = options[:three_d_secure][:version] if options[:three_d_secure][:version] + post[:'3ds'][:xid] = options[:three_d_secure][:ds_transaction_id] || options[:three_d_secure][:xid] + post[:'3ds'][:status] = options[:three_d_secure][:authentication_response_status] + end + end + + 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) + + 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 + + def add_marketplace_data(post, options) + if options[:marketplace] + post[:marketplace] = {} + post[:marketplace][:sub_entity_id] = options[:marketplace][:sub_entity_id] if options[:marketplace][:sub_entity_id] + end + end + + def access_token_header + { + 'Authorization' => "Basic #{Base64.encode64("#{@options[:client_id]}:#{@options[:client_secret]}").delete("\n")}", + 'Content-Type' => 'application/x-www-form-urlencoded' + } + end + + def access_token_url + test? ? TEST_ACCESS_TOKEN_URL : LIVE_ACCESS_TOKEN_URL end - def add_transaction_data(post, options={}) - post[:cardOnFile] = true if options[:card_on_file] == true - post[:transactionIndicator] = options[:transaction_indicator] || 1 - post[:previousChargeId] = options[:previous_charge_id] if options[:previous_charge_id] + def setup_access_token + request = 'grant_type=client_credentials' + begin + raw_response = ssl_post(access_token_url, request, access_token_header) + rescue ResponseError => e + raise OAuthResponseError.new(e) + else + response = parse(raw_response) + + if (access_token = response['access_token']) + access_token + else + raise OAuthResponseError.new(response) + end + end end - def commit(action, post, authorization = nil) + def commit(action, post, options, authorization = nil, method = :post) begin - raw_response = 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') + source_id = authorization if action == :unstore rescue ResponseError => e - raise unless(e.response.code.to_s =~ /4\d\d/) - response = parse(e.response.body) + raise unless e.response.code.to_s =~ /4\d\d/ + + response = parse(e.response.body, error: e.response) end - succeeded = success_from(response) + succeeded = success_from(action, response) - response(action, succeeded, response) + response(action, succeeded, response, source_id) 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 response(action, succeeded, response, 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), + body, + authorization: authorization, + error_code: error_code_from(succeeded, body), test: test?, - avs_result: avs_result, - cvv_result: cvv_result + avs_result: avs_result(response), + cvv_result: cvv_result(response) ) end - def headers - { - 'Authorization' => @options[:secret_key], - 'Content-Type' => 'application/json;charset=UTF-8' + def headers(action, options) + auth_token = @access_token ? "Bearer #{@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 action == :authorize - "#{base_url}/charges/card" + 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" + when :unstore, :store + "#{base_url}/instruments/#{authorization}" + when :capture + "#{base_url}/payments/#{authorization}/captures" + when :refund + "#{base_url}/payments/#{authorization}/refunds" + when :void + "#{base_url}/payments/#{authorization}/voids" + when :incremental_authorize + "#{base_url}/payments/#{authorization}/authorizations" + when :tokens + "#{base_url}/tokens" + when :verify_payment + "#{base_url}/payments/#{authorization}" else - "#{base_url}/charges/#{authorization}/#{action}" + "#{base_url}/payments/#{authorization}/#{action}" end end @@ -171,33 +522,43 @@ def base_url end def avs_result(response) - response['card'] && response['card']['avsCheck'] ? AVSResult.new(code: response['card']['avsCheck']) : nil + response.respond_to?(:dig) && response.dig('source', 'avs_check') ? AVSResult.new(code: response['source']['avs_check']) : nil end def cvv_result(response) - response['card'] && response['card']['cvvCheck'] ? CVVResult.new(response['card']['cvvCheck']) : nil + response.respond_to?(:dig) && response.dig('source', 'cvv_check') ? CVVResult.new(response['source']['cvv_check']) : nil end - def parse(body) + def parse(body, error: nil) JSON.parse(body) rescue JSON::ParserError - { - 'message' => 'Invalid JSON response received from CheckoutV2Gateway. Please contact CheckoutV2Gateway if you continue to receive this message.', + response = { + 'error_type' => error&.code, + 'message' => 'Invalid JSON response received from Checkout.com Unified Payments Gateway. Please contact Checkout.com if you continue to receive this message.', 'raw_response' => scrub(body) } + response['error_codes'] = [error&.message] if error&.message + response end - def success_from(response) - (response['responseCode'] == '10000' && !response['responseMessage'].start_with?('40')) || response['responseCode'] == '10100' + 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'] + if store_response + return true if (action == :tokens && store_response.match(/tok/)) || (action == :store && store_response.match(/src_/)) + end + response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id') end def message_from(succeeded, response) if succeeded 'Succeeded' - elsif response['errors'] - response['message'] + ': ' + response['errors'].first + elsif response['error_type'] + response['error_type'] + ': ' + response['error_codes'].first else - response['responseMessage'] || response['message'] || 'Unable to read error message' + response['response_summary'] || response['response_code'] || response['status'] || response['message'] || 'Unable to read error message' end end @@ -220,12 +581,34 @@ def authorization_from(raw) def error_code_from(succeeded, response) return if succeeded - if response['errorCode'] && response['errorMessageCodes'] - "#{response["errorCode"]}: #{response["errorMessageCodes"].join(", ")}" - elsif response['errorCode'] - response['errorCode'] + + if response['error_type'] && response['error_codes'] + "#{response['error_type']}: #{response['error_codes'].join(', ')}" + elsif response['error_type'] + response['error_type'] + else + STANDARD_ERROR_CODE_MAPPING[response['response_code']] + end + end + + def token_type_from(payment_method) + case payment_method.source + when :network_token + payment_method.brand == 'visa' ? 'vts' : 'mdes' + when :google_pay, :android_pay + 'googlepay' + when :apple_pay + '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 - STANDARD_ERROR_CODE_MAPPING[response['responseCode']] + raise ResponseError.new(response) end end end diff --git a/lib/active_merchant/billing/gateways/citrus_pay.rb b/lib/active_merchant/billing/gateways/citrus_pay.rb index 00ab762d196..b7f30ac2d89 100644 --- a/lib/active_merchant/billing/gateways/citrus_pay.rb +++ b/lib/active_merchant/billing/gateways/citrus_pay.rb @@ -15,8 +15,7 @@ class CitrusPayGateway < Gateway self.homepage_url = 'http://www.citruspay.com/' self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] - + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro] end end end diff --git a/lib/active_merchant/billing/gateways/clearhaus.rb b/lib/active_merchant/billing/gateways/clearhaus.rb index b53d792a4f4..08098a1801d 100644 --- a/lib/active_merchant/billing/gateways/clearhaus.rb +++ b/lib/active_merchant/billing/gateways/clearhaus.rb @@ -4,12 +4,12 @@ class ClearhausGateway < Gateway self.test_url = 'https://gateway.test.clearhaus.com' self.live_url = 'https://gateway.clearhaus.com' - self.supported_countries = ['DK', 'NO', 'SE', 'FI', 'DE', 'CH', 'NL', 'AD', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'FO', 'GL', 'EE', 'FR', 'GR', - 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'GB'] + self.supported_countries = %w[DK NO SE FI DE CH NL AD AT BE BG HR CY CZ FO GL EE FR GR + HU IS IE IT LV LI LT LU MT PL PT RO SK SI ES GB] self.default_currency = 'EUR' - self.currencies_without_fractions = %w(BIF BYR DJF GNF JPY KMF KRW PYG RWF VND VUV XAF XOF XPF) - self.supported_cardtypes = [:visa, :master] + self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) + self.supported_cardtypes = %i[visa master] self.homepage_url = 'https://www.clearhaus.com' self.display_name = 'Clearhaus' @@ -36,31 +36,32 @@ class ClearhausGateway < Gateway 50000 => 'Clearhaus error' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key) options[:private_key] = options[:private_key].strip if options[:private_key] super end - def purchase(amount, payment, options={}) + def purchase(amount, payment, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(amount, payment, options) } r.process { capture(amount, r.authorization, options) } end end - def authorize(amount, payment, options={}) + def authorize(amount, payment, options = {}) post = {} add_invoice(post, amount, options) - action = if payment.respond_to?(:number) - add_payment(post, payment) - '/authorizations' - elsif payment.kind_of?(String) - "/cards/#{payment}/authorizations" - else - raise ArgumentError.new("Unknown payment type #{payment.inspect}") - end + action = + if payment.respond_to?(:number) + add_payment(post, payment) + '/authorizations' + elsif payment.kind_of?(String) + "/cards/#{payment}/authorizations" + else + raise ArgumentError.new("Unknown payment type #{payment.inspect}") + end post[:recurring] = options[:recurring] if options[:recurring] post[:card][:pares] = options[:pares] if options[:pares] @@ -68,14 +69,14 @@ def authorize(amount, payment, options={}) commit(action, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) commit("/authorizations/#{authorization}/captures", post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_amount(post, amount, options) @@ -86,14 +87,14 @@ def void(authorization, options = {}) commit("/authorizations/#{authorization}/voids", options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(0, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end - def store(credit_card, options={}) + def store(credit_card, options = {}) post = {} add_payment(post, credit_card) @@ -127,12 +128,10 @@ def add_amount(post, amount, options) def add_payment(post, payment) card = {} card[:pan] = payment.number - card[:expire_month] = '%02d'% payment.month + card[:expire_month] = '%02d' % payment.month card[:expire_year] = payment.year - if payment.verification_value? - card[:csc] = payment.verification_value - end + card[:csc] = payment.verification_value if payment.verification_value? post[:card] = card if card.any? end @@ -161,12 +160,14 @@ def commit(action, parameters) end end - response = begin - parse(ssl_post(url, body, headers)) - rescue ResponseError => e - raise unless(e.response.code.to_s =~ /400/) - parse(e.response.body) - end + response = + begin + parse(ssl_post(url, body, headers)) + rescue ResponseError => e + raise unless e.response.code.to_s =~ /400/ + + parse(e.response.body) + end Response.new( success_from(response), @@ -205,15 +206,13 @@ def id_of_auth_for_capture(action) def generate_signature(body) key = OpenSSL::PKey::RSA.new(@options[:private_key]) - hex = key.sign(OpenSSL::Digest.new('sha256'), body).unpack('H*').first + hex = key.sign(OpenSSL::Digest.new('sha256'), body).unpack1('H*') "#{@options[:signing_key]} RS256-hex #{hex}" end def error_code_from(response) - unless success_from(response) - response['status']['code'] - end + response['status']['code'] unless success_from(response) end end end diff --git a/lib/active_merchant/billing/gateways/commerce_hub.rb b/lib/active_merchant/billing/gateways/commerce_hub.rb new file mode 100644 index 00000000000..e4d9acca748 --- /dev/null +++ b/lib/active_merchant/billing/gateways/commerce_hub.rb @@ -0,0 +1,370 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CommerceHubGateway < Gateway + self.test_url = 'https://cert.api.fiservapps.com/ch' + self.live_url = 'https://prod.api.fiservapps.com/ch' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://developer.fiserv.com/product/CommerceHub' + self.display_name = 'CommerceHub' + + STANDARD_ERROR_CODE_MAPPING = {} + + SCHEDULED_REASON_TYPES = %w(recurring installment) + ENDPOINTS = { + 'sale' => '/payments/v1/charges', + 'void' => '/payments/v1/cancels', + 'refund' => '/payments/v1/refunds', + 'vault' => '/payments-vas/v1/tokens', + 'verify' => '/payments-vas/v1/accounts/verification' + } + + def initialize(options = {}) + requires!(options, :api_key, :api_secret, :merchant_id, :terminal_id) + super + end + + def purchase(money, payment, options = {}) + post = {} + options[:capture_flag] = true + options[:create_token] = false + + add_transaction_details(post, options, 'sale') + build_purchase_and_auth_request(post, money, payment, options) + + commit('sale', post, options) + end + + def authorize(money, payment, options = {}) + post = {} + options[:capture_flag] = false + options[:create_token] = false + + add_transaction_details(post, options, 'sale') + build_purchase_and_auth_request(post, money, payment, options) + + commit('sale', post, options) + end + + def capture(money, authorization, options = {}) + post = {} + options[:capture_flag] = true + add_invoice(post, money, options) + add_transaction_details(post, options, 'capture') + add_reference_transaction_details(post, authorization, options, :capture) + + commit('sale', post, options) + end + + 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, :refund) + + commit('refund', post, options) + end + + def void(authorization, options = {}) + post = {} + add_transaction_details(post, options) + add_reference_transaction_details(post, authorization, options, :void) + + commit('void', post, options) + end + + def store(credit_card, options = {}) + post = {} + add_payment(post, credit_card, options) + add_billing_address(post, credit_card, options) + add_transaction_details(post, options) + add_transaction_interaction(post, options) + + commit('vault', post, options) + end + + def verify(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? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: )[a-zA-Z0-9+./=]+), '\1[FILTERED]'). + gsub(%r((Api-Key: )\w+), '\1[FILTERED]'). + gsub(%r(("cardData\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("securityCode\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cavv\\?":\\?")\w+), '\1[FILTERED]') + end + + private + + def add_transaction_interaction(post, options) + post[:transactionInteraction] = {} + 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) + 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' + 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) + return unless billing = options[:billing_address] + + billing_address = {} + if payment.is_a?(CreditCard) + billing_address[:firstName] = payment.first_name if payment.first_name + billing_address[:lastName] = payment.last_name if payment.last_name + end + address = {} + address[:street] = billing[:address1] if billing[:address1] + address[:houseNumberOrName] = billing[:address2] if billing[:address2] + address[:recipientNameOrAddress] = billing[:name] if billing[:name] + address[:city] = billing[:city] if billing[:city] + address[:stateOrProvince] = billing[:state] if billing[:state] + address[:postalCode] = billing[:zip] if billing[:zip] + address[:country] = billing[:country] if billing[:country] + + billing_address[:address] = address unless address.empty? + if billing[:phone_number] + billing_address[:phone] = {} + billing_address[:phone][:phoneNumber] = billing[:phone_number] + end + post[:billingAddress] = billing_address + end + + def add_shipping_address(post, options) + return unless shipping = options[:shipping_address] + + shipping_address = {} + address = {} + address[:street] = shipping[:address1] if shipping[:address1] + address[:houseNumberOrName] = shipping[:address2] if shipping[:address2] + address[:recipientNameOrAddress] = shipping[:name] if shipping[:name] + address[:city] = shipping[:city] if shipping[:city] + address[:stateOrProvince] = shipping[:state] if shipping[:state] + address[:postalCode] = shipping[:zip] if shipping[:zip] + address[:country] = shipping[:country] if shipping[:country] + + shipping_address[:address] = address unless address.empty? + if shipping[:phone_number] + shipping_address[:phone] = {} + shipping_address[:phone][:phoneNumber] = shipping[:phone_number] + end + post[:shippingAddress] = shipping_address + end + + def build_purchase_and_auth_request(post, money, 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) + end + + def add_reference_transaction_details(post, authorization, options, action = nil) + 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) + post[:amount] = { + total: amount(money).to_f, + currency: options[:currency] || self.default_currency + } + end + + def add_stored_credentials(post, options) + return unless stored_credential = options[:stored_credential] + + post[:storedCredentials] = {} + 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] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id] + end + + def add_credit_card(source, payment, options) + source[:sourceType] = 'PaymentCard' + source[:card] = {} + source[:card][:cardData] = payment.number + source[:card][:expirationMonth] = format(payment.month, :two_digits) if payment.month + source[:card][:expirationYear] = format(payment.year, :four_digits) if payment.year + if payment.verification_value + source[:card][:securityCode] = payment.verification_value + source[:card][:securityCodeIndicator] = 'PROVIDED' + end + end + + def add_payment_token(source, payment, options) + source[:sourceType] = 'PaymentToken' + source[:tokenData] = payment + source[:tokenSource] = options[:token_source] if options[:token_source] + if options[:card_expiration_month] || options[:card_expiration_year] + source[:card] = {} + source[:card][:expirationMonth] = options[:card_expiration_month] if options[:card_expiration_month] + source[:card][:expirationYear] = options[:card_expiration_year] if options[:card_expiration_year] + end + end + + def add_decrypted_wallet(source, payment, options) + source[:sourceType] = 'DecryptedWallet' + source[:card] = {} + source[:card][:cardData] = payment.number + source[:card][:expirationMonth] = format(payment.month, :two_digits) + source[:card][:expirationYear] = format(payment.year, :four_digits) + source[:cavv] = payment.payment_cryptogram + source[:walletType] = payment.source.to_s.upcase + end + + def add_payment(post, payment, options = {}) + source = {} + case payment + when NetworkTokenizationCreditCard + add_decrypted_wallet(source, payment, options) + when CreditCard + 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 + post[:source] = source + end + + def parse(body) + JSON.parse(body) + end + + def headers(request, options) + time = DateTime.now.strftime('%Q').to_s + client_request_id = options[:client_request_id] || rand.to_s[2..8] + 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], + 'Timestamp' => time, + 'Accept-Language' => 'application/json', + 'Auth-Token-Type' => 'HMAC', + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Authorization' => signature + }.merge!(custom_headers) + end + + def add_merchant_details(post) + post[:merchantDetails] = {} + post[:merchantDetails][:terminalId] = @options[:terminal_id] + post[:merchantDetails][:merchantId] = @options[:merchant_id] + end + + def commit(action, parameters, options) + url = (test? ? test_url : live_url) + ENDPOINTS[action] + add_merchant_details(parameters) + response = parse(ssl_post(url, parameters.to_json, headers(parameters.to_json, options))) + + Response.new( + success_from(response, action), + message_from(response, action), + response, + authorization: authorization_from(action, response, options), + test: test?, + 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 + + def handle_response(response) + case response.code.to_i + when 200...300, 400, 401, 429 + response.body + else + raise ResponseError.new(response) + end + end + + 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, 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, 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, action) + response.dig('error', 0, 'code') unless success_from(response, action) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/commercegate.rb b/lib/active_merchant/billing/gateways/commercegate.rb index c1d6d5bc1f4..825ae5fdf0b 100644 --- a/lib/active_merchant/billing/gateways/commercegate.rb +++ b/lib/active_merchant/billing/gateways/commercegate.rb @@ -11,7 +11,7 @@ class CommercegateGateway < Gateway self.money_format = :dollars self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.commercegate.com/' self.display_name = 'CommerceGate' @@ -74,8 +74,8 @@ def add_auth_purchase_options(post, money, options) post[:customerIP] = options[:ip] || '127.0.0.1' post[:amount] = amount(money) post[:email] = options[:email] || 'unknown@example.com' - post[:currencyCode]= options[:currency] || currency(money) - post[:merchAcct] = options[:merchant] + post[:currencyCode] = options[:currency] || currency(money) + post[:merchAcct] = options[:merchant] end def add_creditcard(params, creditcard) @@ -102,7 +102,7 @@ def commit(action, parameters) response, authorization: response['transID'], test: test?, - avs_result: {code: response['avsCode']}, + avs_result: { code: response['avsCode'] }, cvv_result: response['cvvCode'] ) end diff --git a/lib/active_merchant/billing/gateways/conekta.rb b/lib/active_merchant/billing/gateways/conekta.rb index 06aad777b09..c113aa13ebb 100644 --- a/lib/active_merchant/billing/gateways/conekta.rb +++ b/lib/active_merchant/billing/gateways/conekta.rb @@ -4,7 +4,7 @@ class ConektaGateway < Gateway self.live_url = 'https://api.conekta.io/' self.supported_countries = ['MX'] - self.supported_cardtypes = [:visa, :master, :american_express, :carnet] + self.supported_cardtypes = %i[visa master american_express carnet] self.homepage_url = 'https://conekta.io/' self.display_name = 'Conekta Gateway' self.money_format = :cents @@ -105,7 +105,7 @@ def add_shipment(post, options) end def add_shipment_address(post, options) - if(address = options[:shipping_address]) + if (address = options[:shipping_address]) post[:address] = {} post[:address][:street1] = address[:address1] if address[:address1] post[:address][:street2] = address[:address2] if address[:address2] @@ -124,7 +124,7 @@ def add_line_items(post, options) end def add_billing_address(post, options) - if(address = (options[:billing_address] || options[:address])) + if (address = (options[:billing_address] || options[:address])) post[:billing_address] = {} post[:billing_address][:street1] = address[:address1] if address[:address1] post[:billing_address][:street2] = address[:address2] if address[:address2] @@ -142,7 +142,7 @@ def add_billing_address(post, options) end def add_address(post, options) - if(address = (options[:billing_address] || options[:address])) + if (address = (options[:billing_address] || options[:address])) post[:address] = {} post[:address][:street1] = address[:address1] if address[:address1] post[:address][:street2] = address[:address2] if address[:address2] @@ -170,6 +170,7 @@ def add_payment_source(post, payment_source, options) def parse(body) return {} unless body + JSON.parse(body) end @@ -179,7 +180,7 @@ def headers(options) 'Accept-Language' => 'es', 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:key]}:"), 'RaiseHtmlError' => 'false', - 'Conekta-Client-User-Agent' => {'agent'=>"Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}"}.to_json, + 'Conekta-Client-User-Agent' => { 'agent' => "Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}" }.to_json, 'X-Conekta-Client-User-Agent' => conekta_client_user_agent(options), 'X-Conekta-Client-User-Metadata' => options[:meta].to_json } @@ -187,7 +188,8 @@ def headers(options) def conekta_client_user_agent(options) return user_agent unless options[:application] - JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]})) + + JSON.dump(JSON.parse(user_agent).merge!({ application: options[:application] })) end def commit(method, url, parameters, options = {}) diff --git a/lib/active_merchant/billing/gateways/creditcall.rb b/lib/active_merchant/billing/gateways/creditcall.rb index 45f84569f5d..6f6f0b70d31 100644 --- a/lib/active_merchant/billing/gateways/creditcall.rb +++ b/lib/active_merchant/billing/gateways/creditcall.rb @@ -10,7 +10,7 @@ class CreditcallGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.creditcall.com' self.display_name = 'Creditcall' @@ -24,29 +24,29 @@ class CreditcallGateway < Gateway AVS_CODE = { 'matched;matched' => 'D', - 'matched;notchecked' =>'B', + 'matched;notchecked' => 'B', 'matched;notmatched' => 'A', 'matched;partialmatch' => 'A', 'notchecked;matched' => 'P', - 'notchecked;notchecked' =>'I', + 'notchecked;notchecked' => 'I', 'notchecked;notmatched' => 'I', 'notchecked;partialmatch' => 'I', 'notmatched;matched' => 'W', - 'notmatched;notchecked' =>'C', + 'notmatched;notchecked' => 'C', 'notmatched;notmatched' => 'C', 'notmatched;partialmatch' => 'C', 'partialmatched;matched' => 'W', - 'partialmatched;notchecked' =>'C', + 'partialmatched;notchecked' => 'C', 'partialmatched;notmatched' => 'C', 'partialmatched;partialmatch' => 'C' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :terminal_id, :transaction_key) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) multi_response = MultiResponse.run do |r| r.process { authorize(money, payment_method, options) } r.process { capture(money, r.authorization, options) } @@ -66,7 +66,7 @@ def purchase(money, payment_method, options={}) ) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) request = build_xml_request do |xml| add_transaction_details(xml, money, nil, 'Auth', options) add_terminal_details(xml, options) @@ -76,7 +76,7 @@ def authorize(money, payment_method, options={}) commit(request) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) request = build_xml_request do |xml| add_transaction_details(xml, money, authorization, 'Conf', options) add_terminal_details(xml, options) @@ -85,7 +85,7 @@ def capture(money, authorization, options={}) commit(request) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) request = build_xml_request do |xml| add_transaction_details(xml, money, authorization, 'Refund', options) add_terminal_details(xml, options) @@ -94,7 +94,7 @@ def refund(money, authorization, options={}) commit(request) end - def void(authorization, options={}) + def void(authorization, options = {}) request = build_xml_request do |xml| add_transaction_details(xml, nil, authorization, 'Void', options) add_terminal_details(xml, options) @@ -103,7 +103,7 @@ def void(authorization, options={}) commit(request) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -144,7 +144,7 @@ def build_xml_request builder.to_xml end - def add_transaction_details(xml, amount, authorization, type, options={}) + def add_transaction_details(xml, amount, authorization, type, options = {}) xml.TransactionDetails do xml.MessageType type xml.Amount(unit: 'Minor') { xml.text(amount) } if amount @@ -153,7 +153,7 @@ def add_transaction_details(xml, amount, authorization, type, options={}) end end - def add_terminal_details(xml, options={}) + def add_terminal_details(xml, options = {}) xml.TerminalDetails do xml.TerminalID @options[:terminal_id] xml.TransactionKey @options[:transaction_key] @@ -161,7 +161,7 @@ def add_terminal_details(xml, options={}) end end - def add_card_details(xml, payment_method, options={}) + def add_card_details(xml, payment_method, options = {}) xml.CardDetails do xml.Manual(type: manual_type(options)) do xml.PAN payment_method.number @@ -175,10 +175,11 @@ def add_card_details(xml, payment_method, options={}) def add_additional_verification(xml, options) return unless (options[:verify_zip].to_s == 'true') || (options[:verify_address].to_s == 'true') + if address = options[:billing_address] xml.AdditionalVerification do - xml.Zip address[:zip] if options[:verify_zip].to_s == 'true' - xml.Address address[:address1] if options[:verify_address].to_s == 'true' + xml.Zip address[:zip] if options[:verify_zip].to_s == 'true' + xml.Address address[:address1] if options[:verify_address].to_s == 'true' end end end diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb index ccc42a508ed..6740a48e337 100644 --- a/lib/active_merchant/billing/gateways/credorax.rb +++ b/lib/active_merchant/billing/gateways/credorax.rb @@ -4,8 +4,12 @@ class CredoraxGateway < Gateway class_attribute :test_url, :live_na_url, :live_eu_url self.display_name = 'Credorax Gateway' - self.homepage_url = 'https://www.credorax.com/' + self.homepage_url = 'https://www.finaro.com/' + # NOTE: the IP address you run the remote tests from will need to be + # whitelisted by Credorax; contact support@credorax.com as necessary to + # request your IP address be added to the whitelist for your test + # account. self.test_url = 'https://intconsole.credorax.com/intenv/service/gateway' # The live URL is assigned on a per merchant basis once certification has passed @@ -15,13 +19,20 @@ class CredoraxGateway < Gateway # ActiveMerchant::Billing::CredoraxGateway.live_url = "https://assigned-subdomain.credorax.net/crax_gate/service/gateway" self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway' - self.supported_countries = %w(DE GB FR IT ES PL NL BE GR CZ PT SE HU RS AT CH BG DK FI SK NO IE HR BA AL LT MK SI LV EE ME LU MT IS AD MC LI SM) + self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB) + self.default_currency = 'EUR' - self.currencies_without_fractions = %w(CLP JPY KRW PYG VND) - self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR RSD TND) + self.currencies_without_fractions = %w(BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF VND VUV XAF XOF XPF) + self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LYD OMR TND) self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :maestro] + self.supported_cardtypes = %i[visa master maestro american_express jcb discover diners_club] + + NETWORK_TOKENIZATION_CARD_SOURCE = { + 'apple_pay' => 'applepay', + 'google_pay' => 'googlepay', + 'network_token' => 'vts_mdes_token' + } RESPONSE_MESSAGES = { '00' => 'Approved or completed successfully', @@ -58,26 +69,27 @@ class CredoraxGateway < Gateway '31' => 'Issuer signed-off', '32' => 'Completed partially', '33' => 'Pick-up, expired card', - '34' => 'Suspect Fraud', + '34' => 'Implausible card data', '35' => 'Pick-up, card acceptor contact acquirer', '36' => 'Pick up, card restricted', '37' => 'Pick up, call acquirer security', '38' => 'Pick up, Allowable PIN tries exceeded', - '39' => 'Transaction Not Allowed', + '39' => 'No credit account', '40' => 'Requested function not supported', '41' => 'Lost Card, Pickup', '42' => 'No universal account', '43' => 'Pick up, stolen card', '44' => 'No investment account', + '46' => 'Closed account', '50' => 'Do not renew', - '51' => 'Not sufficient funds', + '51' => 'Insufficient funds', '52' => 'No checking Account', '53' => 'No savings account', '54' => 'Expired card', - '55' => 'Pin incorrect', + '55' => 'Incorrect PIN', '56' => 'No card record', '57' => 'Transaction not allowed for cardholder', - '58' => 'Transaction not allowed for merchant', + '58' => 'Transaction not permitted to terminal', '59' => 'Suspected Fraud', '60' => 'Card acceptor contact acquirer', '61' => 'Exceeds withdrawal amount limit', @@ -88,22 +100,22 @@ class CredoraxGateway < Gateway '66' => 'Call acquirers security department', '67' => 'Card to be picked up at ATM', '68' => 'Response received too late.', - '70' => 'Invalid transaction; contact card issuer', + '70' => 'PIN data required', '71' => 'Decline PIN not changed', '75' => 'Pin tries exceeded', '76' => 'Wrong PIN, number of PIN tries exceeded', '77' => 'Wrong Reference No.', - '78' => 'Record Not Found', - '79' => 'Already reversed', + '78' => 'Blocked, first used/ Record not found', + '79' => 'Declined due to lifecycle event', '80' => 'Network error', - '81' => 'Foreign network error / PIN cryptographic error', - '82' => 'Time out at issuer system', + '81' => 'PIN cryptographic error', + '82' => 'Bad CVV/ Declined due to policy event', '83' => 'Transaction failed', '84' => 'Pre-authorization timed out', '85' => 'No reason to decline', '86' => 'Cannot verify pin', '87' => 'Purchase amount only, no cashback allowed', - '88' => 'MAC sync Error', + '88' => 'Cryptographic failure', '89' => 'Authentication failure', '91' => 'Issuer not available', '92' => 'Unable to route at acquirer Module', @@ -111,91 +123,113 @@ class CredoraxGateway < Gateway '94' => 'Duplicate Transmission', '95' => 'Reconcile error / Auth Not found', '96' => 'System malfunction', + '97' => 'Transaction has been declined by the processor', + 'N3' => 'Cash service not available', + 'N4' => 'Cash request exceeds issuer or approved limit', + 'N7' => 'CVV2 failure', 'R0' => 'Stop Payment Order', 'R1' => 'Revocation of Authorisation Order', - 'R3' => 'Revocation of all Authorisations Order' + 'R3' => 'Revocation of all Authorisation Orders', + '1A' => 'Strong Customer Authentication required' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :cipher_key) super end - def purchase(amount, payment_method, 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) + add_3ds_2_optional_fields(post, options) add_echo(post, options) add_submerchant_id(post, options) - add_transaction_type(post, options) + add_stored_credential(post, options) + add_processor(post, options) commit(:purchase, post) end - def authorize(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) + add_3ds_2_optional_fields(post, options) add_echo(post, options) add_submerchant_id(post, options) - add_transaction_type(post, options) + add_stored_credential(post, options) + add_processor(post, options) + add_authorization_details(post, options) commit(:authorize, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) add_customer_data(post, options) add_echo(post, options) add_submerchant_id(post, options) + add_processor(post, options) commit(:capture, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_customer_data(post, options) reference_action = add_reference(post, authorization) add_echo(post, options) add_submerchant_id(post, options) post[:a1] = generate_unique_id + add_processor(post, options) commit(:void, post, reference_action) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) add_customer_data(post, options) add_echo(post, options) add_submerchant_id(post, options) + add_processor(post, options) + add_email(post, options) + add_recipient(post, options) - commit(:refund, post) + if options[:referral_cft] + add_customer_name(post, options) + commit(:referral_cft, post) + else + commit(:refund, post) + end end - def credit(amount, payment_method, 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) add_submerchant_id(post, options) add_transaction_type(post, options) + add_processor(post, options) + add_customer_name(post, options) commit(:credit, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -212,6 +246,25 @@ def scrub(transcript) gsub(%r((b5=)\d+), '\1[FILTERED]') end + def add_3ds_2_optional_fields(post, options) + three_ds = options[:three_ds_2] || {} + + if three_ds.has_key?(:optional) + three_ds[:optional].each do |key, value| + normalized_value = normalize(value) + next if normalized_value.nil? + + if key == :'3ds_homephonecountry' + next unless options[:billing_address] && options[:billing_address][:phone] + end + + post[key] = normalized_value unless post[key] + end + end + + post + end + private def add_invoice(post, money, options) @@ -230,8 +283,9 @@ def add_invoice(post, money, options) 'maestro' => '9' } - def add_payment_method(post, payment_method) - post[:c1] = payment_method.name + def add_payment_method(post, payment_method, options) + post[:c1] = payment_method&.name || '' + 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 @@ -239,15 +293,40 @@ 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 + return unless (stored_credential = options[:stored_credential]) && options.dig(:transaction_type).nil? + + if stored_credential[:initiator] == 'merchant' + case stored_credential[:reason_type] + when 'recurring' + 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 add_customer_data(post, options) post[:d1] = options[:ip] || '127.0.0.1' if (billing_address = options[:billing_address]) - post[:c5] = billing_address[:address1] - post[:c7] = billing_address[:city] - post[:c10] = billing_address[:zip] - post[:c8] = billing_address[:state] - post[:c9] = billing_address[:country] - post[:c2] = billing_address[:phone] + post[:c5] = billing_address[:address1] if billing_address[:address1] + post[:c7] = billing_address[:city] if billing_address[:city] + post[:c10] = billing_address[:zip] if billing_address[:zip] + post[:c8] = billing_address[:state] if billing_address[:state] + post[:c9] = billing_address[:country] if billing_address[:country] + post[:c2] = billing_address[:phone] if billing_address[:phone] end end @@ -263,9 +342,87 @@ def add_email(post, options) post[:c3] = options[:email] || 'unspecified@example.com' end + def add_recipient(post, options) + return unless options[:recipient_street_address] || options[:recipient_city] || options[:recipient_province_code] || options[:recipient_country_code] + + recipient_country_code = options[:recipient_country_code]&.length == 3 ? options[:recipient_country_code] : Country.find(options[:recipient_country_code]).code(:alpha3).value if options[:recipient_country_code] + post[:j6] = options[:recipient_street_address] if options[:recipient_street_address] + post[:j7] = options[:recipient_city] if options[:recipient_city] + post[:j8] = options[:recipient_province_code] if options[:recipient_province_code] + post[:j9] = recipient_country_code + end + + def add_customer_name(post, options) + post[:j5] = options[:first_name] if options[:first_name] + post[:j13] = options[:last_name] if options[:last_name] + end + def add_3d_secure(post, options) - return unless options[:eci] && options[:xid] - post[:i8] = "#{options[:eci]}:#{(options[:cavv] || "none")}:#{options[:xid]}" + if (options[:eci] && options[:xid]) || (options[:three_d_secure] && options[:three_d_secure][:version]&.start_with?('1')) + add_3d_secure_1_data(post, options) + elsif options[:execute_threed] && options[:three_ds_2] + three_ds_2_options = options[:three_ds_2] + browser_info = three_ds_2_options[:browser_info] + post[:'3ds_initiate'] = options[:three_ds_initiate] || '01' + post[:f23] = options[:f23] if options[:f23] + post[:'3ds_purchasedate'] = Time.now.utc.strftime('%Y%m%d%I%M%S') + options.dig(:stored_credential, :initiator) == 'merchant' ? post[:'3ds_channel'] = '03' : post[:'3ds_channel'] = '02' + post[:'3ds_reqchallengeind'] = options[:three_ds_reqchallengeind] if options[:three_ds_reqchallengeind] + post[:'3ds_redirect_url'] = three_ds_2_options[:notification_url] + post[:'3ds_challengewindowsize'] = options[:three_ds_challenge_window_size] || '03' + post[:d5] = browser_info[:user_agent] + post[:'3ds_transtype'] = options[:three_ds_transtype] || '01' + post[:'3ds_browsertz'] = browser_info[:timezone] + post[:'3ds_browserscreenwidth'] = browser_info[:width] + post[:'3ds_browserscreenheight'] = browser_info[:height] + post[:'3ds_browsercolordepth'] = browser_info[:depth].to_s == '30' ? '32' : browser_info[:depth] + post[:d6] = browser_info[:language] + post[:'3ds_browserjavaenabled'] = browser_info[:java] + post[:'3ds_browseracceptheader'] = browser_info[:accept_header] + add_complete_shipping_address(post, options[:shipping_address]) if options[:shipping_address] + elsif options[:three_d_secure] + add_normalized_3d_secure_2_data(post, options) + end + end + + def add_3d_secure_1_data(post, options) + if three_d_secure_options = options[:three_d_secure] + post[:i8] = build_i8( + three_d_secure_options[:eci], + three_d_secure_options[:cavv], + three_d_secure_options[:xid] + ) + post[:'3ds_version'] = three_d_secure_options[:version]&.start_with?('1') ? '1.0' : three_d_secure_options[:version] + else + post[:i8] = build_i8(options[:eci], options[:cavv], options[:xid]) + post[:'3ds_version'] = options[:three_ds_version].nil? || options[:three_ds_version]&.start_with?('1') ? '1.0' : options[:three_ds_version] + end + end + + def add_complete_shipping_address(post, shipping_address) + return if shipping_address.values.any?(&:blank?) + + post[:'3ds_shipaddrstate'] = shipping_address[:state] + post[:'3ds_shipaddrpostcode'] = shipping_address[:zip] + post[:'3ds_shipaddrline2'] = shipping_address[:address2] + post[:'3ds_shipaddrline1'] = shipping_address[:address1] + post[:'3ds_shipaddrcountry'] = shipping_address[:country] + post[:'3ds_shipaddrcity'] = shipping_address[:city] + end + + def add_normalized_3d_secure_2_data(post, options) + three_d_secure_options = options[:three_d_secure] + + post[:i8] = build_i8( + three_d_secure_options[:eci], + three_d_secure_options[:cavv] + ) + post[:'3ds_version'] = three_d_secure_options[:version]&.start_with?('2') ? '2.0' : three_d_secure_options[:version] + post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id] + end + + def build_i8(eci, cavv = nil, xid = nil) + "#{eci}:#{cavv || 'none'}:#{xid || 'none'}" end def add_echo(post, options) @@ -280,6 +437,17 @@ def add_submerchant_id(post, options) def add_transaction_type(post, options) post[:a9] = options[:transaction_type] if options[:transaction_type] + post[:a2] = '3' if options.dig(:metadata, :manual_entry) + end + + def add_processor(post, options) + post[:r1] = options[:processor] if options[:processor] + post[:r2] = options[:processor_merchant_id] if options[:processor_merchant_id] + end + + def add_authorization_details(post, options) + post[:a10] = options[:authorization_type] if options[:authorization_type] + post[:a11] = options[:multiple_capture_count] if options[:multiple_capture_count] end ACTIONS = { @@ -288,10 +456,12 @@ def add_transaction_type(post, options) capture: '3', authorize_void: '4', refund: '5', - credit: '6', + credit: '35', purchase_void: '7', refund_void: '8', - capture_void: '9' + capture_void: '9', + threeds_completion: '92', + referral_cft: '34' } def commit(action, params, reference_action = nil) @@ -302,7 +472,7 @@ def commit(action, params, reference_action = nil) success_from(response), message_from(response), response, - authorization: "#{response["Z1"]};#{response["Z4"]};#{response["A1"]};#{action}", + authorization: "#{response['Z1']};#{response['Z4']};#{response['A1']};#{action}", avs_result: AVSResult.new(code: response['Z9']), cvv_result: CVVResult.new(response['Z14']), test: test? @@ -311,8 +481,10 @@ def commit(action, params, reference_action = nil) def sign_request(params) params = params.sort - params.each { |param| param[1].gsub!(/[<>()\\]/, ' ') } - values = params.map { |param| param[1].strip } + values = params.map do |param| + value = param[1].gsub(/[<>()\\]/, ' ') + value.strip + end Digest::MD5.hexdigest(values.join + @options[:cipher_key]) end @@ -325,11 +497,9 @@ def post_data(action, params, reference_action) end def request_action(action, reference_action) - if reference_action - ACTIONS["#{reference_action}_#{action}".to_sym] - else - ACTIONS[action] - end + return ACTIONS["#{reference_action}_#{action}".to_sym] if reference_action + + ACTIONS[action] end def url diff --git a/lib/active_merchant/billing/gateways/ct_payment.rb b/lib/active_merchant/billing/gateways/ct_payment.rb index 315f16375d8..6597aeb8be1 100644 --- a/lib/active_merchant/billing/gateways/ct_payment.rb +++ b/lib/active_merchant/billing/gateways/ct_payment.rb @@ -4,9 +4,9 @@ class CtPaymentGateway < Gateway self.test_url = 'https://test.ctpaiement.ca/v1/' self.live_url = 'https://www.ctpaiement.com/v1/' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w[US CA] self.default_currency = 'CAD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover diners_club] self.homepage_url = 'http://www.ct-payment.com/' self.display_name = 'CT Payment' @@ -26,12 +26,12 @@ class CtPaymentGateway < Gateway 'discover' => 'O' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key, :company_number, :merchant_number) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) requires!(options, :order_id) post = {} add_terminal_number(post, options) @@ -45,7 +45,7 @@ def purchase(money, payment, options={}) payment.is_a?(String) ? commit('purchaseWithToken', post) : commit('purchase', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) requires!(options, :order_id) post = {} add_money(post, money) @@ -59,7 +59,7 @@ def authorize(money, payment, options={}) payment.is_a?(String) ? commit('preAuthorizationWithToken', post) : commit('preAuthorization', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) requires!(options, :order_id) post = {} add_invoice(post, money, options) @@ -73,7 +73,7 @@ def capture(money, authorization, options={}) commit('completion', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) requires!(options, :order_id) post = {} add_invoice(post, money, options) @@ -86,7 +86,7 @@ def refund(money, authorization, options={}) commit('refundWithoutCard', post) end - def credit(money, payment, options={}) + def credit(money, payment, options = {}) requires!(options, :order_id) post = {} add_terminal_number(post, options) @@ -100,7 +100,7 @@ def credit(money, payment, options={}) payment.is_a?(String) ? commit('refundWithToken', post) : commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} post[:InputType] = 'I' post[:LanguageCode] = 'E' @@ -113,7 +113,7 @@ def void(authorization, options={}) commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) requires!(options, :order_id) post = {} add_terminal_number(post, options) @@ -126,7 +126,7 @@ def verify(credit_card, options={}) commit('verifyAccount', post) end - def store(credit_card, options={}) + def store(credit_card, options = {}) requires!(options, :email) post = { LanguageCode: 'E', @@ -177,7 +177,7 @@ def add_address(post, creditcard, options) end end - def add_invoice(post, money, options) + def add_invoice(post, money, options) post[:CurrencyCode] = options[:currency] || (currency(money) if money) post[:InvoiceNumber] = options[:order_id].rjust(12, '0') post[:InputType] = 'I' @@ -227,8 +227,8 @@ def commit(action, parameters) r.process { commit_raw(action, parameters) } r.process { split_auth = split_authorization(r.authorization) - auth = (action.include?('recur')? split_auth[4] : split_auth[0]) - action.include?('recur') ? commit_raw('recur/ack', {ID: auth}) : commit_raw('ack', {TransactionNumber: auth}) + auth = (action.include?('recur') ? split_auth[4] : split_auth[0]) + action.include?('recur') ? commit_raw('recur/ack', { ID: auth }) : commit_raw('ack', { TransactionNumber: auth }) } end end @@ -238,6 +238,7 @@ def success_from(response) return true if response['returnCode'] == ' 00' return true if response['returnCode'] == 'true' return true if response['recurReturnCode'] == ' 00' + return false end diff --git a/lib/active_merchant/billing/gateways/culqi.rb b/lib/active_merchant/billing/gateways/culqi.rb index 80b4d030198..150afe671b1 100644 --- a/lib/active_merchant/billing/gateways/culqi.rb +++ b/lib/active_merchant/billing/gateways/culqi.rb @@ -18,18 +18,18 @@ class CulqiGateway < Gateway self.supported_countries = ['PE'] self.default_currency = 'PEN' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :diners_club, :american_express] + self.supported_cardtypes = %i[visa master diners_club american_express] - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :terminal_id, :secret_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) authorize(amount, payment_method, options) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) if payment_method.is_a?(String) action = :tokenpay else @@ -45,7 +45,7 @@ def authorize(amount, payment_method, options={}) commit(action, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) action = :capture post = {} add_credentials(post) @@ -56,7 +56,7 @@ def capture(amount, authorization, options={}) commit(action, post) end - def void(authorization, options={}) + def void(authorization, options = {}) action = :void post = {} add_credentials(post) @@ -67,7 +67,7 @@ def void(authorization, options={}) commit(action, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) action = :refund post = {} add_credentials(post) @@ -78,7 +78,7 @@ def refund(amount, authorization, options={}) commit(action, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(1000, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -90,7 +90,7 @@ def verify_credentials response.message.include? 'Transaction not found' end - def store(credit_card, options={}) + def store(credit_card, options = {}) action = :tokenize post = {} post[:partnerid] = options[:partner_id] if options[:partner_id] @@ -103,7 +103,7 @@ def store(credit_card, options={}) commit(action, post) end - def invalidate(authorization, options={}) + def invalidate(authorization, options = {}) action = :invalidate post = {} post[:partnerid] = options[:partner_id] if options[:partner_id] @@ -173,22 +173,23 @@ def add_customer_data(post, options) post[:city] = billing_address[:city] post[:state] = billing_address[:state] post[:countrycode] = billing_address[:country] - post[:zip] = billing_address[:zip] + post[:zip] = billing_address[:zip] post[:telno] = billing_address[:phone] post[:telnocc] = options[:telephone_country_code] || '051' end end def add_checksum(action, post) - checksum_elements = case action - when :capture then [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]] - when :void then [post[:toid], post[:description], post[:trackingid], @options[:secret_key]] - when :refund then [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]] - when :tokenize then [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]] - when :invalidate then [post[:partnerid], post[:token], @options[:secret_key]] - else [post[:toid], post[:totype], post[:amount], post[:description], post[:redirecturl], - post[:cardnumber] || post[:token], @options[:secret_key]] - end + checksum_elements = + case action + when :capture then [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]] + when :void then [post[:toid], post[:description], post[:trackingid], @options[:secret_key]] + when :refund then [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]] + when :tokenize then [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]] + when :invalidate then [post[:partnerid], post[:token], @options[:secret_key]] + else [post[:toid], post[:totype], post[:amount], post[:description], post[:redirecturl], + post[:cardnumber] || post[:token], @options[:secret_key]] + end post[:checksum] = Digest::MD5.hexdigest(checksum_elements.compact.join('|')) end @@ -204,15 +205,16 @@ def add_reference(post, authorization) refund: 'SingleCallGenericReverse', tokenize: 'SingleCallTokenServlet', invalidate: 'SingleCallInvalidateToken', - tokenpay: 'SingleCallTokenTransaction', + tokenpay: 'SingleCallTokenTransaction' } def commit(action, params) - response = begin - parse(ssl_post(url + ACTIONS[action], post_data(action, params), headers)) - rescue ResponseError => e - parse(e.response.body) - end + response = + begin + parse(ssl_post(url + ACTIONS[action], post_data(action, params), headers)) + rescue ResponseError => e + parse(e.response.body) + end success = success_from(response) @@ -229,8 +231,8 @@ def commit(action, params) def headers { - 'Accept' => 'application/json', - 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + 'Accept' => 'application/json', + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } end diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb index 9bc89b747ce..d9a6cc996e1 100644 --- a/lib/active_merchant/billing/gateways/cyber_source.rb +++ b/lib/active_merchant/billing/gateways/cyber_source.rb @@ -1,7 +1,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: # Initial setup instructions can be found in - # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf + # http://apps.cybersource.com/library/documentation/dev_guides/SOAP_Toolkits/SOAP_toolkits.pdf # # Important Notes # * For checks you can purchase and store. @@ -15,19 +15,37 @@ module Billing #:nodoc: # CyberSource what kind of item you are selling. It is used when # calculating tax/VAT. # * All transactions use dollar values. - # * To process pinless debit cards through the pinless debit card - # network, your Cybersource merchant account must accept pinless - # debit card payments. # * The order of the XML elements does matter, make sure to follow the order in # the documentation exactly. class CyberSourceGateway < Gateway self.test_url = 'https://ics2wstesta.ic3.com/commerce/1.x/transactionProcessor' self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor' - XSD_VERSION = '1.121' + # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/ + TEST_XSD_VERSION = '1.201' + PRODUCTION_XSD_VERSION = '1.201' + ECI_BRAND_MAPPING = { + visa: 'vbv', + master: 'spa', + maestro: 'spa', + american_express: 'aesk', + jcb: 'js', + 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 = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro] - self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB) + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb dankort maestro elo] + self.supported_countries = %w(US AE BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK) self.default_currency = 'USD' self.currencies_without_fractions = %w(JPY) @@ -36,55 +54,96 @@ class CyberSourceGateway < Gateway self.display_name = 'CyberSource' @@credit_card_codes = { - :visa => '001', - :master => '002', - :american_express => '003', - :discover => '004', - :diners_club => '005', - :jcb => '007', - :dankort => '034', - :maestro => '042' + visa: '001', + master: '002', + american_express: '003', + discover: '004', + diners_club: '005', + jcb: '007', + dankort: '034', + maestro: '042', + elo: '054' + } + + @@decision_codes = { + accept: 'ACCEPT', + review: 'REVIEW' } @@response_codes = { - :r100 => 'Successful transaction', - :r101 => 'Request is missing one or more required fields', - :r102 => 'One or more fields contains invalid data', - :r150 => 'General failure', - :r151 => 'The request was received but a server time-out occurred', - :r152 => 'The request was received, but a service timed out', - :r200 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check', - :r201 => 'The issuing bank has questions about the request', - :r202 => 'Expired card', - :r203 => 'General decline of the card', - :r204 => 'Insufficient funds in the account', - :r205 => 'Stolen or lost card', - :r207 => 'Issuing bank unavailable', - :r208 => 'Inactive card or card not authorized for card-not-present transactions', - :r209 => 'American Express Card Identifiction Digits (CID) did not match', - :r210 => 'The card has reached the credit limit', - :r211 => 'Invalid card verification number', - :r221 => "The customer matched an entry on the processor's negative file", - :r230 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check', - :r231 => 'Invalid account number', - :r232 => 'The card type is not accepted by the payment processor', - :r233 => 'General decline by the processor', - :r234 => 'A problem exists with your CyberSource merchant configuration', - :r235 => 'The requested amount exceeds the originally authorized amount', - :r236 => 'Processor failure', - :r237 => 'The authorization has already been reversed', - :r238 => 'The authorization has already been captured', - :r239 => 'The requested transaction amount must match the previous transaction amount', - :r240 => 'The card type sent is invalid or does not correlate with the credit card number', - :r241 => 'The request ID is invalid', - :r242 => 'You requested a capture, but there is no corresponding, unused authorization record.', - :r243 => 'The transaction has already been settled or reversed', - :r244 => 'The bank account number failed the validation check', - :r246 => 'The capture or credit is not voidable because the capture or credit information has already been submitted to your processor', - :r247 => 'You requested a credit for a capture that was previously voided', - :r250 => 'The request was received, but a time-out occurred with the payment processor', - :r254 => 'Your CyberSource account is prohibited from processing stand-alone refunds', - :r255 => 'Your CyberSource account is not configured to process the service in the country you specified' + r100: 'Successful transaction', + r101: 'Request is missing one or more required fields', + r102: 'One or more fields contains invalid data', + r104: 'The merchantReferenceCode sent with this authorization request matches the merchantReferenceCode of another authorization request that you sent in the last 15 minutes.', r110: 'Partial amount was approved', + r150: 'General failure', + r151: 'The request was received but a server time-out occurred', + r152: 'The request was received, but a service timed out', + r200: 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check', + r201: 'The issuing bank has questions about the request', + r202: 'Expired card', + r203: 'General decline of the card', + r204: 'Insufficient funds in the account', + r205: 'Stolen or lost card', + r207: 'Issuing bank unavailable', + r208: 'Inactive card or card not authorized for card-not-present transactions', + r209: 'American Express Card Identifiction Digits (CID) did not match', + r210: 'The card has reached the credit limit', + r211: 'Invalid card verification number', + r220: 'Generic Decline.', + r221: "The customer matched an entry on the processor's negative file", + r222: 'customer\'s account is frozen', + r230: 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check', + r231: 'Invalid account number', + r232: 'The card type is not accepted by the payment processor', + r233: 'General decline by the processor', + r234: 'A problem exists with your CyberSource merchant configuration', + r235: 'The requested amount exceeds the originally authorized amount', + r236: 'Processor failure', + r237: 'The authorization has already been reversed', + r238: 'The authorization has already been captured', + r239: 'The requested transaction amount must match the previous transaction amount', + r240: 'The card type sent is invalid or does not correlate with the credit card number', + r241: 'The request ID is invalid', + r242: 'You requested a capture, but there is no corresponding, unused authorization record.', + r243: 'The transaction has already been settled or reversed', + r244: 'The bank account number failed the validation check', + r246: 'The capture or credit is not voidable because the capture or credit information has already been submitted to your processor', + r247: 'You requested a credit for a capture that was previously voided', + r248: 'The boleto request was declined by your processor.', + r250: 'The request was received, but a time-out occurred with the payment processor', + r251: 'The Pinless Debit card\'s use frequency or maximum amount per use has been exceeded.', + r254: 'Your CyberSource account is prohibited from processing stand-alone refunds', + r255: 'Your CyberSource account is not configured to process the service in the country you specified', + r400: 'Soft Decline - Fraud score exceeds threshold.', + r450: 'Apartment number missing or not found.', + r451: 'Insufficient address information.', + r452: 'House/Box number not found on street.', + r453: 'Multiple address matches were found.', + r454: 'P.O. Box identifier not found or out of range.', + r455: 'Route service identifier not found or out of range.', + r456: 'Street name not found in Postal code.', + r457: 'Postal code not found in database.', + r458: 'Unable to verify or correct address.', + r459: 'Multiple addres matches were found (international)', + r460: 'Address match not found (no reason given)', + r461: 'Unsupported character set', + r475: 'The cardholder is enrolled in Payer Authentication. Please authenticate the cardholder before continuing with the transaction.', + r476: 'Encountered a Payer Authentication problem. Payer could not be authenticated.', + r478: 'Strong customer authentication (SCA) is required for this transaction.', + r480: 'The order is marked for review by Decision Manager', + r481: 'The order has been rejected by Decision Manager', + r490: 'Your aggregator or acquirer is not accepting transactions from you at this time.', + r491: 'Your aggregator or acquirer is not accepting this transaction.', + r520: 'Soft Decline - The authorization request was approved by the issuing bank but declined by CyberSource based on your Smart Authorization settings.', + r700: 'The customer matched the Denied Parties List', + r701: 'Export bill_country/ship_country match', + r702: 'Export email_country match', + r703: 'Export hostname_country/ip_country match' + } + + @@payment_solution = { + apple_pay: '001', + google_pay: '012' } # These are the options that can be used when creating a new CyberSource @@ -121,7 +180,6 @@ def capture(money, authorization, options = {}) commit(build_capture_request(money, authorization, options), :capture, money, options) end - # options[:pinless_debit_card] => true # attempts to process as pinless debit card 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) @@ -135,16 +193,22 @@ def refund(money, identification, options = {}) commit(build_refund_request(money, identification, options), :refund, money, options) end + def adjust(money, authorization, options = {}) + commit(build_adjust_request(money, authorization, options), :adjust, money, options) + end + def verify(payment, options = {}) + amount = eligible_for_zero_auth?(payment, options) ? 0 : 100 MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, payment, options) } - r.process(:ignore_result) { void(r.authorization, options) } + r.process { authorize(amount, payment, options) } + r.process(:ignore_result) { void(r.authorization, options) } unless amount == 0 end end - # Adds credit to a subscription (stand alone credit). - def credit(money, reference, options = {}) - commit(build_credit_request(money, reference, options), :credit, money, options) + # Adds credit to a card or subscription (stand alone credit). + def credit(money, creditcard_or_reference, options = {}) + setup_address_hash(options) + commit(build_credit_request(money, creditcard_or_reference, options), :credit, money, options) end # Stores a customer subscription/profile with type "on-demand". @@ -201,17 +265,11 @@ def retrieve(reference, options = {}) # This functionality is only supported by this particular gateway may # be changed at any time def calculate_tax(creditcard, options) - requires!(options, :line_items) + requires!(options, :line_items) setup_address_hash(options) commit(build_tax_calculation_request(creditcard, options), :calculate_tax, nil, options) end - # Determines if a card can be used for Pinless Debit Card transactions - def validate_pinless_debit_card(creditcard, options = {}) - requires!(options, :order_id) - commit(build_validate_pinless_debit_request(creditcard, options), :validate_pinless_debit_card, nil, options) - end - def supports_scrubbing? true end @@ -237,40 +295,75 @@ def verify_credentials private - # Create all address hash key value pairs so that we still function if we - # were only provided with one or two of them or even none + # 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 def setup_address_hash(options) default_address = { - :address1 => 'Unspecified', - :city => 'Unspecified', - :state => 'NC', - :zip => '00000', - :country => 'US' + address1: 'Unspecified', + city: 'Unspecified', + state: 'NC', + zip: '00000', + country: 'US' } - options[:billing_address] = options[:billing_address] || options[:address] || default_address + + submitted_address = options[:billing_address] || options[:address] || default_address + options[:billing_address] = default_address.merge(submitted_address.symbolize_keys) { |_k, default, submitted| check_billing_field_value(default, submitted) } options[:shipping_address] = options[:shipping_address] || {} end + def check_billing_field_value(default, submitted) + if submitted.nil? + nil + elsif submitted.blank? + default + else + submitted + end + end + def build_auth_request(money, creditcard_or_reference, options) - xml = Builder::XmlMarkup.new :indent => 2 + 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_decision_manager_fields(xml, options) add_mdd_fields(xml, options) add_auth_service(xml, creditcard_or_reference, options) add_threeds_services(xml, options) - add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference) add_business_rules_data(xml, creditcard_or_reference, options) + add_airline_data(xml, options) + add_sales_slip_number(xml, options) + add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference) + add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference) + add_tax_management_indicator(xml, options) + add_stored_credential_subsequent_auth(xml, options) + add_issuer_additional_data(xml, options) + add_partner_solution_id(xml) + add_stored_credential_options(xml, options) + add_merchant_description(xml, options) + xml.target! + end + + def build_adjust_request(money, authorization, options) + _, request_id = authorization.split(';') + + xml = Builder::XmlMarkup.new indent: 2 + add_purchase_data(xml, money, true, options) + add_incremental_auth_service(xml, request_id, options) xml.target! end def build_tax_calculation_request(creditcard, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_address(xml, creditcard, options[:billing_address], options, false) add_address(xml, creditcard, options[:shipping_address], options, true) add_line_item_data(xml, options) add_purchase_data(xml, 0, false, options) add_tax_service(xml) add_business_rules_data(xml, creditcard, options) + add_tax_management_indicator(xml, options) xml.target! end @@ -278,40 +371,78 @@ def build_capture_request(money, authorization, options) order_id, request_id, request_token = authorization.split(';') options[:order_id] = order_id - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_purchase_data(xml, money, true, options) - add_capture_service(xml, request_id, request_token) + add_other_tax(xml, options) + add_mdd_fields(xml, options) + add_capture_service(xml, request_id, request_token, options) add_business_rules_data(xml, authorization, options) + add_tax_management_indicator(xml, options) + add_issuer_additional_data(xml, options) + add_merchant_description(xml, options) + add_partner_solution_id(xml) + xml.target! end def build_purchase_request(money, payment_method_or_reference, options) - xml = Builder::XmlMarkup.new :indent => 2 + 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_decision_manager_fields(xml, options) add_mdd_fields(xml, options) - if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check' + 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) + add_airline_data(xml, options) + add_sales_slip_number(xml, options) + add_tax_management_indicator(xml, options) + add_issuer_additional_data(xml, options) + add_partner_solution_id(xml) + options[:payment_method] = :check else add_purchase_service(xml, payment_method_or_reference, options) add_threeds_services(xml, 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_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card] + add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference) + add_tax_management_indicator(xml, options) + add_stored_credential_subsequent_auth(xml, options) + add_issuer_additional_data(xml, options) + add_partner_solution_id(xml) + add_stored_credential_options(xml, options) + options[:payment_method] = :credit_card end + + add_merchant_description(xml, options) + xml.target! end + def reference_is_a_check?(payment_method_or_reference) + payment_method_or_reference.is_a?(String) && payment_method_or_reference.split(';')[7] == 'check' + end + def build_void_request(identification, options) - order_id, request_id, request_token, action, money, currency = identification.split(';') + order_id, request_id, request_token, action, money, currency = identification.split(';') options[:order_id] = order_id - xml = Builder::XmlMarkup.new :indent => 2 - if action == 'capture' + xml = Builder::XmlMarkup.new indent: 2 + case action + when 'capture', 'purchase' + add_mdd_fields(xml, options) add_void_service(xml, request_id, request_token) else - add_purchase_data(xml, money, true, options.merge(:currency => currency || default_currency)) + add_purchase_data(xml, money, true, options.merge(currency: currency || default_currency)) + add_mdd_fields(xml, options) add_auth_reversal_service(xml, request_id, request_token) end + add_issuer_additional_data(xml, options) + add_partner_solution_id(xml) + xml.target! end @@ -319,38 +450,45 @@ def build_refund_request(money, identification, options) order_id, request_id, request_token = identification.split(';') options[:order_id] = order_id - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_purchase_data(xml, money, true, options) - add_credit_service(xml, request_id, request_token) + add_credit_service(xml, request_id: request_id, + request_token: request_token, + use_check_service: reference_is_a_check?(identification)) + add_partner_solution_id(xml) xml.target! end - def build_credit_request(money, reference, options) - xml = Builder::XmlMarkup.new :indent => 2 + def build_credit_request(money, creditcard_or_reference, options) + xml = Builder::XmlMarkup.new indent: 2 - add_purchase_data(xml, money, true, options) - add_subscription(xml, options, reference) - add_credit_service(xml) + add_payment_method_or_subscription(xml, money, creditcard_or_reference, options) + add_mdd_fields(xml, options) + add_credit_service(xml, use_check_service: creditcard_or_reference.is_a?(Check)) + add_issuer_additional_data(xml, options) + add_merchant_description(xml, options) xml.target! end def build_create_subscription_request(payment_method, options) - default_subscription_params = {:frequency => 'on-demand', :amount => 0, :automatic_renew => false} + default_subscription_params = { frequency: 'on-demand', amount: 0, automatic_renew: false } options[:subscription] = default_subscription_params.update( options[:subscription] || {} ) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_address(xml, payment_method, options[:billing_address], options) add_purchase_data(xml, options[:setup_fee] || 0, true, options) if card_brand(payment_method) == 'check' - add_check(xml, payment_method) + add_check(xml, payment_method, options) add_check_payment_method(xml) + options[:payment_method] = :check else add_creditcard(xml, payment_method) add_creditcard_payment_method(xml) + options[:payment_method] = :credit_card end add_subscription(xml, options) if options[:setup_fee] @@ -363,11 +501,12 @@ def build_create_subscription_request(payment_method, options) end add_subscription_create_service(xml, options) add_business_rules_data(xml, payment_method, options) + add_tax_management_indicator(xml, options) xml.target! end def build_update_subscription_request(reference, creditcard, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_address(xml, creditcard, options[:billing_address], options) unless options[:billing_address].blank? add_purchase_data(xml, options[:setup_fee], true, options) unless options[:setup_fee].blank? add_creditcard(xml, creditcard) if creditcard @@ -375,38 +514,30 @@ def build_update_subscription_request(reference, creditcard, options) add_subscription(xml, options, reference) add_subscription_update_service(xml, options) add_business_rules_data(xml, creditcard, options) + add_tax_management_indicator(xml, options) xml.target! end def build_delete_subscription_request(reference, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_subscription(xml, options, reference) add_subscription_delete_service(xml, options) xml.target! end def build_retrieve_subscription_request(reference, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 add_subscription(xml, options, reference) add_subscription_retrieve_service(xml, options) xml.target! end - def build_validate_pinless_debit_request(creditcard, options) - xml = Builder::XmlMarkup.new :indent => 2 - add_creditcard(xml, creditcard) - add_validate_pinless_debit_service(xml) - xml.target! - end - def add_business_rules_data(xml, payment_method, options) prioritized_options = [options, @options] - unless network_tokenization?(payment_method) - xml.tag! 'businessRules' do - xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs) - xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv) - end + 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 @@ -418,38 +549,99 @@ def extract_option(prioritized_options, option_name) end def add_line_item_data(xml, options) + return unless options[:line_items] + options[:line_items].each_with_index do |value, index| - xml.tag! 'item', {'id' => index} do + xml.tag! 'item', { 'id' => index } do xml.tag! 'unitPrice', localized_amount(value[:declared_value].to_i, options[:currency] || default_currency) xml.tag! 'quantity', value[:quantity] xml.tag! 'productCode', value[:code] || 'shipping_only' xml.tag! 'productName', value[:description] xml.tag! 'productSKU', value[:sku] + xml.tag! 'taxAmount', value[:tax_amount] if value[:tax_amount] + xml.tag! 'nationalTax', value[:national_tax] if value[:national_tax] end end 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 + xml.tag! 'clientLibraryVersion', VERSION xml.tag! 'clientEnvironment', RUBY_PLATFORM + + add_merchant_descriptor(xml, options) + end + + def add_merchant_descriptor(xml, options) + return unless options[:merchant_descriptor] || options[:user_po] || options[:taxable] || options[:reference_data_code] || options[:invoice_number] + + xml.tag! 'invoiceHeader' do + xml.tag! 'merchantDescriptor', options[:merchant_descriptor] if options[:merchant_descriptor] + xml.tag! 'userPO', options[:user_po] if options[:user_po] + xml.tag! 'taxable', options[:taxable] if options[:taxable] + xml.tag! 'referenceDataCode', options[:reference_data_code] if options[:reference_data_code] + xml.tag! 'invoiceNumber', options[:invoice_number] if options[:invoice_number] + end + end + + def add_customer_id(xml, options) + return unless options[:customer_id] + + xml.tag! 'customerID', options[:customer_id] + end + + def add_merchant_description(xml, options) + return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality] + + xml.tag! 'merchantInformation' do + xml.tag! 'merchantDescriptor' do + xml.tag! 'name', options[:merchant_descriptor_name] if options[:merchant_descriptor_name] + xml.tag! 'address1', options[:merchant_descriptor_address1] if options[:merchant_descriptor_address1] + xml.tag! 'locality', options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality] + end + end + end + + def add_sales_slip_number(xml, options) + xml.tag! 'salesSlipNumber', options[:sales_slip_number] if options[:sales_slip_number] + end + + def add_airline_data(xml, options) + return unless options[:airline_agent_code] + + xml.tag! 'airlineData' do + xml.tag! 'agentCode', options[:airline_agent_code] + end end - def add_purchase_data(xml, money = 0, include_grand_total = false, options={}) + def add_tax_management_indicator(xml, options) + return unless options[:tax_management_indicator] + + xml.tag! 'taxManagementIndicator', options[:tax_management_indicator] if options[:tax_management_indicator] + end + + def add_purchase_data(xml, money = 0, include_grand_total = false, options = {}) xml.tag! 'purchaseTotals' do xml.tag! 'currency', options[:currency] || currency(money) - xml.tag!('grandTotalAmount', localized_amount(money.to_i, options[:currency] || default_currency)) if include_grand_total + xml.tag!('discountManagementIndicator', options[:discount_management_indicator]) if options[:discount_management_indicator] + xml.tag!('taxAmount', options[:purchase_tax_amount]) if options[:purchase_tax_amount] + xml.tag!('grandTotalAmount', localized_amount(money.to_i, options[:currency] || default_currency)) if include_grand_total + xml.tag!('originalAmount', options[:original_amount]) if options[:original_amount] + xml.tag!('invoiceAmount', options[:invoice_amount]) if options[:invoice_amount] end end def add_address(xml, payment_method, address, options, shipTo = false) + first_name, last_name = address_names(address[:name], payment_method) + bill_to_merchant_tax_id = options[:merchant_tax_id] unless shipTo + xml.tag! shipTo ? 'shipTo' : 'billTo' do - xml.tag! 'firstName', payment_method.first_name if payment_method - xml.tag! 'lastName', payment_method.last_name if payment_method + xml.tag! 'firstName', first_name if first_name + xml.tag! 'lastName', last_name if last_name xml.tag! 'street1', address[:address1] - xml.tag! 'street2', address[:address2] unless address[:address2].blank? + xml.tag! 'street2', address[:address2] unless address[:address2].blank? xml.tag! 'city', address[:city] xml.tag! 'state', address[:state] xml.tag! 'postalCode', address[:zip] @@ -457,19 +649,30 @@ def add_address(xml, payment_method, address, options, shipTo = false) xml.tag! 'company', address[:company] unless address[:company].blank? xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank? xml.tag! 'phoneNumber', address[:phone] unless address[:phone].blank? - xml.tag! 'email', options[:email] || 'null@cybersource.com' + xml.tag! 'email', options[:email].presence || 'null@cybersource.com' xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank? xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank? + xml.tag! 'merchantTaxID', bill_to_merchant_tax_id unless bill_to_merchant_tax_id.blank? 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 add_creditcard(xml, creditcard) xml.tag! 'card' do xml.tag! 'accountNumber', creditcard.number xml.tag! 'expirationMonth', format(creditcard.month, :two_digits) xml.tag! 'expirationYear', format(creditcard.year, :four_digits) - xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank? + xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv].to_s == 'true' || creditcard.verification_value.blank? xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym] end end @@ -483,27 +686,53 @@ def add_decision_manager_fields(xml, options) end end + def add_payment_solution(xml, source) + return unless (payment_solution = @@payment_solution[source]) + + xml.tag! 'paymentSolution', payment_solution + end + + def add_issuer_additional_data(xml, options) + return unless options[:issuer_additional_data] + + xml.tag! 'issuer' do + xml.tag! 'additionalData', options[:issuer_additional_data] + end + end + + def add_other_tax(xml, options) + return unless options[:local_tax_amount] || options[:national_tax_amount] || options[:national_tax_indicator] + + xml.tag! 'otherTax' do + xml.tag! 'vatTaxRate', options[:vat_tax_rate] if options[:vat_tax_rate] + xml.tag! 'localTaxAmount', options[:local_tax_amount] if options[:local_tax_amount] + xml.tag! 'nationalTaxAmount', options[:national_tax_amount] if options[:national_tax_amount] + xml.tag! 'nationalTaxIndicator', options[:national_tax_indicator] if options[:national_tax_indicator] + end + end + def add_mdd_fields(xml, options) - return unless options.keys.any? { |key| key.to_s.start_with?('mdd_field') } + return unless options.keys.any? { |key| key.to_s.start_with?('mdd_field') && options[key] } xml.tag! 'merchantDefinedData' do (1..100).each do |each| key = "mdd_field_#{each}".to_sym - xml.tag!("field#{each}", options[key]) if options[key] + xml.tag!('mddField', options[key], 'id' => each) if options[key] end end end - def add_check(xml, check) + 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 def add_tax_service(xml) - xml.tag! 'taxService', {'run' => 'true'} do + xml.tag! 'taxService', { 'run' => 'true' } do xml.tag!('nexus', @options[:nexus]) unless @options[:nexus].blank? xml.tag!('sellerRegistration', @options[:vat_reg_number]) unless @options[:vat_reg_number].blank? end @@ -513,7 +742,85 @@ def add_auth_service(xml, payment_method, options) if network_tokenization?(payment_method) add_auth_network_tokenization(xml, payment_method, options) else - xml.tag! 'ccAuthService', {'run' => 'true'} + 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!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id] + 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 + end + xml.tag! 'subsequentAuthReason', options[:auth_reason] + end + + def add_normalized_threeds_2_data(xml, payment_method, options) + threeds_2_options = options[:three_d_secure] + cc_brand = card_brand(payment_method).to_sym + + return if threeds_2_options[:cavv].blank? && infer_commerce_indicator?(options, cc_brand) + + xid = threeds_2_options[:xid] + + xml.tag!('cavv', threeds_2_options[:cavv]) if threeds_2_options[:cavv] && cc_brand != :master + xml.tag!('cavvAlgorithm', threeds_2_options[:cavv_algorithm]) if threeds_2_options[:cavv_algorithm] + xml.tag!('paSpecificationVersion', threeds_2_options[:version]) if threeds_2_options[:version] + xml.tag!('directoryServerTransactionID', threeds_2_options[:ds_transaction_id]) if threeds_2_options[:ds_transaction_id] + xml.tag!('commerceIndicator', options[:commerce_indicator] || ECI_BRAND_MAPPING[cc_brand]) + xml.tag!('eciRaw', threeds_2_options[:eci]) if threeds_2_options[:eci] + + if xid.present? + xml.tag!('xid', xid) + elsif threeds_2_options[:version]&.start_with?('2') && cc_brand != :master + cavv = threeds_2_options[:cavv] + xml.tag!('xid', cavv) if cavv.present? + end + + xml.tag!('veresEnrolled', threeds_2_options[:enrolled]) if threeds_2_options[:enrolled] + xml.tag!('paresStatus', threeds_2_options[:authentication_response_status]) if threeds_2_options[:authentication_response_status] + end + + def infer_commerce_indicator?(options, cc_brand) + options[:commerce_indicator].blank? && ECI_BRAND_MAPPING[cc_brand].present? + end + + def add_threeds_2_ucaf_data(xml, payment_method, options) + return unless options[:three_d_secure] && card_brand(payment_method).to_sym == :master + + xml.tag! 'ucaf' do + xml.tag!('authenticationData', options[:three_d_secure][:cavv]) + xml.tag!('collectionIndicator', options[:collection_indicator] || DEFAULT_COLLECTION_INDICATOR) + end + end + + def stored_credential_commerce_indicator(options) + return unless options[:stored_credential] + + return if options[:stored_credential][:initial_transaction] + + case options[:stored_credential][:reason_type] + when 'installment' then 'install' + when 'recurring' then 'recurring' end end @@ -521,31 +828,47 @@ 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 @@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) - case card_brand(payment_method).to_sym + 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', 'vbv') - xml.tag!('xid', payment_method.payment_cryptogram) + xml.tag! 'ccAuthService', { 'run' => 'true' } do + 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 :mastercard + when :master xml.tag! 'ucaf' do - xml.tag!('authenticationData', payment_method.payment_cryptogram) - xml.tag!('collectionIndicator', '2') + xml.tag!('authenticationData', payment_method.payment_cryptogram) unless commerce_indicator + xml.tag!('collectionIndicator', DEFAULT_COLLECTION_INDICATOR) end - xml.tag! 'ccAuthService', {'run' => 'true'} do - xml.tag!('commerceIndicator', 'spa') + xml.tag! 'ccAuthService', { 'run' => 'true' } do + 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 cryptogram = Base64.decode64(payment_method.payment_cryptogram) - xml.tag! 'ccAuthService', {'run' => 'true'} do + xml.tag! 'ccAuthService', { 'run' => 'true' } do xml.tag!('cavv', Base64.encode64(cryptogram[0...20])) - xml.tag!('commerceIndicator', 'aesk') - xml.tag!('xid', Base64.encode64(cryptogram[20...40])) + xml.tag!('commerceIndicator', ECI_BRAND_MAPPING[brand]) + 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 @@ -555,61 +878,65 @@ def add_payment_network_token(xml) end end - def add_capture_service(xml, request_id, request_token) - xml.tag! 'ccCaptureService', {'run' => 'true'} do + def add_capture_service(xml, request_id, request_token, options) + xml.tag! 'ccCaptureService', { 'run' => 'true' } do xml.tag! 'authRequestID', request_id xml.tag! 'authRequestToken', request_token + xml.tag! 'gratuityAmount', options[:gratuity_amount] if options[:gratuity_amount] + xml.tag! 'reconciliationID', options[:reconciliation_id] if options[:reconciliation_id] end end def add_purchase_service(xml, payment_method, options) - if options[:pinless_debit_card] - xml.tag! 'pinlessDebitService', {'run' => 'true'} - else - add_auth_service(xml, payment_method, options) - xml.tag! 'ccCaptureService', {'run' => 'true'} + add_auth_service(xml, payment_method, options) + xml.tag! 'ccCaptureService', { 'run' => 'true' } do + xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id] end end def add_void_service(xml, request_id, request_token) - xml.tag! 'voidService', {'run' => 'true'} do + xml.tag! 'voidService', { 'run' => 'true' } do xml.tag! 'voidRequestID', request_id xml.tag! 'voidRequestToken', request_token end end def add_auth_reversal_service(xml, request_id, request_token) - xml.tag! 'ccAuthReversalService', {'run' => 'true'} do + xml.tag! 'ccAuthReversalService', { 'run' => 'true' } do xml.tag! 'authRequestID', request_id xml.tag! 'authRequestToken', request_token end end - def add_credit_service(xml, request_id = nil, request_token = nil) - xml.tag! 'ccCreditService', {'run' => 'true'} do - xml.tag! 'captureRequestID', request_id if request_id - xml.tag! 'captureRequestToken', request_token if request_token + def add_credit_service(xml, options = {}) + service = options[:use_check_service] ? 'ecCreditService' : 'ccCreditService' + request_tag = options[:use_check_service] ? 'debitRequestID' : 'captureRequestID' + options.delete :request_token if options[:use_check_service] + + xml.tag! service, { 'run' => 'true' } do + xml.tag! request_tag, options[:request_id] if options[:request_id] + xml.tag! 'captureRequestToken', options[:request_token] if options[:request_token] end end def add_check_service(xml) - xml.tag! 'ecDebitService', {'run' => 'true'} + xml.tag! 'ecDebitService', { 'run' => 'true' } end def add_subscription_create_service(xml, options) - xml.tag! 'paySubscriptionCreateService', {'run' => 'true'} + xml.tag! 'paySubscriptionCreateService', { 'run' => 'true' } end def add_subscription_update_service(xml, options) - xml.tag! 'paySubscriptionUpdateService', {'run' => 'true'} + xml.tag! 'paySubscriptionUpdateService', { 'run' => 'true' } end def add_subscription_delete_service(xml, options) - xml.tag! 'paySubscriptionDeleteService', {'run' => 'true'} + xml.tag! 'paySubscriptionDeleteService', { 'run' => 'true' } end def add_subscription_retrieve_service(xml, options) - xml.tag! 'paySubscriptionRetrieveService', {'run' => 'true'} + xml.tag! 'paySubscriptionRetrieveService', { 'run' => 'true' } end def add_subscription(xml, options, reference = nil) @@ -621,7 +948,7 @@ def add_subscription(xml, options, reference = nil) xml.tag! 'subscriptionID', subscription_id end - xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status] + xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status] xml.tag! 'amount', localized_amount(options[:subscription][:amount].to_i, options[:currency] || default_currency) if options[:subscription][:amount] xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences] xml.tag! 'automaticRenew', options[:subscription][:automatic_renew] if options[:subscription][:automatic_renew] @@ -649,27 +976,40 @@ def add_check_payment_method(xml) def add_payment_method_or_subscription(xml, money, payment_method_or_reference, options) if payment_method_or_reference.is_a?(String) add_purchase_data(xml, money, true, options) + add_installments(xml, options) add_subscription(xml, options, payment_method_or_reference) elsif card_brand(payment_method_or_reference) == 'check' add_address(xml, payment_method_or_reference, options[:billing_address], options) add_purchase_data(xml, money, true, options) - add_check(xml, payment_method_or_reference) + add_installments(xml, options) + add_check(xml, payment_method_or_reference, options) else add_address(xml, payment_method_or_reference, options[:billing_address], options) add_address(xml, payment_method_or_reference, options[:shipping_address], options, true) + add_line_item_data(xml, options) add_purchase_data(xml, money, true, options) + add_installments(xml, options) add_creditcard(xml, payment_method_or_reference) end end - def add_validate_pinless_debit_service(xml) - xml.tag! 'pinlessDebitValidateService', {'run' => 'true'} + def add_installments(xml, options) + return unless %i[installment_total_count installment_total_amount installment_plan_type first_installment_date installment_annual_interest_rate installment_grace_period_duration].any? { |gsf| options.include?(gsf) } + + xml.tag! 'installment' do + xml.tag!('totalCount', options[:installment_total_count]) if options[:installment_total_count] + xml.tag!('totalAmount', options[:installment_total_amount]) if options[:installment_total_amount] + xml.tag!('planType', options[:installment_plan_type]) if options[:installment_plan_type] + xml.tag!('firstInstallmentDate', options[:first_installment_date]) if options[:first_installment_date] + xml.tag!('annualInterestRate', options[:installment_annual_interest_rate]) if options[:installment_annual_interest_rate] + xml.tag!('gracePeriodDuration', options[:installment_grace_period_duration]) if options[:installment_grace_period_duration] + end end def add_threeds_services(xml, options) - xml.tag! 'payerAuthEnrollService', {'run' => 'true'} if options[:payer_auth_enroll_service] + xml.tag! 'payerAuthEnrollService', { 'run' => 'true' } if options[:payer_auth_enroll_service] if options[:payer_auth_validate_service] - xml.tag! 'payerAuthValidateService', {'run' => 'true'} do + xml.tag! 'payerAuthValidateService', { 'run' => 'true' } do xml.tag! 'signedPARes', options[:pares] end end @@ -680,21 +1020,67 @@ def lookup_country_code(country_field) country_code&.code(:alpha2) end + def add_stored_credential_subsequent_auth(xml, options = {}) + return unless options[:stored_credential] || options[:stored_credential_overrides] + + stored_credential_subsequent_auth = 'true' if options.dig(:stored_credential, :initiator) == 'merchant' + + override_subsequent_auth = options.dig(:stored_credential_overrides, :subsequent_auth) + + xml.subsequentAuth override_subsequent_auth.nil? ? stored_credential_subsequent_auth : override_subsequent_auth + end + + def add_stored_credential_options(xml, options = {}) + return unless options[:stored_credential] || options[:stored_credential_overrides] + + 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 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) + override_subsequent_auth_stored_cred = options.dig(:stored_credential_overrides, :subsequent_auth_stored_credential) + + xml.subsequentAuthFirst override_subsequent_auth_first.nil? ? stored_credential_subsequent_auth_first : override_subsequent_auth_first + xml.subsequentAuthTransactionID override_subsequent_auth_transaction_id.nil? ? stored_credential_transaction_id : override_subsequent_auth_transaction_id + 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 + + xml.tag!('partnerSolutionID', application_id) + end + # Where we actually build the full SOAP request using builder def build_request(body, options) - xml = Builder::XmlMarkup.new :indent => 2 + xsd_version = test? ? TEST_XSD_VERSION : PRODUCTION_XSD_VERSION + + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do + xml.tag! 's:Envelope', { 'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/' } do xml.tag! 's:Header' do - xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do + xml.tag! 'wsse:Security', { 's:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' } do xml.tag! 'wsse:UsernameToken' do xml.tag! 'wsse:Username', @options[:login] xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText' end end end - xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do - xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do + xml.tag! 's:Body', { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema' } do + xml.tag! 'requestMessage', { 'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{xsd_version}" } do add_merchant_data(xml, options) xml << body end @@ -718,17 +1104,30 @@ def commit(request, action, amount, options) response = { message: e.to_s } end - success = response[:decision] == 'ACCEPT' - message = response[:message] + success = success?(response) + message = message_from(response) + authorization = success || in_fraud_review?(response) ? authorization_from(response, action, amount, options) : nil + + 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] + ) + end - authorization = success ? authorization_from(response, action, amount, options) : nil + def auto_void?(authorization, response, message, options = {}) + return message unless response[:reasonCode] == '230' && options[:auto_void_230] - Response.new(success, message, response, - :test => test?, - :authorization => authorization, - :avs_result => { :code => response[:avsCode] }, - :cvv_result => response[:cvCode] - ) + 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 @@ -757,7 +1156,7 @@ def parse_element(reply, node) if node.has_elements? node.elements.each { |e| parse_element(reply, e) } else - if node.parent.name =~ /item/ + if /item/.match?(node.parent.name) parent = node.parent.name parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id'] parent += '_' @@ -769,12 +1168,39 @@ def parse_element(reply, node) def reason_message(reason_code) return if reason_code.blank? + @@response_codes[:"r#{reason_code}"] end def authorization_from(response, action, amount, options) [options[:order_id], response[:requestID], response[:requestToken], action, amount, - options[:currency], response[:subscriptionID]].join(';') + options[:currency], response[:subscriptionID], options[:payment_method]].join(';') + end + + def in_fraud_review?(response) + response[:decision] == @@decision_codes[:review] + end + + def success?(response) + response[:decision] == @@decision_codes[:accept] + end + + def message_from(response) + if response[:reasonCode] == '101' && response[:missingField] + "#{response[:message]}: #{response[:missingField]}" + elsif response[:reasonCode] == '102' && response[:invalidField] + "#{response[:message]}: #{response[:invalidField]}" + else + response[:message] + end + end + + 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 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..28c4d9d6f12 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cyber_source_rest.rb @@ -0,0 +1,454 @@ +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' + } + + PAYMENT_SOLUTION = { + apple_pay: '001', + google_pay: '012' + } + + 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(/(signature=")[^"]*/, '\1[FILTERED]'). + gsub(/(keyid=")[^"]*/, '\1[FILTERED]'). + gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]') + end + + private + + 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) + 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][:paymentSolution] = PAYMENT_SOLUTION[payment.source] + post[:processingInformation][:commerceIndicator] = 'internet' unless card_brand(payment) == 'jcb' + + post[:paymentInformation][:tokenizedCard] = { + number: payment.number, + expirationMonth: payment.month, + expirationYear: payment.year, + cryptogram: payment.payment_cryptogram, + transactionType: '1', + type: CREDIT_CARD_CODES[card_brand(payment).to_sym] + } + + if card_brand(payment) == 'master' + post[:consumerAuthenticationInformation] = { + ucafAuthenticationData: payment.payment_cryptogram, + ucafCollectionIndicator: '2' + } + else + post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram } + 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 stored_credential = options[:stored_credential] + + options = stored_credential_options(stored_credential, options.fetch(:reason_code, '')) + post[:processingInformation][:commerceIndicator] = options.fetch(:transaction_type, 'internet') + stored_credential[:initial_transaction] ? initial_transaction(post, options) : subsequent_transaction(post, options) + end + + def stored_credential_options(options, reason_code) + transaction_type = options[:reason_type] + transaction_type = 'install' if transaction_type == 'installment' + initiator = options[:initiator] if options[:initiator] + initiator = 'customer' if initiator == 'cardholder' + stored_on_file = options[:reason_type] == 'recurring' + options.merge({ + transaction_type: transaction_type, + initiator: initiator, + reason_code: reason_code, + stored_on_file: stored_on_file + }) + end + + def add_processing_information(initiator, merchant_initiated_transaction_hash = {}) + { + authorizationOptions: { + initiator: { + type: initiator, + merchantInitiatedTransaction: merchant_initiated_transaction_hash, + storedCredentialUsed: true + } + } + }.compact + end + + def initial_transaction(post, options) + processing_information = add_processing_information(options[:initiator], { + reason: options[:reason_code] + }) + + post[:processingInformation].merge!(processing_information) + end + + def subsequent_transaction(post, options) + network_transaction_id = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id) || '' + processing_information = add_processing_information(options[:initiator], { + originalAuthorizedAmount: post.dig(:orderInformation, :amountDetails, :totalAmount), + previousTransactionID: network_transaction_id, + reason: options[:reason_code], + storedCredentialUsed: options[:stored_on_file] + }) + post[:processingInformation].merge!(processing_information) + 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] = { 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 2218501942a..6a172e77ee4 100644 --- a/lib/active_merchant/billing/gateways/d_local.rb +++ b/lib/active_merchant/billing/gateways/d_local.rb @@ -4,40 +4,43 @@ class DLocalGateway < Gateway self.test_url = 'https://sandbox.dlocal.com' self.live_url = 'https://api.dlocal.com' - self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY', 'TR'] + self.supported_countries = %w[AR BD BO BR CL CM CN CO CR DO EC EG GH GT IN ID JP KE MY MX MA NG PA PY PE PH SN SV TH TR TZ UG UY VN ZA] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal elo alia carnet] self.homepage_url = 'https://dlocal.com/' self.display_name = 'dLocal' - def initialize(options={}) + def initialize(options = {}) requires!(options, :login, :trans_key, :secret_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_auth_purchase_params(post, money, payment, 'purchase', options) + add_three_ds(post, options) commit('purchase', post, options) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_auth_purchase_params(post, money, payment, 'authorize', options) + add_three_ds(post, options) + post[:card][:verify] = true if options[:verify].to_s == 'true' commit('authorize', post, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} - post[:payment_id] = authorization + post[:authorization_id] = authorization add_invoice(post, money, options) if money commit('capture', post, options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} post[:payment_id] = authorization post[:notification_url] = options[:notification_url] @@ -45,23 +48,31 @@ def refund(money, authorization, options={}) commit('refund', post, options) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} - post[:payment_id] = authorization + post[:authorization_id] = authorization commit('void', post, options) end - def verify(credit_card, options={}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } - end + def verify(credit_card, options = {}) + authorize(0, credit_card, options.merge(verify: 'true')) + end + + def inquire(authorization, options = {}) + post = {} + post[:payment_id] = authorization + action = authorization ? 'status' : 'orders' + commit(action, post, options) end def supports_scrubbing? true end + def supports_network_tokenization? + true + end + def scrub(transcript) transcript. gsub(%r((X-Trans-Key: )\w+), '\1[FILTERED]'). @@ -78,7 +89,9 @@ def add_auth_purchase_params(post, money, card, action, options) add_country(post, card, options) add_payer(post, card, options) add_card(post, card, action, options) + add_additional_data(post, options) post[:order_id] = options[:order_id] || generate_unique_id + post[:original_order_id] = options[:original_order_id] if options[:original_order_id] post[:description] = options[:description] if options[:description] end @@ -87,13 +100,20 @@ def add_invoice(post, money, options) post[:currency] = (options[:currency] || currency(money)) end + def add_additional_data(post, options) + post[:additional_risk_data] = options[:additional_data] + end + def add_country(post, card, options) return unless address = options[:billing_address] || options[:address] + post[:country] = lookup_country_code(address[:country]) end - def lookup_country_code(country) - Country.find(country).code(:alpha2) + def lookup_country_code(country_field) + Country.find(country_field).code(:alpha2).value + rescue InvalidCountryCodeError + nil end def add_payer(post, card, options) @@ -106,40 +126,87 @@ def add_payer(post, card, options) post[:payer][:document] = options[:document] if options[:document] post[:payer][:document2] = options[:document2] if options[:document2] post[:payer][:user_reference] = options[:user_reference] if options[:user_reference] + post[:payer][:event_uuid] = options[:device_id] if options[:device_id] + post[:payer][:onboarding_ip_address] = options[:ip] if options[:ip] post[:payer][:address] = add_address(post, card, options) end def add_address(post, card, options) return unless address = options[:billing_address] || options[:address] + address_object = {} address_object[:state] = address[:state] if address[:state] address_object[:city] = address[:city] if address[:city] - address_object[:zip_code] = address[:zip_code] if address[:zip_code] - address_object[:street] = address[:street] if address[:street] - address_object[:number] = address[:number] if address[:number] + address_object[:zip_code] = address[:zip] if address[:zip] + address_object[:street] = address[:street] || parse_street(address) if parse_street(address) + address_object[:number] = address[:number] || parse_house_number(address) if parse_house_number(address) address_object end - def add_card(post, card, action, options={}) + def parse_street(address) + return unless address[:address1] + + street = address[:address1].split(/\s+/).keep_if { |x| x !~ /\d/ }.join(' ') + street.empty? ? nil : street + end + + def parse_house_number(address) + return unless address[:address1] + + house = address[:address1].split(/\s+/).keep_if { |x| x =~ /\d/ }.join(' ') + house.empty? ? nil : house + end + + def add_card(post, card, action, options = {}) post[:card] = {} + if card.is_a?(NetworkTokenizationCreditCard) + 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 + post[:card][:holder_name] = card.name post[:card][:expiration_month] = card.month post[:card][:expiration_year] = card.year - post[:card][:number] = card.number - post[:card][:cvv] = card.verification_value post[:card][:descriptor] = options[:dynamic_descriptor] if options[:dynamic_descriptor] post[:card][:capture] = (action == 'purchase') + 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 parse(body) JSON.parse(body) end - def commit(action, parameters, options={}) + def commit(action, parameters, options = {}) + three_ds_errors = validate_three_ds_params(parameters[:three_dsecure]) if parameters[:three_dsecure].present? + return three_ds_errors if three_ds_errors + url = url(action, parameters, options) post = post_data(action, parameters) begin - raw = ssl_post(url, post, headers(post, options)) + raw = if %w(status orders).include?(action) + ssl_get(url, headers(nil, options)) + else + ssl_post(url, post, headers(post, options)) + end response = parse(raw) rescue ResponseError => e raw = e.response.body @@ -163,7 +230,8 @@ def commit(action, parameters, options={}) # we count 100 as a success. def success_from(action, response) return false unless response['status_code'] - ['100', '200', '400', '600'].include? response['status_code'].to_s + + %w[100 200 400 600 700].include? response['status_code'].to_s end def message_from(action, response) @@ -176,11 +244,12 @@ def authorization_from(response) def error_code_from(action, response) return if success_from(action, response) + code = response['status_code'] || response['code'] code&.to_s end - def url(action, parameters, options={}) + def url(action, parameters, options = {}) "#{(test? ? test_url : live_url)}/#{endpoint(action, parameters, options)}/" end @@ -193,22 +262,27 @@ def endpoint(action, parameters, options) when 'refund' 'refunds' when 'capture' - "payments/#{parameters[:payment_id]}/capture" + 'payments' when 'void' - "payments/#{parameters[:payment_id]}/cancel" + "payments/#{parameters[:authorization_id]}/cancel" + when 'status' + "payments/#{parameters[:payment_id]}/status" + when 'orders' + "orders/#{options[:order_id]}" end end - def headers(post, options={}) + def headers(post, options = {}) timestamp = Time.now.utc.iso8601 headers = { 'Content-Type' => 'application/json', 'X-Date' => timestamp, 'X-Login' => @options[:login], 'X-Trans-Key' => @options[:trans_key], + 'X-Version' => '2.1', 'Authorization' => signature(post, timestamp) } - headers.merge('X-Idempotency-Key' => options[:idempotency_key]) if options[:idempotency_key] + headers['X-Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key] headers end @@ -221,6 +295,49 @@ def signature(post, timestamp) def post_data(action, parameters = {}) parameters.to_json end + + def xid_or_ds_trans_id(three_d_secure) + if three_d_secure[:version].to_f >= 2 + { ds_transaction_id: three_d_secure[:ds_transaction_id] } + else + { xid: three_d_secure[:xid] } + end + end + + def add_three_ds(post, options) + return unless three_d_secure = options[:three_d_secure] + + post[:three_dsecure] = { + mpi: true, + three_dsecure_version: three_d_secure[:version], + cavv: three_d_secure[:cavv], + eci: three_d_secure[:eci], + enrollment_response: formatted_enrollment(three_d_secure[:enrolled]), + authentication_response: three_d_secure[:authentication_response_status] + }.merge(xid_or_ds_trans_id(three_d_secure)) + end + + def validate_three_ds_params(three_ds) + errors = {} + supported_version = %w{1.0 2.0 2.1.0 2.2.0}.include?(three_ds[:three_dsecure_version]) + supported_enrollment = ['Y', 'N', 'U', nil].include?(three_ds[:enrollment_response]) + supported_auth_response = ['Y', 'N', 'U', nil].include?(three_ds[:authentication_response]) + + errors[:three_ds_version] = 'ThreeDs version not supported' unless supported_version + errors[:enrollment] = 'Enrollment value not supported' unless supported_enrollment + errors[:auth_response] = 'Authentication response value not supported' unless supported_auth_response + errors.compact! + + errors.present? ? Response.new(false, 'ThreeDs data is invalid', errors) : nil + 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 end end end diff --git a/lib/active_merchant/billing/gateways/data_cash.rb b/lib/active_merchant/billing/gateways/data_cash.rb index def3288babf..b0bbe98f266 100644 --- a/lib/active_merchant/billing/gateways/data_cash.rb +++ b/lib/active_merchant/billing/gateways/data_cash.rb @@ -6,7 +6,7 @@ class DataCashGateway < Gateway self.default_currency = 'GBP' self.supported_countries = ['GB'] - self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro ] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro] self.homepage_url = 'http://www.datacash.com/' self.display_name = 'DataCash' @@ -92,9 +92,9 @@ def scrub(transcript) def build_void_or_capture_request(type, money, authorization, options) parsed_authorization = parse_authorization_string(authorization) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! :Request, :version => '2' do + xml.tag! :Request, version: '2' do add_authentication(xml) xml.tag! :Transaction do @@ -107,7 +107,7 @@ def build_void_or_capture_request(type, money, authorization, options) if money xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) - xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :amount, amount(money), currency: options[:currency] || currency(money) xml.tag! :capturemethod, 'ecomm' end end @@ -117,22 +117,20 @@ def build_void_or_capture_request(type, money, authorization, options) end def build_purchase_or_authorization_request_with_credit_card_request(type, money, credit_card, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! :Request, :version => '2' do + xml.tag! :Request, version: '2' do add_authentication(xml) xml.tag! :Transaction do - if options[:set_up_continuous_authority] - xml.tag! :ContAuthTxn, :type => 'setup' - end + xml.tag! :ContAuthTxn, type: 'setup' if options[:set_up_continuous_authority] xml.tag! :CardTxn do xml.tag! :method, type add_credit_card(xml, credit_card, options[:billing_address]) end xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) - xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :amount, amount(money), currency: options[:currency] || currency(money) xml.tag! :capturemethod, 'ecomm' end end @@ -144,19 +142,19 @@ def build_purchase_or_authorization_request_with_continuous_authority_reference_ parsed_authorization = parse_authorization_string(authorization) raise ArgumentError, 'The continuous authority reference is required for continuous authority transactions' if parsed_authorization[:ca_reference].blank? - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! :Request, :version => '2' do + xml.tag! :Request, version: '2' do add_authentication(xml) xml.tag! :Transaction do - xml.tag! :ContAuthTxn, :type => 'historic' + xml.tag! :ContAuthTxn, type: 'historic' xml.tag! :HistoricTxn do xml.tag! :reference, parsed_authorization[:ca_reference] xml.tag! :method, type end xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) - xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :amount, amount(money), currency: options[:currency] || currency(money) xml.tag! :capturemethod, 'cont_auth' end end @@ -166,9 +164,9 @@ def build_purchase_or_authorization_request_with_continuous_authority_reference_ def build_transaction_refund_request(money, authorization) parsed_authorization = parse_authorization_string(authorization) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! :Request, :version => '2' do + xml.tag! :Request, version: '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :HistoricTxn do @@ -187,9 +185,9 @@ def build_transaction_refund_request(money, authorization) end def build_credit_request(money, credit_card, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! :Request, :version => '2' do + xml.tag! :Request, version: '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :CardTxn do @@ -237,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 @@ -262,9 +260,12 @@ 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, - :test => test?, - :authorization => "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}" + Response.new( + response[:status] == '1', + response[:reason], + response, + test: test?, + authorization: "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}" ) end @@ -298,7 +299,7 @@ def format_reference_number(number) def parse_authorization_string(authorization) reference, auth_code, ca_reference = authorization.to_s.split(';') - {:reference => reference, :auth_code => auth_code, :ca_reference => ca_reference} + { reference: reference, auth_code: auth_code, ca_reference: ca_reference } end end end diff --git a/lib/active_merchant/billing/gateways/decidir.rb b/lib/active_merchant/billing/gateways/decidir.rb new file mode 100644 index 00000000000..e06ce8da3fb --- /dev/null +++ b/lib/active_merchant/billing/gateways/decidir.rb @@ -0,0 +1,384 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DecidirGateway < Gateway + self.test_url = 'https://developers.decidir.com/api/v2' + self.live_url = 'https://live.decidir.com/api/v2' + + self.supported_countries = ['AR'] + self.money_format = :cents + self.default_currency = 'ARS' + self.supported_cardtypes = %i[visa master american_express diners_club naranja cabal] + + self.homepage_url = 'http://www.decidir.com' + self.display_name = 'Decidir' + + STANDARD_ERROR_CODE_MAPPING = { + 1 => STANDARD_ERROR_CODE[:call_issuer], + 2 => STANDARD_ERROR_CODE[:call_issuer], + 3 => STANDARD_ERROR_CODE[:config_error], + 4 => STANDARD_ERROR_CODE[:pickup_card], + 5 => STANDARD_ERROR_CODE[:card_declined], + 7 => STANDARD_ERROR_CODE[:pickup_card], + 12 => STANDARD_ERROR_CODE[:processing_error], + 14 => STANDARD_ERROR_CODE[:invalid_number], + 28 => STANDARD_ERROR_CODE[:processing_error], + 38 => STANDARD_ERROR_CODE[:incorrect_pin], + 39 => STANDARD_ERROR_CODE[:invalid_number], + 43 => STANDARD_ERROR_CODE[:pickup_card], + 45 => STANDARD_ERROR_CODE[:card_declined], + 46 => STANDARD_ERROR_CODE[:invalid_number], + 47 => STANDARD_ERROR_CODE[:card_declined], + 48 => STANDARD_ERROR_CODE[:card_declined], + 49 => STANDARD_ERROR_CODE[:invalid_expiry_date], + 51 => STANDARD_ERROR_CODE[:card_declined], + 53 => STANDARD_ERROR_CODE[:card_declined], + 54 => STANDARD_ERROR_CODE[:expired_card], + 55 => STANDARD_ERROR_CODE[:incorrect_pin], + 56 => STANDARD_ERROR_CODE[:card_declined], + 57 => STANDARD_ERROR_CODE[:card_declined], + 76 => STANDARD_ERROR_CODE[:call_issuer], + 91 => STANDARD_ERROR_CODE[:call_issuer], + 96 => STANDARD_ERROR_CODE[:processing_error], + 97 => STANDARD_ERROR_CODE[:processing_error] + } + + def initialize(options = {}) + requires!(options, :api_key) + super + @options[:preauth_mode] ||= false + end + + def purchase(money, payment, options = {}) + raise ArgumentError, 'Purchase is not supported on Decidir gateways configured with the preauth_mode option' if @options[:preauth_mode] + + post = {} + add_auth_purchase_params(post, money, payment, options) + commit(:post, 'payments', post) + end + + def authorize(money, payment, options = {}) + raise ArgumentError, 'Authorize is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + post = {} + add_auth_purchase_params(post, money, payment, options) + commit(:post, 'payments', post) + end + + def capture(money, authorization, options = {}) + raise ArgumentError, 'Capture is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + post = {} + add_amount(post, money, options) + commit(:put, "payments/#{authorization}", post) + end + + def refund(money, authorization, options = {}) + post = {} + add_amount(post, money, options) + commit(:post, "payments/#{authorization}/refunds", post) + end + + def void(authorization, options = {}) + post = {} + commit(:post, "payments/#{authorization}/refunds", post) + end + + def inquire(authorization, options = {}) + options[:action] = 'inquire' + commit(:get, "payments/#{authorization}", nil, options) + end + + def verify(credit_card, options = {}) + raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((apikey: )\w+)i, '\1[FILTERED]'). + gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"emv_issuer_data\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def add_auth_purchase_params(post, money, credit_card, options) + post[:payment_method_id] = add_payment_method_id(credit_card, options) + post[:site_transaction_id] = options[:order_id] + post[:bin] = credit_card.number[0..5] + post[:payment_type] = options[:payment_type] || 'single' + post[:installments] = options[:installments] ? options[:installments].to_i : 1 + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + post[:establishment_name] = options[:establishment_name] if options[:establishment_name] + post[:fraud_detection] = add_fraud_detection(options[:fraud_detection]) if options[:fraud_detection].present? + post[:site_id] = options[:site_id] if options[:site_id] + + add_invoice(post, money, options) + add_payment(post, credit_card, options) + add_aggregate_data(post, options) if options[:aggregate_data] + add_sub_payments(post, options) + end + + def add_payment_method_id(credit_card, options) + if options[:payment_method_id] + options[:payment_method_id].to_i + elsif options[:debit] + if CreditCard.brand?(credit_card.number) == 'visa' + 31 + elsif CreditCard.brand?(credit_card.number) == 'master' + 105 + elsif CreditCard.brand?(credit_card.number) == 'maestro' + 106 + elsif CreditCard.brand?(credit_card.number) == 'cabal' + 108 + end + elsif CreditCard.brand?(credit_card.number) == 'master' + 104 + elsif CreditCard.brand?(credit_card.number) == 'american_express' + 65 + elsif CreditCard.brand?(credit_card.number) == 'diners_club' + 8 + elsif CreditCard.brand?(credit_card.number) == 'cabal' + 63 + elsif CreditCard.brand?(credit_card.number) == 'naranja' + 24 + else + 1 + end + end + + def add_invoice(post, money, options) + add_amount(post, money, options) + post[:currency] = (options[:currency] || currency(money)) + end + + def add_amount(post, money, options) + currency = (options[:currency] || currency(money)) + post[:amount] = localized_amount(money, currency).to_i + end + + def add_payment(post, 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? + + # 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 (device_id = options.dig(:fraud_detection, :device_unique_id)) + card_data[:fraud_detection] = { device_unique_identifier: device_id } + end + end + + def add_aggregate_data(post, options) + aggregate_data = {} + data = options[:aggregate_data] + aggregate_data[:indicator] = data[:indicator] if data[:indicator] + aggregate_data[:identification_number] = data[:identification_number] if data[:identification_number] + aggregate_data[:bill_to_pay] = data[:bill_to_pay] if data[:bill_to_pay] + aggregate_data[:bill_to_refund] = data[:bill_to_refund] if data[:bill_to_refund] + aggregate_data[:merchant_name] = data[:merchant_name] if data[:merchant_name] + aggregate_data[:street] = data[:street] if data[:street] + aggregate_data[:number] = data[:number] if data[:number] + aggregate_data[:postal_code] = data[:postal_code] if data[:postal_code] + aggregate_data[:category] = data[:category] if data[:category] + aggregate_data[:channel] = data[:channel] if data[:channel] + aggregate_data[:geographic_code] = data[:geographic_code] if data[:geographic_code] + aggregate_data[:city] = data[:city] if data[:city] + aggregate_data[:merchant_id] = data[:merchant_id] if data[:merchant_id] + aggregate_data[:province] = data[:province] if data[:province] + aggregate_data[:country] = data[:country] if data[:country] + aggregate_data[:merchant_email] = data[:merchant_email] if data[:merchant_email] + aggregate_data[:merchant_phone] = data[:merchant_phone] if data[:merchant_phone] + post[:aggregate_data] = aggregate_data + end + + def add_sub_payments(post, options) + # sub_payments field is required for purchase transactions, even if empty + post[:sub_payments] = [] + + return unless sub_payments = options[:sub_payments] + + sub_payments.each do |sub_payment| + sub_payment_hash = { + site_id: sub_payment[:site_id], + installments: sub_payment[:installments].to_i, + amount: sub_payment[:amount].to_i + } + post[:sub_payments] << sub_payment_hash + end + end + + def add_fraud_detection(options = {}) + {}.tap do |hsh| + hsh[:send_to_cs] = options[:send_to_cs] if valid_fraud_detection_option?(options[:send_to_cs]) # true/false + hsh[:channel] = options[:channel] if valid_fraud_detection_option?(options[:channel]) + hsh[:dispatch_method] = options[:dispatch_method] if valid_fraud_detection_option?(options[:dispatch_method]) + hsh[:csmdds] = options[:csmdds] if valid_fraud_detection_option?(options[:csmdds]) + hsh[:device_unique_id] = options[:device_unique_id] if valid_fraud_detection_option?(options[:device_unique_id]) + hsh[:bill_to] = options[:bill_to] if valid_fraud_detection_option?(options[:bill_to]) + hsh[:purchase_totals] = options[:purchase_totals] if valid_fraud_detection_option?(options[:purchase_totals]) + hsh[:customer_in_site] = options[:customer_in_site] if valid_fraud_detection_option?(options[:customer_in_site]) + hsh[:retail_transaction_data] = options[:retail_transaction_data] if valid_fraud_detection_option?(options[:retail_transaction_data]) + hsh[:ship_to] = options[:ship_to] if valid_fraud_detection_option?(options[:ship_to]) + hsh[:tax_voucher_required] = options[:tax_voucher_required] if valid_fraud_detection_option?(options[:tax_voucher_required]) + hsh[:copy_paste_card_data] = options[:copy_paste_card_data] if valid_fraud_detection_option?(options[:copy_paste_card_data]) + end + end + + # Avoid sending fields with empty or null when not populated. + def valid_fraud_detection_option?(val) + !val.nil? && val != '' + end + + def headers(options = {}) + { + 'apikey' => @options[:api_key], + 'Content-type' => 'application/json', + 'Cache-Control' => 'no-cache' + } + end + + def commit(method, endpoint, parameters, options = {}) + url = "#{(test? ? test_url : live_url)}/#{endpoint}" + + begin + raw_response = ssl_request(method, url, post_data(parameters), headers(options)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = parse(raw_response) + end + + success = success_from(response, options) + Response.new( + success, + message_from(success, response), + response, + authorization: authorization_from(response), + test: test?, + error_code: success ? nil : error_code_from(response) + ) + end + + def post_data(parameters = {}) + parameters&.to_json + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + { + 'message' => "A non-JSON response was received from Decidir where one was expected. The raw response was:\n\n#{body}" + } + end + + def message_from(success, response) + return response['status'] if success + return response['message'] if response['message'] + + message = nil + if error = response.dig('status_details', 'error') + message = "#{error.dig('reason', 'description')} | #{error['type']}" + elsif response['error_type'] + if response['validation_errors'].is_a?(Array) + message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ') + elsif response['validation_errors'].is_a?(Hash) + errors = response['validation_errors'].map { |k, v| "#{k}: #{v}" }.join(', ') + message = "#{response['error_type']} - #{errors}" + end + + message ||= response['error_type'] + end + + message + end + + def success_from(response, options) + status = %w(approved pre_approved) + + if options[:action] == 'inquire' + status.include?(response['status']) || response['status'] == 'rejected' + else + status.include?(response['status']) + end + end + + def authorization_from(response) + response['id'] + end + + def error_code_from(response) + error_code = nil + if error = response.dig('status_details', 'error') + code = error.dig('reason', 'id') + standard_error_code = STANDARD_ERROR_CODE_MAPPING[code] + error_code = "#{code}, #{standard_error_code}" + error_code ||= error['type'] + elsif response['error_type'] + error_code = response['error_type'] if response['validation_errors'] + elsif response.dig('error', 'validation_errors') + error = response.dig('error') + validation_errors = error.dig('validation_errors', 0) + code = validation_errors['code'] if validation_errors && validation_errors['code'] + param = validation_errors['param'] if validation_errors && validation_errors['param'] + error_code = "#{error['error_type']} | #{code} | #{param}" if error['error_type'] + elsif error = response.dig('error') + code = error.dig('reason', 'id') + standard_error_code = STANDARD_ERROR_CODE_MAPPING[code] + error_code = "#{code}, #{standard_error_code}" + end + + error_code || STANDARD_ERROR_CODE[:processing_error] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/decidir_plus.rb b/lib/active_merchant/billing/gateways/decidir_plus.rb new file mode 100644 index 00000000000..5cefebf92e5 --- /dev/null +++ b/lib/active_merchant/billing/gateways/decidir_plus.rb @@ -0,0 +1,344 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DecidirPlusGateway < Gateway + self.test_url = 'https://developers.decidir.com/api/v2' + self.live_url = 'https://live.decidir.com/api/v2' + + self.supported_countries = ['AR'] + self.default_currency = 'ARS' + self.supported_cardtypes = %i[visa master american_express discover diners_club naranja cabal] + + self.homepage_url = 'http://decidir.com.ar/home' + self.display_name = 'Decidir Plus' + + def initialize(options = {}) + requires!(options, :public_key, :private_key) + super + end + + def purchase(money, payment, options = {}) + post = {} + build_purchase_authorize_request(post, money, payment, options) + + commit(:post, 'payments', post) + end + + def authorize(money, payment, options = {}) + post = {} + build_purchase_authorize_request(post, money, payment, options) + + commit(:post, 'payments', post) + end + + def capture(money, authorization, options = {}) + post = {} + post[:amount] = money + + commit(:put, "payments/#{add_reference(authorization)}", post) + end + + def refund(money, authorization, options = {}) + post = {} + post[:amount] = money + + commit(:post, "payments/#{add_reference(authorization)}/refunds", post) + end + + def void(authorization, options = {}) + commit(:post, "payments/#{add_reference(authorization)}/refunds") + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { store(credit_card, options) } + r.process { authorize(100, r.authorization, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment, options = {}) + post = {} + add_payment(post, payment, options) + + commit(:post, 'tokens', post) + end + + def unstore(customer_token) + commit(:delete, "cardtokens/#{customer_token}") + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Apikey: )\w+), '\1[FILTERED]'). + gsub(%r(("card_number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("security_code\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]') + end + + private + + def build_purchase_authorize_request(post, money, payment, options) + add_customer_data(post, options) + add_payment(post, payment, options) + add_purchase_data(post, money, payment, options) + add_fraud_detection(post, options) + end + + def add_reference(authorization) + return unless authorization + + authorization.split('|')[0] + end + + def add_payment(post, payment, options = {}) + if payment.is_a?(String) + token, bin = payment.split('|') + post[:token] = token + post[:bin] = bin + else + post[:card_number] = payment.number + post[:card_expiration_month] = format(payment.month, :two_digits) + post[:card_expiration_year] = format(payment.year, :two_digits) + post[:security_code] = payment.verification_value.to_s + post[:card_holder_name] = payment.name.empty? ? options[:name_override] : payment.name + post[:card_holder_identification] = {} + post[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type] + post[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number] + + # additional data used for Visa transactions + post[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number] + post[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday] + end + end + + def add_customer_data(post, options = {}) + return unless customer = options[:customer] + + post[:customer] = {} + post[:customer][:id] = customer[:id] if customer[:id] + post[:customer][:email] = customer[:email] if customer[:email] + end + + def add_purchase_data(post, money, payment, options = {}) + post[:site_transaction_id] = options[:site_transaction_id] || SecureRandom.hex + post[:payment_method_id] = add_payment_method_id(options) + post[:amount] = money + post[:currency] = options[:currency] || self.default_currency + post[:installments] = options[:installments] || 1 + post[:payment_type] = options[:payment_type] || 'single' + post[:establishment_name] = options[:establishment_name] if options[:establishment_name] + + add_aggregate_data(post, options) if options[:aggregate_data] + add_sub_payments(post, options) + end + + def add_aggregate_data(post, options) + aggregate_data = {} + data = options[:aggregate_data] + aggregate_data[:indicator] = data[:indicator] if data[:indicator] + aggregate_data[:identification_number] = data[:identification_number] if data[:identification_number] + aggregate_data[:bill_to_pay] = data[:bill_to_pay] if data[:bill_to_pay] + aggregate_data[:bill_to_refund] = data[:bill_to_refund] if data[:bill_to_refund] + aggregate_data[:merchant_name] = data[:merchant_name] if data[:merchant_name] + aggregate_data[:street] = data[:street] if data[:street] + aggregate_data[:number] = data[:number] if data[:number] + aggregate_data[:postal_code] = data[:postal_code] if data[:postal_code] + aggregate_data[:category] = data[:category] if data[:category] + aggregate_data[:channel] = data[:channel] if data[:channel] + aggregate_data[:geographic_code] = data[:geographic_code] if data[:geographic_code] + aggregate_data[:city] = data[:city] if data[:city] + aggregate_data[:merchant_id] = data[:merchant_id] if data[:merchant_id] + aggregate_data[:province] = data[:province] if data[:province] + aggregate_data[:country] = data[:country] if data[:country] + aggregate_data[:merchant_email] = data[:merchant_email] if data[:merchant_email] + aggregate_data[:merchant_phone] = data[:merchant_phone] if data[:merchant_phone] + post[:aggregate_data] = aggregate_data + end + + def add_sub_payments(post, options) + # sub_payments field is required for purchase transactions, even if empty + post[:sub_payments] = [] + + return unless sub_payments = options[:sub_payments] + + sub_payments.each do |sub_payment| + sub_payment_hash = { + site_id: sub_payment[:site_id], + installments: sub_payment[:installments].to_i, + amount: sub_payment[:amount].to_i + } + post[:sub_payments] << sub_payment_hash + end + end + + def add_payment_method_id(options) + return options[:payment_method_id].to_i if options[:payment_method_id] + + if options[:debit] + case options[:card_brand] + when 'visa' + 31 + when 'master' + 105 + when 'maestro' + 106 + when 'cabal' + 108 + else + 31 + end + else + case options[:card_brand] + when 'visa' + 1 + when 'master' + 104 + when 'american_express' + 65 + when 'american_express_prisma' + 111 + when 'cabal' + 63 + when 'diners_club' + 8 + else + 1 + end + end + end + + def add_fraud_detection(post, options) + return unless fraud_detection = options[:fraud_detection] + + {}.tap do |hsh| + hsh[:send_to_cs] = fraud_detection[:send_to_cs] == 'true' # true/false + hsh[:channel] = fraud_detection[:channel] if fraud_detection[:channel] + hsh[:dispatch_method] = fraud_detection[:dispatch_method] if fraud_detection[:dispatch_method] + add_csmdds(hsh, fraud_detection) + + post[:fraud_detection] = hsh + end + end + + def add_csmdds(hsh, fraud_detection) + return unless fraud_detection[:csmdds] + + csmdds_arr = [] + fraud_detection[:csmdds].each do |csmdds| + csmdds_hsh = {} + csmdds_hsh[:code] = csmdds[:code].to_i + csmdds_hsh[:description] = csmdds[:description] + csmdds_arr.append(csmdds_hsh) + end + hsh[:csmdds] = csmdds_arr unless csmdds_arr.empty? + end + + def parse(body) + return {} if body.nil? + + JSON.parse(body) + end + + def commit(method, endpoint, parameters = {}, options = {}) + begin + raw_response = ssl_request(method, url(endpoint), post_data(parameters), headers(endpoint)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = parse(raw_response) + end + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['some_avs_response_key']), + cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(response) + ) + end + + def headers(endpoint) + { + 'Content-Type' => 'application/json', + 'apikey' => endpoint == 'tokens' ? @options[:public_key] : @options[:private_key] + } + end + + def url(action, options = {}) + base_url = (test? ? test_url : live_url) + + return "#{base_url}/#{action}" + end + + def success_from(response) + response.dig('status') == 'approved' || response.dig('status') == 'active' || response.dig('status') == 'pre_approved' || response.empty? + end + + def message_from(response) + return '' if response.empty? + + rejected?(response) ? message_from_status_details(response) : response.dig('status') || error_message(response) || response.dig('message') + end + + def authorization_from(response) + return nil unless response.dig('id') || response.dig('bin') + + "#{response.dig('id')}|#{response.dig('bin')}" + end + + def post_data(parameters = {}) + parameters.to_json + end + + def error_code_from(response) + return if success_from(response) + + error_code = nil + if error = response.dig('status_details', 'error') + error_code = error.dig('reason', 'id') || error['type'] + elsif response['error_type'] + error_code = response['error_type'] + elsif response.dig('error', 'validation_errors') + error = response.dig('error') + validation_errors = error.dig('validation_errors', 0) + code = validation_errors['code'] if validation_errors && validation_errors['code'] + param = validation_errors['param'] if validation_errors && validation_errors['param'] + error_code = "#{error['error_type']} | #{code} | #{param}" if error['error_type'] + elsif error = response.dig('error') + error_code = error.dig('reason', 'id') + end + + error_code + end + + def error_message(response) + return error_code_from(response) unless validation_errors = response.dig('validation_errors') + + validation_errors = validation_errors[0] + + "#{validation_errors.dig('code')}: #{validation_errors.dig('param')}" + end + + def rejected?(response) + return response.dig('status') == 'rejected' + end + + def message_from_status_details(response) + return unless error = response.dig('status_details', 'error') + return message_from_fraud_detection(response) if error.dig('type') == 'cybersource_error' + + "#{error.dig('type')}: #{error.dig('reason', 'description')}" + end + + def message_from_fraud_detection(response) + return error_message(response.dig('fraud_detection', 'status', 'details')) + end + end + end +end 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/dibs.rb b/lib/active_merchant/billing/gateways/dibs.rb index e3936bc383f..1e202a206db 100644 --- a/lib/active_merchant/billing/gateways/dibs.rb +++ b/lib/active_merchant/billing/gateways/dibs.rb @@ -6,24 +6,24 @@ class DibsGateway < Gateway self.live_url = 'https://api.dibspayment.com/merchant/v1/JSON/Transaction/' - self.supported_countries = ['US', 'FI', 'NO', 'SE', 'GB'] + self.supported_countries = %w[US FI NO SE GB] self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :secret_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) MultiResponse.run(false) do |r| r.process { authorize(amount, payment_method, options) } r.process { capture(amount, r.authorization, options) } end end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_amount(post, amount) add_invoice(post, amount, options) @@ -36,7 +36,7 @@ def authorize(amount, payment_method, options={}) end end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_amount(post, amount) add_reference(post, authorization) @@ -44,14 +44,14 @@ def capture(amount, authorization, options={}) commit(:capture, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit(:void, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_amount(post, amount) add_reference(post, authorization) @@ -59,7 +59,7 @@ def refund(amount, authorization, options={}) commit(:refund, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -87,7 +87,7 @@ def scrub(transcript) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") } CURRENCY_CODES['USD'] = '840' CURRENCY_CODES['DKK'] = '208' CURRENCY_CODES['NOK'] = '578' diff --git a/lib/active_merchant/billing/gateways/digitzs.rb b/lib/active_merchant/billing/gateways/digitzs.rb index bbc82d4a2b8..7815b2018e8 100644 --- a/lib/active_merchant/billing/gateways/digitzs.rb +++ b/lib/active_merchant/billing/gateways/digitzs.rb @@ -8,25 +8,25 @@ class DigitzsGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.money_format = :cents self.homepage_url = 'https://digitzs.com' self.display_name = 'Digitzs' - def initialize(options={}) + def initialize(options = {}) requires!(options, :app_key, :api_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) MultiResponse.run do |r| r.process { commit('auth/token', app_token_request(options)) } r.process { commit('payments', purchase_request(money, payment, options), options.merge({ app_token: app_token_from(r) })) } end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) MultiResponse.run do |r| r.process { commit('auth/token', app_token_request(options)) } r.process { commit('payments', refund_request(money, authorization, options), options.merge({ app_token: app_token_from(r) })) } @@ -151,7 +151,7 @@ def refund_request(money, authorization, options) post[:data][:type] = 'payments' post[:data][:attributes][:merchantId] = options[:merchant_id] post[:data][:attributes][:paymentType] = 'cardRefund' - post[:data][:attributes][:originalTransaction] = {id: authorization} + post[:data][:attributes][:originalTransaction] = { id: authorization } add_transaction(post, money, options) post @@ -175,7 +175,7 @@ def create_token_request(payment, options) post[:data][:attributes] = { tokenType: 'card', customerId: options[:customer_id], - label: 'Credit Card', + label: 'Credit Card' } add_payment(post, payment, options) add_address(post, options) @@ -188,6 +188,7 @@ def check_customer_exists(options = {}) response = parse(ssl_get(url + "/customers/#{options[:customer_id]}", headers(options))) return response.try(:[], 'data').try(:[], 'customerId') if success_from(response) + return nil end @@ -205,7 +206,7 @@ def parse(body) JSON.parse(body) end - def commit(action, parameters, options={}) + def commit(action, parameters, options = {}) url = (test? ? test_url : live_url) response = parse(ssl_post(url + "/#{action}", parameters.to_json, headers(options))) @@ -228,12 +229,13 @@ def success_from(response) def message_from(response) return response['message'] if response['message'] return 'Success' if success_from(response) + response['errors'].map { |error_hash| error_hash['detail'] }.join(', ') end def authorization_from(response) if customer_id = response.try(:[], 'data').try(:[], 'attributes').try(:[], 'customerId') - "#{customer_id}|#{response.try(:[], "data").try(:[], "id")}" + "#{customer_id}|#{response.try(:[], 'data').try(:[], 'id')}" else response.try(:[], 'data').try(:[], 'id') end @@ -276,6 +278,7 @@ def determine_payment_type(payment, options) return 'cardSplit' if options[:payment_type] == 'card_split' return 'tokenSplit' if options[:payment_type] == 'token_split' return 'token' if payment.is_a? String + 'card' end diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb index b7d1dc15171..4588eddb7f7 100644 --- a/lib/active_merchant/billing/gateways/ebanx.rb +++ b/lib/active_merchant/billing/gateways/ebanx.rb @@ -4,20 +4,14 @@ class EbanxGateway < Gateway self.test_url = 'https://sandbox.ebanxpay.com/ws/' self.live_url = 'https://api.ebanxpay.com/ws/' - self.supported_countries = ['BR', 'MX', 'CO', 'CL', 'AR'] + self.supported_countries = %w(BR MX CO CL AR PE BO EC) self.default_currency = 'USD' - self.supported_cardtypes = [: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' - CARD_BRAND = { - visa: 'visa', - master: 'master_card', - american_express: 'amex', - discover: 'discover', - diners_club: 'diners' - } + TAGS = ['Spreedly'] URL_MAP = { purchase: 'direct', @@ -25,7 +19,9 @@ class EbanxGateway < Gateway capture: 'capture', refund: 'refund', void: 'cancel', - store: 'token' + store: 'token', + inquire: 'query', + verify: 'verifycard' } HTTP_METHOD = { @@ -34,51 +30,55 @@ class EbanxGateway < Gateway capture: :get, refund: :post, void: :get, - store: :post + store: :post, + inquire: :get, + verify: :post } - def initialize(options={}) + def initialize(options = {}) requires!(options, :integration_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = { payment: {} } add_integration_key(post) add_operation(post) add_invoice(post, money, options) add_customer_data(post, payment, options) - add_card_or_token(post, payment) + add_card_or_token(post, payment, options) add_address(post, options) add_customer_responsible_person(post, payment, options) + add_additional_data(post, options) commit(:purchase, post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = { payment: {} } add_integration_key(post) add_operation(post) add_invoice(post, money, options) add_customer_data(post, payment, options) - add_card_or_token(post, payment) + add_card_or_token(post, payment, options) add_address(post, options) add_customer_responsible_person(post, payment, options) + add_additional_data(post, options) post[:payment][:creditcard][:auto_capture] = false commit(:authorize, post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_integration_key(post) post[:hash] = authorization - post[:amount] = amount(money) + post[:amount] = amount(money) if options[:include_capture_amount].to_s == 'true' commit(:capture, post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_integration_key(post) add_operation(post) @@ -89,7 +89,7 @@ def refund(money, authorization, options={}) commit(:refund, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_integration_key(post) add_authorization(post, authorization) @@ -97,20 +97,33 @@ def void(authorization, options={}) commit(:void, post) end - def store(credit_card, 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(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } - end + def verify(credit_card, options = {}) + 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? @@ -119,7 +132,7 @@ def supports_scrubbing? def scrub(transcript) transcript. - gsub(/(integration_key\\?":\\?")(\d*)/, '\1[FILTERED]'). + gsub(/(integration_key\\?":\\?")(\w*)/, '\1[FILTERED]'). gsub(/(card_number\\?":\\?")(\d*)/, '\1[FILTERED]'). gsub(/(card_cvv\\?":\\?")(\d*)/, '\1[FILTERED]') end @@ -140,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 @@ -170,21 +183,20 @@ def add_address(post, options) def add_invoice(post, money, options) post[:payment][:amount_total] = amount(money) post[:payment][:currency_code] = (options[:currency] || currency(money)) - post[:payment][:merchant_payment_code] = options[:order_id] + post[:payment][:merchant_payment_code] = Digest::MD5.hexdigest(options[:order_id]) post[:payment][:instalments] = options[:instalments] || 1 + post[:payment][:order_number] = options[:order_id][0..39] if options[:order_id] end - def add_card_or_token(post, payment) - if payment.is_a?(String) - payment, brand = payment.split('|') - end - post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym] + def add_card_or_token(post, payment, options) + 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) @@ -200,19 +212,29 @@ def payment_details(payment) end end + def add_additional_data(post, options) + post[:device_id] = options[:device_id] if options[:device_id] + post[:metadata] = options[:metadata] if options[:metadata] + post[:metadata] = {} if post[:metadata].nil? + post[:metadata][:merchant_payment_code] = options[:order_id] if options[:order_id] + post[:processing_type] = options[:processing_type] if options[:processing_type] + post[:payment][:tags] = TAGS + end + def parse(body) JSON.parse(body) end def commit(action, parameters) url = url_for((test? ? test_url : live_url), action, parameters) - response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), {})) + + response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), headers(parameters))) success = success_from(action, response) Response.new( success, - message_from(response), + message_from(action, response), response, authorization: authorization_from(action, parameters, response), test: test?, @@ -220,28 +242,55 @@ def commit(action, parameters) ) end + def headers(params) + processing_type = params[:processing_type] + commit_headers = { 'x-ebanx-client-user-agent': "ActiveMerchant/#{ActiveMerchant::VERSION}" } + + add_processing_type_to_commit_headers(commit_headers, processing_type) if processing_type == 'local' + + commit_headers + end + + def add_processing_type_to_commit_headers(commit_headers, processing_type) + commit_headers['x-ebanx-api-processing-type'] = processing_type + end + def success_from(action, response) - if [:purchase, :capture, :refund].include?(action) - response.try(:[], 'payment').try(:[], 'status') == 'CO' - elsif action == :authorize - response.try(:[], 'payment').try(:[], 'status') == 'PE' - elsif action == :void - response.try(:[], 'payment').try(:[], 'status') == 'CA' - elsif action == :store - response.try(:[], 'status') == 'SUCCESS' + 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 @@ -250,22 +299,26 @@ def authorization_from(action, parameters, response) def post_data(action, parameters = {}) return nil if requires_http_get(action) return convert_to_url_form_encoded(parameters) if action == :refund + "request_body=#{parameters.to_json}" end def url_for(hostname, action, parameters) return "#{hostname}#{URL_MAP[action]}?#{convert_to_url_form_encoded(parameters)}" if requires_http_get(action) + "#{hostname}#{URL_MAP[action]}" end def requires_http_get(action) - return true if [:capture, :void].include?(action) + return true if %i[capture void inquire].include?(action) + false end def convert_to_url_form_encoded(parameters) parameters.map do |key, value| next if value != false && value.blank? + "#{key}=#{value}" end.compact.join('&') end @@ -273,13 +326,14 @@ def convert_to_url_form_encoded(parameters) def error_code_from(response, success) unless success return response['status_code'] if response['status'] == 'ERROR' + response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'code') end end - def customer_country(options) + 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 ad16cfbf349..d3ec02270ce 100644 --- a/lib/active_merchant/billing/gateways/efsnet.rb +++ b/lib/active_merchant/billing/gateways/efsnet.rb @@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class EfsnetGateway < Gateway self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.concordefsnet.com/' self.display_name = 'Efsnet' @@ -53,8 +53,8 @@ def refund(money, reference, options = {}) def void(identification, options = {}) requires!(options, :order_id) - original_transaction_id, _ = identification.split(';') - commit(:void_transaction, {:reference_number => format_reference_number(options[:order_id]), :transaction_id => original_transaction_id}) + original_transaction_id, = identification.split(';') + commit(:void_transaction, { reference_number: format_reference_number(options[:order_id]), transaction_id: original_transaction_id }) end def voice_authorize(money, authorization_code, creditcard, options = {}) @@ -81,11 +81,11 @@ def build_refund_or_settle_request(money, identification, options = {}) requires!(options, :order_id) { - :reference_number => format_reference_number(options[:order_id]), - :transaction_amount => amount(money), - :original_transaction_amount => original_transaction_amount, - :original_transaction_id => original_transaction_id, - :client_ip_address => options[:ip] + reference_number: format_reference_number(options[:order_id]), + transaction_amount: amount(money), + original_transaction_amount: original_transaction_amount, + original_transaction_id: original_transaction_id, + client_ip_address: options[:ip] } end @@ -93,10 +93,10 @@ def build_credit_card_request(money, creditcard, options = {}) requires!(options, :order_id) post = { - :reference_number => format_reference_number(options[:order_id]), - :authorization_number => options[:authorization_number], - :transaction_amount => amount(money), - :client_ip_address => options[:ip] + reference_number: format_reference_number(options[:order_id]), + authorization_number: options[:authorization_number], + transaction_amount: amount(money), + client_ip_address: options[:ip] } add_creditcard(post, creditcard) @@ -111,45 +111,48 @@ def format_reference_number(number) def add_address(post, options) if address = options[:billing_address] || options[:address] if address[:address2] - post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s + post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s else post[:billing_address] = address[:address1].to_s end post[:billing_city] = address[:city].to_s - post[:billing_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:billing_state] = address[:state].blank? ? 'n/a' : address[:state] post[:billing_postal_code] = address[:zip].to_s post[:billing_country] = address[:country].to_s end if address = options[:shipping_address] if address[:address2] - post[:shipping_address] = address[:address1].to_s << ' ' << address[:address2].to_s + post[:shipping_address] = address[:address1].to_s << ' ' << address[:address2].to_s else post[:shipping_address] = address[:address1].to_s end post[:shipping_city] = address[:city].to_s - post[:shipping_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:shipping_state] = address[:state].blank? ? 'n/a' : address[:state] post[:shipping_postal_code] = address[:zip].to_s post[:shipping_country] = address[:country].to_s end end def add_creditcard(post, creditcard) - post[:billing_name] = creditcard.name if creditcard.name - post[:account_number] = creditcard.number + post[:billing_name] = creditcard.name if creditcard.name + post[:account_number] = creditcard.number post[:card_verification_value] = creditcard.verification_value if creditcard.verification_value? - post[:expiration_month] = sprintf('%.2i', creditcard.month) - post[:expiration_year] = sprintf('%.4i', creditcard.year)[-2..-1] + post[:expiration_month] = sprintf('%.2i', creditcard.month) + post[:expiration_year] = sprintf('%.4i', creditcard.year)[-2..-1] end def commit(action, parameters) response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml')) - 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] + 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] ) end @@ -158,7 +161,7 @@ def success?(response) end def authorization_from(response, params) - [ response[:transaction_id], params[:transaction_amount] ].compact.join(';') + [response[:transaction_id], params[:transaction_amount]].compact.join(';') end def parse(xml) @@ -190,6 +193,7 @@ def post_data(action, parameters = {}) def message_from(message) return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end @@ -197,18 +201,18 @@ def actions ACTIONS end - CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber ) + CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber) ACTIONS = { - :credit_card_authorize => CREDIT_CARD_FIELDS, - :credit_card_charge => CREDIT_CARD_FIELDS, - :credit_card_voice_authorize => CREDIT_CARD_FIELDS, - :credit_card_capture => CREDIT_CARD_FIELDS, - :credit_card_credit => CREDIT_CARD_FIELDS + ['OriginalTransactionAmount'], - :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), - :void_transaction => %w(ReferenceNumber TransactionID), - :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), - :system_check => %w(SystemCheck), + credit_card_authorize: CREDIT_CARD_FIELDS, + credit_card_charge: CREDIT_CARD_FIELDS, + credit_card_voice_authorize: CREDIT_CARD_FIELDS, + credit_card_capture: CREDIT_CARD_FIELDS, + credit_card_credit: CREDIT_CARD_FIELDS + ['OriginalTransactionAmount'], + credit_card_refund: %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + void_transaction: %w(ReferenceNumber TransactionID), + credit_card_settle: %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + system_check: %w(SystemCheck) } end end diff --git a/lib/active_merchant/billing/gateways/elavon.rb b/lib/active_merchant/billing/gateways/elavon.rb index ab234228e87..3085354dc8d 100644 --- a/lib/active_merchant/billing/gateways/elavon.rb +++ b/lib/active_merchant/billing/gateways/elavon.rb @@ -1,4 +1,5 @@ require 'active_merchant/billing/gateways/viaklix' +require 'nokogiri' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -7,25 +8,28 @@ class ElavonGateway < Gateway class_attribute :test_url, :live_url, :delimiter, :actions - self.test_url = 'https://api.demo.convergepay.com/VirtualMerchantDemo/process.do' - self.live_url = 'https://api.convergepay.com/VirtualMerchant/process.do' + self.test_url = 'https://api.demo.convergepay.com/VirtualMerchantDemo/processxml.do' + self.live_url = 'https://api.convergepay.com/VirtualMerchant/processxml.do' self.display_name = 'Elavon MyVirtualMerchant' self.supported_countries = %w(US CA PR DE IE NO PL LU BE NL MX) - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.elavon.com/' + self.money_format = :dollars + self.default_currency = 'USD' self.delimiter = "\n" self.actions = { - :purchase => 'CCSALE', - :credit => 'CCCREDIT', - :refund => 'CCRETURN', - :authorize => 'CCAUTHONLY', - :capture => 'CCFORCE', - :capture_complete => 'CCCOMPLETE', - :void => 'CCDELETE', - :store => 'CCGETTOKEN', - :update => 'CCUPDATETOKEN', + purchase: 'CCSALE', + credit: 'CCCREDIT', + refund: 'CCRETURN', + authorize: 'CCAUTHONLY', + capture: 'CCFORCE', + capture_complete: 'CCCOMPLETE', + void: 'CCDELETE', + store: 'CCGETTOKEN', + update: 'CCUPDATETOKEN', + verify: 'CCVERIFY' } def initialize(options = {}) @@ -34,106 +38,153 @@ def initialize(options = {}) end def purchase(money, payment_method, options = {}) - form = {} - add_salestax(form, options) - add_invoice(form, options) - if payment_method.is_a?(String) - add_token(form, payment_method) - else - add_creditcard(form, payment_method) + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + 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_invoice(xml, options) + add_salestax(xml, options) + add_currency(xml, money, options) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) + add_ip(xml, options) + add_auth_purchase_params(xml, options) + add_level_3_fields(xml, options) if options[:level_3_data] end - add_address(form, options) - add_customer_data(form, options) - add_test_mode(form, options) - add_ip(form, options) - commit(:purchase, money, form, options) + commit(request) end def authorize(money, creditcard, options = {}) - form = {} - add_salestax(form, options) - add_invoice(form, options) - add_creditcard(form, creditcard) - add_address(form, options) - add_customer_data(form, options) - add_test_mode(form, options) - add_ip(form, options) - commit(:authorize, money, form, 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_currency(xml, money, options) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) + add_ip(xml, options) + add_auth_purchase_params(xml, options) + add_level_3_fields(xml, options) if options[:level_3_data] + end + commit(request) end def capture(money, authorization, options = {}) - form = {} - if options[:credit_card] - action = :capture - add_salestax(form, options) - add_approval_code(form, authorization) - add_invoice(form, options) - add_creditcard(form, options[:credit_card]) - add_customer_data(form, options) - add_test_mode(form, options) - else - action = :capture_complete - add_txn_id(form, authorization) - add_partial_shipment_flag(form, options) - add_test_mode(form, options) + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + + if options[:credit_card] + xml.ssl_transaction_type self.actions[:capture] + xml.ssl_amount amount(money) + add_salestax(xml, options) + add_approval_code(xml, authorization) + add_invoice(xml, options) + add_creditcard(xml, options[:credit_card]) + add_currency(xml, money, options) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) + else + xml.ssl_transaction_type self.actions[:capture_complete] + xml.ssl_amount amount(money) + add_currency(xml, money, options) + add_txn_id(xml, authorization) + add_partial_shipment_flag(xml, options) + add_test_mode(xml, options) + end end - commit(action, money, form, options) + commit(request) end def refund(money, identification, options = {}) - form = {} - add_txn_id(form, identification) - add_test_mode(form, options) - commit(:refund, money, form, 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[:refund] + xml.ssl_amount amount(money) + add_txn_id(xml, identification) + add_test_mode(xml, options) + end + commit(request) end def void(identification, options = {}) - form = {} - add_txn_id(form, identification) - add_test_mode(form, options) - commit(:void, nil, form, 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[:void] + + add_txn_id(xml, identification) + add_test_mode(xml, options) + end + commit(request) end def credit(money, creditcard, options = {}) - if creditcard.is_a?(String) - raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if creditcard.is_a?(String) + + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + xml.ssl_transaction_type self.actions[:credit] + xml.ssl_amount amount(money) + add_invoice(xml, options) + add_creditcard(xml, creditcard) + add_currency(xml, money, options) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) end - - form = {} - add_invoice(form, options) - add_creditcard(form, creditcard) - add_address(form, options) - add_customer_data(form, options) - add_test_mode(form, options) - commit(:credit, money, form, options) + commit(request) end def verify(credit_card, options = {}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + xml.ssl_transaction_type self.actions[:verify] + add_creditcard(xml, credit_card) + add_address(xml, options) + add_test_mode(xml, options) + add_ip(xml, options) end + commit(request) end def store(creditcard, options = {}) - form = {} - add_creditcard(form, creditcard) - add_address(form, options) - add_customer_data(form, options) - add_test_mode(form, options) - add_verification(form, options) - form[:add_token] = 'Y' - commit(:store, nil, form, options) + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + xml.ssl_transaction_type self.actions[:store] + xml.ssl_add_token 'Y' + add_creditcard(xml, creditcard) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) + add_verification(xml, options) + end + commit(request) end def update(token, creditcard, options = {}) - form = {} - add_token(form, token) - add_creditcard(form, creditcard) - add_address(form, options) - add_customer_data(form, options) - add_test_mode(form, options) - commit(:update, nil, form, options) + request = build_xml_request do |xml| + xml.ssl_vendor_id @options[:ssl_vendor_id] || options[:ssl_vendor_id] + xml.ssl_transaction_type self.actions[:update] + add_token(xml, token) + add_creditcard(xml, creditcard) + add_address(xml, options) + add_customer_email(xml, options) + add_test_mode(xml, options) + end + commit(request) end def supports_scrubbing? @@ -142,170 +193,283 @@ def supports_scrubbing? def scrub(transcript) transcript. - gsub(%r((&?ssl_pin=)[^&]*)i, '\1[FILTERED]'). - gsub(%r((&?ssl_card_number=)[^&\\n\r\n]*)i, '\1[FILTERED]'). - gsub(%r((&?ssl_cvv2cvc2=)[^&]*)i, '\1[FILTERED]') + gsub(%r(()(.*)()), '\1[FILTERED]\3'). + gsub(%r(()(.*)()), '\1[FILTERED]\3'). + gsub(%r(()(.*)()), '\1[FILTERED]\3') end private - def add_invoice(form, options) - form[:invoice_number] = truncate((options[:order_id] || options[:invoice]), 10) - form[:description] = truncate(options[:description], 255) + 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) end - def add_approval_code(form, authorization) - form[:approval_code] = authorization.split(';').first + def add_approval_code(xml, authorization) + xml.ssl_approval_code authorization.split(';').first end - def add_txn_id(form, authorization) - form[:txn_id] = authorization.split(';').last + def add_txn_id(xml, authorization) + xml.ssl_txn_id authorization.split(';').last end - def authorization_from(response) - [response['approval_code'], response['txn_id']].join(';') - end + def add_creditcard(xml, creditcard) + xml.ssl_card_number creditcard.number + xml.ssl_exp_date expdate(creditcard) - def add_creditcard(form, creditcard) - form[:card_number] = creditcard.number - form[:exp_date] = expdate(creditcard) + add_verification_value(xml, creditcard) if creditcard.verification_value? - if creditcard.verification_value? - add_verification_value(form, creditcard) - end + xml.ssl_first_name url_encode_truncate(creditcard.first_name, 20) + xml.ssl_last_name url_encode_truncate(creditcard.last_name, 30) + end - form[:first_name] = truncate(creditcard.first_name, 20) - form[:last_name] = truncate(creditcard.last_name, 30) + def add_currency(xml, money, options) + currency = options[:currency] || currency(money) + return unless currency && (@options[:multi_currency] || options[:multi_currency]) + + xml.ssl_transaction_currency currency end - def add_token(form, token) - form[:token] = token + def add_token(xml, token) + xml.ssl_token token end - def add_verification_value(form, creditcard) - form[:cvv2cvc2] = creditcard.verification_value - form[:cvv2cvc2_indicator] = '1' + def add_verification_value(xml, creditcard) + xml.ssl_cvv2cvc2 creditcard.verification_value + xml.ssl_cvv2cvc2_indicator 1 end - def add_customer_data(form, options) - form[:email] = truncate(options[:email], 100) unless empty?(options[:email]) - form[:customer_code] = truncate(options[:customer], 10) unless empty?(options[:customer]) - form[:customer_number] = options[:customer_number] unless empty?(options[:customer_number]) - options[:custom_fields]&.each do |key, value| - form[key.to_s] = value - end + def add_customer_email(xml, options) + xml.ssl_email url_encode_truncate(options[:email], 100) unless empty?(options[:email]) end - def add_salestax(form, options) - form[:salestax] = options[:tax] if options[:tax].present? + def add_salestax(xml, options) + return unless options[:tax].present? + + xml.ssl_salestax options[:tax] end - def add_address(form, options) + def add_address(xml, options) billing_address = options[:billing_address] || options[:address] if billing_address - form[:avs_address] = truncate(billing_address[:address1], 30) - form[:address2] = truncate(billing_address[:address2], 30) - form[:avs_zip] = truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9) - form[:city] = truncate(billing_address[:city], 30) - form[:state] = truncate(billing_address[:state], 10) - form[:company] = truncate(billing_address[:company], 50) - form[:phone] = truncate(billing_address[:phone], 20) - form[:country] = truncate(billing_address[:country], 50) + xml.ssl_avs_address url_encode_truncate(billing_address[:address1], 30) + xml.ssl_address2 url_encode_truncate(billing_address[:address2], 30) + xml.ssl_avs_zip url_encode_truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9) + xml.ssl_city url_encode_truncate(billing_address[:city], 30) + xml.ssl_state url_encode_truncate(billing_address[:state], 10) + xml.ssl_company url_encode_truncate(billing_address[:company], 50) + xml.ssl_phone url_encode_truncate(billing_address[:phone], 20) + xml.ssl_country url_encode_truncate(billing_address[:country], 50) end if shipping_address = options[:shipping_address] - first_name, last_name = split_names(shipping_address[:name]) - form[:ship_to_first_name] = truncate(first_name, 20) - form[:ship_to_last_name] = truncate(last_name, 30) - form[:ship_to_address1] = truncate(shipping_address[:address1], 30) - form[:ship_to_address2] = truncate(shipping_address[:address2], 30) - form[:ship_to_city] = truncate(shipping_address[:city], 30) - form[:ship_to_state] = truncate(shipping_address[:state], 10) - form[:ship_to_company] = truncate(shipping_address[:company], 50) - form[:ship_to_country] = truncate(shipping_address[:country], 50) - form[:ship_to_zip] = truncate(shipping_address[:zip], 10) + xml.ssl_ship_to_address1 url_encode_truncate(shipping_address[:address1], 30) + xml.ssl_ship_to_address2 url_encode_truncate(shipping_address[:address2], 30) + xml.ssl_ship_to_city url_encode_truncate(shipping_address[:city], 30) + xml.ssl_ship_to_company url_encode_truncate(shipping_address[:company], 50) + xml.ssl_ship_to_country url_encode_truncate(shipping_address[:country], 50) + xml.ssl_ship_to_first_name url_encode_truncate(shipping_address[:first_name], 20) + xml.ssl_ship_to_last_name url_encode_truncate(shipping_address[:last_name], 30) + xml.ssl_ship_to_phone url_encode_truncate(shipping_address[:phone], 10) + xml.ssl_ship_to_state url_encode_truncate(shipping_address[:state], 2) + xml.ssl_ship_to_zip url_encode_truncate(shipping_address[:zip], 10) end end - def add_verification(form, options) - form[:verify] = 'Y' if options[:verify] + def add_verification(xml, options) + xml.ssl_verify 'Y' if options[:verify] end - def add_test_mode(form, options) - form[:test_mode] = 'TRUE' if options[:test_mode] + def add_test_mode(xml, options) + xml.ssl_test_mode 'TRUE' if options[:test_mode] end - def add_partial_shipment_flag(form, options) - form[:partial_shipment_flag] = 'Y' if options[:partial_shipment_flag] + def add_partial_shipment_flag(xml, options) + xml.ssl_partial_shipment_flag 'Y' if options[:partial_shipment_flag] end - def add_ip(form, options) - form[:cardholder_ip] = options[:ip] if options.has_key?(:ip) + def add_ip(xml, options) + xml.ssl_cardholder_ip options[:ip] if options.has_key?(:ip) end - def message_from(response) - success?(response) ? response['result_message'] : response['errorMessage'] + # add_recurring_token is a field that can be sent in to obtain a token from Elavon for use with their tokenization program + def add_auth_purchase_params(xml, options) + xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba) + xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options) + xml.ssl_add_token options[:add_recurring_token] if options.has_key?(:add_recurring_token) + xml.ssl_token options[:ssl_token] if options[:ssl_token] + xml.ssl_customer_code options[:customer] if options.has_key?(:customer) + xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number) + xml.ssl_entry_mode entry_mode(options) if entry_mode(options) + add_custom_fields(xml, options) if options[:custom_fields] + add_stored_credential(xml, options) if options[:stored_credential] end - def success?(response) - !response.has_key?('errorMessage') + def add_custom_fields(xml, options) + options[:custom_fields]&.each do |key, value| + xml.send(key.to_sym, value) + end end - def commit(action, money, parameters, options) - parameters[:amount] = amount(money) - parameters[:transaction_type] = self.actions[action] + def add_level_3_fields(xml, options) + level_3_data = options[:level_3_data] + xml.ssl_customer_code level_3_data[:customer_code] if level_3_data[:customer_code] + xml.ssl_salestax level_3_data[:salestax] if level_3_data[:salestax] + xml.ssl_salestax_indicator level_3_data[:salestax_indicator] if level_3_data[:salestax_indicator] + xml.ssl_level3_indicator level_3_data[:level3_indicator] if level_3_data[:level3_indicator] + xml.ssl_ship_to_zip level_3_data[:ship_to_zip] if level_3_data[:ship_to_zip] + xml.ssl_ship_to_country level_3_data[:ship_to_country] if level_3_data[:ship_to_country] + xml.ssl_shipping_amount level_3_data[:shipping_amount] if level_3_data[:shipping_amount] + xml.ssl_ship_from_postal_code level_3_data[:ship_from_postal_code] if level_3_data[:ship_from_postal_code] + xml.ssl_discount_amount level_3_data[:discount_amount] if level_3_data[:discount_amount] + xml.ssl_duty_amount level_3_data[:duty_amount] if level_3_data[:duty_amount] + xml.ssl_national_tax_indicator level_3_data[:national_tax_indicator] if level_3_data[:national_tax_indicator] + xml.ssl_national_tax_amount level_3_data[:national_tax_amount] if level_3_data[:national_tax_amount] + xml.ssl_order_date level_3_data[:order_date] if level_3_data[:order_date] + xml.ssl_other_tax level_3_data[:other_tax] if level_3_data[:other_tax] + xml.ssl_summary_commodity_code level_3_data[:summary_commodity_code] if level_3_data[:summary_commodity_code] + xml.ssl_merchant_vat_number level_3_data[:merchant_vat_number] if level_3_data[:merchant_vat_number] + xml.ssl_customer_vat_number level_3_data[:customer_vat_number] if level_3_data[:customer_vat_number] + xml.ssl_freight_tax_amount level_3_data[:freight_tax_amount] if level_3_data[:freight_tax_amount] + xml.ssl_vat_invoice_number level_3_data[:vat_invoice_number] if level_3_data[:vat_invoice_number] + xml.ssl_tracking_number level_3_data[:tracking_number] if level_3_data[:tracking_number] + xml.ssl_shipping_company level_3_data[:shipping_company] if level_3_data[:shipping_company] + xml.ssl_other_fees level_3_data[:other_fees] if level_3_data[:other_fees] + add_line_items(xml, level_3_data) if level_3_data[:line_items] + end + + def add_line_items(xml, level_3_data) + xml.LineItemProducts { + level_3_data[:line_items].each do |line_item| + xml.product { + line_item.each do |key, value| + prefixed_key = "ssl_line_Item_#{key}" + xml.send(prefixed_key, value) + end + } + end + } + end - response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters, options))) + def add_stored_credential(xml, options) + network_transaction_id = options.dig(:stored_credential, :network_transaction_id) + case + when network_transaction_id.nil? + return + when network_transaction_id.to_s.include?('|') + oar_data, ps2000_data = options[:stored_credential][:network_transaction_id].split('|') + xml.ssl_oar_data oar_data unless oar_data.nil? || oar_data.empty? + xml.ssl_ps2000_data ps2000_data unless ps2000_data.nil? || ps2000_data.empty? + when network_transaction_id.to_s.length > 22 + xml.ssl_oar_data options.dig(:stored_credential, :network_transaction_id) + else + xml.ssl_ps2000_data options.dig(:stored_credential, :network_transaction_id) + end + end - Response.new(response['result'] == '0', message_from(response), response, - :test => @options[:test] || test?, - :authorization => authorization_from(response), - :avs_result => { :code => response['avs_response'] }, - :cvv_result => response['cvv2_response'] - ) + def merchant_initiated_unscheduled(options) + return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled] + return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled' || options.dig(:stored_credential, :reason_type) == 'recurring' end - def post_data(parameters, options) - result = preamble - result.merge!(parameters) - result.collect { |key, value| post_data_string(key, value, options) }.join('&') + def entry_mode(options) + return options[:entry_mode] if options[:entry_mode] + return 12 if options[:stored_credential] end - def post_data_string(key, value, options) - if custom_field?(key, options) - "#{key}=#{CGI.escape(value.to_s)}" - else - "ssl_#{key}=#{CGI.escape(value.to_s)}" + def build_xml_request + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.txn do + xml.ssl_merchant_id @options[:login] + xml.ssl_user_id @options[:user] + xml.ssl_pin @options[:password] + yield(xml) + end end + + builder.to_xml.gsub("\n", '') + end + + def commit(request) + request = "xmldata=#{request}".delete('&') + store_action = request.match?('CCGETTOKEN') + + response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) + response = hash_html_decode(response) + + Response.new( + response[:result] == '0', + response[:result_message] || response[:errorMessage], + response, + test: @options[:test] || test?, + authorization: authorization_from(response, store_action), + error_code: response[:errorCode], + avs_result: { code: response[:avs_response] }, + cvv_result: response[:cvv2_response], + network_transaction_id: build_network_transaction_id(response) + ) end - def custom_field?(field_name, options) - return true if options[:custom_fields]&.include?(field_name.to_sym) - field_name == :customer_number + def build_network_transaction_id(response) + "#{response[:oar_data]}|#{response[:ps2000_data]}" end - def preamble - result = { - 'merchant_id' => @options[:login], - 'pin' => @options[:password], - 'show_form' => 'false', - 'result_format' => 'ASCII' + def headers + { + 'Accept' => 'application/xml', + 'Content-type' => 'application/x-www-form-urlencoded;charset=utf8' } + end - result['user_id'] = @options[:user] unless empty?(@options[:user]) - result + def parse(body) + xml = Nokogiri::XML(body) + response = Hash.from_xml(xml.to_s)['txn'] + + response.deep_transform_keys { |key| key.gsub('ssl_', '').to_sym } end - def parse(msg) - resp = {} - msg.split(self.delimiter).collect { |li| - key, value = li.split('=') - resp[key.to_s.strip.gsub(/^ssl_/, '')] = value.to_s.strip - } - resp + def authorization_from(response, store_action) + return response[:token] if store_action + + [response[:approval_code], response[:txn_id]].join(';') end + def url_encode_truncate(value, size) + return nil unless value + + encoded = url_encode(value) + + while encoded.length > size + value.chop! + encoded = url_encode(value) + end + encoded + end + + 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 + else + value.to_s + end + end + + def hash_html_decode(hash) + hash.each do |k, v| + if v.is_a?(String) + # decode all string params + v = v.gsub('&amp;', '&') # account for Elavon's weird '&' handling + hash[k] = CGI.unescape_html(v) + elsif v.is_a?(Hash) + hash_html_decode(v) + end + end + hash + end end end end diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb index a82803884ba..b685c7bab9c 100644 --- a/lib/active_merchant/billing/gateways/element.rb +++ b/lib/active_merchant/billing/gateways/element.rb @@ -9,7 +9,7 @@ class ElementGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.elementps.com' self.display_name = 'Element' @@ -17,12 +17,17 @@ class ElementGateway < Gateway SERVICE_TEST_URL = 'https://certservices.elementexpress.com/express.asmx' SERVICE_LIVE_URL = 'https://services.elementexpress.com/express.asmx' - def initialize(options={}) + NETWORK_TOKEN_TYPE = { + apple_pay: '2', + google_pay: '1' + } + + def initialize(options = {}) requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) action = payment.is_a?(Check) ? 'CheckSale' : 'CreditCardSale' request = build_soap_request do |xml| @@ -32,13 +37,14 @@ def purchase(money, payment, options={}) add_transaction(xml, money, options) add_terminal(xml, options) add_address(xml, options) + add_lodging(xml, options) end end commit(action, request, money) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) request = build_soap_request do |xml| xml.CreditCardAuthorization(xmlns: 'https://transaction.elementexpress.com') do add_credentials(xml) @@ -46,14 +52,15 @@ def authorize(money, payment, options={}) add_transaction(xml, money, options) add_terminal(xml, options) add_address(xml, options) + add_lodging(xml, options) end end commit('CreditCardAuthorization', request, money) end - def capture(money, authorization, options={}) - trans_id, _ = split_authorization(authorization) + def capture(money, authorization, options = {}) + trans_id, = split_authorization(authorization) options[:trans_id] = trans_id request = build_soap_request do |xml| @@ -67,8 +74,8 @@ def capture(money, authorization, options={}) commit('CreditCardAuthorizationCompletion', request, money) end - def refund(money, authorization, options={}) - trans_id, _ = split_authorization(authorization) + def refund(money, authorization, options = {}) + trans_id, = split_authorization(authorization) options[:trans_id] = trans_id request = build_soap_request do |xml| @@ -82,9 +89,22 @@ def refund(money, authorization, options={}) commit('CreditCardReturn', request, money) end - def void(authorization, options={}) + def credit(money, payment, options = {}) + request = build_soap_request do |xml| + xml.CreditCardCredit(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_payment_method(xml, payment) + add_transaction(xml, money, options) + add_terminal(xml, options) + end + end + + commit('CreditCardCredit', request, money) + end + + def void(authorization, options = {}) trans_id, trans_amount = split_authorization(authorization) - options.merge!({trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full'}) + options.merge!({ trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full' }) request = build_soap_request do |xml| xml.CreditCardReversal(xmlns: 'https://transaction.elementexpress.com') do @@ -110,11 +130,19 @@ def store(payment, options = {}) commit('PaymentAccountCreate', request, nil) end - def verify(credit_card, options={}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } + def verify(credit_card, options = {}) + request = build_soap_request do |xml| + xml.CreditCardAVSOnly(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_payment_method(xml, credit_card) + add_transaction(xml, 0, options) + add_terminal(xml, options) + add_address(xml, options) + end end + + # send request with the transaction amount set to 0 + commit('CreditCardAVSOnly', request, 0) end def supports_scrubbing? @@ -150,6 +178,8 @@ def add_payment_method(xml, payment) 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 @@ -178,21 +208,60 @@ def add_transaction(xml, money, options = {}) xml.ReversalType options[:reversal_type] if options[:reversal_type] xml.TransactionID options[:trans_id] if options[:trans_id] xml.TransactionAmount amount(money.to_i) if money - xml.MarketCode 'Default' if money - xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20) + xml.MarketCode market_code(money, options) if options[:market_code] || money + xml.ReferenceNumber options[:order_id].present? ? options[:order_id][0, 50] : SecureRandom.hex(20) + xml.TicketNumber options[:ticket_number] if options[:ticket_number] + xml.MerchantSuppliedTransactionId options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id] + xml.PaymentType options[:payment_type] if options[:payment_type] + xml.SubmissionType options[:submission_type] if options[:submission_type] + xml.DuplicateCheckDisableFlag options[:duplicate_check_disable_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_check_disable_flag].nil? + xml.DuplicateOverrideFlag options[:duplicate_override_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_override_flag].nil? + xml.MerchantDescriptor options[:merchant_descriptor] if options[:merchant_descriptor] + end + end + + 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 '01' - xml.CardPresentCode 'UseDefault' - xml.CardholderPresentCode 'UseDefault' - xml.CardInputCode 'UseDefault' - xml.CVVPresenceCode 'UseDefault' - xml.TerminalCapabilityCode 'UseDefault' - xml.TerminalEnvironmentCode 'UseDefault' + xml.TerminalID options[:terminal_id] || '01' + xml.TerminalType options[:terminal_type] if options[:terminal_type] + xml.CardPresentCode options[:card_present_code] || '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 @@ -201,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 @@ -214,8 +283,21 @@ def add_echeck(xml, payment) 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.ElectronicCommerceIndicator payment.eci if payment.eci.present? + xml.WalletType NETWORK_TOKEN_TYPE.fetch(payment.source, '0') + end + end + def add_address(xml, options) if address = options[:billing_address] || options[:address] + address[:email] ||= options[:email] xml.address do xml.BillingAddress1 address[:address1] if address[:address1] xml.BillingAddress2 address[:address2] if address[:address2] @@ -246,9 +328,7 @@ def parse(xml) doc.remove_namespaces! root = doc.root.xpath('//response/*') - if root.empty? - root = doc.root.xpath('//Response/*') - end + root = doc.root.xpath('//Response/*') if root.empty? root.each do |node| if node.elements.empty? @@ -313,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 4f33ae1afe8..83c35088833 100644 --- a/lib/active_merchant/billing/gateways/epay.rb +++ b/lib/active_merchant/billing/gateways/epay.rb @@ -5,48 +5,48 @@ class EpayGateway < Gateway self.default_currency = 'DKK' self.money_format = :cents - self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, - :american_express, :diners_club, :jcb, :maestro] - self.supported_countries = ['DK', 'SE', 'NO'] + self.supported_countries = %w[DK SE NO] + self.supported_cardtypes = %i[dankort forbrugsforeningen visa master + american_express diners_club jcb maestro] self.homepage_url = 'http://epay.dk/' self.display_name = 'ePay' CURRENCY_CODES = { - :ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051', - :ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533', - :AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100', - :BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096', - :BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064', - :BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976', - :CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170', - :CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203', - :DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218', - :ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230', - :EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981', - :GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320', - :GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191', - :HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356', - :IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400', - :JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174', - :KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398', - :LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426', - :LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498', - :MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446', - :MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454', - :MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516', - :NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554', - :OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608', - :PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642', - :RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090', - :SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654', - :SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740', - :STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764', - :TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626', - :TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834', - :UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860', - :VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951', - :XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710', - :ZMK => '894', :ZWD => '716' + ADP: '020', AED: '784', AFA: '004', ALL: '008', AMD: '051', + ANG: '532', AOA: '973', ARS: '032', AUD: '036', AWG: '533', + AZM: '031', BAM: '977', BBD: '052', BDT: '050', BGL: '100', + BGN: '975', BHD: '048', BIF: '108', BMD: '060', BND: '096', + BOB: '068', BOV: '984', BRL: '986', BSD: '044', BTN: '064', + BWP: '072', BYR: '974', BZD: '084', CAD: '124', CDF: '976', + CHF: '756', CLF: '990', CLP: '152', CNY: '156', COP: '170', + CRC: '188', CUP: '192', CVE: '132', CYP: '196', CZK: '203', + DJF: '262', DKK: '208', DOP: '214', DZD: '012', ECS: '218', + ECV: '983', EEK: '233', EGP: '818', ERN: '232', ETB: '230', + EUR: '978', FJD: '242', FKP: '238', GBP: '826', GEL: '981', + GHC: '288', GIP: '292', GMD: '270', GNF: '324', GTQ: '320', + GWP: '624', GYD: '328', HKD: '344', HNL: '340', HRK: '191', + HTG: '332', HUF: '348', IDR: '360', ILS: '376', INR: '356', + IQD: '368', IRR: '364', ISK: '352', JMD: '388', JOD: '400', + JPY: '392', KES: '404', KGS: '417', KHR: '116', KMF: '174', + KPW: '408', KRW: '410', KWD: '414', KYD: '136', KZT: '398', + LAK: '418', LBP: '422', LKR: '144', LRD: '430', LSL: '426', + LTL: '440', LVL: '428', LYD: '434', MAD: '504', MDL: '498', + MGF: '450', MKD: '807', MMK: '104', MNT: '496', MOP: '446', + MRO: '478', MTL: '470', MUR: '480', MVR: '462', MWK: '454', + MXN: '484', MXV: '979', MYR: '458', MZM: '508', NAD: '516', + NGN: '566', NIO: '558', NOK: '578', NPR: '524', NZD: '554', + OMR: '512', PAB: '590', PEN: '604', PGK: '598', PHP: '608', + PKR: '586', PLN: '985', PYG: '600', QAR: '634', ROL: '642', + RUB: '643', RUR: '810', RWF: '646', SAR: '682', SBD: '090', + SCR: '690', SDD: '736', SEK: '752', SGD: '702', SHP: '654', + SIT: '705', SKK: '703', SLL: '694', SOS: '706', SRG: '740', + STD: '678', SVC: '222', SYP: '760', SZL: '748', THB: '764', + TJS: '972', TMM: '795', TND: '788', TOP: '776', TPE: '626', + TRL: '792', TRY: '949', TTD: '780', TWD: '901', TZS: '834', + UAH: '980', UGX: '800', USD: '840', UYU: '858', UZS: '860', + VEB: '862', VND: '704', VUV: '548', XAF: '950', XCD: '951', + XOF: '952', XPF: '953', YER: '886', YUM: '891', ZAR: '710', + ZMK: '894', ZWD: '716' } # login: merchant number @@ -63,6 +63,7 @@ def authorize(money, credit_card_or_reference, options = {}) add_invoice(post, options) add_creditcard_or_reference(post, credit_card_or_reference) add_instant_capture(post, false) + add_3ds_auth(post, options) commit(:authorize, post) end @@ -74,6 +75,7 @@ def purchase(money, credit_card_or_reference, options = {}) add_creditcard_or_reference(post, credit_card_or_reference) add_invoice(post, options) add_instant_capture(post, true) + add_3ds_auth(post, options) commit(:authorize, post) end @@ -158,21 +160,35 @@ def add_instant_capture(post, option) post[:instantcapture] = option ? 1 : 0 end + def add_3ds_auth(post, options) + if options[:three_d_secure] + post[:eci] = options.dig(:three_d_secure, :eci) + post[:xid] = options.dig(:three_d_secure, :xid) + post[:cavv] = options.dig(:three_d_secure, :cavv) + post[:threeds_version] = options.dig(:three_d_secure, :version) + post[:ds_transaction_id] = options.dig(:three_d_secure, :ds_transaction_id) + end + end + 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'] + 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] + test: test?, + authorization: params[:transaction] + ) end end @@ -193,7 +209,6 @@ def do_authorize(params) headers['Referer'] = (options[:password] || 'activemerchant.org') response = raw_ssl_request(:post, live_url + 'auth/default.aspx', authorize_post_data(params), headers) - # Authorize gives the response back by redirecting with the values in # the URL query if location = response['Location'] @@ -250,7 +265,7 @@ def make_headers(data, soap_call) end def xml_builder(params, soap_call) - xml = Builder::XmlMarkup.new(:indent => 2) + xml = Builder::XmlMarkup.new(indent: 2) xml.instruct! xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', @@ -268,7 +283,7 @@ def xml_builder(params, soap_call) def authorize_post_data(params = {}) params[:language] = '2' - params[:cms] = 'activemerchant' + params[:cms] = 'activemerchant_3ds' params[:accepturl] = live_url + 'auth/default.aspx?accept=1' params[:declineurl] = live_url + 'auth/default.aspx?decline=1' params[:merchantnumber] = @options[:login] diff --git a/lib/active_merchant/billing/gateways/evo_ca.rb b/lib/active_merchant/billing/gateways/evo_ca.rb index b5f976b7cee..cd9848eb2a7 100644 --- a/lib/active_merchant/billing/gateways/evo_ca.rb +++ b/lib/active_merchant/billing/gateways/evo_ca.rb @@ -36,7 +36,7 @@ class EvoCaGateway < Gateway self.live_url = 'https://secure.evoepay.com/api/transact.php' self.supported_countries = ['CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] + self.supported_cardtypes = %i[visa master american_express jcb discover] self.money_format = :dollars self.homepage_url = 'http://www.evocanada.com/' self.display_name = 'EVO Canada' @@ -139,8 +139,8 @@ def authorize(money, credit_card, options = {}) # options. def capture(money, authorization, options = {}) post = { - :amount => amount(money), - :transactionid => authorization + amount: amount(money), + transactionid: authorization } add_order(post, options) commit('capture', money, post) @@ -153,7 +153,7 @@ def capture(money, authorization, options = {}) # The identification parameter is the transaction ID, retrieved # from {Response#authorization}. def refund(money, identification) - post = {:transactionid => identification} + post = { transactionid: identification } commit('refund', money, post) end @@ -181,7 +181,7 @@ def credit(money, credit_card, options = {}) # The identification parameter is the transaction ID, retrieved # from {Response#authorization}. def void(identification) - post = {:transactionid => identification} + post = { transactionid: identification } commit('void', nil, post) end @@ -192,7 +192,7 @@ def void(identification) # The identification parameter is the transaction ID, retrieved # from {Response#authorization}. def update(identification, options) - post = {:transactionid => identification} + post = { transactionid: identification } add_order(post, options) commit('update', nil, post) end @@ -245,7 +245,7 @@ def add_invoice(post, options) end def add_paymentmethod(post, payment) - if card_brand(payment)=='check' + if card_brand(payment) == 'check' post[:payment] = 'check' post[:checkname] = payment.name post[:checkaba] = payment.routing_number @@ -279,11 +279,14 @@ def commit(action, money, parameters) response = parse(data) message = message_from(response) - Response.new(success?(response), message, response, - :test => test?, - :authorization => response['transactionid'], - :avs_result => { :code => response['avsresponse'] }, - :cvv_result => response['cvvresponse'] + Response.new( + success?(response), + message, + response, + test: test?, + authorization: response['transactionid'], + avs_result: { code: response['avsresponse'] }, + cvv_result: response['cvvresponse'] ) end @@ -292,7 +295,7 @@ def message_from(response) end def post_data(action, parameters = {}) - post = {:type => action} + post = { type: action } if test? post[:username] = 'demo' diff --git a/lib/active_merchant/billing/gateways/eway.rb b/lib/active_merchant/billing/gateways/eway.rb index 04874aac7ba..c6e21c658af 100644 --- a/lib/active_merchant/billing/gateways/eway.rb +++ b/lib/active_merchant/billing/gateways/eway.rb @@ -9,7 +9,7 @@ class EwayGateway < Gateway self.money_format = :cents self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] self.homepage_url = 'http://www.eway.com.au/' self.display_name = 'eWAY' @@ -38,7 +38,7 @@ def purchase(money, creditcard, options = {}) commit(purchase_url(post[:CVN]), money, post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_customer_id(post) @@ -66,12 +66,12 @@ def scrub(transcript) private def requires_address!(options) - raise ArgumentError.new('Missing eWay required parameters: address or billing_address') unless options.has_key?(:address) or options.has_key?(:billing_address) + raise ArgumentError.new('Missing eWay required parameters: address or billing_address') unless options.has_key?(:address) || options.has_key?(:billing_address) end def add_creditcard(post, creditcard) - post[:CardNumber] = creditcard.number - post[:CardExpiryMonth] = sprintf('%.2i', creditcard.month) + post[:CardNumber] = creditcard.number + post[:CardExpiryMonth] = sprintf('%.2i', creditcard.month) post[:CardExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1] post[:CustomerFirstName] = creditcard.first_name post[:CustomerLastName] = creditcard.last_name @@ -82,7 +82,7 @@ def add_creditcard(post, creditcard) def add_address(post, options) if address = options[:billing_address] || options[:address] - post[:CustomerAddress] = [ address[:address1], address[:address2], address[:city], address[:state], address[:country] ].compact.join(', ') + post[:CustomerAddress] = [address[:address1], address[:address2], address[:city], address[:state], address[:country]].compact.join(', ') post[:CustomerPostcode] = address[:zip] end end @@ -111,11 +111,12 @@ 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? + authorization: response[:ewaytrxnnumber], + test: test? ) end @@ -145,6 +146,7 @@ def post_data(parameters = {}) def message_from(message) return '' if message.blank? + MESSAGES[message[0, 2]] || message end diff --git a/lib/active_merchant/billing/gateways/eway_managed.rb b/lib/active_merchant/billing/gateways/eway_managed.rb index fcdd34afbd4..c65ad5206b0 100644 --- a/lib/active_merchant/billing/gateways/eway_managed.rb +++ b/lib/active_merchant/billing/gateways/eway_managed.rb @@ -8,7 +8,7 @@ class EwayManagedGateway < Gateway self.supported_countries = ['AU'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] self.default_currency = 'AUD' @@ -49,7 +49,7 @@ def store(creditcard, options = {}) commit('CreateCustomer', post) end - def update(billing_id, creditcard, options={}) + def update(billing_id, creditcard, options = {}) post = {} # Handle our required fields @@ -59,7 +59,7 @@ def update(billing_id, creditcard, options={}) billing_address = options[:billing_address] eway_requires!(billing_address) - post[:managedCustomerID]=billing_id + post[:managedCustomerID] = billing_id add_creditcard(post, creditcard) add_address(post, billing_address) add_misc_fields(post, options) @@ -80,10 +80,10 @@ def update(billing_id, creditcard, options={}) # * :order_id -- The order number, passed to eWay as the "Invoice Reference" # * :invoice -- The invoice number, passed to eWay as the "Invoice Reference" unless :order_id is also given # * :description -- A description of the payment, passed to eWay as the "Invoice Description" - def purchase(money, billing_id, options={}) + def purchase(money, billing_id, options = {}) post = {} post[:managedCustomerID] = billing_id.to_s - post[:amount]=money + post[:amount] = money add_invoice(post, options) commit('ProcessPayment', post) @@ -122,13 +122,13 @@ def add_address(post, address) end def add_misc_fields(post, options) - post[:CustomerRef]=options[:billing_address][:customer_ref] || options[:customer] - post[:Title]=options[:billing_address][:title] - post[:Company]=options[:billing_address][:company] - post[:JobDesc]=options[:billing_address][:job_desc] - post[:Email]=options[:billing_address][:email] || options[:email] - post[:URL]=options[:billing_address][:url] - post[:Comments]=options[:description] + post[:CustomerRef] = options[:billing_address][:customer_ref] || options[:customer] + post[:Title] = options[:billing_address][:title] + post[:Company] = options[:billing_address][:company] + post[:JobDesc] = options[:billing_address][:job_desc] + post[:Email] = options[:billing_address][:email] || options[:email] + post[:URL] = options[:billing_address][:url] + post[:Comments] = options[:description] end def add_invoice(post, options) @@ -138,8 +138,8 @@ def add_invoice(post, options) # add credit card details to be stored by eway. NOTE eway requires "title" field def add_creditcard(post, creditcard) - post[:CCNumber] = creditcard.number - post[:CCExpiryMonth] = sprintf('%.2i', creditcard.month) + post[:CCNumber] = creditcard.number + post[:CCExpiryMonth] = sprintf('%.2i', creditcard.month) post[:CCExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1] post[:CCNameOnCard] = creditcard.name post[:FirstName] = creditcard.first_name @@ -150,24 +150,24 @@ def parse(body) reply = {} xml = REXML::Document.new(body) if root = REXML::XPath.first(xml, '//soap:Fault') then - reply=parse_fault(root) + reply = parse_fault(root) else if root = REXML::XPath.first(xml, '//ProcessPaymentResponse/ewayResponse') then # Successful payment - reply=parse_purchase(root) + reply = parse_purchase(root) else if root = REXML::XPath.first(xml, '//QueryCustomerResult') then - reply=parse_query_customer(root) + reply = parse_query_customer(root) else if root = REXML::XPath.first(xml, '//CreateCustomerResult') then - reply[:message]='OK' - reply[:CreateCustomerResult]=root.text - reply[:success]=true + reply[:message] = 'OK' + reply[:CreateCustomerResult] = root.text + reply[:success] = true else if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then if root.text.casecmp('true').zero? then - reply[:message]='OK' - reply[:success]=true + reply[:message] = 'OK' + reply[:success] = true else # ERROR: This state should never occur. If there is a problem, # a soap:Fault will be returned. The presence of this @@ -187,43 +187,47 @@ def parse(body) end def parse_fault(node) - reply={} - reply[:message]=REXML::XPath.first(node, '//soap:Reason/soap:Text').text - reply[:success]=false + reply = {} + reply[:message] = REXML::XPath.first(node, '//soap:Reason/soap:Text').text + reply[:success] = false reply end def parse_purchase(node) - reply={} - reply[:message]=REXML::XPath.first(node, '//ewayTrxnError').text - reply[:success]=(REXML::XPath.first(node, '//ewayTrxnStatus').text == 'True') - reply[:auth_code]=REXML::XPath.first(node, '//ewayAuthCode').text - reply[:transaction_number]=REXML::XPath.first(node, '//ewayTrxnNumber').text + reply = {} + reply[:message] = REXML::XPath.first(node, '//ewayTrxnError').text + reply[:success] = (REXML::XPath.first(node, '//ewayTrxnStatus').text == 'True') + reply[:auth_code] = REXML::XPath.first(node, '//ewayAuthCode').text + reply[:transaction_number] = REXML::XPath.first(node, '//ewayTrxnNumber').text reply end def parse_query_customer(node) - reply={} - reply[:message]='OK' - reply[:success]=true - reply[:CCNumber]=REXML::XPath.first(node, '//CCNumber').text - reply[:CCName]=REXML::XPath.first(node, '//CCName').text - reply[:CCExpiryMonth]=REXML::XPath.first(node, '//CCExpiryMonth').text - reply[:CCExpiryYear]=REXML::XPath.first(node, '//CCExpiryYear').text + reply = {} + reply[:message] = 'OK' + reply[:success] = true + reply[:CCNumber] = REXML::XPath.first(node, '//CCNumber').text + reply[:CCName] = REXML::XPath.first(node, '//CCName').text + reply[:CCExpiryMonth] = REXML::XPath.first(node, '//CCExpiryMonth').text + reply[:CCExpiryYear] = REXML::XPath.first(node, '//CCExpiryYear').text reply end def commit(action, post) - raw = begin - ssl_post(test? ? self.test_url : self.live_url, soap_request(post, action), 'Content-Type' => 'application/soap+xml; charset=utf-8') - rescue ResponseError => e - e.response.body - end + raw = + begin + ssl_post(test? ? self.test_url : self.live_url, soap_request(post, action), 'Content-Type' => 'application/soap+xml; charset=utf-8') + rescue ResponseError => e + e.response.body + end response = parse(raw) - EwayResponse.new(response[:success], response[:message], response, - :test => test?, - :authorization => response[:auth_code] + EwayResponse.new( + response[:success], + response[:message], + response, + test: test?, + authorization: response[:auth_code] ) end @@ -241,18 +245,18 @@ def soap_request(arguments, action) default_customer_fields.merge(arguments) end - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! - xml.tag! 'soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'} do + xml.tag! 'soap12:Envelope', { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope' } do xml.tag! 'soap12:Header' do - xml.tag! 'eWAYHeader', {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do + xml.tag! 'eWAYHeader', { 'xmlns' => 'https://www.eway.com.au/gateway/managedpayment' } do xml.tag! 'eWAYCustomerID', @options[:login] xml.tag! 'Username', @options[:username] xml.tag! 'Password', @options[:password] end end xml.tag! 'soap12:Body' do |x| - x.tag! action, {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do |y| + x.tag! action, { 'xmlns' => 'https://www.eway.com.au/gateway/managedpayment' } do |y| post.each do |key, value| y.tag! key, value end @@ -263,17 +267,17 @@ def soap_request(arguments, action) end def default_customer_fields - hash={} - %w( CustomerRef Title FirstName LastName Company JobDesc Email Address Suburb State PostCode Country Phone Mobile Fax URL Comments CCNumber CCNameOnCard CCExpiryMonth CCExpiryYear ).each do |field| - hash[field.to_sym]='' + hash = {} + %w(CustomerRef Title FirstName LastName Company JobDesc Email Address Suburb State PostCode Country Phone Mobile Fax URL Comments CCNumber CCNameOnCard CCExpiryMonth CCExpiryYear).each do |field| + hash[field.to_sym] = '' end return hash end def default_payment_fields - hash={} - %w( managedCustomerID amount invoiceReference invoiceDescription ).each do |field| - hash[field.to_sym]='' + hash = {} + %w(managedCustomerID amount invoiceReference invoiceDescription).each do |field| + hash[field.to_sym] = '' end return hash end @@ -284,7 +288,6 @@ def token @params['CreateCustomerResult'] end end - end end end diff --git a/lib/active_merchant/billing/gateways/eway_rapid.rb b/lib/active_merchant/billing/gateways/eway_rapid.rb index 29d06a837b4..a49e7dd8c1a 100644 --- a/lib/active_merchant/billing/gateways/eway_rapid.rb +++ b/lib/active_merchant/billing/gateways/eway_rapid.rb @@ -7,8 +7,8 @@ class EwayRapidGateway < Gateway self.live_url = 'https://api.ewaypayments.com/' self.money_format = :cents - self.supported_countries = ['AU', 'NZ', 'GB', 'SG', 'MY', 'HK'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_countries = %w[AU NZ GB SG MY HK] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.homepage_url = 'http://www.eway.com.au/' self.display_name = 'eWAY Rapid 3.1' self.default_currency = 'AUD' @@ -47,21 +47,22 @@ def initialize(options = {}) # (default: "https://github.com/activemerchant/active_merchant") # # Returns an ActiveMerchant::Billing::Response object where authorization is the Transaction ID on success - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) params = {} add_metadata(params, options) add_invoice(params, amount, options) - add_customer_data(params, options) + add_customer_data(params, options, payment_method) add_credit_card(params, payment_method, options) + add_3ds_authenticated_data(params, options) if options[:three_d_secure] params['Method'] = payment_method.respond_to?(:number) ? 'ProcessPayment' : 'TokenPayment' commit(url_for('Transaction'), params) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) params = {} add_metadata(params, options) add_invoice(params, amount, options) - add_customer_data(params, options) + add_customer_data(params, options, payment_method) add_credit_card(params, payment_method, options) params['Method'] = 'Authorise' commit(url_for('Authorisation'), params) @@ -137,7 +138,7 @@ def store(payment_method, options = {}) params = {} add_metadata(params, options) add_invoice(params, 0, options) - add_customer_data(params, options) + add_customer_data(params, options, payment_method) add_credit_card(params, payment_method, options) params['Method'] = 'CreateTokenCustomer' commit(url_for('Transaction'), params) @@ -166,7 +167,7 @@ def update(customer_token, payment_method, options = {}) params = {} add_metadata(params, options) add_invoice(params, 0, options) - add_customer_data(params, options) + add_customer_data(params, options, payment_method) add_credit_card(params, payment_method, options) add_customer_token(params, customer_token) params['Method'] = 'UpdateTokenCustomer' @@ -197,6 +198,18 @@ def add_metadata(params, options) params end + def add_3ds_authenticated_data(params, options) + three_d_secure_options = options[:three_d_secure] + params['PaymentInstrument'] ||= {} if params['PaymentInstrument'].nil? + threed_secure_auth = params['PaymentInstrument']['ThreeDSecureAuth'] = {} + threed_secure_auth['Cryptogram'] = three_d_secure_options[:cavv] + threed_secure_auth['ECI'] = three_d_secure_options[:eci] + threed_secure_auth['XID'] = three_d_secure_options[:xid] + threed_secure_auth['AuthStatus'] = three_d_secure_options[:authentication_response_status] + threed_secure_auth['dsTransactionId'] = three_d_secure_options[:ds_transaction_id] + threed_secure_auth['Version'] = three_d_secure_options[:version] + end + def add_invoice(params, money, options, key = 'Payment') currency_code = options[:currency] || currency(money) params[key] = { @@ -204,7 +217,7 @@ def add_invoice(params, money, options, key = 'Payment') 'InvoiceReference' => truncate(options[:order_id], 50), 'InvoiceNumber' => truncate(options[:invoice] || options[:order_id], 12), 'InvoiceDescription' => truncate(options[:description], 64), - 'CurrencyCode' => currency_code, + 'CurrencyCode' => currency_code } end @@ -212,17 +225,48 @@ def add_reference(params, reference) params['TransactionID'] = reference end - def add_customer_data(params, options) - params['Customer'] ||= {} - add_address(params['Customer'], (options[:billing_address] || options[:address]), {:email => options[:email]}) - params['ShippingAddress'] = {} - add_address(params['ShippingAddress'], options[:shipping_address], {:skip_company => true}) + def add_customer_data(params, options, payment_method = nil) + add_customer_fields(params, options, payment_method) + add_shipping_fields(params, options) + end + + def add_customer_fields(params, options, payment_method) + key = 'Customer' + params[key] ||= {} + + customer_address = options[:billing_address] || options[:address] + + add_name_and_email(params[key], customer_address, options[:email], payment_method) + add_address(params[key], customer_address) + end + + def add_shipping_fields(params, options) + key = 'ShippingAddress' + params[key] = {} + + add_name_and_email(params[key], options[:shipping_address], options[:email]) + add_address(params[key], options[:shipping_address], { skip_company: true }) end - def add_address(params, address, options={}) + def add_name_and_email(params, address, email, payment_method = nil) + if address.present? + params['FirstName'], params['LastName'] = split_names(address[:name]) + elsif payment_method_name_available?(payment_method) + params['FirstName'] = payment_method.first_name + params['LastName'] = payment_method.last_name + end + + params['Email'] = email + end + + def payment_method_name_available?(payment_method) + payment_method.respond_to?(:first_name) && payment_method.respond_to?(:last_name) && + payment_method.first_name.present? && payment_method.last_name.present? + end + + def add_address(params, address, options = {}) return unless address - params['FirstName'], params['LastName'] = split_names(address[:name]) params['Title'] = address[:title] params['CompanyName'] = address[:company] unless options[:skip_company] params['Street1'] = truncate(address[:address1], 50) @@ -231,13 +275,13 @@ def add_address(params, address, options={}) params['State'] = address[:state] params['PostalCode'] = address[:zip] params['Country'] = address[:country].to_s.downcase - params['Phone'] = address[:phone] + params['Phone'] = address[:phone] || address[:phone_number] params['Fax'] = address[:fax] - params['Email'] = options[:email] end def add_credit_card(params, credit_card, options) return unless credit_card + params['Customer'] ||= {} if credit_card.respond_to? :number card_details = params['Customer']['CardDetails'] = {} @@ -273,13 +317,13 @@ def commit(url, params) succeeded, message_from(succeeded, raw), raw, - :authorization => authorization_from(raw), - :test => test?, - :avs_result => avs_result_from(raw), - :cvv_result => cvv_result_from(raw) + authorization: authorization_from(raw), + test: test?, + avs_result: avs_result_from(raw), + cvv_result: cvv_result_from(raw) ) rescue ActiveMerchant::ResponseError => e - return ActiveMerchant::Billing::Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) + return ActiveMerchant::Billing::Response.new(false, e.response.message, { status_code: e.response.code }, test: test?) end def parse(data) @@ -325,15 +369,16 @@ def authorization_from(response) def avs_result_from(response) verification = response['Verification'] || {} - code = case verification['Address'] - when 'Valid' - 'M' - when 'Invalid' - 'N' - else - 'I' - end - {:code => code} + code = + case verification['Address'] + when 'Valid' + 'M' + when 'Invalid' + 'N' + else + 'I' + end + { code: code } end def cvv_result_from(response) @@ -526,7 +571,7 @@ def cvv_result_from(response) 'V6150' => 'Invalid Refund Amount', 'V6151' => 'Refund amount greater than original transaction', 'V6152' => 'Original transaction already refunded for total amount', - 'V6153' => 'Card type not support by merchant', + 'V6153' => 'Card type not support by merchant' } end end diff --git a/lib/active_merchant/billing/gateways/exact.rb b/lib/active_merchant/billing/gateways/exact.rb index d9649b84e21..6b99cd66e2a 100644 --- a/lib/active_merchant/billing/gateways/exact.rb +++ b/lib/active_merchant/billing/gateways/exact.rb @@ -5,37 +5,33 @@ class ExactGateway < Gateway API_VERSION = '8.5' - TEST_LOGINS = [ {:login => 'A00049-01', :password => 'test1'}, - {:login => 'A00427-01', :password => 'testus'} ] + TEST_LOGINS = [{ login: 'A00049-01', password: 'test1' }, + { login: 'A00427-01', password: 'testus' }] - TRANSACTIONS = { :sale => '00', - :authorization => '01', - :capture => '32', - :credit => '34' } + TRANSACTIONS = { sale: '00', + authorization: '01', + capture: '32', + credit: '34' } ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/', - 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' - } + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request', - 'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' - } + 'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' } SEND_AND_COMMIT_SOURCE_ATTRIBUTES = { 'xmlns:n2' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes', - 'xsi:type' => 'n2:Transaction' - } + 'xsi:type' => 'n2:Transaction' } POST_HEADERS = { 'soapAction' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit', - 'Content-Type' => 'text/xml' - } + 'Content-Type' => 'text/xml' } SUCCESS = 'true' - SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ] + SENSITIVE_FIELDS = %i[verification_str2 expiry_date card_number] - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] - self.supported_countries = ['CA', 'US'] + self.supported_countries = %w[CA US] + self.supported_cardtypes = %i[visa master american_express jcb discover] self.homepage_url = 'http://www.e-xact.com' self.display_name = 'E-xact' @@ -162,16 +158,19 @@ 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, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response[:avs] }, - :cvv_result => response[:cvv2] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(response), + avs_result: { code: response[:avs] }, + cvv_result: response[:cvv2] ) rescue ResponseError => e case e.response.code when '401' - return Response.new(false, "Invalid Login: #{e.response.body}", {}, :test => test?) + return Response.new(false, "Invalid Login: #{e.response.body}", {}, test: test?) else raise end @@ -211,7 +210,7 @@ def parse(xml) parse_elements(response, root) end - response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) } + response.delete_if { |k, _v| SENSITIVE_FIELDS.include?(k) } end def parse_elements(response, root) diff --git a/lib/active_merchant/billing/gateways/ezic.rb b/lib/active_merchant/billing/gateways/ezic.rb index 3bfe469c857..480c3313cd8 100644 --- a/lib/active_merchant/billing/gateways/ezic.rb +++ b/lib/active_merchant/billing/gateways/ezic.rb @@ -5,17 +5,17 @@ class EzicGateway < Gateway self.supported_countries = %w(AU CA CN FR DE GI IL MT MU MX NL NZ PA PH RU SG KR ES KN GB US) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.homepage_url = 'http://www.ezic.com/' self.display_name = 'Ezic' - def initialize(options={}) + def initialize(options = {}) requires!(options, :account_id) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_account_id(post) @@ -26,7 +26,7 @@ def purchase(money, payment, options={}) commit('S', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_account_id(post) @@ -37,7 +37,7 @@ def authorize(money, payment, options={}) commit('A', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_account_id(post) @@ -48,7 +48,7 @@ def capture(money, authorization, options={}) commit('D', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_account_id(post) @@ -59,7 +59,7 @@ def refund(money, authorization, options={}) commit('R', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_account_id(post) @@ -69,7 +69,7 @@ def void(authorization, options={}) commit('U', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -187,7 +187,7 @@ def post_data(parameters = {}) def headers { - 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}" } end end diff --git a/lib/active_merchant/billing/gateways/fat_zebra.rb b/lib/active_merchant/billing/gateways/fat_zebra.rb index 7a37c767304..91b4e23bb68 100644 --- a/lib/active_merchant/billing/gateways/fat_zebra.rb +++ b/lib/active_merchant/billing/gateways/fat_zebra.rb @@ -9,7 +9,7 @@ class FatZebraGateway < Gateway self.supported_countries = ['AU'] self.default_currency = 'AUD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + self.supported_cardtypes = %i[visa master american_express jcb] self.homepage_url = 'https://www.fatzebra.com.au/' self.display_name = 'Fat Zebra' @@ -27,6 +27,7 @@ def purchase(money, creditcard, options = {}) add_extra_options(post, options) add_order_id(post, options) add_ip(post, options) + add_metadata(post, options) commit(:post, 'purchases', post) end @@ -39,6 +40,7 @@ def authorize(money, creditcard, options = {}) add_extra_options(post, options) add_order_id(post, options) add_ip(post, options) + add_metadata(post, options) post[:capture] = false @@ -46,7 +48,7 @@ def authorize(money, creditcard, options = {}) end def capture(money, authorization, options = {}) - txn_id, _ = authorization.to_s.split('|') + txn_id, = authorization.to_s.split('|') post = {} add_amount(post, money, options) @@ -55,8 +57,8 @@ def capture(money, authorization, options = {}) commit(:post, "purchases/#{CGI.escape(txn_id)}/capture", post) end - def refund(money, authorization, options={}) - txn_id, _ = authorization.to_s.split('|') + def refund(money, authorization, options = {}) + txn_id, = authorization.to_s.split('|') post = {} add_extra_options(post, options) @@ -67,16 +69,17 @@ def refund(money, authorization, options={}) commit(:post, 'refunds', post) end - def void(authorization, options={}) + def void(authorization, options = {}) txn_id, endpoint = authorization.to_s.split('|') commit(:post, "#{endpoint}/void?id=#{txn_id}", {}) end - def store(creditcard, options={}) + def store(creditcard, options = {}) post = {} add_creditcard(post, creditcard) + post[:is_billing] = true if options[:recurring] commit(:post, 'credit_cards', post) end @@ -107,7 +110,7 @@ def add_creditcard(post, creditcard, options = {}) post[:cvv] = creditcard.verification_value if creditcard.verification_value? post[:card_holder] = creditcard.name if creditcard.name elsif creditcard.is_a?(String) - id, _ = creditcard.to_s.split('|') + id, = creditcard.to_s.split('|') post[:card_token] = id post[:cvv] = options[:cvv] elsif creditcard.is_a?(Hash) @@ -122,11 +125,13 @@ def add_creditcard(post, creditcard, options = {}) def add_extra_options(post, options) extra = {} extra[:ecm] = '32' if options[:recurring] - extra[:cavv] = options[:cavv] if options[:cavv] - extra[:xid] = options[:xid] if options[:xid] - extra[:sli] = options[:sli] if options[:sli] + extra[: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) post[:extra] = extra if extra.any? end @@ -138,21 +143,27 @@ def add_ip(post, options) post[:customer_ip] = options[:ip] || '127.0.0.1' end - def commit(method, uri, parameters=nil) - response = begin - parse(ssl_request(method, get_url(uri), parameters.to_json, headers)) - rescue ResponseError => e - return Response.new(false, 'Invalid Login') if(e.response.code == '401') - parse(e.response.body) - end + def add_metadata(post, options) + post[:metadata] = options.fetch(:metadata, {}) + end + + def commit(method, uri, parameters = nil) + response = + begin + parse(ssl_request(method, get_url(uri), parameters.to_json, headers)) + rescue ResponseError => e + return Response.new(false, 'Invalid Login') if e.response.code == '401' + + parse(e.response.body) + end success = success_from(response) Response.new( success, message_from(response), response, - :test => response['test'], - :authorization => authorization_from(response, success, uri) + test: response['test'], + authorization: authorization_from(response, success, uri) ) end diff --git a/lib/active_merchant/billing/gateways/federated_canada.rb b/lib/active_merchant/billing/gateways/federated_canada.rb index b6666a9fa44..43460286317 100644 --- a/lib/active_merchant/billing/gateways/federated_canada.rb +++ b/lib/active_merchant/billing/gateways/federated_canada.rb @@ -12,7 +12,7 @@ class FederatedCanadaGateway < Gateway self.default_currency = 'CAD' # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'http://www.federatedcanada.com/' @@ -54,7 +54,7 @@ def void(authorization, options = {}) end def refund(money, authorization, options = {}) - commit('refund', money, options.merge(:transactionid => authorization)) + commit('refund', money, options.merge(transactionid: authorization)) end def credit(money, authorization, options = {}) @@ -121,11 +121,14 @@ def commit(action, money, parameters) response = parse(data) message = message_from(response) - Response.new(success?(response), message, response, - :test => test?, - :authorization => response['transactionid'], - :avs_result => {:code => response['avsresponse']}, - :cvv_result => response['cvvresponse'] + Response.new( + success?(response), + message, + response, + test: test?, + authorization: response['transactionid'], + avs_result: { code: response['avsresponse'] }, + cvv_result: response['cvvresponse'] ) end diff --git a/lib/active_merchant/billing/gateways/finansbank.rb b/lib/active_merchant/billing/gateways/finansbank.rb index 5f496570853..8d14bc40b95 100644 --- a/lib/active_merchant/billing/gateways/finansbank.rb +++ b/lib/active_merchant/billing/gateways/finansbank.rb @@ -3,14 +3,14 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class FinansbankGateway < CC5Gateway - self.live_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer' + self.live_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer' self.test_url = 'https://entegrasyon.asseco-see.com.tr/fim/api' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US', 'TR'] + self.supported_countries = %w[US TR] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] # The homepage URL of the gateway self.homepage_url = 'https://www.fbwebpos.com/' diff --git a/lib/active_merchant/billing/gateways/first_giving.rb b/lib/active_merchant/billing/gateways/first_giving.rb index 09dea7f8e5a..3059943d457 100644 --- a/lib/active_merchant/billing/gateways/first_giving.rb +++ b/lib/active_merchant/billing/gateways/first_giving.rb @@ -7,7 +7,7 @@ class FirstGivingGateway < Gateway self.live_url = 'https://api.firstgiving.com' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.firstgiving.com/' self.default_currency = 'USD' self.display_name = 'FirstGiving' @@ -30,7 +30,7 @@ def purchase(money, creditcard, options = {}) def refund(money, identifier, options = {}) get = {} get[:transactionId] = identifier - get[:tranType] = 'REFUNDREQUEST' + get[:tranType] = 'REFUNDREQUEST' commit('/transaction/refundrequest?' + encode(get)) end @@ -49,7 +49,7 @@ def add_customer_data(post, options) end def add_address(post, options) - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:billToAddressLine1] = billing_address[:address1] post[:billToCity] = billing_address[:city] post[:billToState] = billing_address[:state] @@ -83,13 +83,14 @@ def parse(body) end element.children.each do |child| next if child.text? + response[child.name] = child.text end response end - def commit(action, post=nil) + def commit(action, post = nil) url = (test? ? self.test_url : self.live_url) + action begin diff --git a/lib/active_merchant/billing/gateways/first_pay.rb b/lib/active_merchant/billing/gateways/first_pay.rb index 3c197f0d79e..e6f92b3cdd8 100644 --- a/lib/active_merchant/billing/gateways/first_pay.rb +++ b/lib/active_merchant/billing/gateways/first_pay.rb @@ -8,17 +8,17 @@ class FirstPayGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://1stpaygateway.net/' self.display_name = '1stPayGateway.Net' - def initialize(options={}) + def initialize(options = {}) requires!(options, :transaction_center_id, :gateway_id) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment, options) @@ -28,7 +28,7 @@ def purchase(money, payment, options={}) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment, options) @@ -38,19 +38,19 @@ def authorize(money, payment, options={}) commit('auth', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_reference(post, 'settle', money, authorization) commit('settle', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_reference(post, 'credit', money, authorization) commit('credit', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, 'void', nil, authorization) commit('void', post) diff --git a/lib/active_merchant/billing/gateways/firstdata_e4.rb b/lib/active_merchant/billing/gateways/firstdata_e4.rb index 12290ad51c6..35191eeee72 100755 --- a/lib/active_merchant/billing/gateways/firstdata_e4.rb +++ b/lib/active_merchant/billing/gateways/firstdata_e4.rb @@ -22,22 +22,22 @@ class FirstdataE4Gateway < Gateway SUCCESS = 'true' - SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number] + SENSITIVE_FIELDS = %i[verification_str2 expiry_date card_number] BRANDS = { - :visa => 'Visa', - :master => 'Mastercard', - :american_express => 'American Express', - :jcb => 'JCB', - :discover => 'Discover' + visa: 'Visa', + master: 'Mastercard', + american_express: 'American Express', + jcb: 'JCB', + discover: 'Discover' } - E4_BRANDS = BRANDS.merge({:mastercard => 'Mastercard'}) + E4_BRANDS = BRANDS.merge({ mastercard: 'Mastercard' }) DEFAULT_ECI = '07' self.supported_cardtypes = BRANDS.keys - self.supported_countries = ['CA', 'US'] + self.supported_countries = %w[CA US] self.default_currency = 'USD' self.homepage_url = 'http://www.firstdata.com' self.display_name = 'FirstData Global Gateway e4' @@ -217,7 +217,7 @@ def add_transaction_type(xml, action) end def add_identification(xml, identification) - authorization_num, transaction_tag, _ = identification.split(';') + authorization_num, transaction_tag, = identification.split(';') xml.tag! 'Authorization_Num', authorization_num xml.tag! 'Transaction_Tag', transaction_tag @@ -245,23 +245,24 @@ def add_credit_card(xml, credit_card, options) end def add_credit_card_eci(xml, credit_card, options) - eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover' - # Discover requires any Apple Pay transaction, regardless of in-app - # or web, and regardless of the ECI contained in the PKPaymentToken, - # to have an ECI value explicitly of 04. - '04' - else - (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI - end + eci = + if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover' + # Discover requires any Apple Pay transaction, regardless of in-app + # or web, and regardless of the ECI contained in the PKPaymentToken, + # to have an ECI value explicitly of 04. + '04' + else + (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI + end - xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci + xml.tag! 'Ecommerce_Flag', /^[0-9]+$/.match?(eci.to_s) ? eci.to_s.rjust(2, '0') : eci end def add_credit_card_verification_strings(xml, credit_card, options) address = options[:billing_address] || options[:address] if address address_values = [] - [:address1, :zip, :city, :state, :country].each { |part| address_values << address[part].to_s } + %i[address1 zip city state country].each { |part| address_values << address[part].to_s.tr("\r\n", ' ').strip } xml.tag! 'VerificationStr1', address_values.join('|') end @@ -297,11 +298,12 @@ def add_card_authentication_data(xml, options) def add_credit_card_token(xml, store_authorization, options) params = store_authorization.split(';') credit_card = CreditCard.new( - :brand => params[1], - :first_name => params[2], - :last_name => params[3], - :month => params[4], - :year => params[5]) + brand: params[1], + first_name: params[2], + last_name: params[3], + month: params[4], + year: params[5] + ) xml.tag! 'TransarmorToken', params[0] xml.tag! 'Expiry_Date', expdate(credit_card) @@ -352,12 +354,15 @@ def commit(action, request, credit_card = nil) response = parse_error(e.response) end - Response.new(successful?(response), message_from(response), response, - :test => test?, - :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '', - :avs_result => {:code => response[:avs]}, - :cvv_result => response[:cvv2], - :error_code => standard_error_code(response) + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: successful?(response) ? response_authorization(action, response, credit_card) : '', + avs_result: { code: response[:avs] }, + cvv_result: response[:cvv2], + error_code: standard_error_code(response) ) end @@ -394,7 +399,7 @@ def store_authorization_from(response, credit_card) credit_card.last_name, credit_card.month, credit_card.year - ].map { |value| value.to_s.gsub(/;/, '') }.join(';') + ].map { |value| value.to_s.delete(';') }.join(';') else raise StandardError, "TransArmor support is not enabled on your #{display_name} account" end @@ -406,9 +411,9 @@ def money_from_authorization(auth) end def message_from(response) - if(response[:faultcode] && response[:faultstring]) + if response[:faultcode] && response[:faultstring] response[:faultstring] - elsif(response[:error_number] && response[:error_number] != '0') + elsif response[:error_number] && response[:error_number] != '0' response[:error_description] else result = (response[:exact_message] || '') @@ -419,10 +424,10 @@ def message_from(response) def parse_error(error) { - :transaction_approved => 'false', - :error_number => error.code, - :error_description => error.body, - :ecommerce_error_code => error.body.gsub(/[^\d]/, '') + transaction_approved: 'false', + error_number: error.code, + error_description: error.body, + ecommerce_error_code: error.body.gsub(/[^\d]/, '') } end @@ -438,7 +443,7 @@ def parse(xml) parse_elements(response, root) end - response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) } + response.delete_if { |k, _v| SENSITIVE_FIELDS.include?(k) } end def parse_elements(response, root) diff --git a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb index 4124139a7ca..7ac0901d891 100644 --- a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +++ b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb @@ -16,20 +16,20 @@ class FirstdataE4V27Gateway < Gateway SUCCESS = 'true' - SENSITIVE_FIELDS = [:cvdcode, :expiry_date, :card_number] + SENSITIVE_FIELDS = %i[cvdcode expiry_date card_number] BRANDS = { - :visa => 'Visa', - :master => 'Mastercard', - :american_express => 'American Express', - :jcb => 'JCB', - :discover => 'Discover' + visa: 'Visa', + master: 'Mastercard', + american_express: 'American Express', + jcb: 'JCB', + discover: 'Discover' } DEFAULT_ECI = '07' self.supported_cardtypes = BRANDS.keys - self.supported_countries = ['CA', 'US'] + self.supported_countries = %w[CA US] self.default_currency = 'USD' self.homepage_url = 'http://www.firstdata.com' self.display_name = 'FirstData Global Gateway e4 v27' @@ -191,7 +191,7 @@ def add_transaction_type(xml, action) end def add_identification(xml, identification) - authorization_num, transaction_tag, _ = identification.split(';') + authorization_num, transaction_tag, = identification.split(';') xml.tag! 'Authorization_Num', authorization_num xml.tag! 'Transaction_Tag', transaction_tag @@ -212,8 +212,8 @@ def add_credit_card(xml, credit_card, options) xml.tag! 'Expiry_Date', expdate(credit_card) xml.tag! 'CardHoldersName', credit_card.name xml.tag! 'CardType', card_type(credit_card.brand) - xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id] + add_wallet_provider_id(xml, credit_card, options) add_credit_card_eci(xml, credit_card, options) add_credit_card_verification_strings(xml, credit_card, options) end @@ -221,15 +221,14 @@ def add_credit_card(xml, credit_card, options) def add_credit_card_eci(xml, credit_card, options) eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover' - # Discover requires any Apple Pay transaction, regardless of in-app - # or web, and regardless of the ECI contained in the PKPaymentToken, - # to have an ECI value explicitly of 04. - '04' + # Payeezy requires an ECI of 5 for apple pay transactions + # See: https://support.payeezy.com/hc/en-us/articles/203730589-Ecommerce-Flag-Values + '05' else (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI end - xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci + xml.tag! 'Ecommerce_Flag', /^[0-9]+$/.match?(eci.to_s) ? eci.to_s.rjust(2, '0') : eci end def add_credit_card_verification_strings(xml, credit_card, options) @@ -265,20 +264,33 @@ def add_card_authentication_data(xml, options) def add_credit_card_token(xml, store_authorization, options) params = store_authorization.split(';') credit_card = CreditCard.new( - :brand => params[1], - :first_name => params[2], - :last_name => params[3], - :month => params[4], - :year => params[5]) + brand: params[1], + first_name: params[2], + last_name: params[3], + month: params[4], + year: params[5] + ) xml.tag! 'TransarmorToken', params[0] xml.tag! 'Expiry_Date', expdate(credit_card) xml.tag! 'CardHoldersName', credit_card.name xml.tag! 'CardType', card_type(credit_card.brand) - xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id] + + add_wallet_provider_id(xml, credit_card, options) add_card_authentication_data(xml, options) end + def add_wallet_provider_id(xml, credit_card, options) + provider_id = if options[:wallet_provider_id] + options[:wallet_provider_id] + elsif credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay + # See: https://support.payeezy.com/hc/en-us/articles/206601408-First-Data-Payeezy-Gateway-Web-Service-API-Reference-Guide#3.9 + 4 + end + + xml.tag! 'WalletProviderID', provider_id if provider_id + end + def add_customer_data(xml, options) xml.tag! 'Customer_Ref', options[:customer] if options[:customer] xml.tag! 'Client_IP', options[:ip] if options[:ip] @@ -287,6 +299,8 @@ def add_customer_data(xml, options) def add_address(xml, options) if (address = options[:billing_address] || options[:address]) + address = strip_line_breaks(address) + xml.tag! 'Address' do xml.tag! 'Address1', address[:address1] xml.tag! 'Address2', address[:address2] if address[:address2] @@ -299,6 +313,12 @@ def add_address(xml, options) end end + def strip_line_breaks(address) + return unless address.is_a?(Hash) + + Hash[address.map { |k, s| [k, s&.tr("\r\n", ' ')&.strip] }] + end + def add_invoice(xml, options) xml.tag! 'Reference_No', options[:order_id] xml.tag! 'Reference_3', options[:description] if options[:description] @@ -315,10 +335,11 @@ def add_level_3(xml, options) def add_stored_credentials(xml, card, options) return unless options[:stored_credential] + xml.tag! 'StoredCredentials' do xml.tag! 'Indicator', stored_credential_indicator(xml, card, options) if initiator = options.dig(:stored_credential, :initiator) - xml.tag! initiator == 'merchant' ? 'M' : 'C' + xml.tag! 'Initiation', initiator == 'merchant' ? 'M' : 'C' end if reason_type = options.dig(:stored_credential, :reason_type) xml.tag! 'Schedule', reason_type == 'unscheduled' ? 'U' : 'S' @@ -359,12 +380,15 @@ def commit(action, data, credit_card = nil) response = parse_error(e.response) end - Response.new(successful?(response), message_from(response), response, - :test => test?, - :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '', - :avs_result => {:code => response[:avs]}, - :cvv_result => response[:cvv2], - :error_code => standard_error_code(response) + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: successful?(response) ? response_authorization(action, response, credit_card) : '', + avs_result: { code: response[:avs] }, + cvv_result: response[:cvv2], + error_code: standard_error_code(response) ) end @@ -443,10 +467,10 @@ def message_from(response) def parse_error(error) { - :transaction_approved => 'false', - :error_number => error.code, - :error_description => error.body, - :ecommerce_error_code => error.body.gsub(/[^\d]/, '') + transaction_approved: 'false', + error_number: error.code, + error_description: error.body, + ecommerce_error_code: error.body.gsub(/[^\d]/, '') } end diff --git a/lib/active_merchant/billing/gateways/flo2cash.rb b/lib/active_merchant/billing/gateways/flo2cash.rb index 1f5c9d8076b..698f0ee74fd 100644 --- a/lib/active_merchant/billing/gateways/flo2cash.rb +++ b/lib/active_merchant/billing/gateways/flo2cash.rb @@ -10,7 +10,7 @@ class Flo2cashGateway < Gateway self.supported_countries = ['NZ'] self.default_currency = 'NZD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] BRAND_MAP = { 'visa' => 'VISA', @@ -19,19 +19,19 @@ class Flo2cashGateway < Gateway 'diners_club' => 'DINERS' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password, :account_id) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) MultiResponse.run do |r| r.process { authorize(amount, payment_method, options) } r.process { capture(amount, r.authorization, options) } end end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -40,7 +40,7 @@ def authorize(amount, payment_method, options={}) commit('ProcessAuthorise', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -49,7 +49,7 @@ def capture(amount, authorization, options={}) commit('ProcessCapture', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -71,7 +71,7 @@ def scrub(transcript) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") } CURRENCY_CODES['NZD'] = '554' def add_invoice(post, money, options) @@ -89,7 +89,7 @@ def add_payment_method(post, payment_method) end def add_customer_data(post, options) - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:Email] = billing_address[:email] end end @@ -107,7 +107,7 @@ def commit(action, post) begin raw = parse(ssl_post(url, data, headers(action)), action) rescue ActiveMerchant::ResponseError => e - if(e.response.code == '500' && e.response.body.start_with?(' authorization_from(action, raw[:transaction_id], post[:OriginalTransactionId]), - :error_code => error_code_from(succeeded, raw), - :test => test? + authorization: authorization_from(action, raw[:transaction_id], post[:OriginalTransactionId]), + error_code: error_code_from(succeeded, raw), + test: test? ) end @@ -133,7 +133,7 @@ def headers(action) end def build_request(action, post) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 post.each do |field, value| xml.tag!(field, value) end @@ -142,16 +142,16 @@ def build_request(action, post) end def envelope_wrap(action, body) - <<-EOS - - - - <#{action} xmlns="http://www.flo2cash.co.nz/webservices/paymentwebservice"> - #{body} - - - - EOS + <<~XML + + + + <#{action} xmlns="http://www.flo2cash.co.nz/webservices/paymentwebservice"> + #{body} + + + + XML end def url @@ -204,7 +204,7 @@ def authorization_from(action, current, original) 'Bank Declined Transaction' => STANDARD_ERROR_CODE[:card_declined], 'Insufficient Funds' => STANDARD_ERROR_CODE[:card_declined], 'Transaction Declined - Bank Error' => STANDARD_ERROR_CODE[:processing_error], - 'No Reply from Bank' => STANDARD_ERROR_CODE[:processing_error], + 'No Reply from Bank' => STANDARD_ERROR_CODE[:processing_error] } def error_code_from(succeeded, response) diff --git a/lib/active_merchant/billing/gateways/flo2cash_simple.rb b/lib/active_merchant/billing/gateways/flo2cash_simple.rb index f0662ff463c..bafe9fa6d21 100644 --- a/lib/active_merchant/billing/gateways/flo2cash_simple.rb +++ b/lib/active_merchant/billing/gateways/flo2cash_simple.rb @@ -3,7 +3,7 @@ module Billing #:nodoc: class Flo2cashSimpleGateway < Flo2cashGateway self.display_name = 'Flo2Cash Simple' - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) diff --git a/lib/active_merchant/billing/gateways/forte.rb b/lib/active_merchant/billing/gateways/forte.rb index bdc06c7a3f5..7163434c3fe 100644 --- a/lib/active_merchant/billing/gateways/forte.rb +++ b/lib/active_merchant/billing/gateways/forte.rb @@ -10,41 +10,45 @@ class ForteGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.forte.net' self.display_name = 'Forte' - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key, :secret, :location_id, :account_id) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = {} add_amount(post, money, options) + add_service_fee(post, options) add_invoice(post, options) - add_payment_method(post, payment_method) + add_payment_method(post, payment_method, options) add_billing_address(post, payment_method, options) add_shipping_address(post, options) + add_xdata(post, options) post[:action] = 'sale' commit(:post, post) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) post = {} add_amount(post, money, options) + add_service_fee(post, options) add_invoice(post, options) - add_payment_method(post, payment_method) + add_payment_method(post, payment_method, options) add_billing_address(post, payment_method, options) add_shipping_address(post, options) + add_xdata(post, options) post[:action] = 'authorize' commit(:post, post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} post[:transaction_id] = transaction_id_from(authorization) post[:authorization_code] = authorization_code_from(authorization) || '' @@ -53,18 +57,18 @@ def capture(money, authorization, options={}) commit(:put, post) end - def credit(money, payment_method, options={}) + def credit(money, payment_method, options = {}) post = {} add_amount(post, money, options) add_invoice(post, options) - add_payment_method(post, payment_method) + add_payment_method(post, payment_method, options) add_billing_address(post, payment_method, options) post[:action] = 'disburse' commit(:post, post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_amount(post, money, options) post[:original_transaction_id] = transaction_id_from(authorization) @@ -74,7 +78,7 @@ def refund(money, authorization, options={}) commit(:post, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} post[:transaction_id] = transaction_id_from(authorization) post[:authorization_code] = authorization_code_from(authorization) @@ -83,7 +87,7 @@ def void(authorization, options={}) commit(:put, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -116,6 +120,20 @@ def add_amount(post, money, options) post[:authorization_amount] = amount(money) end + def add_service_fee(post, options) + post[:service_fee_amount] = options[:service_fee_amount] if options[:service_fee_amount] + end + + def add_xdata(post, options) + post[:xdata] = {} + if xdata = options[:xdata] + (1..9).each do |n| + field = "xdata_#{n}".to_sym + post[:xdata][field] = xdata[field] if xdata[field] + end + end + end + def add_billing_address(post, payment, options) post[:billing_address] = {} if address = options[:billing_address] || options[:address] @@ -130,17 +148,14 @@ def add_billing_address(post, payment, options) post[:billing_address][:physical_address][:locality] = address[:city] if address[:city] end - if empty?(post[:billing_address][:first_name]) && payment.first_name - post[:billing_address][:first_name] = payment.first_name - end + post[:billing_address][:first_name] = payment.first_name if empty?(post[:billing_address][:first_name]) && payment.first_name - if empty?(post[:billing_address][:last_name]) && payment.last_name - post[:billing_address][:last_name] = payment.last_name - end + post[:billing_address][:last_name] = payment.last_name if empty?(post[:billing_address][:last_name]) && payment.last_name end def add_shipping_address(post, options) return unless options[:shipping_address] + address = options[:shipping_address] post[:shipping_address] = {} @@ -154,21 +169,22 @@ def add_shipping_address(post, options) post[:shipping_address][:physical_address][:locality] = address[:city] if address[:city] end - def add_payment_method(post, payment_method) + def add_payment_method(post, payment_method, options) if payment_method.respond_to?(:brand) add_credit_card(post, payment_method) else - add_echeck(post, payment_method) + add_echeck(post, payment_method, options) end end - def add_echeck(post, payment) + def add_echeck(post, payment, options) post[:echeck] = {} post[:echeck][:account_holder] = payment.name post[:echeck][:account_number] = payment.account_number post[:echeck][:routing_number] = payment.routing_number post[:echeck][:account_type] = payment.account_type post[:echeck][:check_number] = payment.number + post[:echeck][:sec_code] = options[:sec_code] || 'PPD' end def add_credit_card(post, payment) @@ -262,7 +278,7 @@ def authorization_code_from(authorization) end def transaction_id_from(authorization) - transaction_id, _, original_auth_transaction_id, _= split_authorization(authorization) + transaction_id, _, original_auth_transaction_id, = split_authorization(authorization) original_auth_transaction_id.present? ? original_auth_transaction_id : transaction_id end end diff --git a/lib/active_merchant/billing/gateways/garanti.rb b/lib/active_merchant/billing/gateways/garanti.rb index 3c7f19efc5d..57a78d4104e 100644 --- a/lib/active_merchant/billing/gateways/garanti.rb +++ b/lib/active_merchant/billing/gateways/garanti.rb @@ -5,10 +5,10 @@ class GarantiGateway < Gateway self.test_url = 'https://sanalposprovtest.garanti.com.tr/VPServlet' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US', 'TR'] + self.supported_countries = %w[US TR] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'https://sanalposweb.garanti.com.tr' @@ -36,17 +36,17 @@ def initialize(options = {}) end def purchase(money, credit_card, options = {}) - options = options.merge(:gvp_order_type => 'sales') + options = options.merge(gvp_order_type: 'sales') commit(money, build_sale_request(money, credit_card, options)) end def authorize(money, credit_card, options = {}) - options = options.merge(:gvp_order_type => 'preauth') + options = options.merge(gvp_order_type: 'preauth') commit(money, build_authorize_request(money, credit_card, options)) end def capture(money, ref_id, options = {}) - options = options.merge(:gvp_order_type => 'postauth') + options = options.merge(gvp_order_type: 'postauth') commit(money, build_capture_request(money, ref_id, options)) end @@ -66,8 +66,8 @@ def build_xml_request(money, credit_card, options, &block) card_number = credit_card.respond_to?(:number) ? credit_card.number : '' hash_data = generate_hash_data(format_order_id(options[:order_id]), @options[:terminal_id], card_number, amount(money), security_data) - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct! :xml, :version => '1.0', :encoding => 'UTF-8' + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! :xml, version: '1.0', encoding: 'UTF-8' xml.tag! 'GVPSRequest' do xml.tag! 'Mode', test? ? 'TEST' : 'PROD' @@ -104,7 +104,7 @@ def build_sale_request(money, credit_card, options) def build_authorize_request(money, credit_card, options) build_xml_request(money, credit_card, options) do |xml| add_customer_data(xml, options) - add_order_data(xml, options) do + add_order_data(xml, options) do add_addresses(xml, options) end add_credit_card(xml, credit_card) @@ -115,7 +115,7 @@ def build_authorize_request(money, credit_card, options) end def build_capture_request(money, ref_id, options) - options = options.merge(:order_id => ref_id) + options = options.merge(order_id: ref_id) build_xml_request(money, ref_id, options) do |xml| add_customer_data(xml, options) add_order_data(xml, options) @@ -137,9 +137,7 @@ def add_order_data(xml, options, &block) xml.tag! 'OrderID', format_order_id(options[:order_id]) xml.tag! 'GroupID' - if block_given? - yield xml - end + yield xml if block_given? end end @@ -221,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]) + test: test?, + authorization: response[:order_id] + ) end def parse(body) @@ -253,7 +253,6 @@ def success?(response) def strip_invalid_xml_chars(xml) xml.gsub(/&(?!(?:[a-z]+|#[0-9]+|x[a-zA-Z0-9]+);)/, '&') end - end end end diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb index 370780d14f1..8e66a82c742 100644 --- a/lib/active_merchant/billing/gateways/global_collect.rb +++ b/lib/active_merchant/billing/gateways/global_collect.rb @@ -1,30 +1,37 @@ 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.homepage_url = 'http://www.globalcollect.com/' self.test_url = 'https://eu.sandbox.api-ingenico.com' - self.live_url = 'https://api.globalcollect.com' + self.preproduction_url = 'https://world.preprod.api-ingenico.com' + self.live_url = 'https://world.api-ingenico.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 = ['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.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 = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover naranja cabal] - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :api_key_id, :secret_api_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) MultiResponse.run do |r| r.process { authorize(money, payment, options) } - r.process { capture(money, r.authorization, options) } unless capture_requested?(r) + r.process { capture(money, r.authorization, options) } if should_request_capture?(r, options[:requires_approval]) end end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = nestable_hash add_order(post, money, options) add_payment(post, payment, options) @@ -32,39 +39,44 @@ def authorize(money, payment, options={}) add_address(post, payment, options) add_creator_info(post, options) add_fraud_fields(post, options) - - commit(:authorize, post) + add_external_cardholder_authentication_data(post, options) + add_threeds_exemption_data(post, options) + commit(:post, :authorize, post, options: options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = nestable_hash add_order(post, money, options, capture: true) add_customer_data(post, options) add_creator_info(post, options) - commit(:capture, post, authorization) + commit(:post, :capture, post, authorization: authorization) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = nestable_hash add_amount(post, money, options) add_refund_customer_data(post, options) add_creator_info(post, options) - commit(:refund, post, authorization) + commit(:post, :refund, post, authorization: authorization) end - def void(authorization, options={}) + def void(authorization, options = {}) post = nestable_hash add_creator_info(post, options) - commit(:void, post, authorization) + commit(:post, :void, post, authorization: authorization) end - def verify(payment, options={}) + def verify(payment, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, payment, options) } r.process { void(r.authorization, options) } end end + def inquire(authorization, options = {}) + commit(:get, :inquire, nil, authorization: authorization) + end + def supports_scrubbing? true end @@ -73,7 +85,10 @@ def scrub(transcript) transcript. gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]'). gsub(%r(("cardNumber\\+":\\+")\d+), '\1[FILTERED]'). - gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]') + gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]'). + gsub(%r(("dpan\\+":\\+")\d+), '\1[FILTERED]'). + gsub(%r(("pan\\+":\\+")\d+), '\1[FILTERED]'). + gsub(%r(("cryptogram\\+":\\+"|("cavv\\+" : \\+"))[^\\]*), '\1[FILTERED]') end private @@ -84,7 +99,11 @@ def scrub(transcript) 'master' => '3', 'discover' => '128', 'jcb' => '125', - 'diners_club' => '132' + 'diners_club' => '132', + 'cabal' => '135', + 'naranja' => '136', + 'apple_pay': '302', + 'google_pay': '320' } def add_order(post, money, options, capture: false) @@ -100,6 +119,127 @@ def add_order(post, money, options, capture: false) post['order']['references']['invoiceData'] = { 'invoiceNumber' => options[:invoice] } + 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 + + def add_airline_data(post, options) + return unless airline_options = options[:airline_data] + + airline_data = {} + + airline_data['flightDate'] = airline_options[:flight_date] if airline_options[:flight_date] + airline_data['passengerName'] = airline_options[:passenger_name] if airline_options[:passenger_name] + airline_data['code'] = airline_options[:code] if airline_options[:code] + airline_data['name'] = airline_options[:name] if airline_options[:name] + airline_data['invoiceNumber'] = options[:airline_data][:invoice_number] if options[:airline_data][:invoice_number] + airline_data['isETicket'] = options[:airline_data][:is_eticket] if options[:airline_data][:is_eticket] + airline_data['isRestrictedTicket'] = options[:airline_data][:is_restricted_ticket] if options[:airline_data][:is_restricted_ticket] + 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) + + post['order']['additionalInput']['airlineData'] = airline_data + end + + def add_flight_legs(airline_options) + flight_legs = [] + airline_options[:flight_legs]&.each do |fl| + leg = {} + leg['airlineClass'] = fl[:airline_class] if fl[:airline_class] + leg['arrivalAirport'] = fl[:arrival_airport] if fl[:arrival_airport] + leg['arrivalTime'] = fl[:arrival_time] if fl[:arrival_time] + leg['carrierCode'] = fl[:carrier_code] if fl[:carrier_code] + leg['conjunctionTicket'] = fl[:conjunction_ticket] if fl[:conjunction_ticket] + leg['couponNumber'] = fl[:coupon_number] if fl[:coupon_number] + leg['date'] = fl[:date] if fl[:date] + leg['departureTime'] = fl[:departure_time] if fl[:departure_time] + leg['endorsementOrRestriction'] = fl[:endorsement_or_restriction] if fl[:endorsement_or_restriction] + leg['exchangeTicket'] = fl[:exchange_ticket] if fl[:exchange_ticket] + leg['fare'] = fl[:fare] if fl[:fare] + leg['fareBasis'] = fl[:fare_basis] if fl[:fare_basis] + leg['fee'] = fl[:fee] if fl[:fee] + leg['flightNumber'] = fl[:flight_number] if fl[:flight_number] + leg['number'] = fl[:number] if fl[:number] + leg['originAirport'] = fl[:origin_airport] if fl[:origin_airport] + leg['passengerClass'] = fl[:passenger_class] if fl[:passenger_class] + leg['stopoverCode'] = fl[:stopover_code] if fl[:stopover_code] + leg['taxes'] = fl[:taxes] if fl[:taxes] + flight_legs << leg + end + flight_legs + end + + def add_passengers(airline_options) + passengers = [] + airline_options[:passengers]&.each do |flyer| + passenger = {} + passenger['firstName'] = flyer[:first_name] if flyer[:first_name] + passenger['surname'] = flyer[:surname] if flyer[:surname] + passenger['surnamePrefix'] = flyer[:surname_prefix] if flyer[:surname_prefix] + passenger['title'] = flyer[:title] if flyer[:title] + passengers << passenger + end + passengers + end + + def add_lodging_data(post, options) + return unless lodging_options = options[:lodging_data] + + lodging_data = {} + + lodging_data['charges'] = add_charges(lodging_options) + lodging_data['checkInDate'] = lodging_options[:check_in_date] if lodging_options[:check_in_date] + lodging_data['checkOutDate'] = lodging_options[:check_out_date] if lodging_options[:check_out_date] + lodging_data['folioNumber'] = lodging_options[:folio_number] if lodging_options[:folio_number] + lodging_data['isConfirmedReservation'] = lodging_options[:is_confirmed_reservation] if lodging_options[:is_confirmed_reservation] + lodging_data['isFacilityFireSafetyConform'] = lodging_options[:is_facility_fire_safety_conform] if lodging_options[:is_facility_fire_safety_conform] + lodging_data['isNoShow'] = lodging_options[:is_no_show] if lodging_options[:is_no_show] + lodging_data['isPreferenceSmokingRoom'] = lodging_options[:is_preference_smoking_room] if lodging_options[:is_preference_smoking_room] + lodging_data['numberOfAdults'] = lodging_options[:number_of_adults] if lodging_options[:number_of_adults] + lodging_data['numberOfNights'] = lodging_options[:number_of_nights] if lodging_options[:number_of_nights] + lodging_data['numberOfRooms'] = lodging_options[:number_of_rooms] if lodging_options[:number_of_rooms] + lodging_data['programCode'] = lodging_options[:program_code] if lodging_options[:program_code] + lodging_data['propertyCustomerServicePhoneNumber'] = lodging_options[:property_customer_service_phone_number] if lodging_options[:property_customer_service_phone_number] + lodging_data['propertyPhoneNumber'] = lodging_options[:property_phone_number] if lodging_options[:property_phone_number] + lodging_data['renterName'] = lodging_options[:renter_name] if lodging_options[:renter_name] + lodging_data['rooms'] = add_rooms(lodging_options) + + post['order']['additionalInput']['lodgingData'] = lodging_data + end + + def add_charges(lodging_options) + charges = [] + lodging_options[:charges]&.each do |item| + charge = {} + charge['chargeAmount'] = item[:charge_amount] if item[:charge_amount] + charge['chargeAmountCurrencyCode'] = item[:charge_amount_currency_code] if item[:charge_amount_currency_code] + charge['chargeType'] = item[:charge_type] if item[:charge_type] + charges << charge + end + charges + end + + def add_rooms(lodging_options) + rooms = [] + lodging_options[:rooms]&.each do |item| + room = {} + room['dailyRoomRate'] = item[:daily_room_rate] if item[:daily_room_rate] + room['dailyRoomRateCurrencyCode'] = item[:daily_room_rate_currency_code] if item[:daily_room_rate_currency_code] + room['dailyRoomTaxAmount'] = item[:daily_room_tax_amount] if item[:daily_room_tax_amount] + room['dailyRoomTaxAmountCurrencyCode'] = item[:daily_room_tax_amount_currency_code] if item[:daily_room_tax_amount_currency_code] + room['numberOfNightsAtRoomRate'] = item[:number_of_nights_at_room_rate] if item[:number_of_nights_at_room_rate] + room['roomLocation'] = item[:room_location] if item[:room_location] + room['roomNumber'] = item[:room_number] if item[:room_number] + room['typeOfBed'] = item[:type_of_bed] if item[:type_of_bed] + room['typeOfRoom'] = item[:type_of_room] if item[:type_of_room] + rooms << room + end + rooms end def add_creator_info(post, options) @@ -113,41 +253,77 @@ def add_creator_info(post, options) post['shoppingCartExtension']['extensionID'] = options[:extension_ID] if options[:extension_ID] end - def add_amount(post, money, options={}) + 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 def add_payment(post, payment, options) year = format(payment.year, :two_digits) month = format(payment.month, :two_digits) - expirydate = "#{month}#{year}" + expirydate = "#{month}#{year}" pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION' - - post['cardPaymentMethodSpecificInput'] = { - 'paymentProductId' => BRAND_MAP[payment.brand], - 'skipAuthentication' => 'true', # refers to 3DSecure - 'skipFraudService' => 'true', - 'authorizationMode' => pre_authorization + product_id = options[:payment_product_id] || BRAND_MAP[payment.brand] + specifics_inputs = { + 'paymentProductId' => product_id, + 'skipAuthentication' => options[:skip_authentication] || 'true', # refers to 3DSecure + 'skipFraudService' => 'true', + 'authorizationMode' => pre_authorization } - post['cardPaymentMethodSpecificInput']['card'] = { + specifics_inputs['requiresApproval'] = options[:requires_approval] unless options[:requires_approval].nil? + 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) + end + end + + def add_credit_card(post, payment, specifics_inputs, expirydate) + post['cardPaymentMethodSpecificInput'] = specifics_inputs.merge({ + 'card' => { 'cvv' => payment.verification_value, 'cardNumber' => payment.number, 'expiryDate' => expirydate, 'cardholderName' => payment.name - } + } + }) + 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] + post['mobilePaymentMethodSpecificInput'] = specifics_inputs + add_decrypted_payment_data(post, payment, options, expirydate) + 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 end def add_customer_data(post, options, payment = nil) if payment - post['order']['customer']['personalInformation'] = { - 'name' => { - 'firstName' => payment.first_name[0..14], - 'surname' => payment.last_name[0..69] - } - } + post['order']['customer']['personalInformation']['name']['firstName'] = payment.first_name[0..14] if payment.first_name + post['order']['customer']['personalInformation']['name']['surname'] = payment.last_name[0..69] if payment.last_name end post['order']['customer']['merchantCustomerId'] = options[:customer] if options[:customer] post['order']['customer']['companyInformation']['name'] = options[:company] if options[:company] @@ -173,21 +349,23 @@ def add_address(post, creditcard, options) shipping_address = options[:shipping_address] if billing_address = options[:billing_address] || options[:address] post['order']['customer']['billingAddress'] = { - 'street' => billing_address[:address1], - 'additionalInfo' => billing_address[:address2], + '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], - 'state' => billing_address[:state], + 'state' => truncate(billing_address[:state], 35), 'countryCode' => billing_address[:country] } end if shipping_address post['order']['customer']['shippingAddress'] = { - 'street' => shipping_address[:address1], - 'additionalInfo' => shipping_address[:address2], + '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], - 'state' => shipping_address[:state], + 'state' => truncate(shipping_address[:state], 35), 'countryCode' => shipping_address[:country] } post['order']['customer']['shippingAddress']['name'] = { @@ -205,46 +383,97 @@ def add_fraud_fields(post, options) post['fraudFields'] = fraud_fields unless fraud_fields.empty? end + def add_external_cardholder_authentication_data(post, options) + return unless threeds_2_options = options[:three_d_secure] + + 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 + def parse(body) JSON.parse(body) end 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 uri + "payments/#{authorization}/cancel" + when :inquire + uri + "payments/#{authorization}" end end - def commit(action, post, authorization = nil) + def idempotency_key_for_signature(options) + "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key] && !ogone_direct? + end + + def commit(method, action, post, authorization: nil, options: {}) begin - raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization)) + raw_response = ssl_request(method, url(action, authorization), post&.to_json, headers(method, action, post, authorization, options)) response = parse(raw_response) rescue ResponseError => e - if e.response.code.to_i >= 400 - response = parse(e.response.body) - end + response = parse(e.response.body) if e.response.code.to_i >= 400 rescue JSON::ParserError response = json_error(raw_response) end - succeeded = success_from(response) + succeeded = success_from(action, response) Response.new( succeeded, message_from(succeeded, response), response, - authorization: authorization_from(succeeded, response), + authorization: authorization_from(response), error_code: error_code_from(succeeded, response), test: test? ) @@ -253,78 +482,90 @@ def commit(action, post, authorization = nil) def json_error(raw_response) { 'error_message' => 'Invalid response received from the Ingenico ePayments (formerly GlobalCollect) API. Please contact Ingenico ePayments if you continue to receive this message.' \ - " (The raw response returned by the API was #{raw_response.inspect})", - 'status' => 'REJECTED' + " (The raw response returned by the API was #{raw_response.inspect})" } end - def headers(action, post, authorization = nil) - { - 'Content-Type' => content_type, - 'Authorization' => auth_digest(action, post, authorization), + def headers(method, action, post, authorization = nil, options = {}) + headers = { + 'Content-Type' => content_type, + 'Authorization' => auth_digest(method, action, post, authorization, options), 'Date' => date } + + headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key] && !ogone_direct? + headers end - def auth_digest(action, post, authorization = nil) - data = <<-EOS -POST -#{content_type} -#{date} -#{uri(action, authorization)} -EOS - digest = OpenSSL::Digest.new('sha256') + def auth_digest(method, action, post, authorization = nil, options = {}) + data = <<~REQUEST + #{method.to_s.upcase} + #{content_type} + #{date} + #{idempotency_key_for_signature(options)} + #{uri(action, authorization)} + REQUEST + data = data.each_line.reject { |line| line.strip == '' }.join + 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.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 'application/json' end - def success_from(response) - !response['errorId'] && response['status'] != 'REJECTED' - end + def success_from(action, response) + return false if response['errorId'] || response['error_message'] - def message_from(succeeded, response) - if succeeded - 'Succeeded' + case action + when :authorize + response.dig('payment', 'statusOutput', 'isAuthorized') + when :capture + capture_status = response.dig('status') || response.dig('payment', 'status') + %w(CAPTURED CAPTURE_REQUESTED).include?(capture_status) + when :void + void_response_id = response.dig('cardPaymentMethodSpecificOutput', 'voidResponseId') || response.dig('mobilePaymentMethodSpecificOutput', 'voidResponseId') + %w(00 0 8 11).include?(void_response_id) || response.dig('payment', 'status') == 'CANCELLED' + when :refund + refund_status = response.dig('status') || response.dig('payment', 'status') + %w(REFUNDED REFUND_REQUESTED).include?(refund_status) else - if errors = response['errors'] - errors.first.try(:[], 'message') - elsif response['error_message'] - response['error_message'] - elsif response['status'] - 'Status: ' + response['status'] - else - 'No message available' - end + response['status'] != 'REJECTED' end end - def authorization_from(succeeded, response) - if succeeded - response['id'] || response['payment']['id'] || response['paymentResult']['payment']['id'] - elsif response['errorId'] - response['errorId'] + def message_from(succeeded, response) + return 'Succeeded' if succeeded + + if errors = response['errors'] + errors.first.try(:[], 'message') + elsif response['error_message'] + response['error_message'] + elsif response['status'] + 'Status: ' + response['status'] else - 'GATEWAY ERROR' + 'No message available' end end + def authorization_from(response) + response.dig('id') || response.dig('payment', 'id') || response.dig('paymentResult', 'payment', 'id') + end + def error_code_from(succeeded, response) - unless succeeded - if errors = response['errors'] - errors.first.try(:[], 'code') - elsif status = response.try(:[], 'statusOutput').try(:[], 'statusCode') - status.to_s - else - 'No error code available' - end + return if succeeded + + if errors = response['errors'] + errors.first.try(:[], 'code') + elsif status = response.try(:[], 'statusOutput').try(:[], 'statusCode') + status.to_s + else + 'No error code available' end end @@ -332,6 +573,13 @@ def nestable_hash Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } end + # Capture hasn't already been requested, + # and + # `requires_approval` is not false + def should_request_capture?(response, requires_approval) + !capture_requested?(response) && requires_approval != false + end + def capture_requested?(response) response.params.try(:[], 'payment').try(:[], 'status') == 'CAPTURE_REQUESTED' end diff --git a/lib/active_merchant/billing/gateways/global_transport.rb b/lib/active_merchant/billing/gateways/global_transport.rb index 94087b23e17..ca3732a8bd8 100644 --- a/lib/active_merchant/billing/gateways/global_transport.rb +++ b/lib/active_merchant/billing/gateways/global_transport.rb @@ -8,7 +8,7 @@ class GlobalTransportGateway < Gateway self.supported_countries = %w(CA PR US) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'https://www.globalpaymentsinc.com' self.display_name = 'Global Transport' @@ -20,12 +20,12 @@ class GlobalTransportGateway < Gateway # :global_password - Your Global password # :term_type - 3 character field assigned by Global Transport after # - your application is certified. - def initialize(options={}) + def initialize(options = {}) requires!(options, :global_user_name, :global_password, :term_type) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = {} add_invoice(post, money, options) add_payment_method(post, payment_method) @@ -34,7 +34,7 @@ def purchase(money, payment_method, options={}) commit('Sale', post, options) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) post = {} add_invoice(post, money, options) add_payment_method(post, payment_method) @@ -43,7 +43,7 @@ def authorize(money, payment_method, options={}) commit('Auth', post, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_invoice(post, money, options) add_auth(post, authorization) @@ -51,7 +51,7 @@ def capture(money, authorization, options={}) commit('Force', post, options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_invoice(post, money, options) add_auth(post, authorization) @@ -59,14 +59,14 @@ def refund(money, authorization, options={}) commit('Return', post, options) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_auth(post, authorization) commit('Void', post, options) end - def verify(payment_method, options={}) + def verify(payment_method, options = {}) post = {} add_payment_method(post, payment_method) add_address(post, options) @@ -188,7 +188,6 @@ def default_params ExtData: '' } end - end end end diff --git a/lib/active_merchant/billing/gateways/hdfc.rb b/lib/active_merchant/billing/gateways/hdfc.rb index 142d7c83b82..8941ab19151 100644 --- a/lib/active_merchant/billing/gateways/hdfc.rb +++ b/lib/active_merchant/billing/gateways/hdfc.rb @@ -12,14 +12,14 @@ class HdfcGateway < Gateway self.supported_countries = ['IN'] self.default_currency = 'INR' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :discover, :diners_club] + self.supported_cardtypes = %i[visa master discover diners_club] - def initialize(options={}) + def initialize(options = {}) requires!(options, :login, :password) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -28,7 +28,7 @@ def purchase(amount, payment_method, options={}) commit('purchase', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -37,7 +37,7 @@ def authorize(amount, payment_method, options={}) commit('authorize', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -46,7 +46,7 @@ def capture(amount, authorization, options={}) commit('capture', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -57,7 +57,7 @@ def refund(amount, authorization, options={}) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } CURRENCY_CODES['AED'] = '784' CURRENCY_CODES['AUD'] = '036' CURRENCY_CODES['CAD'] = '124' @@ -81,14 +81,14 @@ def add_customer_data(post, options) post[:udf2] = escape(options[:email]) if options[:email] if address = (options[:billing_address] || options[:address]) post[:udf3] = escape(address[:phone]) if address[:phone] - post[:udf4] = escape(< '1', 'refund' => '2', 'authorize' => '4', - 'capture' => '5', + 'capture' => '5' } def commit(action, post) @@ -149,13 +149,13 @@ def commit(action, post) succeeded, message_from(succeeded, raw), raw, - :authorization => authorization_from(post, raw), - :test => test? + authorization: authorization_from(post, raw), + test: test? ) end def build_request(post) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! post.each do |field, value| xml.tag!(field, value) @@ -194,11 +194,10 @@ def split_authorization(authorization) [tranid, member] end - def escape(string, max_length=250) + def escape(string, max_length = 250) return '' unless string - if max_length - string = string[0...max_length] - end + + string = string[0...max_length] if max_length string.gsub(/[^A-Za-z0-9 \-_@\.\n]/, '') end end diff --git a/lib/active_merchant/billing/gateways/hps.rb b/lib/active_merchant/billing/gateways/hps.rb index dbc7997370b..5bd92b80e02 100644 --- a/lib/active_merchant/billing/gateways/hps.rb +++ b/lib/active_merchant/billing/gateways/hps.rb @@ -8,68 +8,94 @@ class HpsGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jbc, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jbc diners_club] self.homepage_url = 'http://developer.heartlandpaymentsystems.com/SecureSubmit/' self.display_name = 'Heartland Payment Systems' self.money_format = :dollars - def initialize(options={}) + PAYMENT_DATA_SOURCE_MAPPING = { + apple_pay: 'ApplePay', + master: 'MasterCard 3DSecure', + visa: 'Visa 3DSecure', + american_express: 'AMEX 3DSecure', + discover: 'Discover 3DSecure', + android_pay: 'GooglePayApp', + google_pay: 'GooglePayApp' + } + + def initialize(options = {}) requires!(options, :secret_api_key) super end - def authorize(money, card_or_token, options={}) + def authorize(money, card_or_token, options = {}) commit('CreditAuth') do |xml| add_amount(xml, money) add_allow_dup(xml) - add_customer_data(xml, card_or_token, options) + add_card_or_token_customer_data(xml, card_or_token, options) add_details(xml, options) add_descriptor_name(xml, options) - add_payment(xml, card_or_token, options) + add_card_or_token_payment(xml, card_or_token, options) + add_three_d_secure(xml, card_or_token, options) + add_stored_credentials(xml, options) end end - def capture(money, transaction_id, options={}) - commit('CreditAddToBatch') do |xml| + def capture(money, transaction_id, options = {}) + commit('CreditAddToBatch', transaction_id) do |xml| add_amount(xml, money) add_reference(xml, transaction_id) end end - def purchase(money, card_or_token, options={}) - commit('CreditSale') do |xml| + def purchase(money, payment_method, options = {}) + if payment_method.is_a?(Check) + commit_check_sale(money, payment_method, options) + elsif options.dig(:stored_credential, :reason_type) == 'recurring' + commit_recurring_billing_sale(money, payment_method, options) + else + commit_credit_sale(money, payment_method, options) + end + end + + def refund(money, transaction_id, options = {}) + commit('CreditReturn') do |xml| add_amount(xml, money) add_allow_dup(xml) - add_customer_data(xml, card_or_token, options) + add_reference(xml, transaction_id) + add_card_or_token_customer_data(xml, transaction_id, options) add_details(xml, options) - add_descriptor_name(xml, options) - add_payment(xml, card_or_token, options) end end - def refund(money, transaction_id, options={}) + def credit(money, payment_method, options = {}) commit('CreditReturn') do |xml| add_amount(xml, money) add_allow_dup(xml) - add_reference(xml, transaction_id) - add_customer_data(xml, transaction_id, options) + add_card_or_token_payment(xml, payment_method, options) add_details(xml, options) end end - def verify(card_or_token, options={}) + def verify(card_or_token, options = {}) commit('CreditAccountVerify') do |xml| - add_customer_data(xml, card_or_token, options) + add_card_or_token_customer_data(xml, card_or_token, options) add_descriptor_name(xml, options) - add_payment(xml, card_or_token, options) + add_card_or_token_payment(xml, card_or_token, options) end end - def void(transaction_id, options={}) - commit('CreditVoid') do |xml| - add_reference(xml, transaction_id) + def void(transaction_id, options = {}) + if options[:check_void] + commit('CheckVoid') do |xml| + add_reference(xml, transaction_id) + end + else + commit('CreditVoid') do |xml| + add_reference(xml, transaction_id) + end end end @@ -81,20 +107,61 @@ def scrub(transcript) transcript. gsub(%r(()[^<]*(<\/hps:CardNbr>))i, '\1[FILTERED]\2'). gsub(%r(()[^<]*(<\/hps:CVV2>))i, '\1[FILTERED]\2'). - gsub(%r(()[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2') + gsub(%r(()[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:PaymentData>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:RoutingNumber>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:AccountNumber>))i, '\1[FILTERED]\2') end private + def commit_check_sale(money, check, options) + commit('CheckSale') do |xml| + add_check_payment(xml, check, options) + add_amount(xml, money) + add_sec_code(xml, options) + add_check_customer_data(xml, check, options) + add_details(xml, options) + end + end + + def commit_credit_sale(money, card_or_token, options) + commit('CreditSale') do |xml| + add_amount(xml, money) + add_allow_dup(xml) + add_card_or_token_customer_data(xml, card_or_token, options) + add_details(xml, options) + add_descriptor_name(xml, options) + add_card_or_token_payment(xml, card_or_token, options) + add_three_d_secure(xml, card_or_token, options) + add_stored_credentials(xml, options) + end + end + + def commit_recurring_billing_sale(money, card_or_token, options) + commit('RecurringBilling') do |xml| + add_amount(xml, money) + add_allow_dup(xml) + add_card_or_token_customer_data(xml, card_or_token, options) + add_details(xml, options) + add_descriptor_name(xml, options) + add_card_or_token_payment(xml, card_or_token, options) + add_three_d_secure(xml, card_or_token, options) + add_stored_credentials(xml, options) + add_stored_credentials_for_recurring_billing(xml, options) + end + end + def add_reference(xml, transaction_id) - xml.hps :GatewayTxnId, transaction_id + reference = transaction_id.to_s.include?('|') ? transaction_id.split('|').first : transaction_id + xml.hps :GatewayTxnId, reference end def add_amount(xml, money) xml.hps :Amt, amount(money) if money end - def add_customer_data(xml, credit_card, options) + def add_card_or_token_customer_data(xml, credit_card, options) xml.hps :CardHolderData do if credit_card.respond_to?(:number) xml.hps :CardHolderFirstName, credit_card.first_name if credit_card.first_name @@ -104,20 +171,28 @@ def add_customer_data(xml, credit_card, options) xml.hps :CardHolderEmail, options[:email] if options[:email] xml.hps :CardHolderPhone, options[:phone] if options[:phone] - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) xml.hps :CardHolderAddr, billing_address[:address1] if billing_address[:address1] xml.hps :CardHolderCity, billing_address[:city] if billing_address[:city] xml.hps :CardHolderState, billing_address[:state] if billing_address[:state] - xml.hps :CardHolderZip, billing_address[:zip] if billing_address[:zip] + xml.hps :CardHolderZip, alphanumeric_zip(billing_address[:zip]) if billing_address[:zip] end end end - def add_payment(xml, card_or_token, options) + def add_check_customer_data(xml, check, options) + xml.hps :ConsumerInfo do + xml.hps :FirstName, check.first_name + xml.hps :LastName, check.last_name + xml.hps :CheckName, options[:company_name] if options[:company_name] + end + end + + def add_card_or_token_payment(xml, card_or_token, options) xml.hps :CardData do if card_or_token.respond_to?(:number) if card_or_token.track_data - xml.tag!('hps:TrackData', 'method'=>'swipe') do + xml.tag!('hps:TrackData', 'method' => 'swipe') do xml.text! card_or_token.track_data end if options[:encryption_type] @@ -148,14 +223,29 @@ def add_payment(xml, card_or_token, options) end end + def add_check_payment(xml, check, options) + xml.hps :CheckAction, 'SALE' + xml.hps :AccountInfo do + xml.hps :RoutingNumber, check.routing_number + xml.hps :AccountNumber, check.account_number + xml.hps :CheckNumber, check.number + xml.hps :AccountType, check.account_type&.upcase + end + xml.hps :CheckType, check.account_holder_type&.upcase + end + def add_details(xml, options) xml.hps :AdditionalTxnFields do xml.hps :Description, options[:description] if options[:description] - xml.hps :InvoiceNbr, options[:order_id] if options[:order_id] + xml.hps :InvoiceNbr, options[:order_id][0..59] if options[:order_id] xml.hps :CustomerID, options[:customer_id] if options[:customer_id] end end + def add_sec_code(xml, options) + xml.hps :SECCode, options[:sec_code] || 'WEB' + end + def add_allow_dup(xml) xml.hps :AllowDup, 'Y' end @@ -164,12 +254,80 @@ def add_descriptor_name(xml, options) xml.hps :TxnDescriptor, options[:descriptor_name] if options[:descriptor_name] end + def add_three_d_secure(xml, card_or_token, options) + if card_or_token.is_a?(NetworkTokenizationCreditCard) + build_three_d_secure(xml, { + source: card_or_token.source, + cavv: card_or_token.payment_cryptogram, + eci: card_or_token.eci, + xid: card_or_token.transaction_id + }) + elsif options[:three_d_secure] + options[:three_d_secure][:source] ||= card_brand(card_or_token) + build_three_d_secure(xml, options[:three_d_secure]) + end + end + + def build_three_d_secure(xml, three_d_secure) + # PaymentDataSource is required when supplying the SecureECommerce data group, + # and the gateway currently only allows the values within the mapping + return unless PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym] + + xml.hps :SecureECommerce do + xml.hps :PaymentDataSource, PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym] + xml.hps :TypeOfPaymentData, '3DSecure' # Only type currently supported + xml.hps :PaymentData, three_d_secure[:cavv] if three_d_secure[:cavv] + # the gateway only allows a single character for the ECI + xml.hps :ECommerceIndicator, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci] + xml.hps :XID, three_d_secure[:xid] if three_d_secure[:xid] + end + end + + # We do not currently support installments on this gateway. + # The HPS gateway treats recurring transactions as a seperate transaction type + def add_stored_credentials(xml, options) + return unless options[:stored_credential] + + xml.hps :CardOnFileData do + if options[:stored_credential][:initiator] == 'customer' + xml.hps :CardOnFile, 'C' + elsif options[:stored_credential][:initiator] == 'merchant' + xml.hps :CardOnFile, 'M' + else + return + end + + if options[:stored_credential][:network_transaction_id] + xml.hps :CardBrandTxnId, options[:stored_credential][:network_transaction_id] + else + return + end + end + end + + def add_stored_credentials_for_recurring_billing(xml, options) + xml.hps :RecurringData do + if options[:stored_credential][:reason_type] = 'recurring' + xml.hps :OneTime, 'N' + else + xml.hps :OneTime, 'Y' + end + end + end + + def strip_leading_zero(value) + return value unless value[0] == '0' + + value[1, 1] + end + def build_request(action) xml = Builder::XmlMarkup.new(encoding: 'UTF-8') xml.instruct!(:xml, encoding: 'UTF-8') xml.SOAP :Envelope, { - 'xmlns:SOAP' => 'http://schemas.xmlsoap.org/soap/envelope/', - 'xmlns:hps' => 'http://Hps.Exchange.PosGateway' } do + 'xmlns:SOAP' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:hps' => 'http://Hps.Exchange.PosGateway' + } do xml.SOAP :Body do xml.hps :PosRequest do xml.hps 'Ver1.0'.to_sym do @@ -202,7 +360,7 @@ def parse(raw) doc = Nokogiri::XML(raw) doc.remove_namespaces! - if(header = doc.xpath('//Header').first) + if (header = doc.xpath('//Header').first) header.elements.each do |node| if node.elements.size == 0 response[node.name] = node.text @@ -213,33 +371,34 @@ def parse(raw) end end end - if(transaction = doc.xpath('//Transaction/*[1]').first) + if (transaction = doc.xpath('//Transaction/*[1]').first) transaction.elements.each do |node| response[node.name] = node.text end end - if(fault = doc.xpath('//Fault/Reason/Text').first) + if (fault = doc.xpath('//Fault/Reason/Text').first) response['Fault'] = fault.text end response end - def commit(action, &request) + def commit(action, reference = nil, &request) data = build_request(action, &request) - response = begin - parse(ssl_post((test? ? test_url : live_url), data, 'Content-Type' => 'text/xml')) - rescue ResponseError => e - parse(e.response.body) - end + response = + begin + parse(ssl_post((test? ? test_url : live_url), data, 'Content-Type' => 'text/xml')) + rescue ResponseError => e + parse(e.response.body) + end ActiveMerchant::Billing::Response.new( successful?(response), message_from(response), response, test: test?, - authorization: authorization_from(response), + authorization: authorization_from(response, reference), avs_result: { code: response['AVSRsltCode'], message: response['AVSRsltText'] @@ -248,28 +407,31 @@ def commit(action, &request) ) end + SUCCESSFUL_RESPONSE_CODES = %w(0 00 85) def successful?(response) ( (response['GatewayRspCode'] == '0') && - ((response['RspCode'] || '00') == '00' || response['RspCode'] == '85') + ((SUCCESSFUL_RESPONSE_CODES.include? response['RspCode']) || !response['RspCode']) ) end def message_from(response) - if(response['Fault']) + if response['Fault'] response['Fault'] - elsif(response['GatewayRspCode'] == '0') - if(response['RspCode'] != '00' && response['RspCode'] != '85') - issuer_message(response['RspCode']) - else + elsif response['GatewayRspCode'] == '0' + if SUCCESSFUL_RESPONSE_CODES.include? response['RspCode'] response['GatewayRspMsg'] + else + issuer_message(response['RspCode']) end else (GATEWAY_MESSAGES[response['GatewayRspCode']] || response['GatewayRspMsg']) end end - def authorization_from(response) + def authorization_from(response, reference) + return [reference, response['GatewayTxnId']].join('|') if reference + response['GatewayTxnId'] end @@ -277,6 +439,10 @@ def test? @options[:secret_api_key]&.include?('_cert_') end + def alphanumeric_zip(zip) + zip.gsub(/[^0-9a-z]/i, '') + end + ISSUER_MESSAGES = { '13' => 'Must be greater than or equal 0.', '14' => 'The card number is incorrect.', @@ -290,6 +456,7 @@ def issuer_message(code) return 'The card was declined.' if %w(02 03 04 05 41 43 44 51 56 61 62 63 65 78).include?(code) return 'An error occurred while processing the card.' if %w(06 07 12 15 19 12 52 53 57 58 76 77 91 96 EC).include?(code) return "The card's security code is incorrect." if %w(EB N7).include?(code) + ISSUER_MESSAGES[code] end diff --git a/lib/active_merchant/billing/gateways/iats_payments.rb b/lib/active_merchant/billing/gateways/iats_payments.rb index c2d4505dfb9..ee758ea55fd 100644 --- a/lib/active_merchant/billing/gateways/iats_payments.rb +++ b/lib/active_merchant/billing/gateways/iats_payments.rb @@ -8,22 +8,23 @@ class IatsPaymentsGateway < Gateway self.supported_countries = %w(AU BR CA CH DE DK ES FI FR GR HK IE IT NL NO PT SE SG TR GB US TH ID PH BE) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://home.iatspayments.com/' self.display_name = 'iATS Payments' ACTIONS = { - purchase: 'ProcessCreditCardV1', - purchase_check: 'ProcessACHEFTV1', - refund: 'ProcessCreditCardRefundWithTransactionIdV1', - refund_check: 'ProcessACHEFTRefundWithTransactionIdV1', - store: 'CreateCreditCardCustomerCodeV1', - unstore: 'DeleteCustomerCodeV1' + purchase: 'ProcessCreditCard', + purchase_check: 'ProcessACHEFT', + purchase_customer_code: 'ProcessCreditCardWithCustomerCode', + refund: 'ProcessCreditCardRefundWithTransactionId', + refund_check: 'ProcessACHEFTRefundWithTransactionId', + store: 'CreateCreditCardCustomerCode', + unstore: 'DeleteCustomerCode' } - def initialize(options={}) - if(options[:login]) + def initialize(options = {}) + if options[:login] ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'agent_code' and will be removed in a future version.") options[:agent_code] = options[:login] end @@ -34,18 +35,19 @@ def initialize(options={}) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) add_address(post, options) add_ip(post, options) add_description(post, options) + add_customer_details(post, options) - commit((payment.is_a?(Check) ? :purchase_check : :purchase), post) + commit(determine_purchase_type(payment), post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} transaction_id, payment_type = split_authorization(authorization) post[:transaction_id] = transaction_id @@ -90,17 +92,30 @@ def scrub(transcript) private + def determine_purchase_type(payment) + if payment.is_a?(String) + :purchase_customer_code + elsif payment.is_a?(Check) + :purchase_check + else + :purchase + end + end + def add_ip(post, options) post[:customer_ip_address] = options[:ip] if options.has_key?(:ip) end def add_address(post, options) billing_address = options[:billing_address] || options[:address] - if(billing_address) + if billing_address post[:address] = billing_address[:address1] post[:city] = billing_address[:city] post[:state] = billing_address[:state] post[:zip_code] = billing_address[:zip] + post[:phone] = billing_address[:phone] if billing_address[:phone] + post[:email] = billing_address[:email] if billing_address[:email] + post[:country] = billing_address[:country] if billing_address[:country] end end @@ -114,7 +129,9 @@ def add_description(post, options) end def add_payment(post, payment) - if payment.is_a?(Check) + if payment.is_a?(String) + post[:customer_code] = payment + elsif payment.is_a?(Check) add_check(post, payment) else add_credit_card(post, payment) @@ -144,6 +161,10 @@ def add_store_defaults(post) post[:amount] = 0 end + def add_customer_details(post, options) + post[:email] = options[:email] if options[:email] + end + def expdate(creditcard) year = sprintf('%.4i', creditcard.year) month = sprintf('%.2i', creditcard.month) @@ -164,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), @@ -178,12 +204,13 @@ def commit(action, parameters) def endpoints { - purchase: 'ProcessLink.asmx', - purchase_check: 'ProcessLink.asmx', - refund: 'ProcessLink.asmx', - refund_check: 'ProcessLink.asmx', - store: 'CustomerLink.asmx', - unstore: 'CustomerLink.asmx' + purchase: 'ProcessLinkv3.asmx', + purchase_check: 'ProcessLinkv3.asmx', + purchase_customer_code: 'ProcessLinkv3.asmx', + refund: 'ProcessLinkv3.asmx', + refund_check: 'ProcessLinkv3.asmx', + store: 'CustomerLinkv3.asmx', + unstore: 'CustomerLinkv3.asmx' } end @@ -217,7 +244,7 @@ def hashify_xml!(xml, response) end def recursively_parse_element(node, response) - if(node.has_elements?) + if node.has_elements? node.elements.each { |n| recursively_parse_element(n, response) } else response[dexmlize_param_name(node.name)] = (node.text ? node.text.strip : nil) @@ -235,7 +262,7 @@ def success_from(response) def message_from(response) if !successful_result_message?(response) && response[:authorization_result] return response[:authorization_result].strip - elsif(response[:status] == 'Failure') + elsif response[:status] == 'Failure' return response[:errors] else response[:status] @@ -243,7 +270,7 @@ def message_from(response) end def authorization_from(action, response) - if [:store, :unstore].include?(action) + if %i[store unstore].include?(action) response[:customercode] elsif [:purchase_check].include?(action) response[:transaction_id] ? "#{response[:transaction_id]}|check" : nil @@ -266,7 +293,7 @@ def envelope_namespaces def post_data(action, parameters = {}) xml = Builder::XmlMarkup.new - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') xml.tag! 'soap12:Envelope', envelope_namespaces do xml.tag! 'soap12:Body' do xml.tag! ACTIONS[action], { 'xmlns' => 'https://www.iatspayments.com/NetGate/' } do diff --git a/lib/active_merchant/billing/gateways/in_context_paypal_express.rb b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb index e746d978ca0..6f892d122ca 100644 --- a/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +++ b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb @@ -5,7 +5,7 @@ class InContextPaypalExpressGateway < PaypalExpressGateway self.live_redirect_url = 'https://www.paypal.com/checkoutnow' def redirect_url_for(token, options = {}) - options = {review: true}.update(options) + options = { review: true }.update(options) url = "#{redirect_url}?token=#{token}" url += '&useraction=commit' unless options[:review] url diff --git a/lib/active_merchant/billing/gateways/inspire.rb b/lib/active_merchant/billing/gateways/inspire.rb index e7771e9ec81..742ced15d0b 100644 --- a/lib/active_merchant/billing/gateways/inspire.rb +++ b/lib/active_merchant/billing/gateways/inspire.rb @@ -5,7 +5,7 @@ class InspireGateway < Gateway self.live_url = self.test_url = 'https://secure.inspiregateway.net/api/transact.php' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.homepage_url = 'http://www.inspiregateway.com' self.display_name = 'Inspire Commerce' @@ -51,13 +51,13 @@ def purchase(money, payment_source, options = {}) end def capture(money, authorization, options = {}) - post ={} + post = {} post[:transactionid] = authorization commit('capture', money, post) end def void(authorization, options = {}) - post ={} + post = {} post[:transactionid] = authorization commit('void', nil, post) end @@ -93,33 +93,29 @@ def delete(vault_id) # store and unstore need to be defined def store(creditcard, options = {}) billing_id = options.delete(:billing_id).to_s || true - authorize(100, creditcard, options.merge(:store => billing_id)) + authorize(100, creditcard, options.merge(store: billing_id)) end - alias_method :unstore, :delete + alias unstore delete private def add_customer_data(post, options) - if options.has_key? :email - post[:email] = options[:email] - end + post[:email] = options[:email] if options.has_key? :email - if options.has_key? :ip - post[:ipaddress] = options[:ip] - end + post[:ipaddress] = options[:ip] if options.has_key? :ip end def add_address(post, creditcard, options) if address = options[:billing_address] || options[:address] post[:address1] = address[:address1].to_s post[:address2] = address[:address2].to_s unless address[:address2].blank? - post[:company] = address[:company].to_s - post[:phone] = address[:phone].to_s - post[:zip] = address[:zip].to_s - post[:city] = address[:city].to_s - post[:country] = address[:country].to_s - post[:state] = address[:state].blank? ? 'n/a' : address[:state] + post[:company] = address[:company].to_s + post[:phone] = address[:phone].to_s + post[:zip] = address[:zip].to_s + post[:city] = address[:city].to_s + post[:country] = address[:country].to_s + post[:state] = address[:state].blank? ? 'n/a' : address[:state] end end @@ -128,7 +124,7 @@ def add_invoice(post, options) post[:orderdescription] = options[:description] end - def add_payment_source(params, source, options={}) + def add_payment_source(params, source, options = {}) case determine_funding_source(source) when :vault then add_customer_vault_id(params, source) when :credit_card then add_creditcard(params, source, options) @@ -145,9 +141,9 @@ def add_creditcard(post, creditcard, options) post[:customer_vault] = 'add_customer' post[:customer_vault_id] = options[:store] unless options[:store] == true end - post[:ccnumber] = creditcard.number + post[:ccnumber] = creditcard.number post[:cvv] = creditcard.verification_value if creditcard.verification_value? - post[:ccexp] = expdate(creditcard) + post[:ccexp] = expdate(creditcard) post[:firstname] = creditcard.first_name post[:lastname] = creditcard.last_name end @@ -172,15 +168,17 @@ def parse(body) end def commit(action, money, parameters) - parameters[:amount] = amount(money) if money + parameters[:amount] = amount(money) if money response = parse(ssl_post(self.live_url, post_data(action, parameters))) - Response.new(response['response'] == '1', message_from(response), response, - :authorization => response['transactionid'], - :test => test?, - :cvv_result => response['cvvresponse'], - :avs_result => { :code => response['avsresponse'] } + Response.new( + response['response'] == '1', + message_from(response), response, + authorization: response['transactionid'], + test: test?, + cvv_result: response['cvvresponse'], + avs_result: { code: response['avsresponse'] } ) end @@ -197,7 +195,7 @@ def message_from(response) def post_data(action, parameters = {}) post = {} - post[:username] = @options[:login] + post[:username] = @options[:login] post[:password] = @options[:password] post[:type] = action if action diff --git a/lib/active_merchant/billing/gateways/instapay.rb b/lib/active_merchant/billing/gateways/instapay.rb index 7d18c8da05b..8045c169ad8 100644 --- a/lib/active_merchant/billing/gateways/instapay.rb +++ b/lib/active_merchant/billing/gateways/instapay.rb @@ -8,7 +8,7 @@ class InstapayGateway < Gateway self.money_format = :dollars self.default_currency = 'USD' # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'http://www.instapayllc.com' @@ -140,19 +140,20 @@ def commit(action, parameters) data = ssl_post self.live_url, post_data(action, parameters) response = parse(data) - Response.new(response[:success], response[:message], response, - :authorization => response[:transaction_id], - :avs_result => { :code => response[:avs_result] }, - :cvv_result => response[:cvv_result] + Response.new( + response[:success], + response[:message], + response, + authorization: response[:transaction_id], + avs_result: { code: response[:avs_result] }, + cvv_result: response[:cvv_result] ) end def post_data(action, parameters = {}) post = {} post[:acctid] = @options[:login] - if(@options[:password]) - post[:merchantpin] = @options[:password] - end + 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 diff --git a/lib/active_merchant/billing/gateways/ipg.rb b/lib/active_merchant/billing/gateways/ipg.rb new file mode 100644 index 00000000000..8141d414e89 --- /dev/null +++ b/lib/active_merchant/billing/gateways/ipg.rb @@ -0,0 +1,425 @@ +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/ipgapi/services' + + self.supported_countries = %w(AR) + self.default_currency = 'ARS' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://www.ipg-online.com' + self.display_name = 'IPG' + + CURRENCY_CODES = { + 'ARS' => '032' + } + + ACTION_REQUEST_ITEMS = %w(vault unstore) + + def initialize(options = {}) + requires!(options, :user_id, :password, :pem, :pem_password) + @credentials = options.merge(store_and_user_id_from(options[:user_id])) + @hosted_data_id = nil + super + end + + def purchase(money, payment, options = {}) + xml = build_purchase_and_authorize_request(money, payment, options) + + commit('sale', xml, options) + end + + def authorize(money, payment, options = {}) + xml = build_purchase_and_authorize_request(money, payment, options) + + commit('preAuth', xml, options) + end + + def capture(money, authorization, options = {}) + xml = build_capture_and_refund_request(money, authorization, options) + + commit('postAuth', xml, options) + end + + def refund(money, authorization, options = {}) + xml = build_capture_and_refund_request(money, authorization, options) + + commit('return', xml, options) + end + + def void(authorization, options = {}) + xml = Builder::XmlMarkup.new(indent: 2) + add_transaction_details(xml, options.merge!({ order_id: authorization })) + + commit('void', xml, options) + end + + def store(credit_card, options = {}) + @hosted_data_id = options[:hosted_data_id] || generate_unique_id + xml = Builder::XmlMarkup.new(indent: 2) + add_storage_item(xml, credit_card, options) + + commit('vault', xml, options) + end + + def unstore(hosted_data_id) + xml = Builder::XmlMarkup.new(indent: 2) + add_unstore_item(xml, hosted_data_id) + + commit('unstore', xml) + end + + def verify(credit_card, options = {}) + options[:currency] = self.default_currency unless options[:currency] && !options[:currency].empty? + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + + private + + NAMESPACE_BASE_URL = 'http://ipg-online.com' + + def build_purchase_and_authorize_request(money, payment, options) + xml = Builder::XmlMarkup.new(indent: 2) + + add_credit_card(xml, payment, options) + add_sub_merchant(xml, options[:submerchant]) if options[:submerchant] + add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure] + add_stored_credentials(xml, options) if options[:stored_credential] || options[:recurring_type] + add_payment(xml, money, payment, options) + add_transaction_details(xml, options) + add_billing(xml, options[:billing]) if options[:billing] + add_shipping(xml, options[:shipping]) if options[:shipping] + xml + end + + def build_capture_and_refund_request(money, authorization, options) + xml = Builder::XmlMarkup.new(indent: 2) + add_payment(xml, money, nil, options) + add_transaction_details(xml, options.merge!({ order_id: authorization }), true) + xml + end + + def build_order_request(xml, action, body) + xml.tag!('ipg:IPGApiOrderRequest') do + xml.tag!('v1:Transaction') do + add_transaction_type(xml, action) + xml << body.target! + end + end + end + + def build_action_request(xml, action, body) + xml.tag!('ns4:IPGApiActionRequest', ipg_action_namespaces) do + xml.tag!('ns2:Action') do + xml << body.target! + end + end + end + + def build_soap_request(action, body) + xml = Builder::XmlMarkup.new(indent: 2) + xml.tag!('soapenv:Envelope', envelope_namespaces) do + xml.tag!('soapenv:Header') + xml.tag!('soapenv:Body') do + build_order_request(xml, action, body) unless ACTION_REQUEST_ITEMS.include?(action) + build_action_request(xml, action, body) if ACTION_REQUEST_ITEMS.include?(action) + end + end + xml.target! + end + + def add_stored_credentials(xml, params) + recurring_type = params[:stored_credential][:initial_transaction] ? 'FIRST' : 'REPEAT' if params[:stored_credential] + recurring_type = params[:recurring_type] if params[:recurring_type] + xml.tag!('v1:recurringType', recurring_type) + end + + def add_storage_item(xml, credit_card, options) + requires!(options.merge!({ credit_card: credit_card, hosted_data_id: @hosted_data_id }), :credit_card, :hosted_data_id) + xml.tag!('ns2:StoreHostedData') do + xml.tag!('ns2:DataStorageItem') do + add_credit_card(xml, credit_card, {}, 'ns2') + add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure] + xml.tag!('ns2:HostedDataID', @hosted_data_id) if @hosted_data_id + end + end + end + + def add_unstore_item(xml, hosted_data_id) + requires!({}.merge!({ hosted_data_id: hosted_data_id }), :hosted_data_id) + xml.tag!('ns2:StoreHostedData') do + xml.tag!('ns2:DataStorageItem') do + xml.tag!('ns2:Function', 'delete') + xml.tag!('ns2:HostedDataID', hosted_data_id) + end + end + end + + def add_transaction_type(xml, type) + xml.tag!('v1:CreditCardTxType') do + xml.tag!('v1:StoreId', @credentials[:store_id]) + xml.tag!('v1:Type', type) + end + end + + def add_credit_card(xml, payment, options = {}, credit_envelope = 'v1') + if payment&.is_a?(CreditCard) + requires!(options.merge!({ card_number: payment.number, month: payment.month, year: payment.year }), :card_number, :month, :year) + + xml.tag!("#{credit_envelope}:CreditCardData") do + xml.tag!('v1:CardNumber', payment.number) if payment.number + xml.tag!('v1:ExpMonth', format(payment.month, :two_digits)) if payment.month + xml.tag!('v1:ExpYear', format(payment.year, :two_digits)) if payment.year + xml.tag!('v1:CardCodeValue', payment.verification_value) if payment.verification_value + xml.tag!('v1:Brand', options[:brand]) if options[:brand] + end + end + + if options[:card_function_type] + xml.tag!('v1:cardFunction') do + xml.tag!('v1:Type', options[:card_function_type]) + end + end + + if options[:track_data] + xml.tag!("#{credit_envelope}:CreditCardData") do + xml.tag!('v1:TrackData', options[:track_data]) + end + end + end + + def add_sub_merchant(xml, submerchant) + xml.tag!('v1:SubMerchant') do + xml.tag!('v1:Mcc', submerchant[:mcc]) if submerchant[:mcc] + xml.tag!('v1:LegalName', submerchant[:legal_name]) if submerchant[:legal_name] + add_address(xml, submerchant[:address]) if submerchant[:address] + add_document(xml, submerchant[:document]) if submerchant[:document] + xml.tag!('v1:MerchantID', submerchant[:merchant_id]) if submerchant[:merchant_id] + end + end + + def add_address(xml, address) + xml.tag!('v1:Address') do + xml.tag!('v1:Address1', address[:address1]) if address[:address1] + xml.tag!('v1:Address2', address[:address2]) if address[:address2] + xml.tag!('v1:Zip', address[:zip]) if address[:zip] + xml.tag!('v1:City', address[:city]) if address[:city] + xml.tag!('v1:State', address[:state]) if address[:state] + xml.tag!('v1:Country', address[:country]) if address[:country] + end + end + + def add_document(xml, document) + xml.tag!('v1:Document') do + xml.tag!('v1:Type', document[:type]) if document[:type] + xml.tag!('v1:Number', document[:number]) if document[:number] + end + end + + def add_three_d_secure(xml, three_d_secure) + xml.tag!('v1:CreditCard3DSecure') do + xml.tag!('v1:AuthenticationValue', three_d_secure[:cavv]) if three_d_secure[:cavv] + xml.tag!('v1:XID', three_d_secure[:xid]) if three_d_secure[:xid] + xml.tag!('v1:Secure3D2TransactionStatus', three_d_secure[:directory_response_status]) if three_d_secure[:directory_response_status] + xml.tag!('v1:Secure3D2AuthenticationResponse', three_d_secure[:authentication_response_status]) if three_d_secure[:authentication_response_status] + xml.tag!('v1:Secure3DProtocolVersion', three_d_secure[:version]) if three_d_secure[:version] + xml.tag!('v1:DirectoryServerTransactionId', three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id] + end + end + + def add_transaction_details(xml, options, pre_order = false) + requires!(options, :order_id) if pre_order + xml.tag!('v1:TransactionDetails') do + xml.tag!('v1:OrderId', options[:order_id]) if options[:order_id] + xml.tag!('v1:MerchantTransactionId', options[:merchant_transaction_id]) if options[:merchant_transaction_id] + xml.tag!('v1:Ip', options[:ip]) if options[:ip] + xml.tag!('v1:Tdate', options[:t_date]) if options[:t_date] + xml.tag!('v1:IpgTransactionId', options[:ipg_transaction_id]) if options[:ipg_transaction_id] + xml.tag!('v1:ReferencedMerchantTransactionId', options[:referenced_merchant_transaction_id]) if options[:referenced_merchant_transaction_id] + xml.tag!('v1:TransactionOrigin', options[:transaction_origin]) if options[:transaction_origin] + xml.tag!('v1:InvoiceNumber', options[:invoice_number]) if options[:invoice_number] + xml.tag!('v1:DynamicMerchantName', options[:dynamic_merchant_name]) if options[:dynamic_merchant_name] + xml.tag!('v1:Comments', options[:comments]) if options[:comments] + if options[:terminal_id] + xml.tag!('v1:Terminal') do + xml.tag!('v1:TerminalID', options[:terminal_id]) if options[:terminal_id] + end + end + end + end + + def add_payment(xml, money, payment, options) + requires!(options.merge!({ money: money }), :currency, :money) + xml.tag!('v1:Payment') do + xml.tag!('v1:HostedDataID', payment) if payment&.is_a?(String) + xml.tag!('v1:HostedDataStoreID', options[:hosted_data_store_id]) if options[:hosted_data_store_id] + xml.tag!('v1:DeclineHostedDataDuplicates', options[:decline_hosted_data_duplicates]) if options[:decline_hosted_data_duplicates] + 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:Currency', CURRENCY_CODES[options[:currency]]) + xml.tag!('v1:numberOfInstallments', options[:number_of_installments]) if options[:number_of_installments] + end + end + + def add_billing(xml, billing) + xml.tag!('v1:Billing') do + xml.tag!('v1:CustomerID', billing[:customer_id]) if billing[:customer_id] + xml.tag!('v1:Name', billing[:name]) if billing[:name] + xml.tag!('v1:Company', billing[:company]) if billing[:company] + xml.tag!('v1:Address1', billing[:address_1]) if billing[:address_1] + xml.tag!('v1:Address2', billing[:address_2]) if billing[:address_2] + xml.tag!('v1:City', billing[:city]) if billing[:city] + xml.tag!('v1:State', billing[:state]) if billing[:state] + xml.tag!('v1:Zip', billing[:zip]) if billing[:zip] + xml.tag!('v1:Country', billing[:country]) if billing[:country] + xml.tag!('v1:Phone', billing[:phone]) if billing[:phone] + xml.tag!('v1:Fax', billing[:fax]) if billing[:fax] + xml.tag!('v1:Email', billing[:email]) if billing[:email] + end + end + + def add_shipping(xml, shipping) + xml.tag!('v1:Shipping') do + xml.tag!('v1:Type', shipping[:type]) if shipping[:type] + xml.tag!('v1:Name', shipping[:name]) if shipping[:name] + xml.tag!('v1:Address1', shipping[:address_1]) if shipping[:address_1] + xml.tag!('v1:Address2', shipping[:address_2]) if shipping[:address_2] + xml.tag!('v1:City', shipping[:city]) if shipping[:city] + xml.tag!('v1:State', shipping[:state]) if shipping[:state] + xml.tag!('v1:Zip', shipping[:zip]) if shipping[:zip] + xml.tag!('v1:Country', shipping[:country]) if shipping[:country] + end + end + + def build_header + { + 'Content-Type' => 'text/xml; charset=utf-8', + 'Authorization' => "Basic #{encoded_credentials}" + } + end + + def encoded_credentials + Base64.encode64("WS#{@credentials[:store_id]}._.#{@credentials[:user_id]}:#{@credentials[:password]}").delete("\n") + end + + def envelope_namespaces + { + 'xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:ipg' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi", + 'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1" + } + end + + def ipg_order_namespaces + { + 'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1", + 'xmlns:ipgapi' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi" + } + end + + def ipg_action_namespaces + { + 'xmlns:ns4' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi", + 'xmlns:ns2' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/a1", + 'xmlns:ns3' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1" + } + end + + def override_store_id(options) + @credentials[:store_id] = options[:store_id] if options[:store_id].present? + end + + def commit(action, request, options = {}) + override_store_id(options) + url = (test? ? test_url : live_url) + soap_request = build_soap_request(action, request) + response = parse(ssl_post(url, soap_request, build_header)) + Response.new( + response[:success], + message_from(response), + response, + authorization: authorization_from(action, response), + avs_result: AVSResult.new(code: response[:AVSResponse]), + cvv_result: CVVResult.new(response[:ProcessorCCVResponse]), + test: test?, + error_code: error_code_from(response) + ) + end + + def parse(xml) + reply = {} + xml = REXML::Document.new(xml) + root = REXML::XPath.first(xml, '//ipgapi:IPGApiOrderResponse') || REXML::XPath.first(xml, '//ipgapi:IPGApiActionResponse') || REXML::XPath.first(xml, '//SOAP-ENV:Fault') || REXML::XPath.first(xml, '//ns4:IPGApiActionResponse') + reply[:success] = REXML::XPath.first(xml, '//faultcode') ? false : true + if REXML::XPath.first(xml, '//ns4:IPGApiActionResponse') + reply[:tpv_error_code] = REXML::XPath.first(root, '//ns2:Error').attributes['Code'] + reply[:tpv_error_msg] = REXML::XPath.first(root, '//ns2:ErrorMessage').text + reply[:success] = false + end + root.elements.to_a.each do |node| + parse_element(reply, node) + end + reply[:hosted_data_id] = @hosted_data_id if @hosted_data_id + return reply + end + + def parse_element(reply, node) + if node.has_elements? + node.elements.each { |e| parse_element(reply, e) } + else + if /item/.match?(node.parent.name) + parent = node.parent.name + parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id'] + parent += '_' + end + reply["#{parent}#{node.name}".to_sym] ||= node.text + end + return reply + end + + def store_and_user_id_from(user_id) + split_credentials = user_id.split('._.') + { store_id: split_credentials[0].sub(/^WS/, ''), user_id: split_credentials[1] } + end + + def message_from(response) + [response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ') + end + + def authorization_from(action, response) + return (action == 'vault' ? response[:hosted_data_id] : response[:OrderId]) + end + + def error_code_from(response) + response[:ErrorMessage]&.split(':')&.first unless response[:success] + end + + def handle_response(response) + case response.code.to_i + when 200...300, 500 + response.body + else + raise ResponseError.new(response) + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/ipp.rb b/lib/active_merchant/billing/gateways/ipp.rb index fa672636787..4fd0c5c6293 100644 --- a/lib/active_merchant/billing/gateways/ipp.rb +++ b/lib/active_merchant/billing/gateways/ipp.rb @@ -7,7 +7,7 @@ class IppGateway < Gateway self.test_url = 'https://demo.ippayments.com.au/interface/api/dts.asmx' self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.homepage_url = 'http://www.ippayments.com.au/' self.display_name = 'IPP' @@ -18,16 +18,16 @@ class IppGateway < Gateway '05' => STANDARD_ERROR_CODE[:card_declined], '06' => STANDARD_ERROR_CODE[:processing_error], '14' => STANDARD_ERROR_CODE[:invalid_number], - '54' => STANDARD_ERROR_CODE[:expired_card], + '54' => STANDARD_ERROR_CODE[:expired_card] } - def initialize(options={}) + def initialize(options = {}) ActiveMerchant.deprecated('IPP gateway is now named Bambora Asia-Pacific') requires!(options, :username, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) commit('SubmitSinglePayment') do |xml| xml.Transaction do xml.CustRef options[:order_id] @@ -40,7 +40,7 @@ def purchase(money, payment, options={}) end end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) commit('SubmitSinglePayment') do |xml| xml.Transaction do xml.CustRef options[:order_id] @@ -53,7 +53,7 @@ def authorize(money, payment, options={}) end end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) commit('SubmitSingleCapture') do |xml| xml.Capture do xml.Receipt authorization @@ -63,7 +63,7 @@ def capture(money, authorization, options={}) end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit('SubmitSingleRefund') do |xml| xml.Refund do xml.Receipt authorization @@ -98,7 +98,7 @@ def add_amount(xml, money) end def add_credit_card(xml, payment) - xml.CreditCard :Registered => 'False' do + xml.CreditCard Registered: 'False' do xml.CardNumber payment.number xml.ExpM format(payment.month, :two_digits) xml.ExpY format(payment.year, :four_digits) @@ -121,7 +121,7 @@ def parse(body) def commit(action, &block) headers = { 'Content-Type' => 'text/xml; charset=utf-8', - 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}", + 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}" } response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers)) diff --git a/lib/active_merchant/billing/gateways/iridium.rb b/lib/active_merchant/billing/gateways/iridium.rb index 6b6eda3805b..d139643f992 100644 --- a/lib/active_merchant/billing/gateways/iridium.rb +++ b/lib/active_merchant/billing/gateways/iridium.rb @@ -10,12 +10,12 @@ class IridiumGateway < Gateway self.live_url = self.test_url = 'https://gw1.iridiumcorp.net/' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['GB', 'ES'] + self.supported_countries = %w[GB ES] self.default_currency = 'EUR' self.money_format = :cents # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover maestro jcb diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.iridiumcorp.co.uk/' @@ -203,7 +203,7 @@ class IridiumGateway < Gateway 'YER' => '886', 'ZAR' => '710', 'ZMK' => '894', - 'ZWD' => '716', + 'ZWD' => '716' } AVS_CODE = { @@ -251,16 +251,16 @@ def capture(money, authorization, options = {}) commit(build_reference_request('COLLECTION', money, authorization, options), options) end - def credit(money, authorization, options={}) + def credit(money, authorization, options = {}) ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit(build_reference_request('REFUND', money, authorization, options), options) end - def void(authorization, options={}) + def void(authorization, options = {}) commit(build_reference_request('VOID', nil, authorization, options), options) end @@ -288,15 +288,16 @@ def build_purchase_request(type, money, creditcard, options) def build_reference_request(type, money, authorization, options) options[:action] = 'CrossReferenceTransaction' - order_id, cross_reference, _ = authorization.split(';') + order_id, cross_reference, = authorization.split(';') build_request(options) do |xml| if money - details = {'CurrencyCode' => currency_code(options[:currency] || default_currency), 'Amount' => amount(money)} + currency = options[:currency] || currency(money) + details = { 'CurrencyCode' => currency_code(currency), 'Amount' => localized_amount(money, currency) } else - details = {'CurrencyCode' => currency_code(default_currency), 'Amount' => '0'} + details = { 'CurrencyCode' => currency_code(default_currency), 'Amount' => '0' } end xml.tag! 'TransactionDetails', details do - xml.tag! 'MessageDetails', {'TransactionType' => type, 'CrossReference' => cross_reference} + xml.tag! 'MessageDetails', { 'TransactionType' => type, 'CrossReference' => cross_reference } xml.tag! 'OrderID', (options[:order_id] || order_id) end end @@ -304,13 +305,13 @@ def build_reference_request(type, money, authorization, options) def build_request(options) requires!(options, :action) - xml = Builder::XmlMarkup.new :indent => 2 - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml = Builder::XmlMarkup.new indent: 2 + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') xml.tag! 'soap:Envelope', { 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', - 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema' } do xml.tag! 'soap:Body' do - xml.tag! options[:action], {'xmlns' => 'https://www.thepaymentgateway.net/'} do + xml.tag! options[:action], { 'xmlns' => 'https://www.thepaymentgateway.net/' } do xml.tag! 'PaymentMessage' do add_merchant_data(xml, options) yield(xml) @@ -327,9 +328,10 @@ def setup_address_hash(options) end def add_purchase_data(xml, type, money, options) + currency = options[:currency] || currency(money) requires!(options, :order_id) - xml.tag! 'TransactionDetails', {'Amount' => amount(money), 'CurrencyCode' => currency_code(options[:currency] || currency(money))} do - xml.tag! 'MessageDetails', {'TransactionType' => type} + xml.tag! 'TransactionDetails', { 'Amount' => localized_amount(money, currency), 'CurrencyCode' => currency_code(currency) } do + xml.tag! 'MessageDetails', { 'TransactionType' => type } xml.tag! 'OrderID', options[:order_id] xml.tag! 'TransactionControl' do xml.tag! 'ThreeDSecureOverridePolicy', 'FALSE' @@ -342,9 +344,7 @@ def add_purchase_data(xml, type, money, options) def add_customerdetails(xml, creditcard, address, options, shipTo = false) xml.tag! 'CustomerDetails' do if address - unless address[:country].blank? - country_code = Country.find(address[:country]).code(:numeric) - end + country_code = Country.find(address[:country]).code(:numeric) unless address[:country].blank? xml.tag! 'BillingAddress' do xml.tag! 'Address1', address[:address1] xml.tag! 'Address2', address[:address2] @@ -371,35 +371,44 @@ def add_creditcard(xml, creditcard) end def add_merchant_data(xml, options) - xml.tag! 'MerchantAuthentication', {'MerchantID' => @options[:login], 'Password' => @options[:password]} + xml.tag! 'MerchantAuthentication', { 'MerchantID' => @options[:login], 'Password' => @options[:password] } end def commit(request, options) requires!(options, :action) - response = parse(ssl_post(test? ? self.test_url : self.live_url, request, - {'SOAPAction' => 'https://www.thepaymentgateway.net/' + options[:action], - 'Content-Type' => 'text/xml; charset=utf-8' })) + 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, - :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] ], + authorization = success ? [options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code]].compact.join(';') : nil + + Response.new( + success, + message, + response, + test: test?, + authorization: authorization, + 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) reply = {} xml = REXML::Document.new(xml) - if (root = REXML::XPath.first(xml, '//CardDetailsTransactionResponse')) or - (root = REXML::XPath.first(xml, '//CrossReferenceTransactionResponse')) + if (root = REXML::XPath.first(xml, '//CardDetailsTransactionResponse')) || + (root = REXML::XPath.first(xml, '//CrossReferenceTransactionResponse')) root.elements.to_a.each do |node| case node.name when 'Message' diff --git a/lib/active_merchant/billing/gateways/itransact.rb b/lib/active_merchant/billing/gateways/itransact.rb index 8bac0734e0a..7ad416dc906 100644 --- a/lib/active_merchant/billing/gateways/itransact.rb +++ b/lib/active_merchant/billing/gateways/itransact.rb @@ -38,7 +38,7 @@ class ItransactGateway < Gateway self.supported_countries = ['US'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'http://www.itransact.com/' @@ -301,8 +301,8 @@ def add_customer_data(xml, payment_source, options) def add_invoice(xml, money, options) xml.AuthCode options[:force] if options[:force] if options[:order_items].blank? - xml.Total(amount(money)) unless(money.nil? || money < 0.01) - xml.Description(options[:description]) unless(options[:description].blank?) + xml.Total(amount(money)) unless money.nil? || money < 0.01 + xml.Description(options[:description]) unless options[:description].blank? else xml.OrderItems { options[:order_items].each do |item| @@ -371,6 +371,7 @@ def add_transaction_control(xml, options) def add_vendor_data(xml, options) return if options[:vendor_data].blank? + xml.VendorData { options[:vendor_data].each do |k, v| xml.Element { @@ -386,15 +387,19 @@ 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, - :test => test?, - :authorization => response[:xid], - :avs_result => { :code => response[:avs_response] }, - :cvv_result => response[:cvv_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] + ) end def post_data(payload) - payload_xml = payload.root.to_xml(:indent => 0) + payload_xml = payload.root.to_xml(indent: 0) payload_signature = sign_payload(payload_xml) @@ -409,7 +414,7 @@ def post_data(payload) end.doc request.root.children.first.after payload.root - request.to_xml(:indent => 0) + request.to_xml(indent: 0) end def parse(raw_xml) @@ -438,7 +443,7 @@ def message_from(response) def sign_payload(payload) key = @options[:password].to_s - digest=OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new(key), key, payload) + digest = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new(key), key, payload) signature = Base64.encode64(digest) signature.chomp! end diff --git a/lib/active_merchant/billing/gateways/iveri.rb b/lib/active_merchant/billing/gateways/iveri.rb index 4a8d96e4752..87993b01baf 100644 --- a/lib/active_merchant/billing/gateways/iveri.rb +++ b/lib/active_merchant/billing/gateways/iveri.rb @@ -3,22 +3,25 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class IveriGateway < Gateway + class_attribute :iveri_url + self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx' + self.iveri_url = 'https://portal.host.iveri.com/iVeriWebService/Service.asmx' - self.supported_countries = ['US', 'ZA', 'GB'] + self.supported_countries = %w[US ZA GB] self.default_currency = 'ZAR' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.homepage_url = 'http://www.iveri.com' self.display_name = 'iVeri' - def initialize(options={}) + def initialize(options = {}) requires!(options, :app_id, :cert_id) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = build_vxml_request('Debit', options) do |xml| add_auth_purchase_params(xml, money, payment_method, options) end @@ -26,7 +29,7 @@ def purchase(money, payment_method, options={}) commit(post) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) post = build_vxml_request('Authorisation', options) do |xml| add_auth_purchase_params(xml, money, payment_method, options) end @@ -34,7 +37,7 @@ def authorize(money, payment_method, options={}) commit(post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = build_vxml_request('Debit', options) do |xml| add_authorization(xml, authorization, options) end @@ -42,7 +45,7 @@ def capture(money, authorization, options={}) commit(post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = build_vxml_request('Credit', options) do |xml| add_amount(xml, money, options) add_authorization(xml, authorization, options) @@ -51,7 +54,7 @@ def refund(money, authorization, options={}) commit(post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = build_vxml_request('Void', options) do |xml| add_authorization(xml, authorization, options) end @@ -59,13 +62,17 @@ def void(authorization, options={}) commit(post) end - def verify(credit_card, options={}) - authorize(0, credit_card, options) + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end end def verify_credentials void = void('', options) - return true if void.message == 'Missing OriginalMerchantTrace' + return true if void.message == 'Missing OriginalMerchantTrace' + false end @@ -83,11 +90,11 @@ def scrub(transcript) private def build_xml_envelope(vxml) - builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do xml[:soap].Body do xml.Execute 'xmlns' => 'http://iveri.com/' do - xml.validateRequest 'true' + xml.validateRequest('false') xml.protocol 'V_XML' xml.protocolVersion '2.0' xml.request vxml @@ -114,8 +121,9 @@ def build_vxml_request(action, options) def add_auth_purchase_params(post, money, payment_method, options) add_card_holder_authentication(post, options) add_amount(post, money, options) - add_electronic_commerce_indicator(post, options) + add_electronic_commerce_indicator(post, options) unless options[:three_d_secure] add_payment_method(post, payment_method, options) + add_three_ds(post, options) end def add_amount(post, money, options) @@ -150,11 +158,12 @@ def add_card_holder_authentication(post, options) end def commit(post) - raw_response = begin - ssl_post(live_url, build_xml_envelope(post), headers(post)) - rescue ActiveMerchant::ResponseError => e - e.response.body - end + raw_response = + begin + ssl_post(url, build_xml_envelope(post), headers(post)) + rescue ActiveMerchant::ResponseError => e + e.response.body + end parsed = parse(raw_response) succeeded = success_from(parsed) @@ -173,6 +182,10 @@ def mode test? ? 'Test' : 'Live' end + def url + @options[:url_override].to_s == 'iveri' ? iveri_url : live_url + end + def headers(post) { 'Content-Type' => 'text/xml; charset=utf-8', @@ -201,7 +214,7 @@ def parse(body) def parse_element(parsed, node) if !node.attributes.empty? node.attributes.each do |a| - parsed[underscore(node.name)+ '_' + underscore(a[1].name)] = a[1].value + parsed[underscore(node.name) + '_' + underscore(a[1].name)] = a[1].value end end @@ -234,9 +247,7 @@ def split_auth(authorization) end def error_code_from(response, succeeded) - unless succeeded - response['result_code'] - end + response['result_code'] unless succeeded end def underscore(camel_cased_word) @@ -246,6 +257,34 @@ def underscore(camel_cased_word) tr('-', '_'). downcase end + + def add_three_ds(post, options) + return unless three_d_secure = options[:three_d_secure] + + post.ElectronicCommerceIndicator(formatted_three_ds_eci(three_d_secure[:eci])) if three_d_secure[:eci] + post.CardHolderAuthenticationID(three_d_secure[:xid]) if three_d_secure[:xid] + post.CardHolderAuthenticationData(three_d_secure[:cavv]) if three_d_secure[:cavv] + post.ThreeDSecure_ProtocolVersion(three_d_secure[:version]) if three_d_secure[:version] + post.ThreeDSecure_DSTransID(three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id] + post.ThreeDSecure_VEResEnrolled(formatted_enrollment(three_d_secure[:enrolled])) if three_d_secure[:enrolled] + 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 formatted_three_ds_eci(val) + case val + when '05', '02' then 'ThreeDSecure' + when '06', '01' then 'ThreeDSecureAttempted' + when '07' then 'SecureChannel' + else val + end + end end end end diff --git a/lib/active_merchant/billing/gateways/ixopay.rb b/lib/active_merchant/billing/gateways/ixopay.rb new file mode 100644 index 00000000000..71e299e726e --- /dev/null +++ b/lib/active_merchant/billing/gateways/ixopay.rb @@ -0,0 +1,320 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class IxopayGateway < Gateway + self.test_url = 'https://secure.ixopay.com/transaction' + self.live_url = 'https://secure.ixopay.com/transaction' + + self.supported_countries = %w(AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW) + self.default_currency = 'EUR' + self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LWD OMR TND) + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro] + + self.homepage_url = 'https://www.ixopay.com' + self.display_name = 'Ixopay' + + def initialize(options = {}) + requires!(options, :username, :password, :secret, :api_key) + @secret = options[:secret] + super + end + + def purchase(money, payment_method, options = {}) + request = build_xml_request do |xml| + add_card_data(xml, payment_method) + add_debit(xml, money, options) + end + + commit(request) + end + + def authorize(money, payment_method, options = {}) + request = build_xml_request do |xml| + add_card_data(xml, payment_method) + add_preauth(xml, money, options) + end + + commit(request) + end + + def capture(money, authorization, options = {}) + request = build_xml_request do |xml| + add_capture(xml, money, authorization, options) + end + + commit(request) + end + + def refund(money, authorization, options = {}) + request = build_xml_request do |xml| + add_refund(xml, money, authorization, options) + end + + commit(request) + end + + def void(authorization, options = {}) + request = build_xml_request do |xml| + add_void(xml, authorization) + end + + commit(request) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + clean_transcript = remove_invalid_utf_8_byte_sequences(transcript) + + clean_transcript. + gsub(%r((Authorization: Gateway )(.*)(:)), '\1[FILTERED]\3'). + gsub(%r(()(.*)()), '\1[FILTERED]\3'). + gsub(%r(()(.*)()), '\1[FILTERED]\3'). + gsub(%r(()\d+()), '\1[FILTERED]\2') + end + + private + + def remove_invalid_utf_8_byte_sequences(text) + text.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + end + + def headers(xml) + timestamp = Time.now.httpdate + signature = generate_signature('POST', xml, timestamp) + + { + 'Authorization' => "Gateway #{options[:api_key]}:#{signature}", + 'Date' => timestamp, + 'Content-Type' => 'text/xml; charset=utf-8' + } + end + + def generate_signature(http_method, xml, timestamp) + content_type = 'text/xml; charset=utf-8' + message = "#{http_method}\n#{Digest::MD5.hexdigest(xml)}\n#{content_type}\n#{timestamp}\n\n/transaction" + digest = OpenSSL::Digest.new('sha512') + hmac = OpenSSL::HMAC.digest(digest, @secret, message) + + Base64.encode64(hmac).delete("\n") + end + + def parse(body) + xml = Nokogiri::XML(body) + response = Hash.from_xml(xml.to_s)['result'] + + response.deep_transform_keys(&:underscore).transform_keys(&:to_sym) + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.transactionWithCard 'xmlns' => 'http://secure.ixopay.com/Schema/V2/TransactionWithCard' do + xml.username @options[:username] + xml.password Digest::SHA1.hexdigest(@options[:password]) + yield(xml) + end + end + + builder.to_xml + end + + def add_card_data(xml, payment_method) + xml.cardData do + xml.cardHolder payment_method.name + xml.pan payment_method.number + xml.cvv payment_method.verification_value + xml.expirationMonth format(payment_method.month, :two_digits) + xml.expirationYear format(payment_method.year, :four_digits) + end + end + + def add_debit(xml, money, options) + currency = options[:currency] || currency(money) + description = options[:description].blank? ? 'Purchase' : options[:description] + + xml.debit do + xml.transactionId new_transaction_id + + add_customer_data(xml, options) + add_extra_data(xml, options[:extra_data]) if options[:extra_data] + + xml.amount localized_amount(money, currency) + xml.currency currency + xml.description description + xml.callbackUrl(options[:callback_url]) + add_stored_credentials(xml, options) + end + end + + def add_preauth(xml, money, options) + description = options[:description].blank? ? 'Preauthorize' : options[:description] + currency = options[:currency] || currency(money) + callback_url = options[:callback_url] + + xml.preauthorize do + xml.transactionId new_transaction_id + + add_customer_data(xml, options) + add_extra_data(xml, options[:extra_data]) if options[:extra_data] + + xml.amount localized_amount(money, currency) + xml.currency currency + xml.description description + xml.callbackUrl callback_url + add_stored_credentials(xml, options) + end + end + + def add_refund(xml, money, authorization, options) + currency = options[:currency] || currency(money) + + xml.refund do + xml.transactionId new_transaction_id + add_extra_data(xml, options[:extra_data]) if options[:extra_data] + xml.referenceTransactionId authorization&.split('|')&.first + xml.amount localized_amount(money, currency) + xml.currency currency + end + end + + def add_void(xml, authorization) + xml.void do + xml.transactionId new_transaction_id + add_extra_data(xml, options[:extra_data]) if options[:extra_data] + xml.referenceTransactionId authorization&.split('|')&.first + end + end + + def add_capture(xml, money, authorization, options) + currency = options[:currency] || currency(money) + + xml.capture_ do + xml.transactionId new_transaction_id + add_extra_data(xml, options[:extra_data]) if options[:extra_data] + xml.referenceTransactionId authorization&.split('|')&.first + xml.amount localized_amount(money, currency) + xml.currency currency + end + end + + def add_customer_data(xml, options) + # Ixopay returns an error if the elements are not added in the order used here. + xml.customer do + add_billing_address(xml, options[:billing_address]) if options[:billing_address] + add_shipping_address(xml, options[:shipping_address]) if options[:shipping_address] + + xml.company options[:billing_address][:company] if options.dig(:billing_address, :company) + xml.email options[:email] + xml.ipAddress(options[:ip] || '127.0.0.1') + end + end + + def add_billing_address(xml, address) + if address[:name] + xml.firstName split_names(address[:name])[0] + xml.lastName split_names(address[:name])[1] + end + + xml.billingAddress1 address[:address1] + xml.billingAddress2 address[:address2] + xml.billingCity address[:city] + xml.billingPostcode address[:zip] + xml.billingState address[:state] + xml.billingCountry address[:country] + xml.billingPhone address[:phone] + end + + def add_shipping_address(xml, address) + if address[:name] + xml.shippingFirstName split_names(address[:name])[0] + xml.shippingLastName split_names(address[:name])[1] + end + + xml.shippingCompany address[:company] + xml.shippingAddress1 address[:address1] + xml.shippingAddress2 address[:address2] + xml.shippingCity address[:city] + xml.shippingPostcode address[:zip] + xml.shippingState address[:state] + xml.shippingCountry address[:country] + xml.shippingPhone address[:phone] + end + + def new_transaction_id + SecureRandom.uuid + end + + # Ixopay does not pass any parameters for cardholder/merchant initiated. + # Ixopay also doesn't support installment transactions, only recurring + # ("RECURRING") and unscheduled ("CARDONFILE"). + # + # Furthermore, Ixopay is slightly unusual in its application of stored + # credentials in that the gateway does not return a true + # network_transaction_id that can be sent on subsequent transactions. + def add_stored_credentials(xml, options) + return unless stored_credential = options[:stored_credential] + + if stored_credential[:initial_transaction] + xml.transactionIndicator 'INITIAL' + elsif stored_credential[:reason_type] == 'recurring' + xml.transactionIndicator 'RECURRING' + elsif stored_credential[:reason_type] == 'unscheduled' + xml.transactionIndicator 'CARDONFILE' + end + end + + def add_extra_data(xml, extra_data) + extra_data.each do |k, v| + xml.extraData(v, key: k) + end + end + + def commit(request) + url = (test? ? test_url : live_url) + + # ssl_post raises an exception for any non-2xx HTTP status from the gateway + response = + begin + parse(ssl_post(url, request, headers(request))) + rescue StandardError => e + parse(e.response.body) + end + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response[:success] == 'true' + end + + def message_from(response) + response.dig(:errors, 'error', 'message') || response[:return_type] + end + + def authorization_from(response) + response[:reference_id] ? "#{response[:reference_id]}|#{response[:purchase_id]}" : nil + end + + def error_code_from(response) + response.dig(:errors, 'error', 'code') unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/jetpay.rb b/lib/active_merchant/billing/gateways/jetpay.rb index aaa955dd24a..c2b28b5968e 100644 --- a/lib/active_merchant/billing/gateways/jetpay.rb +++ b/lib/active_merchant/billing/gateways/jetpay.rb @@ -8,10 +8,10 @@ class JetpayGateway < Gateway self.live_ca_url = 'https://gateway17.jetpay.com/canada-bb' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w[US CA] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'http://www.jetpay.com/' @@ -213,9 +213,7 @@ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &blo xml.tag! 'TerminalID', @options[:login] xml.tag! 'TransactionType', transaction_type xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id - if options && options[:origin] - xml.tag! 'Origin', options[:origin] - end + xml.tag! 'Origin', options[:origin] if options && options[:origin] if block_given? yield xml @@ -286,13 +284,14 @@ 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] + test: test?, + authorization: authorization_from(response, money, token), + avs_result: { code: response[:avs] }, + cvv_result: response[:cvv2] ) end @@ -335,7 +334,7 @@ def message_from(response) def authorization_from(response, money, previous_token) original_amount = amount(money) if money - [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') + [response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') end def add_credit_card(xml, credit_card) @@ -343,13 +342,9 @@ def add_credit_card(xml, credit_card) xml.tag! 'CardExpMonth', format_exp(credit_card.month) xml.tag! 'CardExpYear', format_exp(credit_card.year) - if credit_card.first_name || credit_card.last_name - xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') - end + xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') if credit_card.first_name || credit_card.last_name - unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) - xml.tag! 'CVV2', credit_card.verification_value - end + xml.tag! 'CVV2', credit_card.verification_value unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) end def add_addresses(xml, options) diff --git a/lib/active_merchant/billing/gateways/jetpay_v2.rb b/lib/active_merchant/billing/gateways/jetpay_v2.rb index 515bba8fc84..b852215f181 100644 --- a/lib/active_merchant/billing/gateways/jetpay_v2.rb +++ b/lib/active_merchant/billing/gateways/jetpay_v2.rb @@ -6,8 +6,8 @@ class JetpayV2Gateway < Gateway self.money_format = :cents self.default_currency = 'USD' - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.jetpay.com' self.display_name = 'JetPay' @@ -210,8 +210,8 @@ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &blo xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id xml.tag! 'Origin', options[:origin] || 'INTERNET' xml.tag! 'IndustryInfo', 'Type' => options[:industry_info] || 'ECOMMERCE' - xml.tag! 'Application', (options[:application] || 'n/a'), {'Version' => options[:application_version] || '1.0'} - xml.tag! 'Device', (options[:device] || 'n/a'), {'Version' => options[:device_version] || '1.0'} + xml.tag! 'Application', (options[:application] || 'n/a'), { 'Version' => options[:application_version] || '1.0' } + xml.tag! 'Device', (options[:device] || 'n/a'), { 'Version' => options[:device_version] || '1.0' } xml.tag! 'Library', 'VirtPOS SDK', 'Version' => '1.5' xml.tag! 'Gateway', 'JetPay' xml.tag! 'DeveloperID', options[:developer_id] || 'n/a' @@ -295,14 +295,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 => AVSResult.new(:code => response[:avs]), - :cvv_result => CVVResult.new(response[:cvv2]), - :error_code => success ? nil : error_code_from(response) + test: test?, + authorization: authorization_from(response, money, token), + avs_result: AVSResult.new(code: response[:avs]), + cvv_result: CVVResult.new(response[:cvv2]), + error_code: success ? nil : error_code_from(response) ) end @@ -344,7 +345,7 @@ def message_from(response) def authorization_from(response, money, previous_token) original_amount = amount(money) if money - [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') + [response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') end def error_code_from(response) @@ -368,13 +369,9 @@ def add_credit_card(xml, credit_card) xml.tag! 'CardExpMonth', format_exp(credit_card.month) xml.tag! 'CardExpYear', format_exp(credit_card.year) - if credit_card.first_name || credit_card.last_name - xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') - end + xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') if credit_card.first_name || credit_card.last_name - unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) - xml.tag! 'CVV2', credit_card.verification_value - end + xml.tag! 'CVV2', credit_card.verification_value unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) end def add_addresses(xml, options) @@ -410,7 +407,7 @@ def add_customer_data(xml, options) def add_invoice_data(xml, options) xml.tag! 'OrderNumber', options[:order_id] if options[:order_id] if tax_amount = options[:tax_amount] - xml.tag! 'TaxAmount', tax_amount, {'ExemptInd' => options[:tax_exempt] || 'false'} + xml.tag! 'TaxAmount', tax_amount, { 'ExemptInd' => options[:tax_exempt] || 'false' } end end diff --git a/lib/active_merchant/billing/gateways/komoju.rb b/lib/active_merchant/billing/gateways/komoju.rb index d53ab5f5165..1d882c00c00 100644 --- a/lib/active_merchant/billing/gateways/komoju.rb +++ b/lib/active_merchant/billing/gateways/komoju.rb @@ -10,7 +10,7 @@ class KomojuGateway < Gateway self.money_format = :cents self.homepage_url = 'https://www.komoju.com/' self.display_name = 'Komoju' - self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + self.supported_cardtypes = %i[visa master american_express jcb] STANDARD_ERROR_CODE_MAPPING = { 'bad_verification_value' => 'incorrect_cvc', diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb index c97bd2e6f38..7b9d52c20b3 100644 --- a/lib/active_merchant/billing/gateways/kushki.rb +++ b/lib/active_merchant/billing/gateways/kushki.rb @@ -4,40 +4,61 @@ class KushkiGateway < Gateway self.display_name = 'Kushki' self.homepage_url = 'https://www.kushkipagos.com' - self.test_url = 'https://api-uat.kushkipagos.com/v1/' - self.live_url = 'https://api.kushkipagos.com/v1/' + self.test_url = 'https://api-uat.kushkipagos.com/' + self.live_url = 'https://api.kushkipagos.com/' - self.supported_countries = ['CO', 'EC'] + self.supported_countries = %w[BR CL CO EC MX PE] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover diners_club alia] - def initialize(options={}) + def initialize(options = {}) requires!(options, :public_merchant_id, :private_merchant_id) super end - def purchase(amount, payment_method, 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 refund(amount, authorization, options={}) - action = 'refund' + def authorize(amount, payment_method, options = {}) + MultiResponse.run() do |r| + r.process { tokenize(amount, payment_method, options) } + r.process { preauthorize(amount, r.authorization, options, payment_method) } + end + end + + def capture(amount, authorization, options = {}) + action = 'capture' post = {} post[:ticketNumber] = authorization + add_invoice(action, post, amount, options) + add_full_response(post, options) commit(action, post) end - def void(authorization, options={}) + def refund(amount, authorization, options = {}) + action = 'refund' + + post = {} + post[:ticketNumber] = authorization + add_full_response(post, options) + add_invoice(action, post, amount, options) + + commit(action, post, options) + end + + def void(authorization, options = {}) action = 'void' post = {} post[:ticketNumber] = authorization + add_full_response(post, options) commit(action, post) end @@ -61,16 +82,41 @@ def tokenize(amount, payment_method, options) post = {} add_invoice(action, post, amount, 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 = {} add_reference(post, authorization, options) add_invoice(action, post, amount, 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) + + commit(action, post) + end + + def preauthorize(amount, authorization, options, payment_method = {}) + action = 'preAuthorization' + + post = {} + add_reference(post, 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 @@ -90,13 +136,11 @@ 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 - if sum[:currency] != 'COP' - sum[:ice] = 0 - end + sum[:ice] = 0 if sum[:currency] != 'COP' end def add_amount_by_country(sum, options) @@ -105,7 +149,7 @@ def add_amount_by_country(sum, options) sum[:iva] = amount[:iva].to_f if amount[:iva] sum[:subtotalIva0] = amount[:subtotal_iva_0].to_f if amount[:subtotal_iva_0] sum[:ice] = amount[:ice].to_f if amount[:ice] - if (extra_taxes = amount[:extra_taxes]) && sum[:currency] == 'COP' + if (extra_taxes = amount[:extra_taxes]) sum[:extraTaxes] ||= Hash.new sum[:extraTaxes][:propina] = extra_taxes[:propina].to_f if extra_taxes[:propina] sum[:extraTaxes][:tasaAeroportuaria] = extra_taxes[:tasa_aeroportuaria].to_f if extra_taxes[:tasa_aeroportuaria] @@ -129,19 +173,87 @@ def add_reference(post, authorization, options) post[:token] = authorization end + def add_contact_details(post, contact_details_options) + contact_details = {} + contact_details[:documentType] = contact_details_options[:document_type] if contact_details_options[:document_type] + contact_details[:documentNumber] = contact_details_options[:document_number] if contact_details_options[:document_number] + contact_details[:email] = contact_details_options[:email] if contact_details_options[:email] + contact_details[:firstName] = contact_details_options[:first_name] if contact_details_options[:first_name] + contact_details[:lastName] = contact_details_options[:last_name] if contact_details_options[:last_name] + contact_details[:secondLastName] = contact_details_options[:second_last_name] if contact_details_options[:second_last_name] + contact_details[:phoneNumber] = contact_details_options[:phone_number] if contact_details_options[:phone_number] + post[:contactDetails] = contact_details + end + + def add_full_response(post, options) + # 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_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', 'void' => 'charges', - 'refund' => 'refund' + 'refund' => 'refund', + 'preAuthorization' => 'preAuthorization', + 'capture' => 'capture' } - def commit(action, params) - response = begin - parse(ssl_invoke(action, params)) - rescue ResponseError => e - parse(e.response.body) - end + def commit(action, params, options = {}) + response = + begin + parse(ssl_invoke(action, params, options)) + rescue ResponseError => e + parse(e.response.body) + end success = success_from(response) @@ -155,9 +267,11 @@ def commit(action, params) ) end - def ssl_invoke(action, params) - if ['void', 'refund'].include?(action) - ssl_request(:delete, url(action, params), nil, headers(action)) + def ssl_invoke(action, params, options) + if %w[void refund].include?(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 @@ -178,10 +292,10 @@ def post_data(params) def url(action, params) base_url = test? ? test_url : live_url - if ['void', 'refund'].include?(action) - base_url + ENDPOINT[action] + '/' + params[:ticketNumber].to_s + if %w[void refund].include?(action) + base_url + 'v1/' + ENDPOINT[action] + '/' + params[:ticketNumber].to_s else - base_url + ENDPOINT[action] + base_url + 'card/v1/' + ENDPOINT[action] end end diff --git a/lib/active_merchant/billing/gateways/latitude19.rb b/lib/active_merchant/billing/gateways/latitude19.rb index d30b5e14a22..526ec32210e 100644 --- a/lib/active_merchant/billing/gateways/latitude19.rb +++ b/lib/active_merchant/billing/gateways/latitude19.rb @@ -7,10 +7,10 @@ class Latitude19Gateway < Gateway self.live_url = 'https://gateway.l19tech.com/payments/' self.test_url = 'https://gateway-sb.l19tech.com/payments/' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w[US CA] self.default_currency = 'USD' - self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.money_format = :dollars + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] RESPONSE_CODE_MAPPING = { '100' => 'Approved', @@ -51,12 +51,12 @@ class Latitude19Gateway < Gateway 'jcb' => 'JC' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :account_number, :configuration_id, :secret) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) if payment_method.is_a?(String) auth_or_sale('sale', payment_method, amount, nil, options) else @@ -68,7 +68,7 @@ def purchase(amount, payment_method, options={}) end end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) if payment_method.is_a?(String) auth_or_sale('auth', payment_method, amount, nil, options) else @@ -80,7 +80,7 @@ def authorize(amount, payment_method, options={}) end end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} post[:method] = 'deposit' add_request_id(post) @@ -96,7 +96,7 @@ def capture(amount, authorization, options={}) commit('v1/', post) end - def void(authorization, options={}) + def void(authorization, options = {}) method, pgwTID = split_authorization(authorization) case method when 'auth' @@ -109,7 +109,7 @@ def void(authorization, options={}) end end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) if payment_method.is_a?(String) refundWithCard(payment_method, amount, nil, options) else @@ -121,7 +121,7 @@ def credit(amount, payment_method, options={}) end end - def verify(payment_method, options={}, action=nil) + def verify(payment_method, options = {}, action = nil) if payment_method.is_a?(String) verifyOnly(action, payment_method, nil, options) else @@ -133,7 +133,7 @@ def verify(payment_method, options={}, action=nil) end end - def store(payment_method, options={}) + def store(payment_method, options = {}) verify(payment_method, options, 'store') end @@ -201,7 +201,7 @@ def add_customer_data(params, options) end end - def get_session(options={}) + def get_session(options = {}) post = {} post[:method] = 'getSession' add_request_id(post) @@ -213,7 +213,7 @@ def get_session(options={}) commit('session', post) end - def get_token(authorization, payment_method, options={}) + def get_token(authorization, payment_method, options = {}) post = {} post[:method] = 'tokenize' add_request_id(post) @@ -226,7 +226,7 @@ def get_token(authorization, payment_method, options={}) commit('token', post) end - def auth_or_sale(method, authorization, amount, credit_card, options={}) + def auth_or_sale(method, authorization, amount, credit_card, options = {}) post = {} post[:method] = method add_request_id(post) @@ -246,7 +246,7 @@ def auth_or_sale(method, authorization, amount, credit_card, options={}) commit('v1/', post) end - def verifyOnly(action, authorization, credit_card, options={}) + def verifyOnly(action, authorization, credit_card, options = {}) post = {} post[:method] = 'verifyOnly' add_request_id(post) @@ -267,7 +267,7 @@ def verifyOnly(action, authorization, credit_card, options={}) commit('v1/', post) end - def refundWithCard(authorization, amount, credit_card, options={}) + def refundWithCard(authorization, amount, credit_card, options = {}) post = {} post[:method] = 'refundWithCard' add_request_id(post) @@ -286,7 +286,7 @@ def refundWithCard(authorization, amount, credit_card, options={}) commit('v1/', post) end - def reverse_or_void(method, pgwTID, options={}) + def reverse_or_void(method, pgwTID, options = {}) post = {} post[:method] = method add_request_id(post) @@ -324,7 +324,7 @@ def commit(endpoint, post) def headers { - 'Content-Type' => 'application/json' + 'Content-Type' => 'application/json' } end @@ -364,6 +364,7 @@ def message_from(response) def error_from(response) return response['error'] if response['error'] return 'Failed' unless response.key?('result') + return response['result']['pgwResponseCode'] || response['result']['processor']['responseCode'] || 'Failed' end @@ -397,7 +398,7 @@ def response_error(raw_response) false, message_from(response), response, - :test => test? + test: test? ) end diff --git a/lib/active_merchant/billing/gateways/linkpoint.rb b/lib/active_merchant/billing/gateways/linkpoint.rb index 4dd09c89800..11c1b95dc3d 100644 --- a/lib/active_merchant/billing/gateways/linkpoint.rb +++ b/lib/active_merchant/billing/gateways/linkpoint.rb @@ -130,7 +130,7 @@ class LinkpointGateway < Gateway self.live_url = 'https://secure.linkpt.net:1129/' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.homepage_url = 'http://www.linkpoint.com/' self.display_name = 'LinkPoint' @@ -138,8 +138,8 @@ def initialize(options = {}) requires!(options, :login) @options = { - :result => 'LIVE', - :pem => LinkpointGateway.pem_file + result: 'LIVE', + pem: LinkpointGateway.pem_file }.update(options) raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @options[:pem].blank? @@ -166,28 +166,28 @@ def initialize(options = {}) # :threshold Tells how many times to retry the transaction (if it fails) before contacting the merchant. # :comments Uh... comments # - def recurring(money, creditcard, options={}) + def recurring(money, creditcard, options = {}) ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id) + requires!(options, %i[periodicity bimonthly monthly biweekly weekly yearly daily], :installments, :order_id) options.update( - :ordertype => 'SALE', - :action => options[:action] || 'SUBMIT', - :installments => options[:installments] || 12, - :startdate => options[:startdate] || 'immediate', - :periodicity => options[:periodicity].to_s || 'monthly', - :comments => options[:comments] || nil, - :threshold => options[:threshold] || 3 + ordertype: 'SALE', + action: options[:action] || 'SUBMIT', + installments: options[:installments] || 12, + startdate: options[:startdate] || 'immediate', + periodicity: options[:periodicity].to_s || 'monthly', + comments: options[:comments] || nil, + threshold: options[:threshold] || 3 ) commit(money, creditcard, options) end # Buy the thing - def purchase(money, creditcard, options={}) + def purchase(money, creditcard, options = {}) requires!(options, :order_id) options.update( - :ordertype => 'SALE' + ordertype: 'SALE' ) commit(money, creditcard, options) end @@ -200,7 +200,7 @@ def purchase(money, creditcard, options={}) def authorize(money, creditcard, options = {}) requires!(options, :order_id) options.update( - :ordertype => 'PREAUTH' + ordertype: 'PREAUTH' ) commit(money, creditcard, options) end @@ -213,8 +213,8 @@ def authorize(money, creditcard, options = {}) # def capture(money, authorization, options = {}) options.update( - :order_id => authorization, - :ordertype => 'POSTAUTH' + order_id: authorization, + ordertype: 'POSTAUTH' ) commit(money, nil, options) end @@ -222,8 +222,8 @@ def capture(money, authorization, options = {}) # Void a previous transaction def void(identification, options = {}) options.update( - :order_id => identification, - :ordertype => 'VOID' + order_id: identification, + ordertype: 'VOID' ) commit(nil, nil, options) end @@ -235,8 +235,8 @@ def void(identification, options = {}) # def refund(money, identification, options = {}) options.update( - :ordertype => 'CREDIT', - :order_id => identification + ordertype: 'CREDIT', + order_id: identification ) commit(money, nil, options) end @@ -263,11 +263,14 @@ 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, - :test => test?, - :authorization => response[:ordernum], - :avs_result => { :code => response[:avs].to_s[2, 1] }, - :cvv_result => response[:avs].to_s[3, 1] + 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] ) end @@ -311,11 +314,11 @@ def build_items(element, items) options_element = item_element.add_element('options') for option in value opt_element = options_element.add_element('option') - opt_element.add_element('name').text = option[:name] unless option[:name].blank? - opt_element.add_element('value').text = option[:value] unless option[:value].blank? + opt_element.add_element('name').text = option[:name] unless option[:name].blank? + opt_element.add_element('value').text = option[:value] unless option[:value].blank? end else - item_element.add_element(key.to_s).text = item[key].to_s unless item[key].blank? + item_element.add_element(key.to_s).text = item[key].to_s unless item[key].blank? end end end @@ -325,55 +328,55 @@ def build_items(element, items) # for every action. def parameters(money, creditcard, options = {}) params = { - :payment => { - :subtotal => amount(options[:subtotal]), - :tax => amount(options[:tax]), - :vattax => amount(options[:vattax]), - :shipping => amount(options[:shipping]), - :chargetotal => amount(money) + payment: { + subtotal: amount(options[:subtotal]), + tax: amount(options[:tax]), + vattax: amount(options[:vattax]), + shipping: amount(options[:shipping]), + chargetotal: amount(money) }, - :transactiondetails => { - :transactionorigin => options[:transactionorigin] || 'ECI', - :oid => options[:order_id], - :ponumber => options[:ponumber], - :taxexempt => options[:taxexempt], - :terminaltype => options[:terminaltype], - :ip => options[:ip], - :reference_number => options[:reference_number], - :recurring => options[:recurring] || 'NO', # DO NOT USE if you are using the periodic billing option. - :tdate => options[:tdate] + transactiondetails: { + transactionorigin: options[:transactionorigin] || 'ECI', + oid: options[:order_id], + ponumber: options[:ponumber], + taxexempt: options[:taxexempt], + terminaltype: options[:terminaltype], + ip: options[:ip], + reference_number: options[:reference_number], + recurring: options[:recurring] || 'NO', # DO NOT USE if you are using the periodic billing option. + tdate: options[:tdate] }, - :orderoptions => { - :ordertype => options[:ordertype], - :result => @options[:result] + orderoptions: { + ordertype: options[:ordertype], + result: @options[:result] }, - :periodic => { - :action => options[:action], - :installments => options[:installments], - :threshold => options[:threshold], - :startdate => options[:startdate], - :periodicity => options[:periodicity], - :comments => options[:comments] + periodic: { + action: options[:action], + installments: options[:installments], + threshold: options[:threshold], + startdate: options[:startdate], + periodicity: options[:periodicity], + comments: options[:comments] }, - :telecheck => { - :routing => options[:telecheck_routing], - :account => options[:telecheck_account], - :checknumber => options[:telecheck_checknumber], - :bankname => options[:telecheck_bankname], - :dl => options[:telecheck_dl], - :dlstate => options[:telecheck_dlstate], - :void => options[:telecheck_void], - :accounttype => options[:telecheck_accounttype], - :ssn => options[:telecheck_ssn], + telecheck: { + routing: options[:telecheck_routing], + account: options[:telecheck_account], + checknumber: options[:telecheck_checknumber], + bankname: options[:telecheck_bankname], + dl: options[:telecheck_dl], + dlstate: options[:telecheck_dlstate], + void: options[:telecheck_void], + accounttype: options[:telecheck_accounttype], + ssn: options[:telecheck_ssn] } } if creditcard params[:creditcard] = { - :cardnumber => creditcard.number, - :cardexpmonth => creditcard.month, - :cardexpyear => format_creditcard_expiry_year(creditcard.year), - :track => nil + cardnumber: creditcard.number, + cardexpmonth: creditcard.month, + cardexpyear: format_creditcard_expiry_year(creditcard.year), + track: nil } if creditcard.verification_value? @@ -395,8 +398,8 @@ def parameters(money, creditcard, options = {}) params[:billing][:zip] = billing_address[:zip] unless billing_address[:zip].blank? params[:billing][:country] = billing_address[:country] unless billing_address[:country].blank? params[:billing][:company] = billing_address[:company] unless billing_address[:company].blank? - params[:billing][:phone] = billing_address[:phone] unless billing_address[:phone].blank? - params[:billing][:email] = options[:email] unless options[:email].blank? + params[:billing][:phone] = billing_address[:phone] unless billing_address[:phone].blank? + params[:billing][:email] = options[:email] unless options[:email].blank? end if shipping_address = options[:shipping_address] @@ -431,7 +434,7 @@ def parse(xml) # APPROVED # - response = {:message => 'Global Error Receipt', :complete => false} + response = { message: 'Global Error Receipt', complete: false } xml = REXML::Document.new("#{xml}") xml.root&.elements&.each do |node| diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb index 2a871d892d0..ac5e4c66f5b 100644 --- a/lib/active_merchant/billing/gateways/litle.rb +++ b/lib/active_merchant/billing/gateways/litle.rb @@ -5,22 +5,26 @@ module Billing #:nodoc: class LitleGateway < Gateway SCHEMA_VERSION = '9.14' + 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' self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] - self.homepage_url = 'http://www.vantiv.com/' + self.homepage_url = 'https://www.fisglobal.com/' self.display_name = 'Vantiv eCommerce' - def initialize(options={}) + def initialize(options = {}) requires!(options, :login, :password, :merchant_id) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) request = build_xml_request do |doc| add_authentication(doc) if check?(payment_method) @@ -36,7 +40,97 @@ def purchase(money, payment_method, options={}) check?(payment_method) ? commit(:echeckSales, request, money) : commit(:sale, request, money) end - def authorize(money, payment_method, options={}) + def add_level_two_data(doc, payment_method, options = {}) + level_2_data = options[:level_2_data] + if level_2_data + doc.enhancedData do + case payment_method.brand + when 'visa' + doc.salesTax(level_2_data[:sales_tax]) if level_2_data[:sales_tax] + when 'master' + doc.customerReference(level_2_data[:customer_code]) if level_2_data[:customer_code] + doc.salesTax(level_2_data[:total_tax_amount]) if level_2_data[:total_tax_amount] + doc.detailTax do + doc.taxIncludedInTotal(level_2_data[:tax_included_in_total]) if level_2_data[:tax_included_in_total] + doc.taxAmount(level_2_data[:tax_amount]) if level_2_data[:tax_amount] + doc.cardAcceptorTaxId(level_2_data[:card_acceptor_tax_id]) if level_2_data[:card_acceptor_tax_id] + end + end + end + end + end + + def add_level_three_data(doc, payment_method, options = {}) + level_3_data = options[:level_3_data] + if level_3_data + doc.enhancedData do + case payment_method.brand + when 'visa' + add_level_three_information_tags_visa(doc, payment_method, level_3_data) + when 'master' + add_level_three_information_tags_master(doc, payment_method, level_3_data) + end + end + end + end + + def add_level_three_information_tags_visa(doc, payment_method, level_3_data) + doc.discountAmount(level_3_data[:discount_amount]) if level_3_data[:discount_amount] + doc.shippingAmount(level_3_data[:shipping_amount]) if level_3_data[:shipping_amount] + doc.dutyAmount(level_3_data[:duty_amount]) if level_3_data[:duty_amount] + doc.detailTax do + doc.taxIncludedInTotal(level_3_data[:tax_included_in_total]) if level_3_data[:tax_included_in_total] + doc.taxAmount(level_3_data[:tax_amount]) if level_3_data[:tax_amount] + doc.taxRate(level_3_data[:tax_rate]) if level_3_data[:tax_rate] + doc.taxTypeIdentifier(level_3_data[:tax_type_identifier]) if level_3_data[:tax_type_identifier] + doc.cardAcceptorTaxId(level_3_data[:card_acceptor_tax_id]) if level_3_data[:card_acceptor_tax_id] + end + add_line_item_information_for_level_three_visa(doc, payment_method, level_3_data) + end + + def add_level_three_information_tags_master(doc, payment_method, level_3_data) + doc.customerReference :customerReference, level_3_data[:customer_code] if level_3_data[:customer_code] + doc.salesTax(level_3_data[:total_tax_amount]) if level_3_data[:total_tax_amount] + doc.detailTax do + doc.taxIncludedInTotal(level_3_data[:tax_included_in_total]) if level_3_data[:tax_included_in_total] + doc.taxAmount(level_3_data[:tax_amount]) if level_3_data[:tax_amount] + doc.cardAcceptorTaxId :cardAcceptorTaxId, level_3_data[:card_acceptor_tax_id] if level_3_data[:card_acceptor_tax_id] + end + doc.lineItemData do + level_3_data[:line_items].each do |line_item| + 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.lineItemTotal(line_item[:line_item_total]) if line_item[:line_item_total] + end + end + end + + def add_line_item_information_for_level_three_visa(doc, payment_method, level_3_data) + 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.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] + doc.taxRate(line_item[:tax_rate]) if line_item[:tax_rate] + doc.taxTypeIdentifier(line_item[:tax_type_identifier]) if line_item[:tax_type_identifier] + doc.cardAcceptorTaxId(line_item[:card_acceptor_tax_id]) if line_item[:card_acceptor_tax_id] + end + end + end + end + + def authorize(money, payment_method, options = {}) request = build_xml_request do |doc| add_authentication(doc) if check?(payment_method) @@ -52,8 +146,8 @@ def authorize(money, payment_method, options={}) check?(payment_method) ? commit(:echeckVerification, request, money) : commit(:authorization, request, money) end - def capture(money, authorization, options={}) - transaction_id, _, _ = split_authorization(authorization) + def capture(money, authorization, options = {}) + transaction_id, = split_authorization(authorization) request = build_xml_request do |doc| add_authentication(doc) @@ -72,19 +166,19 @@ def credit(money, authorization, options = {}) refund(money, authorization, options) end - def refund(money, payment, options={}) + def refund(money, payment, options = {}) request = build_xml_request do |doc| add_authentication(doc) add_descriptor(doc, options) doc.send(refund_type(payment), transaction_attributes(options)) do if payment.is_a?(String) - transaction_id, _, _ = split_authorization(payment) + transaction_id, = split_authorization(payment) doc.litleTxnId(transaction_id) doc.amount(money) if money elsif check?(payment) add_echeck_purchase_params(doc, money, payment, options) else - add_auth_purchase_params(doc, money, payment, options) + add_credit_params(doc, money, payment, options) end end end @@ -99,7 +193,7 @@ def verify(creditcard, options = {}) end end - def void(authorization, options={}) + def void(authorization, options = {}) transaction_id, kind, money = split_authorization(authorization) request = build_xml_request do |doc| @@ -164,21 +258,21 @@ def scrub(transcript) } AVS_RESPONSE_CODE = { - '00' => 'Y', - '01' => 'X', - '02' => 'D', - '10' => 'Z', - '11' => 'W', - '12' => 'A', - '13' => 'A', - '14' => 'P', - '20' => 'N', - '30' => 'S', - '31' => 'R', - '32' => 'U', - '33' => 'R', - '34' => 'I', - '40' => 'E' + '00' => 'Y', + '01' => 'X', + '02' => 'D', + '10' => 'Z', + '11' => 'W', + '12' => 'A', + '13' => 'A', + '14' => 'P', + '20' => 'N', + '30' => 'S', + '31' => 'R', + '32' => 'U', + '33' => 'R', + '34' => 'I', + '40' => 'E' } def void_type(kind) @@ -192,8 +286,8 @@ def void_type(kind) end def refund_type(payment) - _, kind, _ = split_authorization(payment) - if check?(payment) || kind == 'echeckSales' + _, kind, = split_authorization(payment) + if check?(payment) || kind == 'echeckSales' :echeckCredit else :credit @@ -202,6 +296,7 @@ def refund_type(payment) def check?(payment_method) return false if payment_method.is_a?(String) + card_brand(payment_method) == 'check' end @@ -221,12 +316,26 @@ def add_auth_purchase_params(doc, money, payment_method, options) add_payment_method(doc, payment_method, options) add_pos(doc, payment_method) add_descriptor(doc, options) + add_level_two_data(doc, payment_method, options) + add_level_three_data(doc, payment_method, options) add_merchant_data(doc, options) add_debt_repayment(doc, options) add_stored_credential_params(doc, options) + add_fraud_filter_override(doc, options) end - def add_merchant_data(doc, options={}) + def add_credit_params(doc, money, payment_method, options) + doc.orderId(truncate(options[:order_id], 24)) + doc.amount(money) + add_order_source(doc, payment_method, options) + add_billing_address(doc, payment_method, options) + add_payment_method(doc, payment_method, options) + add_pos(doc, payment_method) + add_descriptor(doc, options) + add_merchant_data(doc, options) + end + + def add_merchant_data(doc, options = {}) if options[:affiliate] || options[:campaign] || options[:merchant_grouping_id] doc.merchantData do doc.affiliate(options[:affiliate]) if options[:affiliate] @@ -258,10 +367,15 @@ def add_debt_repayment(doc, options) doc.debtRepayment(true) if options[:debt_repayment] == true end + def add_fraud_filter_override(doc, options) + doc.fraudFilterOverride(options[:fraud_filter_override]) if options[:fraud_filter_override] + end + def add_payment_method(doc, payment_method, options) if payment_method.is_a?(String) doc.token do doc.litleToken(payment_method) + doc.expDate(format_exp_date(options[:basis_expiration_month], options[:basis_expiration_year])) if options[:basis_expiration_month] && options[:basis_expiration_year] end elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present? doc.card do @@ -272,7 +386,7 @@ def add_payment_method(doc, payment_method, options) doc.accType(payment_method.account_type.capitalize) doc.accNum(payment_method.account_number) doc.routingNum(payment_method.routing_number) - doc.checkNum(payment_method.number) + doc.checkNum(payment_method.number) if payment_method.number end else doc.card do @@ -294,7 +408,7 @@ def add_payment_method(doc, payment_method, options) end end - def add_stored_credential_params(doc, options={}) + def add_stored_credential_params(doc, options = {}) return unless options[:stored_credential] if options[:stored_credential][:initial_transaction] @@ -355,9 +469,9 @@ def add_address(doc, address) return unless address doc.companyName(address[:company]) unless address[:company].blank? - doc.addressLine1(address[:address1]) unless address[:address1].blank? - doc.addressLine2(address[:address2]) unless address[:address2].blank? - doc.city(address[:city]) unless address[:city].blank? + doc.addressLine1(truncate(address[:address1], 35)) unless address[:address1].blank? + doc.addressLine2(truncate(address[:address2], 35)) unless address[:address2].blank? + doc.city(truncate(address[:city], 35)) unless address[:city].blank? doc.state(address[:state]) unless address[:state].blank? doc.zip(address[:zip]) unless address[:zip].blank? doc.country(address[:country]) unless address[:country].blank? @@ -370,7 +484,7 @@ def add_order_source(doc, payment_method, options) doc.orderSource(order_source) elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay doc.orderSource('applepay') - elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :android_pay + elsif payment_method.is_a?(NetworkTokenizationCreditCard) && %i[google_pay android_pay].include?(payment_method.source) doc.orderSource('androidpay') elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present? doc.orderSource('retail') @@ -379,8 +493,9 @@ def add_order_source(doc, payment_method, options) end end - def order_source(options={}) + def order_source(options = {}) return options[:order_source] unless options[:stored_credential] + order_source = nil case options[:stored_credential][:reason_type] @@ -414,13 +529,19 @@ def add_pos(doc, payment_method) end def exp_date(payment_method) - "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}" + format_exp_date(payment_method.month, payment_method.year) + end + + def format_exp_date(month, year) + "#{format(month, :two_digits)}#{format(year, :two_digits)}" end def parse(kind, xml) parsed = {} - doc = Nokogiri::XML(xml).remove_namespaces! + + parsed['duplicate'] = doc.at_xpath('//saleResponse').try(:[], 'duplicate') == 'true' if kind == :sale + doc.xpath("//litleOnlineResponse/#{kind}Response/*").each do |node| if node.elements.empty? parsed[node.name.to_sym] = node.text @@ -441,24 +562,36 @@ def parse(kind, xml) parsed end - def commit(kind, request, money=nil) + def commit(kind, request, money = nil) parsed = parse(kind, ssl_post(url, request, headers)) options = { authorization: authorization_from(kind, parsed, money), test: test?, - :avs_result => { :code => AVS_RESPONSE_CODE[parsed[:fraudResult_avsResult]] }, - :cvv_result => parsed[:fraudResult_cardValidationResult] + avs_result: { code: AVS_RESPONSE_CODE[parsed[:fraudResult_avsResult]] }, + 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).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 @@ -472,8 +605,8 @@ def transaction_attributes(options) attributes = {} attributes[:id] = truncate(options[:id] || options[:order_id], 24) attributes[:reportGroup] = options[:merchant] || 'Default Report Group' - attributes[:customerId] = options[:customer] - attributes.delete_if { |key, value| value == nil } + attributes[:customerId] = options[:customer_id] + attributes.delete_if { |_key, value| value == nil } attributes end @@ -494,6 +627,9 @@ def build_xml_request 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 609039cb9e3..894ad2be3f5 100644 --- a/lib/active_merchant/billing/gateways/mastercard.rb +++ b/lib/active_merchant/billing/gateways/mastercard.rb @@ -1,22 +1,33 @@ module ActiveMerchant module Billing module MastercardGateway - def initialize(options={}) + def initialize(options = {}) requires!(options, :userid, :password) super end - def purchase(amount, payment_method, options={}) - MultiResponse.run do |r| - r.process { authorize(amount, payment_method, options) } - r.process { capture(amount, r.authorization, options) } + def purchase(amount, payment_method, options = {}) + if options[:pay_mode] + post = new_post + add_invoice(post, amount, options) + add_reference(post, *new_authorization(options)) + add_payment_method(post, payment_method) + add_customer_data(post, payment_method, options) + add_3dsecure_id(post, options) + + commit('pay', post) + else + MultiResponse.run do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(amount, r.authorization, options) } + end end end - def authorize(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) @@ -24,7 +35,7 @@ def authorize(amount, payment_method, options={}) commit('authorize', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = new_post add_invoice(post, amount, options, :transaction) add_reference(post, *next_authorization(authorization)) @@ -34,7 +45,7 @@ def capture(amount, authorization, options={}) commit('capture', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = new_post add_invoice(post, amount, options, :transaction) add_reference(post, *next_authorization(authorization)) @@ -43,14 +54,14 @@ def refund(amount, authorization, options={}) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = new_post add_reference(post, *next_authorization(authorization), :targetTransactionId) commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -94,16 +105,16 @@ def new_post billing: {}, device: {}, shipping: {}, - transaction: {}, + transaction: {} } end - def add_invoice(post, amount, options, node=:order) + def add_invoice(post, amount, options, node = :order) post[node][:amount] = amount(amount) post[node][:currency] = (options[:currency] || currency(amount)) end - def add_reference(post, orderid, transactionid, transaction_reference, reference_key=:reference) + def add_reference(post, orderid, transactionid, transaction_reference, reference_key = :reference) post[:orderid] = orderid post[:transactionid] = transactionid post[:transaction][reference_key] = transaction_reference if transaction_reference @@ -164,7 +175,8 @@ def add_customer_data(post, payment_method, options) def add_3dsecure_id(post, options) return unless options[:threed_secure_id] - post.merge!({'3DSecureId' => options[:threed_secure_id]}) + + post.merge!({ '3DSecureId' => options[:threed_secure_id] }) end def country_code(country) @@ -178,7 +190,7 @@ def country_code(country) def headers { 'Authorization' => 'Basic ' + Base64.encode64("merchant.#{@options[:userid]}:#{@options[:password]}").strip.delete("\r\n"), - 'Content-Type' => 'application/json', + 'Content-Type' => 'application/json' } end @@ -195,8 +207,8 @@ def commit(action, post) succeeded, message_from(succeeded, raw), raw, - :authorization => authorization_from(post, raw), - :test => test? + authorization: authorization_from(post, raw), + test: test? ) end @@ -206,9 +218,16 @@ def build_url(orderid, transactionid) def base_url if test? - @options[:region] == 'asia_pacific' ? test_ap_url : test_na_url + test_url else - @options[:region] == 'asia_pacific' ? live_ap_url : live_na_url + case @options[:region] + when 'asia_pacific' + live_ap_url + when 'europe' + live_eu_url + when 'north_america', nil + live_na_url + end end end @@ -245,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 b4ca346327b..c22ceaeaf01 100644 --- a/lib/active_merchant/billing/gateways/maxipago.rb +++ b/lib/active_merchant/billing/gateways/maxipago.rb @@ -11,7 +11,7 @@ class MaxipagoGateway < Gateway self.supported_countries = ['BR'] self.default_currency = 'BRL' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master discover american_express diners_club] self.homepage_url = 'http://www.maxipago.com/' self.display_name = 'maxiPago!' @@ -212,7 +212,7 @@ def add_billing_address(xml, creditcard, options) end def add_order_id(xml, authorization) - order_id, _ = split_authorization(authorization) + order_id, = split_authorization(authorization) xml.orderID order_id end end diff --git a/lib/active_merchant/billing/gateways/mercado_pago.rb b/lib/active_merchant/billing/gateways/mercado_pago.rb index f8bcc688fb7..36949c0422e 100644 --- a/lib/active_merchant/billing/gateways/mercado_pago.rb +++ b/lib/active_merchant/billing/gateways/mercado_pago.rb @@ -3,19 +3,19 @@ module Billing #:nodoc: class MercadoPagoGateway < Gateway self.live_url = self.test_url = 'https://api.mercadopago.com/v1' - self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY'] - self.supported_cardtypes = [:visa, :master, :american_express, :elo] + self.supported_countries = %w[AR BR CL CO MX PE UY] + self.supported_cardtypes = %i[visa master american_express elo cabal naranja creditel] self.homepage_url = 'https://www.mercadopago.com/' self.display_name = 'Mercado Pago' self.money_format = :dollars - def initialize(options={}) + def initialize(options = {}) requires!(options, :access_token) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) MultiResponse.run do |r| r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) } options[:card_token] = r.authorization.split('|').first @@ -23,7 +23,7 @@ def purchase(money, payment, options={}) end end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) MultiResponse.run do |r| r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) } options[:card_token] = r.authorization.split('|').first @@ -31,34 +31,40 @@ def authorize(money, payment, options={}) end end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} - authorization, _ = authorization.split('|') + authorization, = authorization.split('|') post[:capture] = true post[:transaction_amount] = amount(money).to_f commit('capture', "payments/#{authorization}", post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} authorization, original_amount = authorization.split('|') post[:amount] = amount(money).to_f if original_amount && original_amount.to_f > amount(money).to_f commit('refund', "payments/#{authorization}/refunds", post) end - def void(authorization, options={}) - authorization, _ = authorization.split('|') + def void(authorization, options = {}) + authorization, = authorization.split('|') post = { status: 'cancelled' } commit('void', "payments/#{authorization}", post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) + verify_amount = 100 + verify_amount = options[:amount].to_i if options[:amount] MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } + r.process { authorize(verify_amount, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end + def inquire(authorization, options = {}) + commit('inquire', inquire_path(authorization, options), {}) + end + def supports_scrubbing? true end @@ -96,25 +102,31 @@ def purchase_request(money, payment, options = {}) add_customer_data(post, payment, options) add_address(post, options) add_processing_mode(post, options) + add_net_amount(post, options) + add_taxes(post, options) + add_notification_url(post, options) post[:binary_mode] = (options[:binary_mode].nil? ? true : options[:binary_mode]) post end def authorize_request(money, payment, options = {}) post = purchase_request(money, payment, options) - post[:capture] = false + post[:capture] = options[:capture] || false post end def add_processing_mode(post, options) return unless options[:processing_mode] + post[:processing_mode] = options[:processing_mode] post[:merchant_account_id] = options[:merchant_account_id] if options[:merchant_account_id] + post[:payment_method_option_id] = options[:payment_method_option_id] if options[:payment_method_option_id] add_merchant_services(post, options) end def add_merchant_services(post, options) return unless options[:fraud_scoring] || options[:fraud_manual_review] + merchant_services = {} merchant_services[:fraud_scoring] = options[:fraud_scoring] if options[:fraud_scoring] merchant_services[:fraud_manual_review] = options[:fraud_manual_review] if options[:fraud_manual_review] @@ -123,6 +135,7 @@ def add_merchant_services(post, options) def add_additional_data(post, options) post[:sponsor_id] = options[:sponsor_id] + post[:metadata] = options[:metadata] if options[:metadata] post[:device_id] = options[:device_id] if options[:device_id] post[:additional_info] = { ip_address: options[:ip_address] @@ -137,7 +150,7 @@ def add_customer_data(post, payment, options) email: options[:email], first_name: payment.first_name, last_name: payment.last_name - } + }.merge(options[:payer] || {}) end def add_address(post, options) @@ -185,7 +198,7 @@ def add_invoice(post, money, options) post[:description] = options[:description] post[:installments] = options[:installments] ? options[:installments].to_i : 1 post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor] - post[:external_reference] = options[:order_id] || SecureRandom.hex(16) + post[:external_reference] = options[:order_id] || options[:external_reference] || SecureRandom.hex(16) end def add_payment(post, options) @@ -194,6 +207,51 @@ def add_payment(post, options) post[:payment_method_id] = options[:payment_method_id] if options[:payment_method_id] end + def add_net_amount(post, options) + post[:net_amount] = Float(options[:net_amount]) if options[:net_amount] + end + + def add_notification_url(post, options) + post[:notification_url] = options[:notification_url] if options[:notification_url] + end + + def add_taxes(post, options) + return unless (tax_object = options[:taxes]) + + if tax_object.is_a?(Array) + post[:taxes] = process_taxes_array(tax_object) + elsif tax_object.is_a?(Hash) + post[:taxes] = process_taxes_hash(tax_object) + else + raise taxes_error + end + end + + def process_taxes_hash(tax_object) + [sanitize_taxes_hash(tax_object)] + end + + def process_taxes_array(taxes_array) + taxes_array.map do |tax_object| + raise taxes_error unless tax_object.is_a?(Hash) + + sanitize_taxes_hash(tax_object) + end + end + + def sanitize_taxes_hash(tax_object) + tax_value = tax_object['value'] || tax_object[:value] + tax_type = tax_object['type'] || tax_object[:type] + + raise taxes_error if tax_value.nil? || tax_type.nil? + + { value: Float(tax_value), type: tax_type } + end + + def taxes_error + ArgumentError.new("Taxes should be a single object or array of objects with the shape: { value: 500, type: 'IVA' }") + end + def parse(body) JSON.parse(body) rescue JSON::ParserError @@ -205,8 +263,12 @@ def parse(body) end def commit(action, path, parameters) - if ['capture', 'void'].include?(action) + if %w[capture void].include?(action) response = parse(ssl_request(:put, url(path), post_data(parameters), headers)) + elsif action == 'inquire' + response = parse(ssl_get(url(path), headers)) + + response = response[0]['results'][0] if response.is_a?(Array) else response = parse(ssl_post(url(path), post_data(parameters), headers(parameters))) end @@ -225,7 +287,7 @@ def success_from(action, response) if action == 'refund' response['status'] != 404 && response['error'].nil? else - ['active', 'approved', 'authorized', 'cancelled', 'in_process'].include?(response['status']) + %w[active approved authorized cancelled in_process].include?(response['status']) end end @@ -241,6 +303,15 @@ def post_data(parameters = {}) parameters.clone.tap { |p| p.delete(:device_id) }.to_json end + def inquire_path(authorization, options) + if authorization + authorization, = authorization.split('|') + "payments/#{authorization}" + else + "payments/search?external_reference=#{options[:order_id] || options[:external_reference]}" + end + end + def error_code_from(action, response) unless success_from(action, response) if cause = response['cause'] @@ -260,7 +331,7 @@ def headers(options = {}) headers = { 'Content-Type' => 'application/json' } - headers['X-Device-Session-ID'] = options[:device_id] if options[:device_id] + headers['X-meli-session-id'] = options[:device_id] if options[:device_id] headers end diff --git a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb index bad8070e2d1..a5bf6bdce81 100644 --- a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +++ b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb @@ -10,7 +10,7 @@ class MerchantESolutionsGateway < Gateway self.supported_countries = ['US'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb] + self.supported_cardtypes = %i[visa master american_express discover jcb] # The homepage URL of the gateway self.homepage_url = 'http://www.merchante-solutions.com/' @@ -18,6 +18,8 @@ class MerchantESolutionsGateway < Gateway # The name of the gateway self.display_name = 'Merchant e-Solutions' + SUCCESS_RESPONSE_CODES = %w(000 085) + def initialize(options = {}) requires!(options, :login, :password) super @@ -25,28 +27,26 @@ def initialize(options = {}) def authorize(money, creditcard_or_card_id, options = {}) post = {} - post[:client_reference_number] = options[:customer] if options.has_key?(:customer) - post[:moto_ecommerce_ind] = options[:moto_ecommerce_ind] if options.has_key?(:moto_ecommerce_ind) add_invoice(post, options) add_payment_source(post, creditcard_or_card_id, options) add_address(post, options) add_3dsecure_params(post, options) + add_stored_credentials(post, options) commit('P', money, post) end def purchase(money, creditcard_or_card_id, options = {}) post = {} - post[:client_reference_number] = options[:customer] if options.has_key?(:customer) - post[:moto_ecommerce_ind] = options[:moto_ecommerce_ind] if options.has_key?(:moto_ecommerce_ind) add_invoice(post, options) add_payment_source(post, creditcard_or_card_id, options) add_address(post, options) add_3dsecure_params(post, options) + add_stored_credentials(post, options) commit('D', money, post) end def capture(money, transaction_id, options = {}) - post ={} + post = {} post[:transaction_id] = transaction_id post[:client_reference_number] = options[:customer] if options.has_key?(:customer) add_invoice(post, options) @@ -55,10 +55,10 @@ def capture(money, transaction_id, options = {}) end def store(creditcard, options = {}) - post = {} - post[:client_reference_number] = options[:customer] if options.has_key?(:customer) - add_creditcard(post, creditcard, options) - commit('T', nil, post) + MultiResponse.run do |r| + r.process { temporary_store(creditcard, options) } + r.process { verify(r.authorization, { store_card: 'y' }) } + end end def unstore(card_id, options = {}) @@ -94,6 +94,13 @@ def void(transaction_id, options = {}) commit('V', nil, options.merge(post)) end + def verify(credit_card, options = {}) + post = {} + post[:store_card] = options[:store_card] if options[:store_card] + add_payment_source(post, credit_card, options) + commit('A', 0, post) + end + def supports_scrubbing? true end @@ -107,6 +114,13 @@ def scrub(transcript) private + def temporary_store(creditcard, options = {}) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + add_creditcard(post, creditcard, options) + commit('T', nil, post) + end + def add_address(post, options) if address = options[:billing_address] || options[:address] post[:cardholder_street_address] = address[:address1].to_s.gsub(/[^\w.]/, '+') @@ -133,9 +147,9 @@ def add_payment_source(post, creditcard_or_card_id, options) end def add_creditcard(post, creditcard, options) - post[:card_number] = creditcard.number + post[:card_number] = creditcard.number post[:cvv2] = creditcard.verification_value if creditcard.verification_value? - post[:card_exp_date] = expdate(creditcard) + post[:card_exp_date] = expdate(creditcard) end def add_3dsecure_params(post, options) @@ -145,6 +159,16 @@ def add_3dsecure_params(post, options) post[:ucaf_auth_data] = options[:ucaf_auth_data] unless empty?(options[:ucaf_auth_data]) end + def add_stored_credentials(post, options) + post[:client_reference_number] = options[:client_reference_number] if options[:client_reference_number] + post[:moto_ecommerce_ind] = options[:moto_ecommerce_ind] if options[:moto_ecommerce_ind] + post[:recurring_pmt_num] = options[:recurring_pmt_num] if options[:recurring_pmt_num] + post[:recurring_pmt_count] = options[:recurring_pmt_count] if options[:recurring_pmt_count] + post[:card_on_file] = options[:card_on_file] if options[:card_on_file] + post[:cit_mit_indicator] = options[:cit_mit_indicator] if options[:cit_mit_indicator] + post[:account_data_source] = options[:account_data_source] if options[:account_data_source] + end + def parse(body) results = {} body.split(/&/).each do |pair| @@ -156,20 +180,34 @@ 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 money unless action == 'V' + + response = + begin + parse(ssl_post(url, post_data(action, parameters))) + rescue ActiveMerchant::ResponseError => e + { 'error_code' => '404', 'auth_response_text' => e.to_s } + end + + 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'] } + ) + end - response = begin - parse(ssl_post(url, post_data(action, parameters))) - rescue ActiveMerchant::ResponseError => e - { 'error_code' => '404', 'auth_response_text' => e.to_s } - end + def authorization_from(response) + return response['card_id'] if response['card_id'] - Response.new(response['error_code'] == '000', message_from(response), response, - :authorization => response['transaction_id'], - :test => test?, - :cvv_result => response['cvv2_result'], - :avs_result => { :code => response['avs_result'] } - ) + response['transaction_id'] + end + + def success_from(response) + SUCCESS_RESPONSE_CODES.include?(response['error_code']) end def message_from(response) diff --git a/lib/active_merchant/billing/gateways/merchant_one.rb b/lib/active_merchant/billing/gateways/merchant_one.rb index 20f60dd0cd2..373b9243f8b 100644 --- a/lib/active_merchant/billing/gateways/merchant_one.rb +++ b/lib/active_merchant/billing/gateways/merchant_one.rb @@ -3,7 +3,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class MerchantOneGateway < Gateway - class MerchantOneSslConnection < ActiveMerchant::Connection def configure_ssl(http) super(http) @@ -14,7 +13,7 @@ def configure_ssl(http) BASE_URL = 'https://secure.merchantonegateway.com/api/transact.php' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://merchantone.com/' self.display_name = 'Merchant One Gateway' self.money_format = :dollars @@ -77,10 +76,10 @@ def add_address(post, creditcard, options) def add_creditcard(post, creditcard) post['cvv'] = creditcard.verification_value post['ccnumber'] = creditcard.number - post['ccexp'] = "#{sprintf("%02d", creditcard.month)}#{creditcard.year.to_s[-2, 2]}" + post['ccexp'] = "#{sprintf('%02d', creditcard.month)}#{creditcard.year.to_s[-2, 2]}" end - def commit(action, money, parameters={}) + def commit(action, money, parameters = {}) parameters['username'] = @options[:username] parameters['password'] = @options[:password] parse(ssl_post(BASE_URL, post_data(action, parameters))) @@ -91,21 +90,19 @@ def post_data(action, parameters = {}) ret = '' for key in parameters.keys ret += "#{key}=#{CGI.escape(parameters[key].to_s)}" - if key != parameters.keys.last - ret += '&' - end + ret += '&' if key != parameters.keys.last end ret.to_s end def parse(data) - responses = CGI.parse(data).inject({}) { |h, (k, v)| h[k] = v.first; h } + responses = CGI.parse(data).inject({}) { |h, (k, v)| h[k] = v.first; h } Response.new( (responses['response'].to_i == 1), responses['responsetext'], responses, - :test => test?, - :authorization => responses['transactionid'] + test: test?, + authorization: responses['transactionid'] ) end end diff --git a/lib/active_merchant/billing/gateways/merchant_partners.rb b/lib/active_merchant/billing/gateways/merchant_partners.rb index e4630211a5d..e32dff8b9b2 100644 --- a/lib/active_merchant/billing/gateways/merchant_partners.rb +++ b/lib/active_merchant/billing/gateways/merchant_partners.rb @@ -11,14 +11,14 @@ class MerchantPartnersGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] - def initialize(options={}) + def initialize(options = {}) requires!(options, :account_id, :merchant_pin) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -27,7 +27,7 @@ def purchase(amount, payment_method, options={}) commit(payment_method.is_a?(String) ? :stored_purchase : :purchase, post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -36,7 +36,7 @@ def authorize(amount, payment_method, options={}) commit(:authorize, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -45,14 +45,14 @@ def capture(amount, authorization, options={}) commit(:capture, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit(:void, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -61,7 +61,7 @@ def refund(amount, authorization, options={}) commit(:refund, post) end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) @@ -69,7 +69,7 @@ def credit(amount, payment_method, options={}) commit(payment_method.is_a?(String) ? :stored_credit : :credit, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -110,10 +110,10 @@ def add_invoice(post, money, options) end def add_payment_method(post, payment_method) - if(payment_method.is_a?(String)) - user_profile_id, last_4 = split_authorization(payment_method) + if payment_method.is_a?(String) + user_profile_id, last4 = split_authorization(payment_method) post[:userprofileid] = user_profile_id - post[:last4digits] = last_4 + post[:last4digits] = last4 else post[:ccname] = payment_method.name post[:ccnum] = payment_method.number @@ -127,13 +127,13 @@ def add_payment_method(post, payment_method) def add_customer_data(post, options) post[:email] = options[:email] if options[:email] post[:ipaddress] = options[:ip] if options[:ip] - if(billing_address = options[:billing_address]) + if (billing_address = options[:billing_address]) post[:billaddr1] = billing_address[:address1] post[:billaddr2] = billing_address[:address2] post[:billcity] = billing_address[:city] post[:billstate] = billing_address[:state] post[:billcountry] = billing_address[:country] - post[:bilzip] = billing_address[:zip] + post[:bilzip] = billing_address[:zip] post[:phone] = billing_address[:phone] end end @@ -172,20 +172,20 @@ def commit(action, post) message_from(succeeded, response_data), response_data, authorization: authorization_from(post, response_data), - :avs_result => AVSResult.new(code: response_data['avs_response']), - :cvv_result => CVVResult.new(response_data['cvv2_response']), + avs_result: AVSResult.new(code: response_data['avs_response']), + cvv_result: CVVResult.new(response_data['cvv2_response']), test: test? ) end def headers { - 'Content-Type' => 'application/xml' + 'Content-Type' => 'application/xml' } end def build_request(post) - Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml| xml.interface_driver { xml.trans_catalog { xml.transaction(name: 'creditcard') { @@ -235,7 +235,7 @@ def split_authorization(authorization) end def error_message_from(response) - if(response[:status] == 'Declined') + if response[:status] == 'Declined' match = response[:result].match(/DECLINED:\d{10}:(.+):/) match[1] if match end diff --git a/lib/active_merchant/billing/gateways/merchant_ware.rb b/lib/active_merchant/billing/gateways/merchant_ware.rb index d028024ccb0..cc934aa6fd7 100644 --- a/lib/active_merchant/billing/gateways/merchant_ware.rb +++ b/lib/active_merchant/billing/gateways/merchant_ware.rb @@ -7,29 +7,27 @@ class MerchantWareGateway < Gateway self.v4_live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://merchantwarehouse.com/merchantware' self.display_name = 'MerchantWARE' ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/' - } + 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/' } ENV_NAMESPACES_V4 = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' - } + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } TX_NAMESPACE = 'http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail' TX_NAMESPACE_V4 = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/' ACTIONS = { - :purchase => 'IssueKeyedSale', - :authorize => 'IssueKeyedPreAuth', - :capture => 'IssuePostAuth', - :void => 'VoidPreAuthorization', - :credit => 'IssueKeyedRefund', - :reference_credit => 'IssueRefundByReference' + purchase: 'IssueKeyedSale', + authorize: 'IssueKeyedPreAuth', + capture: 'IssuePostAuth', + void: 'VoidPreAuthorization', + credit: 'IssueKeyedRefund', + reference_credit: 'IssueRefundByReference' } # Creates a new MerchantWareGateway @@ -119,7 +117,7 @@ def refund(money, reference, options = {}) private def soap_request(action) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'env:Envelope', ENV_NAMESPACES do xml.tag! 'env:Body' do @@ -133,7 +131,7 @@ def soap_request(action) end def v4_soap_request(action) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'soap:Envelope', ENV_NAMESPACES_V4 do xml.tag! 'soap:Body' do @@ -274,7 +272,7 @@ def parse_error(http_response) response[element.name] = element.text end - response[:message] = response['faultstring'].to_s.gsub("\n", ' ') + response[:message] = response['faultstring'].to_s.tr("\n", ' ') response rescue REXML::ParseException response[:http_body] = http_response.body @@ -292,7 +290,9 @@ 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) ) @@ -301,18 +301,19 @@ def commit(action, request, v4 = false) response = parse_error(e.response) end - Response.new(response[:success], response[:message], response, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response['AVSResponse'] }, - :cvv_result => response['CVResponse'] + Response.new( + response[:success], + response[:message], + response, + test: test?, + authorization: authorization_from(response), + avs_result: { code: response['AVSResponse'] }, + cvv_result: response['CVResponse'] ) end def authorization_from(response) - if response[:success] - [ response['ReferenceID'], response['OrderNumber'] ].join(';') - end + [response['ReferenceID'], response['OrderNumber']].join(';') if response[:success] end end end diff --git a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb index 4b1667334a4..9657a5631ed 100644 --- a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +++ b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb @@ -5,7 +5,7 @@ class MerchantWareVersionFourGateway < Gateway self.test_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://merchantwarehouse.com/merchantware' self.display_name = 'MerchantWARE' @@ -16,12 +16,12 @@ class MerchantWareVersionFourGateway < Gateway TX_NAMESPACE = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/' ACTIONS = { - :purchase => 'SaleKeyed', - :reference_purchase => 'RepeatSale', - :authorize => 'PreAuthorizationKeyed', - :capture => 'PostAuthorization', - :void => 'Void', - :refund => 'Refund' + purchase: 'SaleKeyed', + reference_purchase: 'RepeatSale', + authorize: 'PreAuthorizationKeyed', + capture: 'PostAuthorization', + void: 'Void', + refund: 'Refund' } # Creates a new MerchantWareVersionFourGateway @@ -108,7 +108,7 @@ def refund(money, identification, options = {}) commit(:refund, request) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -129,7 +129,7 @@ def scrub(transcript) private def soap_request(action) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'soap:Envelope', ENV_NAMESPACES do xml.tag! 'soap:Body' do @@ -243,7 +243,7 @@ def parse_error(http_response, action) response[element.name] = element.text end - response[:message] = response['ErrorMessage'].to_s.gsub("\n", ' ') + response[:message] = response['ErrorMessage'].to_s.tr("\n", ' ') response rescue REXML::ParseException response[:http_body] = http_response.body @@ -261,7 +261,9 @@ 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) ) @@ -270,11 +272,14 @@ def commit(action, request) response = parse_error(e.response, action) end - Response.new(response[:success], response[:message], response, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response['AvsResponse'] }, - :cvv_result => response['CvResponse'] + Response.new( + response[:success], + response[:message], + response, + test: test?, + authorization: authorization_from(response), + avs_result: { code: response['AvsResponse'] }, + cvv_result: response['CvResponse'] ) end diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb index 8e92e21811e..337b18be643 100644 --- a/lib/active_merchant/billing/gateways/merchant_warrior.rb +++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb @@ -11,8 +11,8 @@ class MerchantWarriorGateway < Gateway POST_LIVE_URL = 'https://api.merchantwarrior.com/post/' self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express, - :diners_club, :discover, :jcb] + self.supported_cardtypes = %i[visa master american_express + diners_club discover jcb] self.homepage_url = 'https://www.merchantwarrior.com/' self.display_name = 'Merchant Warrior' @@ -30,6 +30,8 @@ def authorize(money, payment_method, options = {}) add_order_id(post, options) add_address(post, options) add_payment_method(post, payment_method) + add_recurring_flag(post, options) + add_soft_descriptors(post, options) commit('processAuth', post) end @@ -39,6 +41,8 @@ def purchase(money, payment_method, options = {}) add_order_id(post, options) add_address(post, options) add_payment_method(post, payment_method) + add_recurring_flag(post, options) + add_soft_descriptors(post, options) commit('processCard', post) end @@ -46,6 +50,7 @@ def capture(money, identification, options = {}) post = {} add_amount(post, money, options) add_transaction(post, identification) + add_soft_descriptors(post, options) post['captureAmount'] = amount(money) commit('processCapture', post) end @@ -54,10 +59,21 @@ def refund(money, identification, options = {}) post = {} add_amount(post, money, options) add_transaction(post, identification) + add_soft_descriptors(post, options) post['refundAmount'] = amount(money) commit('refundCard', post) end + def void(identification, options = {}) + post = {} + # The amount parameter is required for void transactions + # on the Merchant Warrior gateway. + post['transactionAmount'] = options[:amount] + post['hash'] = void_verification_hash(identification) + add_transaction(post, identification) + commit('processVoid', post) + end + def store(creditcard, options = {}) post = { 'cardName' => scrub_name(creditcard.name), @@ -87,7 +103,7 @@ def add_transaction(post, identification) end def add_address(post, options) - return unless(address = (options[:billing_address] || options[:address])) + return unless (address = (options[:billing_address] || options[:address])) post['customerName'] = scrub_name(address[:name]) post['customerCountry'] = address[:country] @@ -135,6 +151,18 @@ def add_amount(post, money, options) post['hash'] = verification_hash(amount(money), currency) end + def add_recurring_flag(post, options) + return if options[:recurring_flag].nil? + + post['recurringFlag'] = options[:recurring_flag] + end + + def add_soft_descriptors(post, options) + post['descriptorName'] = options[:descriptor_name] if options[:descriptor_name] + post['descriptorCity'] = options[:descriptor_city] if options[:descriptor_city] + post['descriptorState'] = options[:descriptor_state] if options[:descriptor_state] + end + def verification_hash(money, currency) Digest::MD5.hexdigest( ( @@ -146,9 +174,21 @@ def verification_hash(money, currency) ) end + def void_verification_hash(transaction_id) + Digest::MD5.hexdigest( + ( + @options[:api_passphrase].to_s + + @options[:merchant_uuid].to_s + + transaction_id + ).downcase + ) + end + def parse(body) xml = REXML::Document.new(body) + return { response_message: 'Invalid gateway response' } unless xml.root.present? + response = {} xml.root.elements.to_a.each do |node| parse_element(response, node) @@ -173,17 +213,15 @@ def commit(action, post) success?(response), response[:response_message], response, - :test => test?, - :authorization => (response[:card_id] || response[:transaction_id]) + test: test?, + authorization: (response[:card_id] || response[:transaction_id]) ) end def add_auth(action, post) post['merchantUUID'] = @options[:merchant_uuid] post['apiKey'] = @options[:api_key] - unless token?(post) - post['method'] = action - end + post['method'] = action unless token?(post) end def url_for(action, post) diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb index 6fea6dd9197..5648640d734 100644 --- a/lib/active_merchant/billing/gateways/mercury.rb +++ b/lib/active_merchant/billing/gateways/mercury.rb @@ -12,14 +12,14 @@ module Billing #:nodoc: # and +refund+ will become mandatory. class MercuryGateway < Gateway URLS = { - :test => 'https://w1.mercurycert.net/ws/ws.asmx', - :live => 'https://w1.mercurypay.com/ws/ws.asmx' + test: 'https://w1.mercurycert.net/ws/ws.asmx', + live: 'https://w1.mercurypay.com/ws/ws.asmx' } self.homepage_url = 'http://www.mercurypay.com' self.display_name = 'Mercury' - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.default_currency = 'USD' STANDARD_ERROR_CODE_MAPPING = { @@ -51,14 +51,14 @@ def credit(money, credit_card, options = {}) def authorize(money, credit_card, options = {}) requires!(options, :order_id) - request = build_non_authorized_request('PreAuth', money, credit_card, options.merge(:authorized => money)) + request = build_non_authorized_request('PreAuth', money, credit_card, options.merge(authorized: money)) commit('PreAuth', request) end def capture(money, authorization, options = {}) requires!(options, :credit_card) unless @use_tokenization - request = build_authorized_request('PreAuthCapture', money, authorization, options[:credit_card], options.merge(:authorized => money)) + request = build_authorized_request('PreAuthCapture', money, authorization, options[:credit_card], options.merge(authorized: money)) commit('PreAuthCapture', request) end @@ -69,14 +69,14 @@ def refund(money, authorization, options = {}) commit('Return', request) end - def void(authorization, options={}) + def void(authorization, options = {}) requires!(options, :credit_card) unless @use_tokenization request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options) commit('VoidSale', request) end - def store(credit_card, options={}) + def store(credit_card, options = {}) request = build_card_lookup_request(credit_card, options) commit('CardLookup', request) end @@ -103,9 +103,7 @@ def build_non_authorized_request(action, money, credit_card, options) xml.tag! 'Transaction' do xml.tag! 'TranType', 'Credit' xml.tag! 'TranCode', action - if options[:allow_partial_auth] && ['PreAuth', 'Sale'].include?(action) - xml.tag! 'PartialAuth', 'Allow' - end + xml.tag! 'PartialAuth', 'Allow' if options[:allow_partial_auth] && %w[PreAuth Sale].include?(action) add_invoice(xml, options[:order_id], nil, options) add_reference(xml, 'RecordNumberRequested') add_customer_data(xml, options) @@ -126,9 +124,7 @@ def build_authorized_request(action, money, authorization, credit_card, options) xml.tag! 'TStream' do xml.tag! 'Transaction' do xml.tag! 'TranType', 'Credit' - if options[:allow_partial_auth] && (action == 'PreAuthCapture') - xml.tag! 'PartialAuth', 'Allow' - end + xml.tag! 'PartialAuth', 'Allow' if options[:allow_partial_auth] && (action == 'PreAuthCapture') xml.tag! 'TranCode', (@use_tokenization ? (action + 'ByRecordNo') : action) add_invoice(xml, invoice_no, ref_no, options) add_reference(xml, record_no) @@ -215,11 +211,11 @@ def add_credit_card(xml, credit_card, action) # handle with the validation error as it sees fit. # Track 2 requires having the STX and ETX stripped. Track 1 does not. # Max-length track 1s require having the STX and ETX stripped. Max is 79 bytes including LRC. - is_track_2 = credit_card.track_data[0] == ';' + is_track2 = credit_card.track_data[0] == ';' etx_index = credit_card.track_data.rindex('?') || credit_card.track_data.length is_max_track1 = etx_index >= 77 - if is_track_2 + if is_track2 xml.tag! 'Track2', credit_card.track_data[1...etx_index] elsif is_max_track1 xml.tag! 'Track1', credit_card.track_data[1...etx_index] @@ -234,7 +230,7 @@ def add_credit_card(xml, credit_card, action) xml.tag! 'CardType', CARD_CODES[credit_card.brand] if credit_card.brand include_cvv = !%w(Return PreAuthCapture).include?(action) && !credit_card.track_data.present? - xml.tag! 'CVVData', credit_card.verification_value if(include_cvv && credit_card.verification_value) + xml.tag! 'CVVData', credit_card.verification_value if include_cvv && credit_card.verification_value end def add_address(xml, options) @@ -298,7 +294,7 @@ def build_header } end - SUCCESS_CODES = [ 'Approved', 'Success' ] + SUCCESS_CODES = %w[Approved Success] def commit(action, request) response = parse(action, ssl_post(endpoint_url, build_soap_request(request), build_header)) @@ -306,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, - :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]]) + 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]] + ) 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 314f403c83c..c5b28a94990 100644 --- a/lib/active_merchant/billing/gateways/metrics_global.rb +++ b/lib/active_merchant/billing/gateways/metrics_global.rb @@ -31,12 +31,12 @@ class MetricsGlobalGateway < Gateway AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38 self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.metricsglobal.com' self.display_name = 'Metrics Global' - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) + CARD_CODE_ERRORS = %w(N S) + AVS_ERRORS = %w(A E N R W Z) AVS_REASON_CODES = %w(27 45) # Creates a new MetricsGlobalGateway @@ -99,7 +99,7 @@ def purchase(money, creditcard, options = {}) # * money -- The amount to be captured as an Integer value in cents. # * authorization -- The authorization returned from the previous authorize request. def capture(money, authorization, options = {}) - post = {:trans_id => authorization} + post = { trans_id: authorization } add_customer_data(post, options) commit('PRIOR_AUTH_CAPTURE', money, post) end @@ -110,7 +110,7 @@ def capture(money, authorization, options = {}) # # * authorization - The authorization returned from the previous authorize request. def void(authorization, options = {}) - post = {:trans_id => authorization} + post = { trans_id: authorization } add_duplicate_window(post) commit('VOID', nil, post) end @@ -135,9 +135,8 @@ def void(authorization, options = {}) def refund(money, identification, options = {}) requires!(options, :card_number) - post = { :trans_id => identification, - :card_num => options[:card_number] - } + post = { trans_id: identification, + card_num: options[:card_number] } post[:first_name] = options[:first_name] if options[:first_name] post[:last_name] = options[:last_name] if options[:last_name] @@ -176,12 +175,15 @@ def commit(action, money, parameters) # (TESTMODE) Successful Sale test_mode = test? || message =~ /TESTMODE/ - 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] + 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] ) end @@ -197,12 +199,12 @@ def parse(body) fields = split(body) results = { - :response_code => fields[RESPONSE_CODE].to_i, - :response_reason_code => fields[RESPONSE_REASON_CODE], - :response_reason_text => fields[RESPONSE_REASON_TEXT], - :avs_result_code => fields[AVS_RESULT_CODE], - :transaction_id => fields[TRANSACTION_ID], - :card_code => fields[CARD_CODE_RESPONSE_CODE] + response_code: fields[RESPONSE_CODE].to_i, + response_reason_code: fields[RESPONSE_REASON_CODE], + response_reason_text: fields[RESPONSE_REASON_TEXT], + avs_result_code: fields[AVS_RESULT_CODE], + transaction_id: fields[TRANSACTION_ID], + card_code: fields[CARD_CODE_RESPONSE_CODE] } results end @@ -243,21 +245,15 @@ def add_customer_data(post, options) post[:email_customer] = false end - if options.has_key? :customer - post[:cust_id] = options[:customer] - end + post[:cust_id] = options[:customer] if options.has_key? :customer - if options.has_key? :ip - post[:customer_ip] = options[:ip] - end + post[:customer_ip] = options[:ip] if options.has_key? :ip end # x_duplicate_window won't be sent by default, because sending it changes the response. # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent." def add_duplicate_window(post) - unless duplicate_window.nil? - post[:duplicate_window] = duplicate_window - end + post[:duplicate_window] = duplicate_window unless duplicate_window.nil? end def add_address(post, options) @@ -268,7 +264,7 @@ def add_address(post, options) post[:zip] = address[:zip].to_s post[:city] = address[:city].to_s post[:country] = address[:country].to_s - post[:state] = address[:state].blank? ? 'n/a' : address[:state] + post[:state] = address[:state].blank? ? 'n/a' : address[:state] end if address = options[:shipping_address] @@ -280,16 +276,14 @@ def add_address(post, options) post[:ship_to_zip] = address[:zip].to_s post[:ship_to_city] = address[:city].to_s post[:ship_to_country] = address[:country].to_s - post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] end end def message_from(results) if results[:response_code] == DECLINED return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code]) - if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) - return AVSResult.messages[results[:avs_result_code]] - end + return AVSResult.messages[results[:avs_result_code]] if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) end (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') diff --git a/lib/active_merchant/billing/gateways/micropayment.rb b/lib/active_merchant/billing/gateways/micropayment.rb index fc416311865..e6cf5f97df4 100644 --- a/lib/active_merchant/billing/gateways/micropayment.rb +++ b/lib/active_merchant/billing/gateways/micropayment.rb @@ -1,7 +1,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class MicropaymentGateway < Gateway - self.display_name = 'micropayment' self.homepage_url = 'https://www.micropayment.de/' @@ -10,14 +9,14 @@ class MicropaymentGateway < Gateway self.supported_countries = %w(DE) self.default_currency = 'EUR' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] - def initialize(options={}) + def initialize(options = {}) requires!(options, :access_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method, options) @@ -26,7 +25,7 @@ def purchase(amount, payment_method, options={}) commit('purchase', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method, options) @@ -35,27 +34,27 @@ def authorize(amount, payment_method, options={}) commit('authorize', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, amount, options) commit('capture', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit('void', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, amount, options) commit('refund', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(250, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -84,7 +83,7 @@ def add_invoice(post, money, options) post['params[title]'] = options[:description] if options[:description] end - def add_payment_method(post, payment_method, options={}) + def add_payment_method(post, payment_method, options = {}) post[:number] = payment_method.number post[:recurring] = 1 if options[:recurring] == true post[:cvc2] = payment_method.verification_value @@ -136,7 +135,7 @@ def commit(action, params) end def headers - { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } end def post_data(action, params) @@ -176,7 +175,7 @@ def split_authorization(authorization) def authorization_from(response, request_params) session_id = response['sessionId'] || request_params[:sessionId] - "#{session_id}|#{response["transactionId"]}" + "#{session_id}|#{response['transactionId']}" end end end diff --git a/lib/active_merchant/billing/gateways/migs.rb b/lib/active_merchant/billing/gateways/migs.rb index f689a41fc4f..f50b3d29de5 100644 --- a/lib/active_merchant/billing/gateways/migs.rb +++ b/lib/active_merchant/billing/gateways/migs.rb @@ -20,7 +20,7 @@ class MigsGateway < Gateway self.supported_countries = %w(AU AE BD BN EG HK ID JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN) # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.money_format = :cents self.currencies_without_fractions = %w(IDR) @@ -63,13 +63,14 @@ def purchase(money, creditcard, options = {}) add_creditcard(post, creditcard) add_standard_parameters('pay', post, options[:unique_id]) add_3ds(post, options) + add_tx_source(post, options) commit(post) end # MiGS works by merchants being either purchase only or authorize/capture # So authorize is the same as purchase when in authorize mode - alias_method :authorize, :purchase + alias authorize purchase # ==== Options # @@ -78,11 +79,12 @@ def purchase(money, creditcard, options = {}) def capture(money, authorization, options = {}) requires!(@options, :advanced_login, :advanced_password) - post = options.merge(:TransNo => authorization) + post = options.merge(TransNo: authorization) add_amount(post, money, options) add_advanced_user(post) add_standard_parameters('capture', post, options[:unique_id]) + add_tx_source(post, options) commit(post) end @@ -94,11 +96,12 @@ def capture(money, authorization, options = {}) def refund(money, authorization, options = {}) requires!(@options, :advanced_login, :advanced_password) - post = options.merge(:TransNo => authorization) + post = options.merge(TransNo: authorization) add_amount(post, money, options) add_advanced_user(post) add_standard_parameters('refund', post, options[:unique_id]) + add_tx_source(post, options) commit(post) end @@ -106,10 +109,11 @@ def refund(money, authorization, options = {}) def void(authorization, options = {}) requires!(@options, :advanced_login, :advanced_password) - post = options.merge(:TransNo => authorization) + post = options.merge(TransNo: authorization) add_advanced_user(post) add_standard_parameters('voidAuthorisation', post, options[:unique_id]) + add_tx_source(post, options) commit(post) end @@ -119,7 +123,7 @@ def credit(money, authorization, options = {}) refund(money, authorization, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -191,9 +195,7 @@ def purchase_offsite_response(data) response_hash = parse(data) expected_secure_hash = calculate_secure_hash(response_hash, @options[:secure_hash]) - unless response_hash[:SecureHash] == expected_secure_hash - raise SecurityError, 'Secure Hash mismatch, response may be tampered with' - end + raise SecurityError, 'Secure Hash mismatch, response may be tampered with' unless response_hash[:SecureHash] == expected_secure_hash response_object(response_hash) end @@ -241,6 +243,10 @@ def add_3ds(post, options) post['3DSstatus'] = options[:three_ds_status] if options[:three_ds_status] end + def add_tx_source(post, options) + post[:TxSource] = options[:tx_source] if options[:tx_source] + end + def add_creditcard(post, creditcard) post[:CardNum] = creditcard.number post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value? @@ -248,7 +254,7 @@ def add_creditcard(post, creditcard) end def add_creditcard_type(post, card_type) - post[:Gateway] = 'ssl' + post[:Gateway] = 'ssl' post[:card] = CARD_TYPES.detect { |ct| ct.am_code == card_type }.migs_long_code end @@ -275,12 +281,15 @@ 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, - :test => test?, - :authorization => response[:TransactionNo], - :fraud_review => fraud_review?(response), - :avs_result => { :code => avs_response_code }, - :cvv_result => cvv_result_code + 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 ) end @@ -294,11 +303,11 @@ def fraud_review?(response) def add_standard_parameters(action, post, unique_id = nil) post.merge!( - :Version => API_VERSION, - :Merchant => @options[:login], - :AccessCode => @options[:password], - :Command => action, - :MerchTxnRef => unique_id || generate_unique_id.slice(0, 40) + Version: API_VERSION, + Merchant: @options[:login], + AccessCode: @options[:password], + Command: action, + MerchTxnRef: unique_id || generate_unique_id.slice(0, 40) ) end diff --git a/lib/active_merchant/billing/gateways/migs/migs_codes.rb b/lib/active_merchant/billing/gateways/migs/migs_codes.rb index 9eae5f4bdfc..32929ed8abe 100644 --- a/lib/active_merchant/billing/gateways/migs/migs_codes.rb +++ b/lib/active_merchant/billing/gateways/migs/migs_codes.rb @@ -85,13 +85,13 @@ def initialize(am_code, migs_code, migs_long_code, name) # migs_code: Used in response for purchase/authorize/status # migs_long_code: Used to pre-select card for server_purchase_url # name: The nice display name - %w(american_express AE Amex American\ Express), - %w(diners_club DC Dinersclub Diners\ Club), - %w(jcb JC JCB JCB\ Card), - %w(maestro MS Maestro Maestro\ Card), - %w(master MC Mastercard MasterCard), - %w(na PL PrivateLabelCard Private\ Label\ Card), - %w(visa VC Visa Visa\ Card') + %w(american_express AE Amex American\ Express), + %w(diners_club DC Dinersclub Diners\ Club), + %w(jcb JC JCB JCB\ Card), + %w(maestro MS Maestro Maestro\ Card), + %w(master MC Mastercard MasterCard), + %w(na PL PrivateLabelCard Private\ Label\ Card), + %w(visa VC Visa Visa\ Card) ].map do |am_code, migs_code, migs_long_code, name| CreditCardType.new(am_code, migs_code, migs_long_code, name) end diff --git a/lib/active_merchant/billing/gateways/mit.rb b/lib/active_merchant/billing/gateways/mit.rb new file mode 100644 index 00000000000..785be9368da --- /dev/null +++ b/lib/active_merchant/billing/gateways/mit.rb @@ -0,0 +1,260 @@ +require 'json' +require 'openssl' +require 'digest' +require 'base64' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MitGateway < Gateway + self.live_url = 'https://wpy.mitec.com.mx/ModuloUtilWS/activeCDP.htm' + + self.supported_countries = ['MX'] + self.default_currency = 'MXN' + self.supported_cardtypes = %i[visa master] + + self.homepage_url = 'http://www.centrodepagos.com.mx/' + self.display_name = 'MIT Centro de pagos' + + self.money_format = :dollars + + def initialize(options = {}) + requires!(options, :commerce_id, :user, :api_key, :key_session) + super + end + + def purchase(money, payment, options = {}) + MultiResponse.run do |r| + r.process { authorize(money, payment, options) } + r.process { capture(money, r.authorization, options) } + end + end + + def cipher_key + @options[:key_session] + end + + def decrypt(val, keyinhex) + # Splits the first 16 bytes (the IV bytes) in array format + unpacked = val.unpack('m') + iv_base64 = unpacked[0].bytes.slice(0, 16) + # Splits the second bytes (the encrypted text bytes) these would be the + # original message + full_data = unpacked[0].bytes.slice(16, unpacked[0].bytes.length) + # Creates the engine + engine = OpenSSL::Cipher::AES128.new(:CBC) + # Set engine as decrypt mode + engine.decrypt + # Converts the key from hex to bytes + engine.key = [keyinhex].pack('H*') + # Converts the ivBase64 array into bytes + engine.iv = iv_base64.pack('c*') + # Decrypts the texts and returns the original string + engine.update(full_data.pack('c*')) + engine.final + end + + def encrypt(val, keyinhex) + # Creates the engine motor + engine = OpenSSL::Cipher::AES128.new(:CBC) + # Set engine as encrypt mode + engine.encrypt + # Converts the key from hex to bytes + engine.key = [keyinhex].pack('H*') + # Generates a random iv with this settings + iv_rand = engine.random_iv + # Packs IV as a Base64 string + iv_base64 = [iv_rand].pack('m') + # Converts the packed key into bytes + unpacked = iv_base64.unpack('m') + iv = unpacked[0] + # Sets the IV into the engine + engine.iv = iv + # Encrypts the texts and stores the bytes + encrypted_bytes = engine.update(val) + engine.final + # Concatenates the (a) IV bytes and (b) the encrypted bytes then returns a + # base64 representation + [iv << encrypted_bytes].pack('m') + end + + def authorize(money, payment, options = {}) + post = { + operation: 'Authorize', + commerce_id: @options[:commerce_id], + user: @options[:user], + apikey: @options[:api_key], + testMode: (test? ? 'YES' : 'NO') + } + add_invoice(post, money, options) + # Payments contains the card information + add_payment(post, payment) + add_customer_data(post, options) + post[:key_session] = @options[:key_session] + + post_to_json = post.to_json + post_to_json_encrypt = encrypt(post_to_json, @options[:key_session]) + + final_post = '' + post_to_json_encrypt + '' + @options[:user] + '' + json_post = final_post + commit('sale', json_post) + end + + def capture(money, authorization, options = {}) + post = { + operation: 'Capture', + commerce_id: @options[:commerce_id], + user: @options[:user], + apikey: @options[:api_key], + testMode: (test? ? 'YES' : 'NO'), + transaction_id: authorization, + amount: amount(money) + } + post[:key_session] = @options[:key_session] + + post_to_json = post.to_json + post_to_json_encrypt = encrypt(post_to_json, @options[:key_session]) + + final_post = '' + post_to_json_encrypt + '' + @options[:user] + '' + json_post = final_post + commit('capture', json_post) + end + + def refund(money, authorization, options = {}) + post = { + operation: 'Refund', + commerce_id: @options[:commerce_id], + user: @options[:user], + apikey: @options[:api_key], + testMode: (test? ? 'YES' : 'NO'), + transaction_id: authorization, + auth: authorization, + amount: amount(money) + } + post[:key_session] = @options[:key_session] + + post_to_json = post.to_json + post_to_json_encrypt = encrypt(post_to_json, @options[:key_session]) + + final_post = '' + post_to_json_encrypt + '' + @options[:user] + '' + json_post = final_post + commit('refund', json_post) + end + + 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]' + auth_json['cvv'] = '[FILTERED]' + auth_json['apikey'] = '[FILTERED]' + auth_json['key_session'] = '[FILTERED]' + auth_to_json = auth_json.to_json + auth_tagged = '' + auth_to_json + '' + ret_transcript = ret_transcript.gsub(/(.*?)<\/authorization>/, auth_tagged) + end + + 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]' + cap_json['key_session'] = '[FILTERED]' + cap_to_json = cap_json.to_json + cap_tagged = '' + cap_to_json + '' + ret_transcript = ret_transcript.gsub(/(.*?)<\/capture>/, cap_tagged) + end + + 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]' + ref_json['key_session'] = '[FILTERED]' + ref_to_json = ref_json.to_json + ref_tagged = '' + ref_to_json + '' + ret_transcript = ret_transcript.gsub(/(.*?)<\/refund>/, ref_tagged) + end + + 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 + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] || 'nadie@mit.test' + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:reference] = options[:order_id] + post[:transaction_id] = options[:order_id] + end + + def add_payment(post, payment) + post[:installments] = 1 + post[:card] = payment.number + post[:expmonth] = payment.month + post[:expyear] = payment.year + post[:cvv] = payment.verification_value + post[:name_client] = [payment.first_name, payment.last_name].join(' ') + end + + def commit(action, parameters) + raw_response = ssl_post(live_url, parameters, { 'Content-type' => 'text/plain' }) + response = JSON.parse(decrypt(raw_response, @options[:key_session])) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['some_avs_response_key']), + cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response['response'] == 'approved' + end + + def message_from(response) + response['response'] + end + + def authorization_from(response) + response['reference'] + end + + def post_data(action, parameters = {}) + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def error_code_from(response) + response['message'].split(' -- ', 2)[0] unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/lib/active_merchant/billing/gateways/modern_payments_cim.rb index b1626434d38..9a8f760ec8e 100644 --- a/lib/active_merchant/billing/gateways/modern_payments_cim.rb +++ b/lib/active_merchant/billing/gateways/modern_payments_cim.rb @@ -8,7 +8,7 @@ class ModernPaymentsCimGateway < Gateway #:nodoc: TEST_XMLNS = 'https://secure.modpay.com/netservices/test/' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.modpay.com' self.display_name = 'Modern Payments' @@ -17,8 +17,8 @@ class ModernPaymentsCimGateway < Gateway #:nodoc: ERROR_MESSAGE = 'Transaction error' PAYMENT_METHOD = { - :check => 1, - :credit_card => 2 + check: 1, + credit_card: 2 } def initialize(options = {}) @@ -81,7 +81,7 @@ def add_customer_id(post, customer_id) end def add_customer_data(post, options) - post[:acctNum] = options[:customer] + post[:acctNum] = options[:customer] end def add_address(post, options) @@ -111,13 +111,12 @@ def add_credit_card(post, credit_card) end def build_request(action, params) - xml = Builder::XmlMarkup.new :indent => 2 + 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,16 +145,23 @@ 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, - :test => test?, - :authorization => authorization_from(action, response), - :avs_result => { :code => response[:avs_code] } + Response.new( + successful?(action, response), + message_from(action, response), + response, + test: test?, + authorization: authorization_from(action, response), + avs_result: { code: response[:avs_code] } ) end diff --git a/lib/active_merchant/billing/gateways/moka.rb b/lib/active_merchant/billing/gateways/moka.rb new file mode 100644 index 00000000000..c86b37299df --- /dev/null +++ b/lib/active_merchant/billing/gateways/moka.rb @@ -0,0 +1,290 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MokaGateway < Gateway + self.test_url = 'https://service.refmoka.com' + self.live_url = 'https://service.moka.com' + + self.supported_countries = %w[GB TR US] + self.default_currency = 'TRY' + self.money_format = :dollars + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'http://developer.moka.com/' + self.display_name = 'Moka' + + ERROR_CODE_MAPPING = { + '000' => 'General error', + '001' => '3D Not authenticated', + '002' => 'Limit is insufficient', + '003' => 'Credit card number format is wrong', + '004' => 'General decline', + '005' => 'This process is invalid for the card owner', + '006' => 'Expiration date is wrong', + '007' => 'Invalid transaction', + '008' => 'Connection with the bank not established', + '009' => 'Undefined error code', + '010' => 'Bank SSL error', + '011' => 'Call the bank for the manual authentication', + '012' => 'Card info is wrong - Kart Number or CVV2', + '013' => '3D secure is not supported other than Visa MC cards', + '014' => 'Invalid account number', + '015' => 'CVV is wrong', + '016' => 'Authentication process is not present', + '017' => 'System error', + '018' => 'Stolen card', + '019' => 'Lost card', + '020' => 'Card with limited properties', + '021' => 'Timeout', + '022' => 'Invalid merchant', + '023' => 'False authentication', + '024' => '3D authorization is successful but the process cannot be completed', + '025' => '3D authorization failure', + '026' => 'Either the issuer bank or the card is not enrolled to the 3D process', + '027' => 'The bank did not allow the process', + '028' => 'Fraud suspect', + '029' => 'The card is closed to the e-commerce operations' + } + + def initialize(options = {}) + requires!(options, :dealer_code, :username, :password) + super + end + + def purchase(money, payment, options = {}) + post = {} + post[:PaymentDealerRequest] = {} + options[:pre_auth] = 0 + add_auth_purchase(post, money, payment, options) + add_3ds_data(post, options) if options[:execute_threed] + + action = options[:execute_threed] ? 'three_ds_purchase' : 'purchase' + commit(action, post) + end + + def authorize(money, payment, options = {}) + post = {} + post[:PaymentDealerRequest] = {} + options[:pre_auth] = 1 + add_auth_purchase(post, money, payment, options) + add_3ds_data(post, options) if options[:execute_threed] + + action = options[:execute_threed] ? 'three_ds_authorize' : 'authorize' + commit(action, post) + end + + def capture(money, authorization, options = {}) + post = {} + post[:PaymentDealerRequest] = {} + add_payment_dealer_authentication(post) + add_transaction_reference(post, authorization) + add_invoice(post, money, options) + + commit('capture', post) + end + + def refund(money, authorization, options = {}) + post = {} + post[:PaymentDealerRequest] = {} + add_payment_dealer_authentication(post) + add_transaction_reference(post, authorization) + add_void_refund_reason(post) + add_amount(post, money) + + commit('refund', post) + end + + def void(authorization, options = {}) + post = {} + post[:PaymentDealerRequest] = {} + add_payment_dealer_authentication(post) + add_transaction_reference(post, authorization) + add_void_refund_reason(post) + + commit('void', post) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(("CardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("CvcNumber\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("DealerCode\\?":\\?"?)[^"?]*)i, '\1[FILTERED]'). + gsub(%r(("Username\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("Password\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("CheckKey\\?":\\?")[^"]*)i, '\1[FILTERED]') + end + + private + + def add_auth_purchase(post, money, payment, options) + add_payment_dealer_authentication(post) + add_invoice(post, money, options) + add_payment(post, payment) + add_additional_auth_purchase_data(post, options) + add_additional_transaction_data(post, options) + add_buyer_information(post, payment, options) + add_basket_product(post, options[:basket_product]) if options[:basket_product] + end + + def add_3ds_data(post, options) + post[:PaymentDealerRequest][:ReturnHash] = 1 + post[:PaymentDealerRequest][:RedirectUrl] = options[:redirect_url] || '' + post[:PaymentDealerRequest][:RedirectType] = options[:redirect_type] || 0 + end + + def add_payment_dealer_authentication(post) + post[:PaymentDealerAuthentication] = { + DealerCode: @options[:dealer_code], + Username: @options[:username], + Password: @options[:password], + CheckKey: check_key + } + end + + def check_key + str = "#{@options[:dealer_code]}MK#{@options[:username]}PD#{@options[:password]}" + Digest::SHA256.hexdigest(str) + end + + def add_invoice(post, money, options) + post[:PaymentDealerRequest][:Amount] = amount(money) || 0 + post[:PaymentDealerRequest][:Currency] = options[:currency] || 'TL' + end + + def add_payment(post, card) + post[:PaymentDealerRequest][:CardHolderFullName] = card.name + post[:PaymentDealerRequest][:CardNumber] = card.number + post[:PaymentDealerRequest][:ExpMonth] = card.month.to_s.rjust(2, '0') + post[:PaymentDealerRequest][:ExpYear] = card.year + post[:PaymentDealerRequest][:CvcNumber] = card.verification_value || '' + end + + def add_amount(post, money) + post[:PaymentDealerRequest][:Amount] = amount(money) || 0 + end + + def add_additional_auth_purchase_data(post, options) + post[:PaymentDealerRequest][:IsPreAuth] = options[:pre_auth] + post[:PaymentDealerRequest][:Description] = options[:description] if options[:description] + post[:PaymentDealerRequest][:InstallmentNumber] = options[:installment_number].to_i if options[:installment_number] + post[:SubMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name] + post[:IsPoolPayment] = options[:is_pool_payment] || 0 + end + + def add_buyer_information(post, card, options) + obj = {} + + obj[:BuyerFullName] = card.name || '' + obj[:BuyerEmail] = options[:email] if options[:email] + obj[:BuyerAddress] = options[:billing_address][:address1] if options[:billing_address] + obj[:BuyerGsmNumber] = options[:billing_address][:phone] if options[:billing_address] + + post[:PaymentDealerRequest][:BuyerInformation] = obj + end + + def add_basket_product(post, basket_options) + basket = [] + + basket_options.each do |product| + obj = {} + obj[:ProductId] = product[:product_id] if product[:product_id] + obj[:ProductCode] = product[:product_code] if product[:product_code] + obj[:UnitPrice] = amount(product[:unit_price]) if product[:unit_price] + obj[:Quantity] = product[:quantity] if product[:quantity] + basket << obj + end + + post[:PaymentDealerRequest][:BasketProduct] = basket + end + + def add_additional_transaction_data(post, options) + post[:PaymentDealerRequest][:ClientIP] = options[:ip] if options[:ip] + post[:PaymentDealerRequest][:OtherTrxCode] = options[:order_id] if options[:order_id] + end + + def add_transaction_reference(post, authorization) + post[:PaymentDealerRequest][:VirtualPosOrderId] = authorization + end + + def add_void_refund_reason(post) + post[:PaymentDealerRequest][:VoidRefundReason] = 2 + end + + def commit(action, parameters) + response = parse(ssl_post(url(action), post_data(parameters), request_headers)) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def url(action) + host = (test? ? test_url : live_url) + endpoint = endpoint(action) + + "#{host}/PaymentDealer/#{endpoint}" + end + + def endpoint(action) + case action + when 'three_ds_authorize', 'three_ds_purchase' + 'DoDirectPaymentThreeD' + when 'purchase', 'authorize' + 'DoDirectPayment' + when 'capture' + 'DoCapture' + when 'void' + 'DoVoid' + when 'refund' + 'DoCreateRefundRequest' + end + end + + def request_headers + { 'Content-Type' => 'application/json' } + end + + def post_data(parameters = {}) + JSON.generate(parameters) + end + + def parse(body) + JSON.parse(body) + end + + def success_from(response) + return response.dig('Data', 'IsSuccessful') if response.dig('Data', 'IsSuccessful').to_s.present? + + response['ResultCode']&.casecmp('success') == 0 + end + + def message_from(response) + response.dig('Data', 'ResultMessage').presence || response['ResultCode'] + end + + def authorization_from(response) + response.dig('Data', 'VirtualPosOrderId') + end + + def error_code_from(response) + codes = [response['ResultCode'], response.dig('Data', 'ResultCode')].flatten + codes.reject! { |code| code.blank? || code.casecmp('success').zero? } + codes.map { |code| ERROR_CODE_MAPPING[code] || code }.join(', ') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/monei.rb b/lib/active_merchant/billing/gateways/monei.rb index a5fce7eb0f0..c7e1a5b9b5a 100755 --- a/lib/active_merchant/billing/gateways/monei.rb +++ b/lib/active_merchant/billing/gateways/monei.rb @@ -5,39 +5,36 @@ module Billing #:nodoc: # # == Monei gateway # This class implements Monei gateway for Active Merchant. For more information about Monei - # gateway please go to http://www.monei.net + # gateway please go to http://www.monei.com # # === Setup - # In order to set-up the gateway you need four paramaters: sender_id, channel_id, login and pwd. + # In order to set-up the gateway you need only one paramater: the api_key # Request that data to Monei. class MoneiGateway < Gateway - self.test_url = 'https://test.monei-api.net/payment/ctpe' - self.live_url = 'https://monei-api.net/payment/ctpe' + self.live_url = self.test_url = 'https://api.monei.com/v1/payments' - self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CA', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FO', 'FR', 'GB', 'GI', 'GR', 'HU', 'IE', 'IL', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'TR', 'US', 'VA'] + self.supported_countries = %w[AD AT BE BG CA CH CY CZ DE DK EE ES FI FO FR GB GI GR HU IE IL IS IT LI LT LU LV MT NL NO PL PT RO SE SI SK TR US VA] self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :maestro, :jcb, :american_express] + self.money_format = :cents + self.supported_cardtypes = %i[visa master maestro jcb american_express] - self.homepage_url = 'http://www.monei.net/' - self.display_name = 'Monei' + self.homepage_url = 'https://monei.com/' + self.display_name = 'MONEI' # Constructor # # options - Hash containing the gateway credentials, ALL MANDATORY - # :sender_id Sender ID - # :channel_id Channel ID - # :login User login - # :pwd User password + # :api_key Account's API KEY # - def initialize(options={}) - requires!(options, :sender_id, :channel_id, :login, :pwd) + def initialize(options = {}) + requires!(options, :api_key) super end # Public: Performs purchase operation # # money - Amount of purchase - # credit_card - Credit card + # payment_method - Credit card # options - Hash containing purchase options # :order_id Merchant created id for the purchase # :billing_address Hash with billing address information @@ -45,14 +42,14 @@ def initialize(options={}) # :currency Sale currency to override money object or default (optional) # # Returns Active Merchant response object - def purchase(money, credit_card, options={}) - execute_new_order(:purchase, money, credit_card, options) + def purchase(money, payment_method, options = {}) + execute_new_order(:purchase, money, payment_method, options) end # Public: Performs authorization operation # # money - Amount to authorize - # credit_card - Credit card + # payment_method - Credit card # options - Hash containing authorization options # :order_id Merchant created id for the authorization # :billing_address Hash with billing address information @@ -60,8 +57,8 @@ def purchase(money, credit_card, options={}) # :currency Sale currency to override money object or default (optional) # # Returns Active Merchant response object - def authorize(money, credit_card, options={}) - execute_new_order(:authorize, money, credit_card, options) + def authorize(money, payment_method, options = {}) + execute_new_order(:authorize, money, payment_method, options) end # Public: Performs capture operation on previous authorization @@ -76,7 +73,7 @@ def authorize(money, credit_card, options={}) # Note: you should pass either order_id or description # # Returns Active Merchant response object - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) execute_dependant(:capture, money, authorization, options) end @@ -92,7 +89,7 @@ def capture(money, authorization, options={}) # Note: you should pass either order_id or description # # Returns Active Merchant response object - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) execute_dependant(:refund, money, authorization, options) end @@ -103,13 +100,13 @@ def refund(money, authorization, options={}) # :order_id Merchant created id for the authorization (optional) # # Returns Active Merchant response object - def void(authorization, options={}) + def void(authorization, options = {}) execute_dependant(:void, nil, authorization, options) end # Public: Verifies credit card. Does this by doing a authorization of 1.00 Euro and then voiding it. # - # credit_card - Credit card + # payment_method - Credit card # options - Hash containing authorization options # :order_id Merchant created id for the authorization # :billing_address Hash with billing address information @@ -117,189 +114,309 @@ def void(authorization, options={}) # :currency Sale currency to override money object or default (optional) # # Returns Active Merchant response object of Authorization operation - def verify(credit_card, options={}) + def verify(payment_method, options = {}) MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } + r.process { authorize(100, payment_method, options) } r.process(:ignore_result) { void(r.authorization, options) } end end + def store(payment_method, options = {}) + execute_new_order(:store, 0, payment_method, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: )\w+), '\1[FILTERED]'). + gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cavv\\?":\\?")[^"]*)i, '\1[FILTERED]') + end + private # Private: Execute purchase or authorize operation - def execute_new_order(action, money, credit_card, options) - request = build_request do |xml| - add_identification_new_order(xml, options) - add_payment(xml, action, money, options) - add_account(xml, credit_card) - add_customer(xml, credit_card, options) - end - - commit(request) + def execute_new_order(action, money, payment_method, options) + request = build_request + add_identification_new_order(request, options) + add_transaction(request, action, money, options) + add_payment(request, payment_method) + add_customer(request, payment_method, options) + add_3ds_authenticated_data(request, options) + add_browser_info(request, options) + commit(request, action, options) end # Private: Execute operation that depends on authorization code from previous purchase or authorize operation def execute_dependant(action, money, authorization, options) - request = build_request do |xml| - add_identification_authorization(xml, authorization, options) - add_payment(xml, action, money, options) - end + request = build_request + + add_identification_authorization(request, authorization, options) + add_transaction(request, action, money, options) - commit(request) + commit(request, action, options) end - # Private: Build XML wrapping code yielding to code to fill the transaction information + # Private: Build request object def build_request - builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| - xml.Request(:version => '1.0') do - xml.Header { xml.Security(:sender => @options[:sender_id]) } - xml.Transaction(:mode => test? ? 'CONNECTOR_TEST' : 'LIVE', :response => 'SYNC', :channel => @options[:channel_id]) do - xml.User(:login => @options[:login], :pwd => @options[:pwd]) - yield xml - end - end - end - builder.to_xml + request = {} + request[:livemode] = test? ? 'false' : 'true' + request end - # Private: Add identification part to XML for new orders - def add_identification_new_order(xml, options) + # Private: Add identification part to request for new orders + def add_identification_new_order(request, options) requires!(options, :order_id) - xml.Identification do - xml.TransactionID options[:order_id] + request[:orderId] = options[:order_id] + end + + # Private: Add identification part to request for orders that depend on authorization from previous operation + def add_identification_authorization(request, authorization, options) + options[:paymentId] = authorization + request[:orderId] = options[:order_id] if options[:order_id] + end + + # Private: Add payment part to request + def add_transaction(request, action, money, options) + request[:transactionType] = translate_payment_code(action) + request[:description] = options[:description] || options[:order_id] + unless money.nil? + request[:amount] = amount(money).to_i + request[:currency] = options[:currency] || currency(money) end end - # Private: Add identification part to XML for orders that depend on authorization from previous operation - def add_identification_authorization(xml, authorization, options) - xml.Identification do - xml.ReferenceID authorization - xml.TransactionID options[:order_id] + # Private: Add payment method to request + def add_payment(request, payment_method) + if payment_method.is_a? String + request[:paymentToken] = payment_method + else + request[:paymentMethod] = {} + request[:paymentMethod][:card] = {} + request[:paymentMethod][:card][:number] = payment_method.number + request[:paymentMethod][:card][:expMonth] = format(payment_method.month, :two_digits) + request[:paymentMethod][:card][:expYear] = format(payment_method.year, :two_digits) + request[:paymentMethod][:card][:cvc] = payment_method.verification_value.to_s + request[:paymentMethod][:card][:cardholderName] = payment_method.name end end - # Private: Add payment part to XML - def add_payment(xml, action, money, options) - code = tanslate_payment_code(action) + # Private: Add customer part to request + def add_customer(request, payment_method, options) + address = options[:billing_address] || options[:address] + + request[:customer] = {} + request[:customer][:email] = options[:email] || 'support@monei.net' + + if address + request[:customer][:name] = address[:name].to_s if address[:name] + + request[:billingDetails] = {} + request[:billingDetails][:email] = options[:email] if options[:email] + request[:billingDetails][:name] = address[:name] if address[:name] + request[:billingDetails][:company] = address[:company] if address[:company] + request[:billingDetails][:phone] = address[:phone] if address[:phone] + request[:billingDetails][:address] = {} + request[:billingDetails][:address][:line1] = address[:address1] if address[:address1] + request[:billingDetails][:address][:line2] = address[:address2] if address[:address2] + request[:billingDetails][:address][:city] = address[:city] if address[:city] + request[:billingDetails][:address][:state] = address[:state] if address[:state].present? + request[:billingDetails][:address][:zip] = address[:zip].to_s if address[:zip] + request[:billingDetails][:address][:country] = address[:country] if address[:country] + end + + request[:sessionDetails] = {} + request[:sessionDetails][:ip] = options[:ip] if options[:ip] + end - xml.Payment(:code => code) do - xml.Presentation do - xml.Amount amount(money) - xml.Currency options[:currency] || currency(money) - xml.Usage options[:description] || options[:order_id] - end unless money.nil? + # Private : Convert ECI to ResultIndicator + # Possible ECI values: + # 02 or 05 - Fully Authenticated Transaction + # 00 or 07 - Non 3D Secure Transaction + # Possible ResultIndicator values: + # 01 = MASTER_3D_ATTEMPT + # 02 = MASTER_3D_SUCCESS + # 05 = VISA_3D_SUCCESS + # 06 = VISA_3D_ATTEMPT + # 07 = DEFAULT_E_COMMERCE + def eci_to_result_indicator(eci) + case eci + when '02', '05' + return eci + else + return '07' end end - # Private: Add account part to XML - def add_account(xml, credit_card) - xml.Account do - xml.Holder credit_card.name - xml.Number credit_card.number - xml.Brand credit_card.brand.upcase - xml.Expiry(:month => credit_card.month, :year => credit_card.year) - xml.Verification credit_card.verification_value + # Private: add the already validated 3DSecure info to request + def add_3ds_authenticated_data(request, options) + if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid] + add_3ds1_authenticated_data(request, options) + elsif options[:three_d_secure] + add_3ds2_authenticated_data(request, options) end end - # Private: Add customer part to XML - def add_customer(xml, credit_card, options) - requires!(options, :billing_address) - address = options[:billing_address] - xml.Customer do - xml.Name do - xml.Given credit_card.first_name - xml.Family credit_card.last_name - end - xml.Address do - xml.Street address[:address1].to_s - xml.Zip address[:zip].to_s - xml.City address[:city].to_s - xml.State address[:state].to_s if address.has_key? :state - xml.Country address[:country].to_s - end - xml.Contact do - xml.Email options[:email] || 'noemail@monei.net' - xml.Ip options[:ip] || '0.0.0.0' - end + def add_3ds1_authenticated_data(request, options) + three_d_secure_options = options[:three_d_secure] + request[:paymentMethod][:card][:auth] = { + cavv: three_d_secure_options[:cavv], + cavvAlgorithm: three_d_secure_options[:cavv_algorithm], + eci: three_d_secure_options[:eci], + xid: three_d_secure_options[:xid], + directoryResponse: three_d_secure_options[:enrolled], + authenticationResponse: three_d_secure_options[:authentication_response_status] + } + end + + def add_3ds2_authenticated_data(request, options) + three_d_secure_options = options[:three_d_secure] + # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes. + if three_d_secure_options[:authentication_response_status].nil? + authentication_response = three_d_secure_options[:directory_response_status] + else + authentication_response = three_d_secure_options[:authentication_response_status] end + request[:paymentMethod][:card][:auth] = { + threeDSVersion: three_d_secure_options[:version], + eci: three_d_secure_options[:eci], + cavv: three_d_secure_options[:cavv], + dsTransID: three_d_secure_options[:ds_transaction_id], + directoryResponse: three_d_secure_options[:directory_response_status], + authenticationResponse: authentication_response + } + end + + def add_browser_info(request, options) + request[:sessionDetails][:ip] = options[:ip] if options[:ip] + request[:sessionDetails][:userAgent] = options[:user_agent] if options[:user_agent] + request[:sessionDetails][:lang] = options[:lang] if options[:lang] end - # Private: Parse XML response from Monei servers + # Private: Parse JSON response from Monei servers def parse(body) - xml = Nokogiri::XML(body) + JSON.parse(body) + end + + def json_error(raw_response) + msg = 'Invalid response received from the MONEI API. Please contact support@monei.net if you continue to receive this message.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" { - :unique_id => xml.xpath('//Response/Transaction/Identification/UniqueID').text, - :status => translate_status_code(xml.xpath('//Response/Transaction/Processing/Status/@code').text), - :reason => translate_status_code(xml.xpath('//Response/Transaction/Processing/Reason/@code').text), - :message => xml.xpath('//Response/Transaction/Processing/Return').text + 'status' => 'error', + 'message' => msg } end - # Private: Send XML transaction to Monei servers and create AM response - def commit(xml) + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def api_request(url, parameters, options = {}) + raw_response = response = nil + begin + raw_response = ssl_post(url, post_data(parameters), options) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + response + end + + # Private: Send transaction to Monei servers and create AM response + def commit(request, action, options) url = (test? ? test_url : live_url) + endpoint = translate_action_endpoint(action, options) + headers = { + 'Content-Type': 'application/json;charset=UTF-8', + 'Authorization': @options[:api_key], + 'User-Agent': 'MONEI/Shopify/0.1.0' + } - response = parse(ssl_post(url, post_data(xml), 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8')) + response = api_request(url + endpoint, params(request, action), headers) + success = success_from(response) Response.new( - success_from(response), - message_from(response), + success, + message_from(response, success), response, - authorization: authorization_from(response), + authorization: authorization_from(response, action), test: test?, - error_code: error_code_from(response) + error_code: error_code_from(response, success) ) end # Private: Decide success from servers response def success_from(response) - response[:status] == :success || response[:status] == :new + %w[ + SUCCEEDED + AUTHORIZED + REFUNDED + PARTIALLY_REFUNDED + CANCELED + ].include? response['status'] end # Private: Get message from servers response - def message_from(response) - response[:message] + def message_from(response, success) + success ? 'Transaction approved' : response.fetch('statusMessage', response.fetch('message', 'No error details')) end # Private: Get error code from servers response - def error_code_from(response) - success_from(response) ? nil : STANDARD_ERROR_CODE[:card_declined] + def error_code_from(response, success) + success ? nil : STANDARD_ERROR_CODE[:card_declined] end # Private: Get authorization code from servers response - def authorization_from(response) - response[:unique_id] + def authorization_from(response, action) + case action + when :store + return response['paymentToken'] + else + return response['id'] + end end # Private: Encode POST parameters - def post_data(xml) - "load=#{CGI.escape(xml)}" + def post_data(params) + params.clone.to_json end - # Private: Translate Monei status code to native ruby symbols - def translate_status_code(code) - { - '00' => :success, - '40' => :neutral, - '59' => :waiting_bank, - '60' => :rejected_bank, - '64' => :waiting_risk, - '65' => :rejected_risk, - '70' => :rejected_validation, - '80' => :waiting, - '90' => :new - }[code] + # Private: generate request params depending on action + def params(request, action) + request[:generatePaymentToken] = true if action == :store + request end # Private: Translate AM operations to Monei operations codes - def tanslate_payment_code(action) + def translate_payment_code(action) + { + purchase: 'SALE', + store: 'SALE', + authorize: 'AUTH', + capture: 'CAPTURE', + refund: 'REFUND', + void: 'CANCEL' + }[action] + end + + # Private: Translate AM operations to Monei endpoints + def translate_action_endpoint(action, options) { - :purchase => 'CC.DB', - :authorize => 'CC.PA', - :capture => 'CC.CP', - :refund => 'CC.RF', - :void => 'CC.RV' + purchase: '', + store: '', + authorize: '', + capture: "/#{options[:paymentId]}/capture", + refund: "/#{options[:paymentId]}/refund", + void: "/#{options[:paymentId]}/cancel" }[action] end end diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb index f17f0e28acd..53629e02195 100644 --- a/lib/active_merchant/billing/gateways/moneris.rb +++ b/lib/active_merchant/billing/gateways/moneris.rb @@ -9,11 +9,13 @@ 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' self.supported_countries = ['CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover] + self.supported_cardtypes = %i[visa master american_express diners_club discover] self.homepage_url = 'http://www.moneris.com/' self.display_name = 'Moneris' @@ -47,18 +49,19 @@ 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_cof(post, options) - action = if post[:cavv] + add_external_mpi_fields(post, options) + add_stored_credential(post, options) + action = if post[:cavv] || options[:three_d_secure] 'cavv_preauth' elsif post[:data_key].blank? 'preauth' else 'res_preauth_cc' end - commit(action, post) + commit(action, post, options) end # This action verifies funding on a customer's card and readies them for @@ -70,18 +73,19 @@ 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_cof(post, options) - action = if post[:cavv] + add_external_mpi_fields(post, options) + add_stored_credential(post, options) + action = if post[:cavv] || options[:three_d_secure] 'cavv_purchase' elsif post[:data_key].blank? 'purchase' else 'res_purchase_cc' end - commit(action, post) + commit(action, post, options) end # This method retrieves locked funds from a customer's account (from a @@ -92,7 +96,7 @@ def purchase(money, creditcard_or_datakey, options = {}) # gateways the two numbers are concatenated together with a ; separator as # the authorization number returned by authorization def capture(money, authorization, options = {}) - commit 'completion', crediting_params(authorization, :comp_amount => amount(money)) + commit 'completion', crediting_params(authorization, comp_amount: amount(money)) end # Voiding requires the original transaction ID and order ID of some open @@ -130,22 +134,43 @@ def credit(money, authorization, options = {}) end def refund(money, authorization, options = {}) - commit 'refund', crediting_params(authorization, :amount => amount(money)) + commit 'refund', crediting_params(authorization, amount: amount(money)) end - def verify(credit_card, options={}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } - end + def verify(credit_card, options = {}) + requires!(options, :order_id) + post = {} + add_payment_source(post, credit_card, options) + post[:order_id] = options[:order_id] + post[:address] = options[:billing_address] || options[:address] + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + add_stored_credential(post, options) + action = if post[:data_key].blank? + 'card_verification' + else + 'res_card_verification_cc' + end + commit(action, post) end + # When passing a :duration option (time in seconds) you can create a + # temporary vault record to avoid incurring Moneris vault storage fees + # + # https://developer.moneris.com/Documentation/NA/E-Commerce%20Solutions/API/Vault#vaulttokenadd def store(credit_card, options = {}) post = {} post[:pan] = credit_card.number post[:expdate] = expdate(credit_card) + post[:address] = options[:billing_address] || options[:address] post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - commit('res_add_cc', post) + add_stored_credential(post, options) + + if options[:duration] + post[:duration] = options[:duration] + commit('res_temp_add', post) + else + commit('res_add_cc', post) + end end def unstore(data_key, options = {}) @@ -182,6 +207,21 @@ def expdate(creditcard) sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month) end + def add_external_mpi_fields(post, options) + # See these pages: + # https://developer.moneris.com/livedemo/3ds2/cavv_purchase/tool/php + # https://developer.moneris.com/livedemo/3ds2/cavv_preauth/guide/php + return unless options[:three_d_secure] + + three_d_secure_options = options[:three_d_secure] + + post[:threeds_version] = three_d_secure_options[:version] + post[:crypt_type] = three_d_secure_options[:eci] + post[:cavv] = three_d_secure_options[:cavv] + post[:threeds_server_trans_id] = three_d_secure_options[:three_ds_server_trans_id] + post[:ds_trans_id] = three_d_secure_options[:ds_transaction_id] + end + def add_payment_source(post, payment_method, options) if payment_method.is_a?(String) post[:data_key] = payment_method @@ -208,12 +248,55 @@ def add_cof(post, options) post[:payment_information] = options[:payment_information] if options[:payment_information] end + def add_stored_credential(post, options) + add_cof(post, options) + # if any of :issuer_id, :payment_information, or :payment_indicator is not passed, + # then check for :stored credential options + return unless (stored_credential = options[:stored_credential]) && !cof_details_present?(options) + + if stored_credential[:initial_transaction] + add_stored_credential_initial(post, options) + else + add_stored_credential_used(post, options) + end + end + + def add_stored_credential_initial(post, options) + post[:payment_information] ||= '0' + post[:issuer_id] ||= '' + if options[:stored_credential][:initiator] == 'merchant' + case options[:stored_credential][:reason_type] + when 'recurring', 'installment' + post[:payment_indicator] ||= 'R' + when 'unscheduled' + post[:payment_indicator] ||= 'C' + end + else + post[:payment_indicator] ||= 'C' + end + end + + def add_stored_credential_used(post, options) + post[:payment_information] ||= '2' + post[:issuer_id] = options[:stored_credential][:network_transaction_id] if options[:issuer_id].blank? + if options[:stored_credential][:initiator] == 'merchant' + case options[:stored_credential][:reason_type] + when 'recurring', 'installment' + post[:payment_indicator] ||= 'R' + when '', 'unscheduled' + post[:payment_indicator] ||= 'U' + end + else + post[:payment_indicator] ||= 'Z' + end + end + # Common params used amongst the +credit+, +void+ and +capture+ methods def crediting_params(authorization, options = {}) { - :txn_number => split_authorization(authorization).first, - :order_id => split_authorization(authorization).last, - :crypt_type => options[:crypt_type] || @options[:crypt_type] + txn_number: split_authorization(authorization).first, + order_id: split_authorization(authorization).last, + crypt_type: options[:crypt_type] || @options[:crypt_type] }.merge(options) end @@ -227,38 +310,47 @@ def split_authorization(authorization) end end - def commit(action, parameters = {}) + def commit(action, parameters = {}, options = {}) + threed_ds_transaction = options[:three_d_secure].present? + data = post_data(action, parameters) url = test? ? self.test_url : self.live_url raw = ssl_post(url, data) response = parse(raw) Response.new( - successful?(response), + successful?(action, response, threed_ds_transaction), message_from(response[:message]), response, - :test => test?, - :avs_result => {:code => response[:avs_result_code]}, - :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1], - :authorization => authorization_from(response)) + test: test?, + avs_result: { code: response[:avs_result_code] }, + cvv_result: response[:cvd_result_code] && response[:cvd_result_code][-1, 1], + authorization: authorization_from(response) + ) end # Generates a Moneris authorization string of the form 'trans_id;receipt_id'. def authorization_from(response = {}) - if response[:trans_id] && response[:receipt_id] - "#{response[:trans_id]};#{response[:receipt_id]}" - end + "#{response[:trans_id]};#{response[:receipt_id]}" if response[:trans_id] && response[:receipt_id] end # Tests for a successful response from Moneris' servers - def successful?(response) - response[:response_code] && - response[:complete] && - (0..49).cover?(response[:response_code].to_i) + def successful?(action, response, threed_ds_transaction = false) + # See 9.4 CAVV Result Codes in https://developer.moneris.com/livedemo/3ds2/reference/guide/php + cavv_accepted = if threed_ds_transaction + response[:cavv_result_code] && response[:cavv_result_code] == '2' + else + true + end + + cavv_accepted && + response[:response_code] && + response[:complete] && + (0..49).cover?(response[:response_code].to_i) end def parse(xml) - response = { :message => 'Global Error Receipt', :complete => false } + response = { message: 'Global Error Receipt', complete: false } hashify_xml!(xml, response) response end @@ -266,6 +358,7 @@ def parse(xml) def hashify_xml!(xml, response) xml = REXML::Document.new(xml) return if xml.root.nil? + xml.elements.each('//receipt/*') do |node| response[node.name.underscore.to_sym] = normalize(node.text) end @@ -340,36 +433,54 @@ def credential_on_file(parameters) end def wallet_indicator(token_source) - return 'APP' if token_source == 'apple_pay' - return 'ANP' if token_source == 'android_pay' - nil + return { + 'apple_pay' => 'APP', + 'google_pay' => 'GPP', + 'android_pay' => 'ANP' + }[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? + message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end def actions { - 'purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info], - 'preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info], - 'command' => [:order_id], - 'refund' => [:order_id, :amount, :txn_number, :crypt_type], - 'indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - 'completion' => [:order_id, :comp_amount, :txn_number, :crypt_type], - 'purchasecorrection' => [:order_id, :txn_number, :crypt_type], - 'cavv_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator], - 'cavv_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator], - 'transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - 'Batchcloseall' => [], - 'opentotals' => [:ecr_number], - 'batchclose' => [:ecr_number], - 'res_add_cc' => [:pan, :expdate, :crypt_type, :cof_info], - 'res_delete' => [:data_key], - 'res_update_cc' => [:data_key, :pan, :expdate, :crypt_type], - 'res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info], - 'res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info] + 'purchase' => %i[order_id cust_id amount pan expdate crypt_type avs_info cvd_info track2 pos_code cof_info], + 'preauth' => %i[order_id cust_id amount pan expdate crypt_type avs_info cvd_info track2 pos_code cof_info], + 'command' => [:order_id], + 'refund' => %i[order_id amount txn_number crypt_type], + '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 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' => [], + 'opentotals' => [:ecr_number], + 'batchclose' => [:ecr_number], + 'res_add_cc' => %i[pan expdate crypt_type avs_info cof_info], + 'res_temp_add' => %i[pan expdate crypt_type duration], + 'res_delete' => [:data_key], + 'res_update_cc' => %i[data_key pan expdate crypt_type avs_info cof_info], + 'res_purchase_cc' => %i[data_key order_id cust_id amount crypt_type cof_info], + 'res_preauth_cc' => %i[data_key order_id cust_id amount crypt_type cof_info], + 'res_card_verification_cc' => %i[order_id data_key expdate crypt_type cof_info] } end end diff --git a/lib/active_merchant/billing/gateways/moneris_us.rb b/lib/active_merchant/billing/gateways/moneris_us.rb deleted file mode 100644 index 28c06ea91d0..00000000000 --- a/lib/active_merchant/billing/gateways/moneris_us.rb +++ /dev/null @@ -1,352 +0,0 @@ -require 'rexml/document' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - # To learn more about the Moneris (US) gateway, please contact - # ussales@moneris.com for a copy of their integration guide. For - # information on remote testing, please see "Test Environment Penny Value - # Response Table", and "Test Environment eFraud (AVS and CVD) Penny - # Response Values", available at Moneris' {eSelect Plus Documentation - # Centre}[https://www3.moneris.com/connect/en/documents/index.html]. - class MonerisUsGateway < Gateway - self.test_url = 'https://esplusqa.moneris.com/gateway_us/servlet/MpgRequest' - self.live_url = 'https://esplus.moneris.com/gateway_us/servlet/MpgRequest' - - self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover] - self.homepage_url = 'http://www.monerisusa.com/' - self.display_name = 'Moneris (US)' - - # Initialize the Gateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login -- Your Store ID - # * :password -- Your API Token - # * :cvv_enabled -- Specify that you would like the CVV passed to the gateway. - # Only particular account types at Moneris will allow this. - # Defaults to false. (optional) - def initialize(options = {}) - requires!(options, :login, :password) - @cvv_enabled = options[:cvv_enabled] - @avs_enabled = options[:avs_enabled] - options = { :crypt_type => 7 }.merge(options) - super - end - - def verify(creditcard_or_datakey, options = {}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, creditcard_or_datakey, options) } - r.process(:ignore_result) { capture(0, r.authorization) } - end - end - - # Referred to as "PreAuth" in the Moneris integration guide, this action - # verifies and locks funds on a customer's card, which then must be - # captured at a later date. - # - # Pass in +order_id+ and optionally a +customer+ parameter. - def authorize(money, creditcard_or_datakey, options = {}) - requires!(options, :order_id) - post = {} - add_payment_source(post, creditcard_or_datakey, options) - post[:amount] = amount(money) - post[:order_id] = options[:order_id] - post[:address] = options[:billing_address] || options[:address] - post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - action = post[:data_key].blank? ? 'us_preauth' : 'us_res_preauth_cc' - commit(action, post) - end - - # This action verifies funding on a customer's card and readies them for - # deposit in a merchant's account. - # - # Pass in order_id and optionally a customer parameter - def purchase(money, creditcard_or_datakey, options = {}) - requires!(options, :order_id) - post = {} - add_payment_source(post, creditcard_or_datakey, options) - post[:amount] = amount(money) - post[:order_id] = options[:order_id] - add_address(post, creditcard_or_datakey, options) - post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - action = if creditcard_or_datakey.is_a?(String) - 'us_res_purchase_cc' - elsif card_brand(creditcard_or_datakey) == 'check' - 'us_ach_debit' - elsif post[:data_key].blank? - 'us_purchase' - end - commit(action, post) - end - - # This method retrieves locked funds from a customer's account (from a - # PreAuth) and prepares them for deposit in a merchant's account. - # - # Note: Moneris requires both the order_id and the transaction number of - # the original authorization. To maintain the same interface as the other - # gateways the two numbers are concatenated together with a ; separator as - # the authorization number returned by authorization - def capture(money, authorization, options = {}) - commit 'us_completion', crediting_params(authorization, :comp_amount => amount(money)) - end - - # Voiding requires the original transaction ID and order ID of some open - # transaction. Closed transactions must be refunded. Note that the only - # methods which may be voided are +capture+ and +purchase+. - # - # Concatenate your transaction number and order_id by using a semicolon - # (';'). This is to keep the Moneris interface consistent with other - # gateways. (See +capture+ for details.) - def void(authorization, options = {}) - commit 'us_purchasecorrection', crediting_params(authorization) - end - - # Performs a refund. This method requires that the original transaction - # number and order number be included. Concatenate your transaction - # number and order_id by using a semicolon (';'). This is to keep the - # Moneris interface consistent with other gateways. (See +capture+ for - # details.) - def credit(money, authorization, options = {}) - ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, authorization, options) - end - - def refund(money, authorization, options = {}) - commit 'us_refund', crediting_params(authorization, :amount => amount(money)) - end - - def store(payment_source, options = {}) - post = {} - add_payment_source(post, payment_source, options) - post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - card_brand(payment_source) == 'check' ? commit('us_res_add_ach', post) : commit('us_res_add_cc', post) - end - - def unstore(data_key, options = {}) - post = {} - post[:data_key] = data_key - commit('us_res_delete', post) - end - - def update(data_key, payment_source, options = {}) - post = {} - add_payment_source(post, payment_source, options) - post[:data_key] = data_key - card_brand(payment_source) == 'check' ? commit('us_res_update_ach', post) : commit('us_res_update_cc', post) - end - - def supports_scrubbing? - true - end - - def scrub(transcript) - transcript. - gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). - gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). - gsub(%r(()[^<]*())i, '\1[FILTERED]\2') - end - - private # :nodoc: all - - def expdate(creditcard) - sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month) - end - - def add_address(post, payment_method, options) - if !payment_method.is_a?(String) && card_brand(payment_method) == 'check' - post[:ach_info][:cust_first_name] = payment_method.first_name if payment_method.first_name - post[:ach_info][:cust_last_name] = payment_method.last_name if payment_method.last_name - if address = options[:billing_address] || options[:address] - post[:ach_info][:cust_address1] = address[:address1] if address[:address1] - post[:ach_info][:cust_address2] = address[:address2] if address[:address2] - post[:ach_info][:city] = address[:city] if address[:city] - post[:ach_info][:state] = address[:state] if address[:state] - post[:ach_info][:zip] = address[:zip] if address[:zip] - end - else - post[:address] = options[:billing_address] || options[:address] - end - end - - def add_payment_source(post, source, options) - if source.is_a?(String) - post[:data_key] = source - post[:cust_id] = options[:customer] - elsif card_brand(source) == 'check' - ach_info = {} - ach_info[:sec] = 'web' - ach_info[:routing_num] = source.routing_number - ach_info[:account_num] = source.account_number - ach_info[:account_type] = source.account_type - ach_info[:check_num] = source.number if source.number - post[:ach_info] = ach_info - else - post[:pan] = source.number - post[:expdate] = expdate(source) - post[:cvd_value] = source.verification_value if source.verification_value? - if crypt_type = options[:crypt_type] || @options[:crypt_type] - post[:crypt_type] = crypt_type - end - post[:cust_id] = options[:customer] || source.name - end - end - - # Common params used amongst the +credit+, +void+ and +capture+ methods - def crediting_params(authorization, options = {}) - { - :txn_number => split_authorization(authorization).first, - :order_id => split_authorization(authorization).last, - :crypt_type => options[:crypt_type] || @options[:crypt_type] - }.merge(options) - end - - # Splits an +authorization+ param and retrieves the order id and - # transaction number in that order. - def split_authorization(authorization) - if authorization.nil? || authorization.empty? || authorization !~ /;/ - raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")' - else - authorization.split(';') - end - end - - def commit(action, parameters = {}) - data = post_data(action, parameters) - url = test? ? self.test_url : self.live_url - raw = ssl_post(url, data) - response = parse(raw) - - Response.new(successful?(response), message_from(response[:message]), response, - :test => test?, - :avs_result => { :code => response[:avs_result_code] }, - :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1], - :authorization => authorization_from(response) - ) - end - - # Generates a Moneris authorization string of the form 'trans_id;receipt_id'. - def authorization_from(response = {}) - if response[:trans_id] && response[:receipt_id] - "#{response[:trans_id]};#{response[:receipt_id]}" - end - end - - # Tests for a successful response from Moneris' servers - def successful?(response) - response[:response_code] && - response[:complete] && - (0..49).cover?(response[:response_code].to_i) - end - - def parse(xml) - response = { :message => 'Global Error Receipt', :complete => false } - hashify_xml!(xml, response) - response - end - - def hashify_xml!(xml, response) - xml = REXML::Document.new(xml) - return if xml.root.nil? - xml.elements.each('//receipt/*') do |node| - response[node.name.underscore.to_sym] = normalize(node.text) - end - end - - def post_data(action, parameters = {}) - xml = REXML::Document.new - root = xml.add_element('request') - root.add_element('store_id').text = options[:login] - root.add_element('api_token').text = options[:password] - root.add_element(transaction_element(action, parameters)) - - xml.to_s - end - - def transaction_element(action, parameters) - transaction = REXML::Element.new(action) - - # Must add the elements in the correct order - actions[action].each do |key| - case key - when :avs_info - transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address] - when :cvd_info - transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled - when :ach_info - transaction.add_element(ach_element(parameters[:ach_info])) - else - transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? - end - end - - transaction - end - - def avs_element(address) - full_address = "#{address[:address1]} #{address[:address2]}" - tokens = full_address.split(/\s+/) - - element = REXML::Element.new('avs_info') - element.add_element('avs_street_number').text = tokens.select { |x| x =~ /\d/ }.join(' ') - element.add_element('avs_street_name').text = tokens.reject { |x| x =~ /\d/ }.join(' ') - element.add_element('avs_zipcode').text = address[:zip] - element - end - - def cvd_element(cvd_value) - element = REXML::Element.new('cvd_info') - if cvd_value - element.add_element('cvd_indicator').text = '1' - element.add_element('cvd_value').text = cvd_value - else - element.add_element('cvd_indicator').text = '0' - end - element - end - - def ach_element(ach_info) - element = REXML::Element.new('ach_info') - actions['ach_info'].each do |key| - element.add_element(key.to_s).text = ach_info[key] unless ach_info[key].blank? - end - element - end - - def message_from(message) - return 'Unspecified error' if message.blank? - message.gsub(/[^\w]/, ' ').split.join(' ').capitalize - end - - def actions - { - 'us_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info], - 'us_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info], - 'us_command' => [:order_id], - 'us_refund' => [:order_id, :amount, :txn_number, :crypt_type], - 'us_indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - 'us_completion' => [:order_id, :comp_amount, :txn_number, :crypt_type], - 'us_purchasecorrection' => [:order_id, :txn_number, :crypt_type], - 'us_cavvpurcha' => [:order_id, :cust_id, :amount, :pan, :expdate, :cav], - 'us_cavvpreaut' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], - 'us_transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - 'us_Batchcloseall' => [], - 'us_opentotals' => [:ecr_number], - 'us_batchclose' => [:ecr_number], - 'us_res_add_cc' => [:pan, :expdate, :crypt_type], - 'us_res_delete' => [:data_key], - 'us_res_update_cc' => [:data_key, :pan, :expdate, :crypt_type], - 'us_res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type], - 'us_res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type], - 'us_ach_debit' => [:order_id, :cust_id, :amount, :ach_info], - 'us_res_add_ach' => [:order_id, :cust_id, :amount, :ach_info], - 'us_res_update_ach' => [:order_id, :data_key, :cust_id, :amount, :ach_info], - 'ach_info' => [:sec, :cust_first_name, :cust_last_name, :cust_address1, :cust_address2, :cust_city, :cust_state, :cust_zip, :routing_num, :account_num, :check_num, :account_type] - } - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/money_movers.rb b/lib/active_merchant/billing/gateways/money_movers.rb index 8a7a1e9e9a4..2ba6c35cae7 100644 --- a/lib/active_merchant/billing/gateways/money_movers.rb +++ b/lib/active_merchant/billing/gateways/money_movers.rb @@ -8,7 +8,7 @@ class MoneyMoversGateway < Gateway self.homepage_url = 'http://mmoa.us/' self.display_name = 'MoneyMovers' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] def initialize(options = {}) requires!(options, :login, :password) @@ -45,7 +45,7 @@ def void(authorization, options = {}) end def refund(money, authorization, options = {}) - commit('refund', money, options.merge(:transactionid => authorization)) + commit('refund', money, options.merge(transactionid: authorization)) end def credit(money, authorization, options = {}) @@ -113,11 +113,14 @@ def commit(action, money, parameters) response = parse(data) message = message_from(response) - Response.new(success?(response), message, response, - :test => test?, - :authorization => response['transactionid'], - :avs_result => {:code => response['avsresponse']}, - :cvv_result => response['cvvresponse'] + Response.new( + success?(response), + message, + response, + test: test?, + authorization: response['transactionid'], + avs_result: { code: response['avsresponse'] }, + cvv_result: response['cvvresponse'] ) end diff --git a/lib/active_merchant/billing/gateways/mundipagg.rb b/lib/active_merchant/billing/gateways/mundipagg.rb index 04f39464ab4..1549a3ea4af 100644 --- a/lib/active_merchant/billing/gateways/mundipagg.rb +++ b/lib/active_merchant/billing/gateways/mundipagg.rb @@ -5,7 +5,7 @@ class MundipaggGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover alelo] self.homepage_url = 'https://www.mundipagg.com/' self.display_name = 'Mundipagg' @@ -28,56 +28,62 @@ class MundipaggGateway < Gateway '500' => 'An internal error occurred;' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_customer_data(post, options) unless payment.is_a?(String) add_shipping_address(post, options) add_payment(post, payment, options) - + add_submerchant(post, options) + add_auth_key(post, options) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_customer_data(post, options) unless payment.is_a?(String) add_shipping_address(post, options) add_payment(post, payment, options) add_capture_flag(post, payment) + add_submerchant(post, options) + add_auth_key(post, options) commit('authonly', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} post[:code] = authorization add_invoice(post, money, options) + add_auth_key(post, options) commit('capture', post, authorization) end - def refund(money, authorization, options={}) - add_invoice(post={}, money, options) + def refund(money, authorization, options = {}) + add_invoice(post = {}, money, options) + add_auth_key(post, options) commit('refund', post, authorization) end - def void(authorization, options={}) + def void(authorization, options = {}) commit('void', nil, authorization) end - def store(payment, options={}) + def store(payment, options = {}) post = {} options.update(name: payment.name) options = add_customer(options) unless options[:customer_id] add_payment(post, payment, options) + add_auth_key(post, options) commit('store', post, options[:customer_id]) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -155,7 +161,8 @@ def add_payment(post, payment, options) post[:customer][:name] = payment.name if post[:customer] post[:customer_id] = parse_auth(payment)[0] if payment.is_a?(String) post[:payment] = {} - post[:payment][:gateway_affiliation_id] = @options[:gateway_id] if @options[:gateway_id] + affiliation = options[:gateway_affiliation_id] || @options[:gateway_id] + post[:payment][:gateway_affiliation_id] = affiliation if affiliation post[:payment][:metadata] = { mundipagg_payment_method_code: '1' } if test? if voucher?(payment) add_voucher(post, payment, options) @@ -197,12 +204,45 @@ def add_voucher(post, payment, options) def voucher?(payment) return false if payment.is_a?(String) + %w[sodexo vr].include? card_brand(payment) end - def headers + def add_submerchant(post, options) + if submerchant = options[:submerchant] + post[:SubMerchant] = {} + post[:SubMerchant][:Merchant_Category_Code] = submerchant[:merchant_category_code] if submerchant[:merchant_category_code] + post[:SubMerchant][:Payment_Facilitator_Code] = submerchant[:payment_facilitator_code] if submerchant[:payment_facilitator_code] + post[:SubMerchant][:Code] = submerchant[:code] if submerchant[:code] + post[:SubMerchant][:Name] = submerchant[:name] if submerchant[:name] + post[:SubMerchant][:Document] = submerchant[:document] if submerchant[:document] + post[:SubMerchant][:Type] = submerchant[:type] if submerchant[:type] + post[:SubMerchant][:Phone] = {} + post[:SubMerchant][:Phone][:Country_Code] = submerchant[:phone][:country_code] if submerchant.dig(:phone, :country_code) + post[:SubMerchant][:Phone][:Number] = submerchant[:phone][:number] if submerchant.dig(:phone, :number) + post[:SubMerchant][:Phone][:Area_Code] = submerchant[:phone][:area_code] if submerchant.dig(:phone, :area_code) + post[:SubMerchant][:Address] = {} + post[:SubMerchant][:Address][:Street] = submerchant[:address][:street] if submerchant.dig(:address, :street) + post[:SubMerchant][:Address][:Number] = submerchant[:address][:number] if submerchant.dig(:address, :number) + post[:SubMerchant][:Address][:Complement] = submerchant[:address][:complement] if submerchant.dig(:address, :complement) + post[:SubMerchant][:Address][:Neighborhood] = submerchant[:address][:neighborhood] if submerchant.dig(:address, :neighborhood) + post[:SubMerchant][:Address][:City] = submerchant[:address][:city] if submerchant.dig(:address, :city) + post[:SubMerchant][:Address][:State] = submerchant[:address][:state] if submerchant.dig(:address, :state) + post[:SubMerchant][:Address][:Country] = submerchant[:address][:country] if submerchant.dig(:address, :country) + post[:SubMerchant][:Address][:Zip_Code] = submerchant[:address][:zip_code] if submerchant.dig(:address, :zip_code) + end + end + + def add_auth_key(post, options) + if authorization_secret_key = options[:authorization_secret_key] + post[:authorization_secret_key] = authorization_secret_key + end + end + + def headers(authorization_secret_key = nil) + basic_token = authorization_secret_key || @options[:api_key] { - 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:api_key]}:"), + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{basic_token}:"), 'Content-Type' => 'application/json', 'Accept' => 'application/json' } @@ -230,25 +270,27 @@ def url_for(action, auth = nil) def commit(action, parameters, auth = nil) url = url_for(action, auth) + authorization_secret_key = parameters[:authorization_secret_key] if parameters parameters.merge!(parameters[:payment][:credit_card].delete(:card)).delete(:payment) if action == 'store' response = if %w[refund void].include? action - parse(ssl_request(:delete, url, post_data(parameters), headers)) + parse(ssl_request(:delete, url, post_data(parameters), headers(authorization_secret_key))) else - parse(ssl_post(url, post_data(parameters), headers)) + parse(ssl_post(url, post_data(parameters), headers(authorization_secret_key))) end Response.new( - success_from(response), + success_from(response, action), message_from(response), response, authorization: authorization_from(response, action), avs_result: AVSResult.new(code: response['some_avs_response_key']), cvv_result: CVVResult.new(response['some_cvv_response_key']), test: test?, - error_code: error_code_from(response) + error_code: error_code_from(response, action) ) rescue ResponseError => e - message = get_error_message(e) + message = get_error_messages(e) + return Response.new( false, "#{STANDARD_ERROR_MESSAGE_MAPPING[e.response.code]} #{message}", @@ -258,21 +300,50 @@ def commit(action, parameters, auth = nil) ) end - def success_from(response) - %w[pending paid processing canceled active].include? response['status'] - end - - def get_error_message(error) - JSON.parse(error.response.body)['message'] + def success_from(response, action) + success = response.try(:[], 'last_transaction').try(:[], 'success') unless action == 'store' + success = !response.try(:[], 'id').nil? if action == 'store' + success end def message_from(response) + return gateway_response_errors(response) if gateway_response_errors?(response) return response['message'] if response['message'] return response['last_transaction']['acquirer_message'] if response['last_transaction'] end + def get_error_messages(error) + parsed_response_body = parse(error.response.body) + message = parsed_response_body['message'] + + parsed_response_body['errors']&.each do |_type, descriptions| + message += ' | ' + message += descriptions.join(', ') + end + + message + end + + def gateway_response_errors?(response) + response.try(:[], 'last_transaction').try(:[], 'gateway_response').try(:[], 'errors').present? + end + + def gateway_response_errors(response) + error_string = '' + + response['last_transaction']['gateway_response']['errors']&.each do |error| + error.each do |_key, value| + error_string += ' | ' unless error_string.blank? + error_string += value + end + end + + error_string + end + def authorization_from(response, action) return "#{response['customer']['id']}|#{response['id']}" if action == 'store' + response['id'] end @@ -284,8 +355,11 @@ def post_data(parameters = {}) parameters.to_json end - def error_code_from(response) - STANDARD_ERROR_CODE[:processing_error] unless success_from(response) + def error_code_from(response, action) + return if success_from(response, action) + return response['last_transaction']['acquirer_return_code'] if response['last_transaction'] + + STANDARD_ERROR_CODE[:processing_error] end end end diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb index f7df53da09b..7c81d230bcf 100644 --- a/lib/active_merchant/billing/gateways/nab_transact.rb +++ b/lib/active_merchant/billing/gateways/nab_transact.rb @@ -12,11 +12,11 @@ class NabTransactGateway < Gateway self.test_url = 'https://demo.transact.nab.com.au/xmlapi/payment' self.live_url = 'https://transact.nab.com.au/live/xmlapi/payment' - self.test_periodic_url = 'https://transact.nab.com.au/xmlapidemo/periodic' + self.test_periodic_url = 'https://demo.transact.nab.com.au/xmlapi/periodic' self.live_periodic_url = 'https://transact.nab.com.au/xmlapi/periodic' self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.homepage_url = 'http://transact.nab.com.au' self.display_name = 'NAB Transact' @@ -25,21 +25,21 @@ class NabTransactGateway < Gateway # Transactions currently accepted by NAB Transact XML API TRANSACTIONS = { - :purchase => 0, # Standard Payment - :refund => 4, # Refund - :void => 6, # Client Reversal (Void) - :unmatched_refund => 666, # Unmatched Refund - :authorization => 10, # Preauthorise - :capture => 11 # Preauthorise Complete (Advice) + purchase: 0, # Standard Payment + refund: 4, # Refund + void: 6, # Client Reversal (Void) + unmatched_refund: 666, # Unmatched Refund + authorization: 10, # Preauthorise + capture: 11 # Preauthorise Complete (Advice) } PERIODIC_TYPES = { - :addcrn => 5, - :deletecrn => 5, - :trigger => 8 + addcrn: 5, + deletecrn: 5, + trigger: 8 } - SUCCESS_CODES = [ '00', '08', '11', '16', '77' ] + SUCCESS_CODES = %w[00 08 11 16 77] def initialize(options = {}) requires!(options, :login, :password) @@ -84,6 +84,7 @@ def supports_scrubbing? def scrub(transcript) return '' if transcript.blank? + transcript. gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). @@ -95,8 +96,8 @@ def scrub(transcript) def add_metadata(xml, options) if options[:merchant_name] || options[:merchant_location] xml.tag! 'metadata' do - xml.tag! 'meta', :name => 'ca_name', :value => options[:merchant_name] if options[:merchant_name] - xml.tag! 'meta', :name => 'ca_location', :value => options[:merchant_location] if options[:merchant_location] + xml.tag! 'meta', name: 'ca_name', value: options[:merchant_name] if options[:merchant_name] + xml.tag! 'meta', name: 'ca_location', value: options[:merchant_location] if options[:merchant_location] end end end @@ -232,17 +233,23 @@ 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, - :test => test?, - :authorization => authorization_from(action, response) + Response.new( + success?(response), + message_from(response), + response, + test: test?, + 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, - :test => test?, - :authorization => authorization_from(action, response) + Response.new( + success?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(action, response) ) end @@ -295,7 +302,6 @@ def generate_timestamp def request_timeout @options[:request_timeout] || 60 end - end end end diff --git a/lib/active_merchant/billing/gateways/ncr_secure_pay.rb b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb index 1a595dd196d..4db01742e62 100644 --- a/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +++ b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb @@ -8,17 +8,17 @@ class NcrSecurePayGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.ncrretailonline.com' self.display_name = 'NCR Secure Pay' - def initialize(options={}) + def initialize(options = {}) requires!(options, :username, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -27,7 +27,7 @@ def purchase(money, payment, options={}) commit('sale', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) @@ -36,7 +36,7 @@ def authorize(money, payment, options={}) commit('preauth', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, money, options) @@ -44,7 +44,7 @@ def capture(money, authorization, options={}) commit('preauthcomplete', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_reference(post, authorization) add_invoice(post, money, options) @@ -52,13 +52,13 @@ def refund(money, authorization, options={}) commit('credit', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit('void', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -141,7 +141,7 @@ def authorization_from(response) end def request_body(action, parameters = {}) - Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml| xml.MonetraTrans do xml.Trans(identifier: parameters.delete(:identifier) || '1') do xml.username(options[:username]) @@ -156,9 +156,7 @@ def request_body(action, parameters = {}) end def error_code_from(response) - unless success_from(response) - response[:msoft_code] || response[:phard_code] - end + response[:msoft_code] || response[:phard_code] unless success_from(response) end end end diff --git a/lib/active_merchant/billing/gateways/net_registry.rb b/lib/active_merchant/billing/gateways/net_registry.rb index 8f199a88ca8..7052581b7ba 100644 --- a/lib/active_merchant/billing/gateways/net_registry.rb +++ b/lib/active_merchant/billing/gateways/net_registry.rb @@ -24,7 +24,7 @@ module Billing class NetRegistryGateway < Gateway self.live_url = self.test_url = 'https://paygate.ssllock.net/external2.pl' - FILTERED_PARAMS = [ 'card_no', 'card_expiry', 'receipt_array' ] + FILTERED_PARAMS = %w[card_no card_expiry receipt_array] self.supported_countries = ['AU'] @@ -32,16 +32,16 @@ class NetRegistryGateway < Gateway # steps in setting up your account, as detailed in # "Programming for NetRegistry's E-commerce Gateway." # [http://rubyurl.com/hNG] - self.supported_cardtypes = [:visa, :master, :diners_club, :american_express, :jcb] + self.supported_cardtypes = %i[visa master diners_club american_express jcb] self.display_name = 'NetRegistry' self.homepage_url = 'http://www.netregistry.com.au' TRANSACTIONS = { - :authorization => 'preauth', - :purchase => 'purchase', - :capture => 'completion', - :status => 'status', - :refund => 'refund' + authorization: 'preauth', + purchase: 'purchase', + capture: 'completion', + status: 'status', + refund: 'refund' } # Create a new NetRegistry gateway. @@ -144,8 +144,11 @@ 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 diff --git a/lib/active_merchant/billing/gateways/netaxept.rb b/lib/active_merchant/billing/gateways/netaxept.rb index 6681259e657..a3377284cc5 100644 --- a/lib/active_merchant/billing/gateways/netaxept.rb +++ b/lib/active_merchant/billing/gateways/netaxept.rb @@ -7,10 +7,10 @@ class NetaxeptGateway < Gateway self.live_url = 'https://epayment.bbs.no/' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['NO', 'DK', 'SE', 'FI'] + self.supported_countries = %w[NO DK SE FI] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] # The homepage URL of the gateway self.homepage_url = 'http://www.betalingsterminal.no/Netthandel-forside/' @@ -94,12 +94,12 @@ def query_transaction(authorization, options) commit('Netaxept/query.aspx', post) end - def add_credentials(post, options, secure=true) + def add_credentials(post, options, secure = true) post[:merchantId] = @options[:login] post[:token] = @options[:password] if secure end - def add_authorization(post, authorization, money=nil) + def add_authorization(post, authorization, money = nil) post[:transactionId] = authorization post[:transactionAmount] = amount(money) if money end @@ -118,12 +118,12 @@ def add_creditcard(post, options) post[:securityCode] = options.verification_value end - def commit(path, parameters, xml=true) + def commit(path, parameters, xml = true) raw = parse(ssl_get(build_url(path, parameters)), xml) success = false authorization = (raw['TransactionId'] || parameters[:transactionId]) - if raw[:container] =~ /Exception|Error/ + if /Exception|Error/.match?(raw[:container]) message = (raw['Message'] || raw['Error']['Message']) elsif raw['Error'] && !raw['Error'].empty? message = (raw['Error']['ResponseText'] || raw['Error']['ResponseCode']) @@ -136,17 +136,17 @@ def commit(path, parameters, xml=true) success, message, raw, - :test => test?, - :authorization => authorization + test: test?, + authorization: authorization ) end - def parse(result, expects_xml=true) + def parse(result, expects_xml = true) if expects_xml doc = REXML::Document.new(result) - extract_xml(doc.root).merge(:container => doc.root.name) + extract_xml(doc.root).merge(container: doc.root.name) else - {:result => result} + { result: result } end end @@ -162,7 +162,7 @@ def extract_xml(element) end end - def build_url(base, parameters=nil) + def build_url(base, parameters = nil) url = (test? ? self.test_url : self.live_url).dup url << base if parameters diff --git a/lib/active_merchant/billing/gateways/netbanx.rb b/lib/active_merchant/billing/gateways/netbanx.rb index 88e53fe1298..b50053583df 100644 --- a/lib/active_merchant/billing/gateways/netbanx.rb +++ b/lib/active_merchant/billing/gateways/netbanx.rb @@ -7,14 +7,14 @@ class NetbanxGateway < Gateway self.supported_countries = %w(AF AX AL DZ AS AD AO AI AQ AG AR AM AW AU AT AZ BS BH BD BB BY BE BZ BJ BM BT BO BQ BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL CN CX CC CO KM CG CD CK CR CI HR CU CW CY CZ DK DJ DM DO EC EG SV GQ ER EE ET FK FO FJ FI FR GF PF TF GA GM GE DE GH GI GR GL GD GP GU GT GG GN GW GY HT HM HN HK HU IS IN ID IR IQ IE IM IL IT JM JP JE JO KZ KE KI KP KR KW KG LA LV LB LS LR LY LI LT LU MO MK MG MW MY MV ML MT MH MQ MR MU YT MX FM MD MC MN ME MS MA MZ MM NA NR NP NC NZ NI NE NG NU NF MP NO OM PK PW PS PA PG PY PE PH PN PL PT PR QA RE RO RU RW BL SH KN LC MF VC WS SM ST SA SN RS SC SL SG SX SK SI SB SO ZA GS SS ES LK PM SD SR SJ SZ SE CH SY TW TJ TZ TH NL TL TG TK TO TT TN TR TM TC TV UG UA AE GB US UM UY UZ VU VA VE VN VG VI WF EH YE ZM ZW) self.default_currency = 'CAD' - self.supported_cardtypes = [ - :american_express, - :diners_club, - :discover, - :jcb, - :master, - :maestro, - :visa + self.supported_cardtypes = %i[ + american_express + diners_club + discover + jcb + master + maestro + visa ] self.money_format = :cents @@ -22,56 +22,98 @@ class NetbanxGateway < Gateway self.homepage_url = 'https://processing.paysafe.com/' self.display_name = 'Netbanx by PaySafe' - def initialize(options={}) + AVS_CODE_CONVERTER = { + 'MATCH' => 'X', + 'MATCH_ADDRESS_ONLY' => 'A', + 'MATCH_ZIP_ONLY' => 'Z', + 'NO_MATCH' => 'N', + 'NOT_PROCESSED' => 'U', + 'UNKNOWN' => 'Q' + } + + CVV_CODE_CONVERTER = { + 'MATCH' => 'M', + 'NO_MATCH' => 'N', + 'NOT_PROCESSED' => 'P', + 'UNKNOWN' => 'U' + } + + def initialize(options = {}) requires!(options, :account_number, :api_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) + # Do a Verification with AVS prior to purchase + verification_response = verify(payment, options) + return verification_response if verification_response.message != 'OK' + post = {} add_invoice(post, money, options) add_settle_with_auth(post) add_payment(post, payment, options) + add_customer_detail_data(post, options) commit(:post, 'auths', post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) + # Do a Verification with AVS prior to Auth + Settle + verification_response = verify(payment, options) + return verification_response if verification_response.message != 'OK' + post = {} add_invoice(post, money, options) add_payment(post, payment, options) + add_customer_detail_data(post, options) commit(:post, 'auths', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_invoice(post, money, options) commit(:post, "auths/#{authorization}/settlements", post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) + # If the transactions that are pending, API call needs to be Cancellation + settlement_data = get_settlement(authorization) + return settlement_data if settlement_data.message != 'OK' + post = {} - add_invoice(post, money, options) + if settlement_data.params['status'] == 'PENDING' && money == settlement_data.params['amount'] + post[:status] = 'CANCELLED' + commit(:put, "settlements/#{authorization}", post) + elsif settlement_data.params['status'] == 'PENDING' && (money < settlement_data.params['amount'] || money > settlement_data.params['amount']) + return Response.new(false, 'Transaction not settled. Either do a full refund or try partial refund after settlement.') + else + add_invoice(post, money, options) + + # Setting merchantRefNumber to a unique id for each refund + # This is to support multiple partial refunds for the same order + post[:merchantRefNum] = SecureRandom.uuid - # Setting merchantRefNumber to a unique id for each refund - # This is to support multiple partial refunds for the same order - post[:merchantRefNum] = SecureRandom.uuid + commit(:post, "settlements/#{authorization}/refunds", post) + end + end - commit(:post, "settlements/#{authorization}/refunds", post) + def get_settlement(authorization) + post = {} + commit(:get, "settlements/#{authorization}", post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_order_id(post, options) commit(:post, "auths/#{authorization}/voidauths", post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) post = {} - add_payment(post, credit_card) + add_payment(post, credit_card, options) add_order_id(post, options) commit(:post, 'verifications', post) @@ -79,7 +121,7 @@ def verify(credit_card, options={}) # note: when passing options[:customer] we only attempt to add the # card to the profile_id passed as the options[:customer] - def store(credit_card, options={}) + def store(credit_card, options = {}) # locale can only be one of en_US, fr_CA, en_GB requires!(options, :locale) post = {} @@ -123,15 +165,24 @@ def add_customer_data(post, options) post[:locale] = options[:locale] end + def add_customer_detail_data(post, options) + post[:profile] ||= {} + post[:profile][:email] = options[:email] if options[:email] + post[:customerIp] = options[:ip] if options[:ip] + if (billing_address = options[:billing_address]) + post[:profile][:firstName], post[:profile][:lastName] = split_names(billing_address[:name]) + end + end + def add_credit_card(post, credit_card, options = {}) post[:card] ||= {} post[:card][:cardNum] = credit_card.number post[:card][:holderName] = credit_card.name post[:card][:cvv] = credit_card.verification_value post[:card][:cardExpiry] = expdate(credit_card) - if options[:billing_address] - post[:card][:billingAddress] = map_address(options[:billing_address]) - end + + post[:authentication] = map_3ds(options[:three_d_secure]) if options[:three_d_secure] + post[:card][:billingAddress] = map_address(options[:billing_address]) if options[:billing_address] end def add_invoice(post, money, options) @@ -139,18 +190,19 @@ def add_invoice(post, money, options) add_order_id(post, options) end - def add_payment(post, credit_card_or_reference, options = {}) + def add_payment(post, credit_card_reference, options = {}) post[:card] ||= {} - if credit_card_or_reference.is_a?(String) - post[:card][:paymentToken] = credit_card_or_reference + if credit_card_reference.is_a?(String) + post[:card][:paymentToken] = credit_card_reference else - post[:card][:cardNum] = credit_card_or_reference.number - post[:card][:cvv] = credit_card_or_reference.verification_value - post[:card][:cardExpiry] = expdate(credit_card_or_reference) + post[:card][:cardNum] = credit_card_reference.number + post[:card][:cvv] = credit_card_reference.verification_value + post[:card][:cardExpiry] = expdate(credit_card_reference) end post[:currencyCode] = options[:currency] if options[:currency] post[:billingDetails] = map_address(options[:billing_address]) if options[:billing_address] + post[:authentication] = map_3ds(options[:three_d_secure]) if options[:three_d_secure] end def expdate(credit_card) @@ -158,7 +210,7 @@ def expdate(credit_card) month = format(credit_card.month, :two_digits) # returns a hash (necessary in the card JSON object) - { :month => month, :year => year } + { month: month, year: year } end def add_order_id(post, options) @@ -167,45 +219,75 @@ def add_order_id(post, options) def map_address(address) return {} if address.nil? + country = Country.find(address[:country]) if address[:country] mapped = { - :street => address[:address1], - :city => address[:city], - :zip => address[:zip], - :state => address[:state], + street: address[:address1], + city: address[:city], + zip: address[:zip], + state: address[:state] } mapped[:country] = country.code(:alpha2).value unless country.blank? mapped 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], + threeDResult: three_d_secure_options[:directory_response_status], + threeDSecureVersion: three_d_secure_options[:version], + directoryServerTransactionId: three_d_secure_options[:ds_transaction_id] + } + + mapped + end + def parse(body) body.blank? ? {} : JSON.parse(body) end def commit(method, uri, parameters) params = parameters.to_json unless parameters.nil? - response = begin - parse(ssl_request(method, get_url(uri), params, headers)) - rescue ResponseError => e - return Response.new(false, 'Invalid Login') if(e.response.code == '401') - parse(e.response.body) - end + response = + begin + if method == :get + parse(ssl_request(method, get_url(uri), nil, headers)) + else + parse(ssl_request(method, get_url(uri), params, headers)) + end + rescue ResponseError => e + return Response.new(false, 'Invalid Login') if e.response.code == '401' + + parse(e.response.body) + end success = success_from(response) Response.new( success, message_from(success, response), response, - :test => test?, - :error_code => error_code_from(response), - :authorization => authorization_from(success, get_url(uri), method, response) + test: test?, + error_code: error_code_from(response), + authorization: authorization_from(success, get_url(uri), method, response), + avs_result: avs_result(response), + cvv_result: cvv_result(response) ) end + def avs_result(response) + AVSResult.new(code: AVS_CODE_CONVERTER[response['avsResponse']]) + end + + def cvv_result(response) + CVVResult.new(CVV_CODE_CONVERTER[response['cvvVerification']]) + end + def get_url(uri) url = (test? ? test_url : live_url) - if uri =~ /^customervault/ + if /^customervault/.match?(uri) "#{url}#{uri}" else "#{url}cardpayments/v1/accounts/#{@options[:account_number]}/#{uri}" diff --git a/lib/active_merchant/billing/gateways/netbilling.rb b/lib/active_merchant/billing/gateways/netbilling.rb index 296d9e198ad..dfa479a495d 100644 --- a/lib/active_merchant/billing/gateways/netbilling.rb +++ b/lib/active_merchant/billing/gateways/netbilling.rb @@ -14,16 +14,16 @@ class NetbillingGateway < Gateway self.live_url = self.test_url = 'https://secure.netbilling.com:1402/gw/sas/direct3.1' TRANSACTIONS = { - :authorization => 'A', - :purchase => 'S', - :refund => 'R', - :credit => 'C', - :capture => 'D', - :void => 'U', - :quasi => 'Q' + authorization: 'A', + purchase: 'S', + refund: 'R', + credit: 'C', + capture: 'D', + void: 'U', + quasi: 'Q' } - SUCCESS_CODES = [ '1', 'T' ] + SUCCESS_CODES = %w[1 T] SUCCESS_MESSAGE = 'The transaction was approved' FAILURE_MESSAGE = 'The transaction failed' TEST_LOGIN = '104901072025' @@ -31,7 +31,7 @@ class NetbillingGateway < Gateway self.display_name = 'NETbilling' self.homepage_url = 'http://www.netbilling.com' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] def initialize(options = {}) requires!(options, :login) @@ -166,9 +166,7 @@ def add_payment_source(params, source) end def add_user_data(post, options) - if options[:order_id] - post[:user_data] = "order_id:#{options[:order_id]}" - end + post[:user_data] = "order_id:#{options[:order_id]}" if options[:order_id] end def add_transaction_id(post, transaction_id) @@ -195,15 +193,19 @@ 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, - :test => test_response?(response), - :authorization => response[:trans_id], - :avs_result => { :code => response[:avs_code]}, - :cvv_result => response[:cvv2_code] + 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] ) rescue ActiveMerchant::ResponseError => e - raise unless(e.response.code =~ /^[67]\d\d$/) - return Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) + raise unless e.response.code =~ /^[67]\d\d$/ + + return Response.new(false, e.response.message, { status_code: e.response.code }, test: test?) end def test_response?(response) @@ -224,9 +226,8 @@ def post_data(action, parameters = {}) parameters[:pay_type] = 'C' parameters[:tran_type] = TRANSACTIONS[action] - parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + parameters.reject { |_k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end - end end end diff --git a/lib/active_merchant/billing/gateways/netpay.rb b/lib/active_merchant/billing/gateways/netpay.rb index 48e2a0a14d5..8c2ba44cb30 100644 --- a/lib/active_merchant/billing/gateways/netpay.rb +++ b/lib/active_merchant/billing/gateways/netpay.rb @@ -42,7 +42,7 @@ class NetpayGateway < Gateway self.default_currency = 'MXN' # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.netpay.com.mx' @@ -55,7 +55,7 @@ class NetpayGateway < Gateway } # The header keys that we will provide in the response params hash - RESPONSE_KEYS = ['ResponseMsg', 'ResponseText', 'ResponseCode', 'TimeIn', 'TimeOut', 'AuthCode', 'OrderId', 'CardTypeName', 'MerchantId', 'IssuerAuthDate'] + RESPONSE_KEYS = %w[ResponseMsg ResponseText ResponseCode TimeIn TimeOut AuthCode OrderId CardTypeName MerchantId IssuerAuthDate] def initialize(options = {}) requires!(options, :store_id, :login, :password) @@ -180,8 +180,8 @@ def parse(response, request_params) success = (response_params['ResponseCode'] == '00') message = response_params['ResponseText'] || response_params['ResponseMsg'] - options = @options.merge(:test => test?, - :authorization => build_authorization(request_params, response_params)) + options = @options.merge(test: test?, + authorization: build_authorization(request_params, response_params)) Response.new(success, message, response_params, options) end @@ -215,6 +215,7 @@ def params_from_response(response) def currency_code(currency) return currency if currency =~ /^\d+$/ + CURRENCY_CODES[currency] end end diff --git a/lib/active_merchant/billing/gateways/network_merchants.rb b/lib/active_merchant/billing/gateways/network_merchants.rb index aadc09d425f..a72f133dce0 100644 --- a/lib/active_merchant/billing/gateways/network_merchants.rb +++ b/lib/active_merchant/billing/gateways/network_merchants.rb @@ -4,7 +4,7 @@ class NetworkMerchantsGateway < Gateway self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.nmi.com/' self.display_name = 'Network Merchants (NMI)' @@ -190,7 +190,7 @@ def add_amount(post, money, options) end def commit_vault(action, parameters) - commit(nil, parameters.merge(:customer_vault => action)) + commit(nil, parameters.merge(customer_vault: action)) end def commit(action, parameters) @@ -200,11 +200,14 @@ def commit(action, parameters) authorization = authorization_from(success, parameters, raw) - Response.new(success, raw['responsetext'], raw, - :test => test?, - :authorization => authorization, - :avs_result => { :code => raw['avsresponse']}, - :cvv_result => raw['cvvresponse'] + Response.new( + success, + raw['responsetext'], + raw, + test: test?, + authorization: authorization, + avs_result: { code: raw['avsresponse'] }, + cvv_result: raw['cvvresponse'] ) end @@ -218,9 +221,7 @@ def authorization_from(success, parameters, response) return nil unless success authorization = response['transactionid'] - if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?)) - authorization = response['customer_vault_id'] - end + authorization = response['customer_vault_id'] if parameters[:customer_vault] && (authorization.nil? || authorization.empty?) authorization end diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb index 5b3c452e39f..de09204b839 100644 --- a/lib/active_merchant/billing/gateways/nmi.rb +++ b/lib/active_merchant/billing/gateways/nmi.rb @@ -5,11 +5,11 @@ class NmiGateway < Gateway DUP_WINDOW_DEPRECATION_MESSAGE = 'The class-level duplicate_window variable is deprecated. Please use the :dup_seconds transaction option instead.' - self.test_url = self.live_url = 'https://secure.nmi.com/api/transact.php' + self.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_cardtypes = [:visa, :master, :american_express, :discover] + 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' @@ -23,33 +23,42 @@ def self.duplicate_window end def initialize(options = {}) - requires!(options, :login, :password) + if options.has_key?(:security_key) + requires!(options, :security_key) + else + requires!(options, :login, :password) + end super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method, options) + add_stored_credential(post, options) add_customer_data(post, options) add_vendor_data(post, options) add_merchant_defined_fields(post, options) + add_level3_fields(post, options) + add_three_d_secure(post, options) commit('sale', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method, options) + add_stored_credential(post, options) add_customer_data(post, options) add_vendor_data(post, options) add_merchant_defined_fields(post, options) - + add_level3_fields(post, options) + add_three_d_secure(post, options) commit('auth', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -58,7 +67,7 @@ def capture(amount, authorization, options={}) commit('capture', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) add_payment_type(post, authorization) @@ -66,7 +75,7 @@ def void(authorization, options={}) commit('void', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -75,22 +84,24 @@ def refund(amount, authorization, options={}) commit('refund', post) end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method, options) add_customer_data(post, options) add_vendor_data(post, options) + add_level3_fields(post, options) commit('credit', post) end - def verify(payment_method, options={}) + def verify(payment_method, options = {}) post = {} add_payment_method(post, payment_method, options) add_customer_data(post, options) add_vendor_data(post, options) add_merchant_defined_fields(post, options) + add_level3_fields(post, options) commit('validate', post) end @@ -117,7 +128,8 @@ def supports_scrubbing? def scrub(transcript) transcript. - gsub(%r((password=)\w+), '\1[FILTERED]'). + gsub(%r((password=)[^&\n]*), '\1[FILTERED]'). + gsub(%r((security_key=)[^&\n]*), '\1[FILTERED]'). gsub(%r((ccnumber=)\d+), '\1[FILTERED]'). gsub(%r((cvv=)\d+), '\1[FILTERED]'). gsub(%r((checkaba=)\d+), '\1[FILTERED]'). @@ -131,8 +143,13 @@ def supports_network_tokenization? private + def add_level3_fields(post, options) + add_fields_to_post_if_present(post, options, %i[tax shipping ponumber]) + end + 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) @@ -143,14 +160,14 @@ def add_invoice(post, money, options) end def add_payment_method(post, payment_method, options) - if(payment_method.is_a?(String)) - customer_vault_id, _ = split_authorization(payment_method) + if payment_method.is_a?(String) + customer_vault_id, = split_authorization(payment_method) post[:customer_vault_id] = customer_vault_id elsif payment_method.is_a?(NetworkTokenizationCreditCard) post[:ccnumber] = payment_method.number post[:ccexp] = exp_date(payment_method) post[:token_cryptogram] = payment_method.payment_cryptogram - elsif(card_brand(payment_method) == 'check') + elsif card_brand(payment_method) == 'check' post[:payment] = 'check' post[:firstname] = payment_method.first_name post[:lastname] = payment_method.last_name @@ -170,31 +187,77 @@ def add_payment_method(post, payment_method, options) end end + def add_stored_credential(post, options) + return unless (stored_credential = options[:stored_credential]) + + if stored_credential[:initiator] == 'cardholder' + post[:initiated_by] = 'customer' + else + post[:initiated_by] = 'merchant' + end + + # :reason_type, when provided, overrides anything previously set in + # post[:billing_method] (see `add_invoice` and the :recurring) option + case stored_credential[:reason_type] + when 'recurring' + post[:billing_method] = 'recurring' + when 'installment' + post[:billing_method] = 'installment' + when 'unscheduled' + post.delete(:billing_method) + end + + if stored_credential[:initial_transaction] + post[:stored_credential_indicator] = 'stored' + else + post[:stored_credential_indicator] = 'used' + # 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' + end + end + def add_customer_data(post, options) post[:email] = options[:email] post[:ipaddress] = options[:ip] post[:customer_id] = options[:customer_id] || options[:customer] - if(billing_address = options[:billing_address] || options[:address]) + if (billing_address = options[:billing_address] || options[:address]) post[:company] = billing_address[:company] post[:address1] = billing_address[:address1] post[:address2] = billing_address[:address2] post[:city] = billing_address[:city] post[:state] = billing_address[:state] post[:country] = billing_address[:country] - post[:zip] = billing_address[:zip] + post[:zip] = billing_address[:zip] post[:phone] = billing_address[:phone] end - if(shipping_address = options[:shipping_address]) + 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] post[:shipping_city] = shipping_address[:city] post[:shipping_state] = shipping_address[:state] post[:shipping_country] = shipping_address[:country] - post[:shipping_zip] = shipping_address[:zip] + post[:shipping_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]) + post[:descriptor] = descriptor[:descriptor] + post[:descriptor_phone] = descriptor[:descriptor_phone] + post[:descriptor_address] = descriptor[:descriptor_address] + post[:descriptor_city] = descriptor[:descriptor_city] + post[:descriptor_state] = descriptor[:descriptor_state] + post[:descriptor_postal] = descriptor[:descriptor_postal] + post[:descriptor_country] = descriptor[:descriptor_country] + post[:descriptor_mcc] = descriptor[:descriptor_mcc] + post[:descriptor_merchant_id] = descriptor[:descriptor_merchant_id] + post[:descriptor_url] = descriptor[:descriptor_url] end end @@ -210,8 +273,25 @@ def add_merchant_defined_fields(post, options) end end + def add_three_d_secure(post, options) + three_d_secure = options[:three_d_secure] + return unless three_d_secure + + post[:cardholder_auth] = cardholder_auth(three_d_secure[:authentication_response_status]) + post[:cavv] = three_d_secure[:cavv] + post[:xid] = three_d_secure[:xid] + post[:three_ds_version] = three_d_secure[:version] + post[:directory_server_id] = three_d_secure[:ds_transaction_id] + end + + def cardholder_auth(trans_status) + return nil if trans_status.nil? + + trans_status == 'Y' ? 'verified' : 'attempted' + end + def add_reference(post, authorization) - transaction_id, _ = split_authorization(authorization) + transaction_id, = split_authorization(authorization) post[:transactionid] = transaction_id end @@ -226,9 +306,9 @@ def exp_date(payment_method) def commit(action, params) params[action == 'add_customer' ? :customer_vault : :type] = action - params[:username] = @options[:login] - params[:password] = @options[:password] - + params[:username] = @options[:login] unless @options[:login].nil? + params[:password] = @options[:password] unless @options[:password].nil? + params[:security_key] = @options[:security_key] unless @options[:security_key].nil? raw_response = ssl_post(url, post_data(action, params), headers) response = parse(raw_response) succeeded = success_from(response) @@ -246,7 +326,7 @@ def commit(action, params) def authorization_from(response, payment_type, action) authorization = (action == 'add_customer' ? response[:customer_vault_id] : response[:transactionid]) - [ authorization, payment_type ].join('#') + [authorization, payment_type].join('#') end def split_authorization(authorization) @@ -254,7 +334,8 @@ def split_authorization(authorization) end def headers - { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + headers = { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + headers end def post_data(action, params) @@ -280,7 +361,6 @@ def message_from(succeeded, response) response[:responsetext] end end - end end end diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb index 883ac94a05a..898c2ca8cd9 100644 --- a/lib/active_merchant/billing/gateways/ogone.rb +++ b/lib/active_merchant/billing/gateways/ogone.rb @@ -122,10 +122,10 @@ class OgoneGateway < Gateway SUCCESS_MESSAGE = 'The transaction was successful' - THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window (default value). + THREE_D_SECURE_DISPLAY_WAYS = { main_window: 'MAINW', # display the identification page in the main window (default value). - :pop_up => 'POPUP', # display the identification page in a pop-up window and return to the main window at the end. - :pop_ix => 'POPIX' } # display the identification page in a pop-up window and remain in the pop-up window. + pop_up: 'POPUP', # display the identification page in a pop-up window and return to the main window at the end. + pop_ix: 'POPIX' } # display the identification page in a pop-up window and remain in the pop-up window. OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = 'Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)' OGONE_STORE_OPTION_DEPRECATION_MESSAGE = "The 'store' option has been renamed to 'billing_id', and its usage is deprecated." @@ -133,10 +133,10 @@ class OgoneGateway < Gateway self.test_url = 'https://secure.ogone.com/ncol/test/' self.live_url = 'https://secure.ogone.com/ncol/prod/' - self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH'] + self.supported_countries = %w[BE DE FR NL AT CH] # also supports Airplus and UATP - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] - self.homepage_url = 'http://www.ogone.com/' + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb maestro] + self.homepage_url = 'https://www.ingenico.com/login/ogone/' self.display_name = 'Ogone' self.default_currency = 'EUR' self.money_format = :cents @@ -204,7 +204,7 @@ def refund(money, reference, options = {}) perform_reference_credit(money, reference, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -213,7 +213,7 @@ def verify(credit_card, options={}) # Store a credit card by creating an Ogone Alias def store(payment_source, options = {}) - options[:alias_operation] = 'BYPSP' unless(options.has_key?(:billing_id) || options.has_key?(:store)) + options[:alias_operation] = 'BYPSP' unless options.has_key?(:billing_id) || options.has_key?(:store) response = authorize(@options[:store_amount] || 1, payment_source, options) void(response.authorization) if response.success? response @@ -239,6 +239,7 @@ def reference_from(authorization) def reference_transaction?(identifier) return false unless identifier.is_a?(String) + _, action = identifier.split(';') !action.nil? end @@ -262,8 +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') @@ -284,17 +284,46 @@ def add_d3d(post, options) THREE_D_SECURE_DISPLAY_WAYS[options[:win_3ds]] : THREE_D_SECURE_DISPLAY_WAYS[:main_window] add_pair post, 'WIN3DS', win_3ds - - add_pair post, 'HTTP_ACCEPT', options[:http_accept] || '*/*' add_pair post, 'HTTP_USER_AGENT', options[:http_user_agent] if options[:http_user_agent] add_pair post, 'ACCEPTURL', options[:accept_url] if options[:accept_url] add_pair post, 'DECLINEURL', options[:decline_url] if options[:decline_url] add_pair post, 'EXCEPTIONURL', options[:exception_url] if options[:exception_url] add_pair post, 'CANCELURL', options[:cancel_url] if options[:cancel_url] - add_pair post, 'PARAMVAR', options[:paramvar] if options[:paramvar] + add_pair post, 'PARAMVAR', options[:paramvar] if options[:paramvar] add_pair post, 'PARAMPLUS', options[:paramplus] if options[:paramplus] add_pair post, 'COMPLUS', options[:complus] if options[:complus] add_pair post, 'LANGUAGE', options[:language] if options[:language] + if options[:three_ds_2] + browser_info = options[:three_ds_2][:browser_info] + ecom_postal = options[:billing_address] + if browser_info + add_pair post, 'BROWSERACCEPTHEADER', browser_info[:accept_header] + add_pair post, 'BROWSERCOLORDEPTH', browser_info[:depth] + + # for 3ds v2.1 to v2.2 add BROWSERJAVASCRIPTENABLED: This boolean indicates whether your customers have enabled JavaScript in their browsers when making a purchase. + # the following BROWSER parameters will remain mandatory unless browser_info[:javascript] = false + # her documentation https://epayments-support.ingenico.com/en/integration-solutions/integrations/directlink#directlink_integration_guides_secure_payment_with_3_d_secure + add_pair post, 'BROWSERJAVASCRIPTENABLED', browser_info[:javascript] + add_pair post, 'BROWSERJAVAENABLED', 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, 'BROWSERTIMEZONE', browser_info[:timezone] + add_pair post, 'BROWSERUSERAGENT', browser_info[:user_agent] + end + # recommended + if ecom_postal + add_pair post, 'ECOM_BILLTO_POSTAL_CITY', ecom_postal[:city] + add_pair post, 'ECOM_BILLTO_POSTAL_COUNTRYCODE', ecom_postal[:country] + add_pair post, 'ECOM_BILLTO_POSTAL_STREET_LINE1', ecom_postal[:address1] + add_pair post, 'ECOM_BILLTO_POSTAL_STREET_LINE2', ecom_postal[:address2] + add_pair post, 'ECOM_BILLTO_POSTAL_POSTALCODE', ecom_postal[:zip] + end + # optional + add_pair post, 'Mpi.threeDSRequestorChallengeIndicator', options[:three_ds_reqchallengeind] + else + add_pair post, 'HTTP_ACCEPT', options[:http_accept] || '*/*' + end end def add_eci(post, eci) @@ -322,6 +351,7 @@ def add_customer_data(post, options) def add_address(post, creditcard, options) return unless options[:billing_address] + add_pair post, 'Owneraddress', options[:billing_address][:address1] add_pair post, 'OwnerZip', options[:billing_address][:zip] add_pair post, 'ownertown', options[:billing_address][:city] @@ -364,10 +394,10 @@ def commit(action, parameters) response = parse(ssl_post(url(parameters['PAYID']), post_data(action, parameters))) options = { - :authorization => [response['PAYID'], action].join(';'), - :test => test?, - :avs_result => { :code => AVS_MAPPING[response['AAVCheck']] }, - :cvv_result => CVV_MAPPING[response['CVCCheck']] + authorization: [response['PAYID'], action].join(';'), + test: test?, + avs_result: { code: AVS_MAPPING[response['AAVCheck']] }, + cvv_result: CVV_MAPPING[response['CVCCheck']] } OgoneResponse.new(successful?(response), message_from(response), response, options) end @@ -408,30 +438,31 @@ def post_data(action, parameters = {}) def add_signature(parameters) if @options[:signature].blank? - ActiveMerchant.deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == 'none') + ActiveMerchant.deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless @options[:signature_encryptor] == 'none' return end - add_pair parameters, 'SHASign', calculate_signature(parameters, @options[:signature_encryptor], @options[:signature]) + add_pair parameters, 'SHASIGN', calculate_signature(parameters, @options[:signature_encryptor], @options[:signature]) end def calculate_signature(signed_parameters, algorithm, secret) return legacy_calculate_signature(signed_parameters, secret) unless algorithm - sha_encryptor = case algorithm - when 'sha256' - Digest::SHA256 - when 'sha512' - Digest::SHA512 - when 'sha1' - Digest::SHA1 - else - raise "Unknown signature algorithm #{algorithm}" - end + sha_encryptor = + case algorithm + when 'sha256' + Digest::SHA256 + when 'sha512' + Digest::SHA512 + when 'sha1' + Digest::SHA1 + else + raise "Unknown signature algorithm #{algorithm}" + end - filtered_params = signed_parameters.select { |k, v| !v.blank? } + filtered_params = signed_parameters.compact sha_encryptor.hexdigest( - filtered_params.sort_by { |k, v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('') + filtered_params.sort_by { |k, _v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('') ).upcase end @@ -453,7 +484,7 @@ def legacy_calculate_signature(parameters, secret) end def add_pair(post, key, value) - post[key] = value if !value.blank? + post[key] = value unless value.nil? end def convert_attributes_to_hash(rexml_attributes) @@ -463,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/omise.rb b/lib/active_merchant/billing/gateways/omise.rb index 218b099590f..a53880fe4f4 100644 --- a/lib/active_merchant/billing/gateways/omise.rb +++ b/lib/active_merchant/billing/gateways/omise.rb @@ -21,13 +21,13 @@ class OmiseGateway < Gateway # Country supported by Omise # * Thailand - self.supported_countries = %w( TH JP ) + self.supported_countries = %w(TH JP) # Credit cards supported by Omise # * VISA # * MasterCard # * JCB - self.supported_cardtypes = [:visa, :master, :jcb] + self.supported_cardtypes = %i[visa master jcb] # Omise main page self.homepage_url = 'https://www.omise.co/' @@ -46,7 +46,7 @@ class OmiseGateway < Gateway # * :api_version -- Omise's API Version (OPTIONAL), default version is '2014-07-27' # See version at page https://dashboard.omise.co/api-version/edit - def initialize(options={}) + def initialize(options = {}) requires!(options, :public_key, :secret_key) @public_key = options[:public_key] @secret_key = options[:secret_key] @@ -79,7 +79,7 @@ def initialize(options={}) # # purchase(money, nil, { :customer_id => customer_id }) - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) create_charge(money, payment_method, options) end @@ -91,7 +91,7 @@ def purchase(money, payment_method, options={}) # * payment_method -- The CreditCard object # * options -- An optional parameters, such as token or capture - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) options[:capture] = 'false' create_charge(money, payment_method, options) end @@ -104,7 +104,7 @@ def authorize(money, payment_method, options={}) # * charge_id -- The CreditCard object # * options -- An optional parameters, such as token or capture - def capture(money, charge_id, options={}) + def capture(money, charge_id, options = {}) post = {} add_amount(post, money, options) commit(:post, "charges/#{CGI.escape(charge_id)}/capture", post, options) @@ -118,7 +118,7 @@ def capture(money, charge_id, options={}) # * charge_id -- The CreditCard object # * options -- An optional parameters, such as token or capture - def refund(money, charge_id, options={}) + def refund(money, charge_id, options = {}) options[:amount] = money if money commit(:post, "charges/#{CGI.escape(charge_id)}/refunds", options) end @@ -132,7 +132,7 @@ def refund(money, charge_id, options={}) # 'email' (A customer email) # 'description' (A customer description) - def store(payment_method, options={}) + def store(payment_method, options = {}) post, card_params = {}, {} add_customer_data(post, options) add_token(card_params, payment_method, options) @@ -145,7 +145,7 @@ def store(payment_method, options={}) # # * customer_id -- The Customer identifier (REQUIRED). - def unstore(customer_id, options={}) + def unstore(customer_id, options = {}) commit(:delete, "customers/#{CGI.escape(customer_id)}") end @@ -178,7 +178,7 @@ def create_charge(money, payment_method, options) commit(:post, 'charges', post, options) end - def headers(options={}) + def headers(options = {}) key = options[:key] || @secret_key { 'Content-Type' => 'application/json;utf-8', @@ -197,7 +197,7 @@ def post_data(parameters) parameters.present? ? parameters.to_json : nil end - def https_request(method, endpoint, parameters=nil, options={}) + def https_request(method, endpoint, parameters = nil, options = {}) raw_response = response = nil begin raw_response = ssl_request(method, url_for(endpoint), post_data(parameters), headers(options)) @@ -221,7 +221,7 @@ def json_error(raw_response) { message: msg } end - def commit(method, endpoint, params=nil, options={}) + def commit(method, endpoint, params = nil, options = {}) response = https_request(method, endpoint, params, options) Response.new( successful?(response), @@ -284,7 +284,7 @@ def get_token(post, credit_card) commit(:post, 'tokens', post, { key: @public_key }) end - def add_token(post, credit_card, options={}) + def add_token(post, credit_card, options = {}) if options[:token_id].present? post[:card] = options[:token_id] else @@ -304,11 +304,11 @@ def add_creditcard(post, payment_method) post[:card] = card end - def add_customer(post, options={}) + def add_customer(post, options = {}) post[:customer] = options[:customer_id] if options[:customer_id] end - def add_customer_data(post, options={}) + def add_customer_data(post, options = {}) post[:description] = options[:description] if options[:description] post[:email] = options[:email] if options[:email] end @@ -318,7 +318,6 @@ def add_amount(post, money, options) post[:currency] = (options[:currency] || currency(money)) post[:description] = options[:description] if options.key?(:description) end - end end end diff --git a/lib/active_merchant/billing/gateways/openpay.rb b/lib/active_merchant/billing/gateways/openpay.rb index 166f1f44b43..e655a5b599a 100644 --- a/lib/active_merchant/billing/gateways/openpay.rb +++ b/lib/active_merchant/billing/gateways/openpay.rb @@ -1,11 +1,18 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class OpenpayGateway < Gateway - self.live_url = 'https://api.openpay.mx/v1/' - self.test_url = 'https://sandbox-api.openpay.mx/v1/' - - self.supported_countries = ['MX'] - self.supported_cardtypes = [:visa, :master, :american_express, :carnet] + class_attribute :mx_live_url, :mx_test_url + class_attribute :co_live_url, :co_test_url + + self.co_live_url = 'https://api.openpay.co/v1/' + self.co_test_url = 'https://sandbox-api.openpay.co/v1/' + self.mx_live_url = 'https://api.openpay.mx/v1/' + self.mx_test_url = 'https://sandbox-api.openpay.mx/v1/' + self.live_url = self.co_live_url + self.test_url = self.co_test_url + + self.supported_countries = %w(CO MX) + self.supported_cardtypes = %i[visa master american_express carnet] self.homepage_url = 'http://www.openpay.mx/' self.display_name = 'Openpay' self.default_currency = 'MXN' @@ -24,6 +31,16 @@ def initialize(options = {}) super end + def gateway_url(options = {}) + country = options[:merchant_country] || @options[:merchant_country] + + if country == 'MX' + test? ? mx_test_url : mx_live_url + else + test? ? co_test_url : co_live_url + end + end + def purchase(money, creditcard, options = {}) post = create_post_for_auth_or_purchase(money, creditcard, options) commit(:post, 'charges', post, options) @@ -75,7 +92,7 @@ def store(creditcard, options = {}) MultiResponse.run(:first) do |r| r.process { commit(:post, 'customers', post, options) } - if(r.success? && !r.params['id'].blank?) + if r.success? && !r.params['id'].blank? customer_id = r.params['id'] r.process { commit(:post, "customers/#{customer_id}/cards", card, options) } end @@ -116,7 +133,7 @@ def create_post_for_auth_or_purchase(money, creditcard, options) post[:device_session_id] = options[:device_session_id] post[:currency] = (options[:currency] || currency(money)).upcase post[:use_card_points] = options[:use_card_points] if options[:use_card_points] - post[:payment_plan] = {payments: options[:payments]} if options[:payments] + post[:payment_plan] = { payments: options[:payments] } if options[:payments] add_creditcard(post, creditcard, options) post end @@ -151,6 +168,7 @@ def add_customer_data(post, creditcard, options) def add_address(card, options) return unless card.kind_of?(Hash) + if address = (options[:billing_address] || options[:address]) card[:address] = { line1: address[:address1], @@ -175,6 +193,7 @@ def headers(options = {}) def parse(body) return {} unless body + JSON.parse(body) end @@ -182,16 +201,17 @@ 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'] + test: test?, + authorization: response['id'] ) end - def http_request(method, resource, parameters={}, options={}) - url = (test? ? self.test_url : self.live_url) + @merchant_id + '/' + resource + def http_request(method, resource, parameters = {}, options = {}) + url = gateway_url(options) + @merchant_id + '/' + resource raw_response = nil begin raw_response = ssl_request(method, url, (parameters ? parameters.to_json : nil), headers(options)) @@ -218,9 +238,9 @@ def json_error(raw_response) msg = 'Invalid response received from the Openpay API. Please contact soporte@openpay.mx if you continue to receive this message.' msg += " (The raw response returned by the API was #{raw_response.inspect})" { - 'category' => 'request', - 'error_code' => '9999', - 'description' => msg + 'category' => 'request', + 'error_code' => '9999', + 'description' => msg } end end diff --git a/lib/active_merchant/billing/gateways/opp.rb b/lib/active_merchant/billing/gateways/opp.rb index 8c82574a670..38d1f3b3e18 100644 --- a/lib/active_merchant/billing/gateways/opp.rb +++ b/lib/active_merchant/billing/gateways/opp.rb @@ -13,8 +13,7 @@ class OppGateway < Gateway # == Usage # # gateway = ActiveMerchant::Billing::OppGateway.new( - # user_id: 'merchant user id', - # password: 'password', + # access_token: 'access_token', # entity_id: 'entity id', # ) # @@ -113,62 +112,75 @@ class OppGateway < Gateway self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IN IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY) self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :dankort] + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb maestro dankort] self.homepage_url = 'https://docs.oppwa.com' self.display_name = 'Open Payment Platform' - def initialize(options={}) - requires!(options, :user_id, :password, :entity_id) + def initialize(options = {}) + requires!(options, :access_token, :entity_id) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) # debit - execute_dbpa(options[:risk_workflow] ? 'PA.CP': 'DB', - money, payment, options) + options[:registrationId] = payment if payment.is_a?(String) + execute_dbpa(options[:risk_workflow] ? 'PA.CP' : 'DB', money, payment, options) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) # preauthorization PA execute_dbpa('PA', money, payment, options) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) # capture CP execute_referencing('CP', money, authorization, options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) # refund RF execute_referencing('RF', money, authorization, options) end - def void(authorization, options={}) + def void(authorization, options = {}) # reversal RV execute_referencing('RV', nil, authorization, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end + def store(credit_card, options = {}) + execute_store(credit_card, options.merge(store: true)) + end + def supports_scrubbing? true end def scrub(transcript) transcript. - gsub(%r((authentication\.password=)\w+), '\1[FILTERED]'). + gsub(%r((Authorization: Bearer )\w+)i, '\1[FILTERED]'). gsub(%r((card\.number=)\d+), '\1[FILTERED]'). gsub(%r((card\.cvv=)\d+), '\1[FILTERED]') end private + def execute_store(payment, options) + post = {} + add_payment_method(post, payment, options) + add_address(post, options) + add_options(post, options) + add_3d_secure(post, options) + commit(post, nil, options) + end + def execute_dbpa(txtype, money, payment, options) post = {} post[:paymentType] = txtype @@ -189,7 +201,7 @@ def execute_referencing(txtype, money, authorization, options) end def add_authentication(post) - post[:authentication] = { entityId: @options[:entity_id], password: @options[:password], userId: @options[:user_id]} + post[:authentication] = { entityId: @options[:entity_id] } end def add_customer_data(post, payment, options) @@ -230,7 +242,7 @@ def address(post, address, prefix) city: address[:city], state: address[:state], postcode: address[:zip], - country: address[:country], + country: address[:country] } end @@ -243,9 +255,11 @@ def add_invoice(post, money, options) end def add_payment_method(post, payment, options) + return if payment.is_a?(String) + if options[:registrationId] post[:card] = { - cvv: payment.verification_value, + cvv: payment.verification_value } else post[:paymentBrand] = payment.brand.upcase @@ -254,7 +268,7 @@ def add_payment_method(post, payment, options) number: payment.number, expiryMonth: '%02d' % payment.month, expiryYear: payment.year, - cvv: payment.verification_value, + cvv: payment.verification_value } end end @@ -272,13 +286,15 @@ def add_3d_secure(post, options) def add_options(post, options) post[:createRegistration] = options[:create_registration] if options[:create_registration] && !options[:registrationId] post[:testMode] = options[:test_mode] if test? && options[:test_mode] - options.each { |key, value| post[key] = value if key.to_s.match('customParameters\[[a-zA-Z0-9\._]{3,64}\]') } + options.each { |key, value| post[key] = value if key.to_s =~ /'customParameters\[[a-zA-Z0-9\._]{3,64}\]'/ } post['customParameters[SHOPPER_pluginId]'] = 'activemerchant' post['customParameters[custom_disable3DSecure]'] = options[:disable_3d_secure] if options[:disable_3d_secure] end def build_url(url, authorization, options) - if options[:registrationId] + if options[:store] + url.gsub(/payments/, 'registrations') + elsif options[:registrationId] "#{url.gsub(/payments/, 'registrations')}/#{options[:registrationId]}/payments" elsif authorization "#{url}/#{authorization}" @@ -292,17 +308,18 @@ def commit(post, authorization, options) add_authentication(post) post = flatten_hash(post) - response = begin - parse( - ssl_post( - url, - post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'), - 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + response = + begin + parse( + ssl_post( + url, + post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'), + headers + ) ) - ) - rescue ResponseError => e - parse(e.response.body) - end + rescue ResponseError => e + parse(e.response.body) + end success = success_from(response) @@ -316,6 +333,13 @@ def commit(post, authorization, options) ) end + def headers + { + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8', + 'Authorization' => "Bearer #{@options[:access_token]}" + } + end + def parse(body) JSON.parse(body) rescue JSON::ParserError @@ -324,7 +348,7 @@ def parse(body) def json_error(body) message = "Invalid response received #{body.inspect}" - { 'result' => {'description' => message, 'code' => 'unknown' } } + { 'result' => { 'description' => message, 'code' => 'unknown' } } end def success_from(response) @@ -332,7 +356,7 @@ def success_from(response) success_regex = /^(000\.000\.|000\.100\.1|000\.[36])/ - if success_regex =~ response['result']['code'] + if success_regex.match?(response['result']['code']) true else false diff --git a/lib/active_merchant/billing/gateways/optimal_payment.rb b/lib/active_merchant/billing/gateways/optimal_payment.rb index 30f7bf57e02..8ec37ff413d 100644 --- a/lib/active_merchant/billing/gateways/optimal_payment.rb +++ b/lib/active_merchant/billing/gateways/optimal_payment.rb @@ -5,12 +5,12 @@ class OptimalPaymentGateway < Gateway self.live_url = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['CA', 'US', 'GB', 'AU', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', - 'EE', 'FI', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', - 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH'] + self.supported_countries = %w[CA US GB AU AT BE BG HR CY CZ DK + EE FI DE GR HU IE IT LV LT LU MT + NL NO PL PT RO SK SI ES SE CH] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.optimalpayments.com/' @@ -19,12 +19,12 @@ class OptimalPaymentGateway < Gateway self.display_name = 'Optimal Payments' def initialize(options = {}) - if(options[:login]) + if options[:login] ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.") options[:store_id] = options[:login] end - if(options[:account]) + if options[:account] ActiveMerchant.deprecated("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.") options[:account_number] = options[:account] end @@ -65,6 +65,10 @@ def verify(credit_card, options = {}) commit('ccVerification', 0, options) end + def store(credit_card, options = {}) + verify(credit_card, options) + end + def supports_scrubbing? true end @@ -95,32 +99,36 @@ def parse(body) def commit(action, money, post) post[:order_id] ||= 'order_id' - xml = case action - when 'ccAuthorize', 'ccPurchase', 'ccVerification' - cc_auth_request(money, post) - when 'ccCredit', 'ccSettlement' - cc_post_auth_request(money, post) - when 'ccStoredDataAuthorize', 'ccStoredDataPurchase' - cc_stored_data_request(money, post) - when 'ccAuthorizeReversal' - cc_auth_reversal_request(post) - # when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment' - # cc_cancel_request(money, post) - # when 'ccPayment' - # cc_payment_request(money, post) - # when 'ccAuthenticate' - # cc_authenticate_request(money, post) - else - raise 'Unknown Action' - end + xml = + case action + when 'ccAuthorize', 'ccPurchase', 'ccVerification' + cc_auth_request(money, post) + when 'ccCredit', 'ccSettlement' + cc_post_auth_request(money, post) + when 'ccStoredDataAuthorize', 'ccStoredDataPurchase' + cc_stored_data_request(money, post) + when 'ccAuthorizeReversal' + cc_auth_reversal_request(post) + # when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment' + # cc_cancel_request(money, post) + # when 'ccPayment' + # cc_payment_request(money, post) + # when 'ccAuthenticate' + # cc_authenticate_request(money, post) + else + raise 'Unknown Action' + end 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), - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => avs_result_from(response) }, - :cvv_result => cvv_result_from(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) ) end @@ -135,9 +143,7 @@ def successful?(response) def message_from(response) REXML::XPath.each(response, '//detail') do |detail| - if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription' - return detail.elements['value'].text - end + return detail.elements['value'].text if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription' end nil end @@ -159,13 +165,13 @@ def hash_from_xml(response) %w(confirmationNumber authCode decision code description actionCode avsResponse cvdResponse - txnTime duplicateFound - ).each do |tag| + txnTime duplicateFound).each do |tag| node = REXML::XPath.first(response, "//#{tag}") hsh[tag] = node.text if node end REXML::XPath.each(response, '//detail') do |detail| next unless detail.is_a?(REXML::Element) + tag = detail.elements['tag'].text value = detail.elements['value'].text hsh[tag] = value @@ -174,7 +180,7 @@ def hash_from_xml(response) end def xml_document(root_tag) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag!(root_tag, schema) do yield xml end @@ -190,7 +196,7 @@ def cc_auth_request(money, opts) xml_document('ccAuthRequestV1') do |xml| build_merchant_account(xml) xml.merchantRefNum opts[:order_id] - xml.amount(money/100.0) + xml.amount(money / 100.0) build_card(xml, opts) build_billing_details(xml, opts) build_shipping_details(xml, opts) @@ -211,7 +217,7 @@ def cc_post_auth_request(money, opts) build_merchant_account(xml) xml.confirmationNumber opts[:confirmationNumber] xml.merchantRefNum opts[:order_id] - xml.amount(money/100.0) + xml.amount(money / 100.0) end end @@ -220,7 +226,7 @@ def cc_stored_data_request(money, opts) build_merchant_account(xml) xml.merchantRefNum opts[:order_id] xml.confirmationNumber opts[:confirmationNumber] - xml.amount(money/100.0) + xml.amount(money / 100.0) end end @@ -254,8 +260,7 @@ def cc_stored_data_request(money, opts) def schema { 'xmlns' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:schemaLocation' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1' - } + 'xsi:schemaLocation' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1' } end def build_merchant_account(xml) @@ -321,12 +326,10 @@ def build_address(xml, addr) def card_type(key) { 'visa' => 'VI', 'master' => 'MC', - 'american_express'=> 'AM', + 'american_express' => 'AM', 'discover' => 'DI', - 'diners_club' => 'DC', - }[key] + 'diners_club' => 'DC' }[key] end - end end end diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb index 93246833526..5af61e29213 100644 --- a/lib/active_merchant/billing/gateways/orbital.rb +++ b/lib/active_merchant/billing/gateways/orbital.rb @@ -30,11 +30,11 @@ module Billing #:nodoc: class OrbitalGateway < Gateway include Empty - API_VERSION = '7.7' + API_VERSION = '9.0' POST_HEADERS = { 'MIME-Version' => '1.1', - 'Content-Type' => "application/PTI#{API_VERSION.gsub(/\./, '')}", + 'Content-Type' => "application/PTI#{API_VERSION.delete('.')}", 'Content-transfer-encoding' => 'text', 'Request-number' => '1', 'Document-type' => 'Request', @@ -42,6 +42,7 @@ class OrbitalGateway < Gateway } SUCCESS = '0' + APPROVAL_SUCCESS = '1' APPROVED = [ '00', # Approved @@ -60,7 +61,8 @@ class OrbitalGateway < Gateway '93', # Approved high fraud '94', # Approved fraud service unavailable 'E7', # Stored - 'PA' # Partial approval + 'PA', # Partial approval + 'P1' # ECP - AVS - Account Status Verification and/or AOA data is in a positive status. ] class_attribute :secondary_test_url, :secondary_live_url @@ -71,16 +73,16 @@ class OrbitalGateway < Gateway self.live_url = 'https://orbital1.chasepaymentech.com/authorize' self.secondary_live_url = 'https://orbital2.chasepaymentech.com/authorize' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w[US CA] self.default_currency = 'CAD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.display_name = 'Orbital Paymentech' self.homepage_url = 'http://chasepaymentech.com/' self.money_format = :cents - AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB'] + AVS_SUPPORTED_COUNTRIES = %w[US CA UK GB] CURRENCY_CODES = { 'AUD' => '036', @@ -96,6 +98,7 @@ class OrbitalGateway < Gateway 'NZD' => '554', 'NOK' => '578', 'SGD' => '702', + 'ZAR' => '710', 'SEK' => '752', 'CHF' => '756', 'GBP' => '826', @@ -117,6 +120,7 @@ class OrbitalGateway < Gateway 'NZD' => '2', 'NOK' => '2', 'SGD' => '2', + 'ZAR' => '2', 'SEK' => '2', 'CHF' => '2', 'GBP' => '2', @@ -181,75 +185,101 @@ class OrbitalGateway < Gateway USE_ORDER_ID = 'O' # Use OrderID field USE_COMMENTS = 'D' # Use Comments field - SENSITIVE_FIELDS = [:account_num, :cc_account_num] + SENSITIVE_FIELDS = %i[account_num cc_account_num] + + # Bank account types to be used for check processing + ACCOUNT_TYPE = { + 'savings' => 'S', + '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] super @options[:merchant_id] = @options[:merchant_id].to_s + @use_secondary_url = false end # A – Authorization request - def authorize(money, creditcard, options = {}) - order = build_new_order_xml(AUTH_ONLY, money, creditcard, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) - add_address(xml, creditcard, options) - if @options[:customer_profiles] - add_customer_data(xml, creditcard, options) - add_managed_billing(xml, options) - end - end - commit(order, :authorize, options[:trace_number]) + def authorize(money, payment_source, options = {}) + # ECP for Orbital requires $0 prenotes so ensure + # if we are doing a force capture with a check, that + # we do a purchase here + return purchase(money, payment_source, options) if force_capture_with_echeck?(payment_source, options) + + order = build_new_auth_purchase_order(AUTH_ONLY, money, payment_source, options) + + commit(order, :authorize, options[:retry_logic], options[:trace_number]) end - def verify(creditcard, options = {}) + def verify(credit_card, options = {}) + amount = options[:verify_amount] ? options[:verify_amount].to_i : default_verify_amount(credit_card) MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, creditcard, options) } - r.process(:ignore_result) { void(r.authorization) } + r.process { authorize(amount, credit_card, options) } + r.process(:ignore_result) { void(r.authorization) } unless amount == 0 end end # AC – Authorization and Capture - def purchase(money, creditcard, options = {}) - order = build_new_order_xml(AUTH_AND_CAPTURE, money, creditcard, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) - add_address(xml, creditcard, options) - if @options[:customer_profiles] - add_customer_data(xml, creditcard, options) - add_managed_billing(xml, options) - end - end - commit(order, :purchase, options[:trace_number]) + def purchase(money, payment_source, options = {}) + action = options[:force_capture] ? FORCE_AUTH_AND_CAPTURE : AUTH_AND_CAPTURE + order = build_new_auth_purchase_order(action, money, payment_source, options) + + commit(order, :purchase, options[:retry_logic], options[:trace_number]) end # MFC - Mark For Capture def capture(money, authorization, options = {}) - commit(build_mark_for_capture_xml(money, authorization, options), :capture) + commit(build_mark_for_capture_xml(money, authorization, options), :capture, options[:retry_logic], options[:trace_number]) end # R – Refund request def refund(money, authorization, options = {}) - order = build_new_order_xml(REFUND, money, nil, options.merge(:authorization => authorization)) do |xml| - add_refund(xml, options[:currency]) + payment_method = options[:payment_method] + order = build_new_order_xml(REFUND, money, payment_method, options.merge(authorization: authorization)) do |xml| + add_payment_source(xml, payment_method, options) xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn] end - commit(order, :refund, options[:trace_number]) + + commit(order, :refund, options[:retry_logic], options[:trace_number]) + end + + def credit(money, payment_method, options = {}) + order = build_new_order_xml(REFUND, money, payment_method, options) do |xml| + add_payment_source(xml, payment_method, options) + end + + commit(order, :refund, options[:retry_logic], options[:trace_number]) end - def credit(money, authorization, options= {}) - ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, authorization, options) + # 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)) + 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.') - return void(options, deprecated.merge(:amount => authorization)) + return void(options, deprecated.merge(amount: authorization)) end order = build_void_request_xml(authorization, options) - commit(order, :void, options[:trace_number]) + + commit(order, :void, options[:retry_logic], options[:trace_number]) + end + + def default_verify_amount(credit_card) + allow_zero_auth?(credit_card) ? 0 : 100 + end + + def allow_zero_auth?(credit_card) + # Discover does not support a $0.00 authorization instead use $1.00 + %w(visa master american_express diners_club jcb).include?(credit_card.brand) end # ==== Customer Profiles @@ -273,30 +303,34 @@ def void(authorization, options = {}, deprecated = {}) # 'I' - Inactive # 'MS' - Manual Suspend - def add_customer_profile(creditcard, options = {}) + def add_customer_profile(credit_card, options = {}) options[:customer_profile_action] = CREATE - order = build_customer_request_xml(creditcard, options) + order = build_customer_request_xml(credit_card, options) commit(order, :add_customer_profile) end - def update_customer_profile(creditcard, options = {}) + def update_customer_profile(credit_card, options = {}) options[:customer_profile_action] = UPDATE - order = build_customer_request_xml(creditcard, options) + order = build_customer_request_xml(credit_card, options) commit(order, :update_customer_profile) end def retrieve_customer_profile(customer_ref_num) - options = {:customer_profile_action => RETRIEVE, :customer_ref_num => customer_ref_num} + options = { customer_profile_action: RETRIEVE, customer_ref_num: customer_ref_num } order = build_customer_request_xml(nil, options) commit(order, :retrieve_customer_profile) end def delete_customer_profile(customer_ref_num) - options = {:customer_profile_action => DELETE, :customer_ref_num => customer_ref_num} + options = { customer_profile_action: DELETE, customer_ref_num: customer_ref_num } order = build_customer_request_xml(nil, options) commit(order, :delete_customer_profile) end + def supports_network_tokenization? + true + end + def supports_scrubbing? true end @@ -306,14 +340,55 @@ def scrub(transcript) gsub(%r(().+()), '\1[FILTERED]\2'). gsub(%r(().+()), '\1[FILTERED]\2'). gsub(%r(().+()), '\1[FILTERED]\2'). - gsub(%r(().+()), '\1[FILTERED]\2'). + # the response sometimes contains a new line that cuts off the end of the closing tag + gsub(%r(().+().+()), '\1[FILTERED]\2'). gsub(%r(().+()), '\1[FILTERED]\2'). - gsub(%r(().+()), '\1[FILTERED]\2') + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') end private + def force_capture_with_echeck?(payment_source, options) + return false unless options[:force_capture] + return false unless payment_source.is_a?(Check) + + %w(W8 W9 ND).include?(options[:action_code]) + end + + #=====REFERENCE FIELDS===== + + def add_customer_data(xml, credit_card, options) + add_customer_ref_num(xml, options) + + return if options[:profile_txn] + + xml.tag! :CustomerProfileFromOrderInd, profile_number(options) if add_profile_number?(options, credit_card) + xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA + end + + def add_profile_number?(options, credit_card) + return true unless options[:customer_ref_num] && credit_card.nil? + end + + def profile_number(options) + options[:customer_ref_num] ? USE_CUSTOMER_REF_NUM : AUTO_GENERATE + end + + def add_customer_ref_num(xml, options) + xml.tag! :CustomerRefNum, options[:customer_ref_num] if options[:customer_ref_num] + end + + def add_tx_ref_num(xml, authorization) + return unless authorization + + xml.tag! :TxRefNum, split_authorization(authorization).first + end + def authorization_string(*args) args.compact.join(';') end @@ -322,23 +397,22 @@ def split_authorization(authorization) authorization.split(';') end - def add_customer_data(xml, creditcard, options) - if options[:profile_txn] - xml.tag! :CustomerRefNum, options[:customer_ref_num] - else - if options[:customer_ref_num] - if creditcard - xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM - end - xml.tag! :CustomerRefNum, options[:customer_ref_num] - else - xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE - end - xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA - end + #=====DESCRIPTOR FIELDS===== + + def add_soft_descriptors(xml, descriptors) + return unless descriptors + + add_soft_descriptors_from_specialized_class(xml, descriptors) if descriptors.is_a?(OrbitalSoftDescriptors) + add_soft_descriptors_from_hash(xml, descriptors) if descriptors.is_a?(Hash) + end + + def add_payment_action_ind(xml, payment_action_ind) + return unless payment_action_ind + + xml.tag! :PaymentActionInd, payment_action_ind end - def add_soft_descriptors(xml, soft_desc) + def add_soft_descriptors_from_specialized_class(xml, soft_desc) xml.tag! :SDMerchantName, soft_desc.merchant_name if soft_desc.merchant_name xml.tag! :SDProductDescription, soft_desc.product_description if soft_desc.product_description xml.tag! :SDMerchantCity, soft_desc.merchant_city if soft_desc.merchant_city @@ -356,95 +430,175 @@ def add_soft_descriptors_from_hash(xml, soft_desc) xml.tag! :SDMerchantEmail, soft_desc[:merchant_email] || nil end - def add_level_2_tax(xml, options={}) - if (level_2 = options[:level_2_data]) - xml.tag! :TaxInd, level_2[:tax_indicator] if [TAX_NOT_PROVIDED, TAX_INCLUDED, NON_TAXABLE_TRANSACTION].include?(level_2[:tax_indicator].to_i) - xml.tag! :Tax, level_2[:tax].to_i if level_2[:tax] + def add_level2_tax(xml, options = {}) + if (level2 = options[:level_2_data]) + xml.tag! :TaxInd, level2[:tax_indicator] if [TAX_NOT_PROVIDED, TAX_INCLUDED, NON_TAXABLE_TRANSACTION].include?(level2[:tax_indicator].to_i) + xml.tag! :Tax, level2[:tax].to_i if level2[:tax] end end - def add_level_2_advice_addendum(xml, options={}) - if (level_2 = options[:level_2_data]) - xml.tag! :AMEXTranAdvAddn1, byte_limit(level_2[:advice_addendum_1], 40) if level_2[:advice_addendum_1] - xml.tag! :AMEXTranAdvAddn2, byte_limit(level_2[:advice_addendum_2], 40) if level_2[:advice_addendum_2] - xml.tag! :AMEXTranAdvAddn3, byte_limit(level_2[:advice_addendum_3], 40) if level_2[:advice_addendum_3] - xml.tag! :AMEXTranAdvAddn4, byte_limit(level_2[:advice_addendum_4], 40) if level_2[:advice_addendum_4] + def add_level3_tax(xml, options = {}) + if (level3 = options[:level_3_data]) + xml.tag! :PC3VATtaxAmt, byte_limit(level3[:vat_tax], 12) if level3[:vat_tax] + xml.tag! :PC3VATtaxRate, byte_limit(level3[:vat_rate], 4) if level3[:vat_rate] + xml.tag! :PC3AltTaxInd, byte_limit(level3[:alt_ind], 15) if level3[:alt_ind] + xml.tag! :PC3AltTaxAmt, byte_limit(level3[:alt_tax], 9) if level3[:alt_tax] end end - def add_level_2_purchase(xml, options={}) - if (level_2 = options[:level_2_data]) - xml.tag! :PCOrderNum, byte_limit(level_2[:purchase_order], 17) if level_2[:purchase_order] - xml.tag! :PCDestZip, byte_limit(format_address_field(level_2[:zip]), 10) if level_2[:zip] - xml.tag! :PCDestName, byte_limit(format_address_field(level_2[:name]), 30) if level_2[:name] - xml.tag! :PCDestAddress1, byte_limit(format_address_field(level_2[:address1]), 30) if level_2[:address1] - xml.tag! :PCDestAddress2, byte_limit(format_address_field(level_2[:address2]), 30) if level_2[:address2] - xml.tag! :PCDestCity, byte_limit(format_address_field(level_2[:city]), 20) if level_2[:city] - xml.tag! :PCDestState, byte_limit(format_address_field(level_2[:state]), 2) if level_2[:state] + def add_level2_advice_addendum(xml, options = {}) + if (level2 = options[:level_2_data]) + xml.tag! :AMEXTranAdvAddn1, byte_limit(level2[:advice_addendum_1], 40) if level2[:advice_addendum_1] + xml.tag! :AMEXTranAdvAddn2, byte_limit(level2[:advice_addendum_2], 40) if level2[:advice_addendum_2] + xml.tag! :AMEXTranAdvAddn3, byte_limit(level2[:advice_addendum_3], 40) if level2[:advice_addendum_3] + xml.tag! :AMEXTranAdvAddn4, byte_limit(level2[:advice_addendum_4], 40) if level2[:advice_addendum_4] end end - def add_address(xml, creditcard, options) - if(address = (options[:billing_address] || options[:address])) - avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) || empty?(address[:country]) - - if avs_supported - xml.tag! :AVSzip, byte_limit(format_address_field(address[:zip]), 10) - xml.tag! :AVSaddress1, byte_limit(format_address_field(address[:address1]), 30) - xml.tag! :AVSaddress2, byte_limit(format_address_field(address[:address2]), 30) - xml.tag! :AVScity, byte_limit(format_address_field(address[:city]), 20) - xml.tag! :AVSstate, byte_limit(format_address_field(address[:state]), 2) - xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil) - end + def add_level2_purchase(xml, options = {}) + if (level2 = options[:level_2_data]) + xml.tag! :PCOrderNum, byte_limit(level2[:purchase_order], 17) if level2[:purchase_order] + xml.tag! :PCDestZip, byte_limit(format_address_field(level2[:zip]), 10) if level2[:zip] + xml.tag! :PCDestName, byte_limit(format_address_field(level2[:name]), 30) if level2[:name] + xml.tag! :PCDestAddress1, byte_limit(format_address_field(level2[:address1]), 30) if level2[:address1] + xml.tag! :PCDestAddress2, byte_limit(format_address_field(level2[:address2]), 30) if level2[:address2] + xml.tag! :PCDestCity, byte_limit(format_address_field(level2[:city]), 20) if level2[:city] + xml.tag! :PCDestState, byte_limit(format_address_field(level2[:state]), 2) if level2[:state] + end + end - xml.tag! :AVSname, (creditcard&.name ? creditcard.name[0..29] : nil) - xml.tag! :AVScountryCode, (avs_supported ? byte_limit(format_address_field(address[:country]), 2) : '') + def add_level3_purchase(xml, options = {}) + if (level3 = options[:level_3_data]) + xml.tag! :PC3FreightAmt, byte_limit(level3[:freight_amount], 12) if level3[:freight_amount] + xml.tag! :PC3DutyAmt, byte_limit(level3[:duty_amount], 12) if level3[:duty_amount] + xml.tag! :PC3DestCountryCd, byte_limit(level3[:dest_country], 3) if level3[:dest_country] + xml.tag! :PC3ShipFromZip, byte_limit(level3[:ship_from_zip], 10) if level3[:ship_from_zip] + xml.tag! :PC3DiscAmt, byte_limit(level3[:discount_amount], 12) if level3[:discount_amount] + end + end - # Needs to come after AVScountryCode - add_destination_address(xml, address) if avs_supported + def add_line_items(xml, options = {}) + xml.tag! :PC3LineItemCount, byte_limit(options[:line_items].count, 2) + xml.tag! :PC3LineItemArray do + options[:line_items].each_with_index do |line_item, index| + xml.tag! :PC3LineItem do + xml.tag! :PC3DtlIndex, byte_limit(index + 1, 2) + line_item.each do |key, value| + if [:line_tot, 'line_tot'].include? key + formatted_key = :PC3Dtllinetot + else + formatted_key = "PC3Dtl#{key.to_s.camelize}".to_sym + end + xml.tag! formatted_key, value + end + end + end end end - def add_destination_address(xml, address) - if address[:dest_zip] - avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:dest_country].to_s) + #=====ADDRESS FIELDS===== - xml.tag! :AVSDestzip, byte_limit(format_address_field(address[:dest_zip]), 10) - xml.tag! :AVSDestaddress1, byte_limit(format_address_field(address[:dest_address1]), 30) - xml.tag! :AVSDestaddress2, byte_limit(format_address_field(address[:dest_address2]), 30) - xml.tag! :AVSDestcity, byte_limit(format_address_field(address[:dest_city]), 20) - xml.tag! :AVSDeststate, byte_limit(format_address_field(address[:dest_state]), 2) - xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil) + def add_address(xml, payment_source, options) + return unless (address = get_address(options)) - xml.tag! :AVSDestname, byte_limit(address[:dest_name], 30) - xml.tag! :AVSDestcountryCode, (avs_supported ? address[:dest_country] : '') + if avs_supported?(address[:country]) || empty?(address[:country]) + xml.tag! :AVSzip, byte_limit(format_address_field(address[:zip]), 10) + xml.tag! :AVSaddress1, byte_limit(format_address_field(address[:address1]), 30) + xml.tag! :AVSaddress2, byte_limit(format_address_field(address[:address2]), 30) + xml.tag! :AVScity, byte_limit(format_address_field(address[:city]), 20) + xml.tag! :AVSstate, byte_limit(format_address_field(address[:state]), 2) + xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil) end + + xml.tag! :AVSname, billing_name(payment_source, options) + xml.tag! :AVScountryCode, byte_limit(format_address_field(filter_unsupported_countries(address[:country])), 2) + + # Needs to come after AVScountryCode + add_destination_address(xml, address) if avs_supported?(address[:country]) || empty?(address[:country]) + end + + def add_destination_address(xml, address) + return unless address[:dest_zip] + + xml.tag! :AVSDestzip, byte_limit(format_address_field(address[:dest_zip]), 10) + xml.tag! :AVSDestaddress1, byte_limit(format_address_field(address[:dest_address1]), 30) + xml.tag! :AVSDestaddress2, byte_limit(format_address_field(address[:dest_address2]), 30) + xml.tag! :AVSDestcity, byte_limit(format_address_field(address[:dest_city]), 20) + xml.tag! :AVSDeststate, byte_limit(format_address_field(address[:dest_state]), 2) + xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil) + xml.tag! :AVSDestname, byte_limit(address[:dest_name], 30) + xml.tag! :AVSDestcountryCode, filter_unsupported_countries(address[:dest_country]) end # For Profile requests def add_customer_address(xml, options) - if(address = (options[:billing_address] || options[:address])) - avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) + return unless (address = get_address(options)) + + xml.tag! :CustomerAddress1, byte_limit(format_address_field(address[:address1]), 30) + xml.tag! :CustomerAddress2, byte_limit(format_address_field(address[:address2]), 30) + xml.tag! :CustomerCity, byte_limit(format_address_field(address[:city]), 20) + xml.tag! :CustomerState, byte_limit(format_address_field(address[:state]), 2) + xml.tag! :CustomerZIP, byte_limit(format_address_field(address[:zip]), 10) + xml.tag! :CustomerEmail, byte_limit(address[:email], 50) if address[:email] + xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil) + xml.tag! :CustomerCountryCode, filter_unsupported_countries(address[:country]) + end - xml.tag! :CustomerAddress1, byte_limit(format_address_field(address[:address1]), 30) - xml.tag! :CustomerAddress2, byte_limit(format_address_field(address[:address2]), 30) - xml.tag! :CustomerCity, byte_limit(format_address_field(address[:city]), 20) - xml.tag! :CustomerState, byte_limit(format_address_field(address[:state]), 2) - xml.tag! :CustomerZIP, byte_limit(format_address_field(address[:zip]), 10) - xml.tag! :CustomerEmail, byte_limit(address[:email], 50) if address[:email] - xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil) - xml.tag! :CustomerCountryCode, (avs_supported ? address[:country] : '') + def billing_name(payment_source, options) + if !payment_source.is_a?(String) && payment_source&.name.present? + payment_source.name[0..29] + elsif options[:billing_address] && options[:billing_address][:name].present? + options[:billing_address][:name][0..29] end end - def add_creditcard(xml, creditcard, currency=nil) - unless creditcard.nil? - xml.tag! :AccountNum, creditcard.number - xml.tag! :Exp, expiry_date(creditcard) - end + def avs_supported?(address) + AVS_SUPPORTED_COUNTRIES.include?(address.to_s) + end - xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, currency_exponents(currency) + def filter_unsupported_countries(address) + avs_supported?(address) ? address.to_s : '' + end + + def get_address(options) + options[:billing_address] || options[:address] + end + + def add_safetech_token_data(xml, payment_source, options) + xml.tag! :CardBrand, options[:card_brand] + xml.tag! :AccountNum, payment_source + 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 options[:token_txn_type] == USE_TOKEN + payment_source.is_a?(Check) ? add_echeck(xml, payment_source, options) : add_credit_card(xml, payment_source, options) + end + + def add_echeck(xml, check, options = {}) + return unless check + + xml.tag! :CardBrand, 'EC' + add_currency_fields(xml, options[:currency]) + xml.tag! :BCRtNum, check.routing_number + xml.tag! :CheckDDA, check.account_number if check.account_number + add_bank_account_type(xml, check) + xml.tag! :ECPAuthMethod, options[:auth_method] if options[:auth_method] + xml.tag! :BankPmtDelv, options[:payment_delivery] || 'B' + xml.tag! :AVSname, (check&.name ? check.name[0..29] : nil) if get_address(options).blank? + end + + def add_credit_card(xml, credit_card, options) + 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.is_a?(CreditCard) + end + + def add_verification_value(xml, credit_card) + return unless credit_card&.verification_value? # If you are trying to collect a Card Verification Number # (CardSecVal) for a Visa or Discover transaction, pass one of these values: @@ -455,59 +609,131 @@ def add_creditcard(xml, creditcard, currency=nil) # Null-fill this attribute OR # Do not submit the attribute at all. # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf - unless creditcard.nil? - if creditcard.verification_value? - if %w( visa discover ).include?(creditcard.brand) - xml.tag! :CardSecValInd, '1' - end - xml.tag! :CardSecVal, creditcard.verification_value + xml.tag! :CardSecValInd, '1' if %w(visa discover diners_club).include?(credit_card.brand) + xml.tag! :CardSecVal, credit_card.verification_value + end + + def add_currency_fields(xml, currency) + xml.tag! :CurrencyCode, currency_code(currency) + xml.tag! :CurrencyExponent, currency_exponents(currency) + end + + def add_bank_account_type(xml, check) + bank_account_type = + if check.account_holder_type == 'business' + 'X' + else + ACCOUNT_TYPE[check.account_type] end - end + + xml.tag! :BankAccountType, bank_account_type if bank_account_type end - def add_cdpt_eci_and_xid(xml, creditcard) - xml.tag! :AuthenticationECIInd, creditcard.eci - xml.tag! :XID, creditcard.transaction_id if creditcard.transaction_id + def add_card_indicators(xml, options) + xml.tag! :CardIndicators, options[:card_indicators] if options[:card_indicators] end - def add_cdpt_payment_cryptogram(xml, creditcard) - xml.tag! :DPANInd, 'Y' - xml.tag! :DigitalTokenCryptogram, creditcard.payment_cryptogram + def currency_code(currency) + CURRENCY_CODES[(currency || self.default_currency)].to_s end - def add_refund(xml, currency=nil) - xml.tag! :AccountNum, nil + def currency_exponents(currency) + CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s + end - xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, currency_exponents(currency) + def expiry_date(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end - def add_managed_billing(xml, options) - if mb = options[:managed_billing] - ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - - # default to recurring (R). Other option is deferred (D). - xml.tag! :MBType, mb[:type] || RECURRING - # default to Customer Reference Number - xml.tag! :MBOrderIdGenerationMethod, mb[:order_id_generation_method] || 'IO' - # By default use MBRecurringEndDate, set to N. - # MMDDYYYY - xml.tag! :MBRecurringStartDate, mb[:start_date].scan(/\d/).join.to_s if mb[:start_date] - # MMDDYYYY - xml.tag! :MBRecurringEndDate, mb[:end_date].scan(/\d/).join.to_s if mb[:end_date] - # By default listen to any value set in MBRecurringEndDate. - xml.tag! :MBRecurringNoEndDateFlag, mb[:no_end_date_flag] || 'N' # 'Y' || 'N' (Yes or No). - xml.tag! :MBRecurringMaxBillings, mb[:max_billings] if mb[:max_billings] - xml.tag! :MBRecurringFrequency, mb[:frequency] if mb[:frequency] - xml.tag! :MBDeferredBillDate, mb[:deferred_bill_date] if mb[:deferred_bill_date] - xml.tag! :MBMicroPaymentMaxDollarValue, mb[:max_dollar_value] if mb[:max_dollar_value] - xml.tag! :MBMicroPaymentMaxBillingDays, mb[:max_billing_days] if mb[:max_billing_days] - xml.tag! :MBMicroPaymentMaxTransactions, mb[:max_transactions] if mb[:max_transactions] - end + def bin + @options[:bin] || (salem_mid? ? '000001' : '000002') + end + + def salem_mid? + @options[:merchant_id].length == 6 + end + + #=====BRAND-SPECIFIC FIELDS===== + + def add_cavv(xml, credit_card, three_d_secure) + return unless three_d_secure && credit_card.brand == 'visa' + + xml.tag!(:CAVV, three_d_secure[:cavv]) + end + + def add_aav(xml, credit_card, three_d_secure) + return unless three_d_secure && credit_card.brand == 'master' + + xml.tag!(:AAV, three_d_secure[:cavv]) + end + + def add_aevv(xml, credit_card, three_d_secure) + return unless three_d_secure && credit_card.brand == 'american_express' + + xml.tag!(:AEVV, three_d_secure[:cavv]) + end + + def add_xid(xml, credit_card, three_d_secure) + return unless three_d_secure && credit_card.brand == 'visa' + + xml.tag!(:XID, three_d_secure[:xid]) if three_d_secure[:xid] + end + + def add_pymt_brand_program_code(xml, credit_card, three_d_secure) + return unless three_d_secure && credit_card.brand == 'american_express' + + xml.tag!(:PymtBrandProgramCode, 'ASK') + end + + def mastercard?(payment_source) + payment_source.is_a?(CreditCard) && payment_source.brand == 'master' + end + + def add_mastercard_fields(xml, credit_card, parameters, three_d_secure) + add_mc_sca_merchant_initiated(xml, credit_card, parameters, three_d_secure) + add_mc_sca_recurring(xml, credit_card, parameters, three_d_secure) + add_mc_program_protocol(xml, credit_card, three_d_secure) + add_mc_directory_trans_id(xml, credit_card, three_d_secure) + add_mc_ucafind(xml, credit_card, three_d_secure) + end + + def add_mc_sca_merchant_initiated(xml, credit_card, parameters, three_d_secure) + return unless parameters.try(:[], :sca_merchant_initiated) + return unless three_d_secure.try(:[], :eci) == '7' + + xml.tag!(:SCAMerchantInitiatedTransaction, parameters[:sca_merchant_initiated]) + end + + def add_mc_sca_recurring(xml, credit_card, parameters, three_d_secure) + return unless parameters.try(:[], :sca_recurring) + return unless three_d_secure.try(:[], :eci) == '7' + + xml.tag!(:SCARecurringPayment, parameters[:sca_recurring]) + end + + def add_mc_program_protocol(xml, credit_card, three_d_secure) + return unless version = three_d_secure.try(:[], :version) + + xml.tag!(:MCProgramProtocol, version.to_s[0]) end + def add_mc_directory_trans_id(xml, credit_card, three_d_secure) + return unless three_d_secure + + xml.tag!(:MCDirectoryTransID, three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id] + end + + def add_mc_ucafind(xml, credit_card, three_d_secure) + return unless three_d_secure + + xml.tag! :UCAFInd, '4' + end + + #=====SCA (STORED CREDENTIAL) FIELDS===== + def add_stored_credentials(xml, parameters) return unless parameters[:mit_stored_credential_ind] == 'Y' || parameters[:stored_credential] && !parameters[:stored_credential].values.all?(&:nil?) + if msg_type = get_msg_type(parameters) xml.tag! :MITMsgType, msg_type end @@ -523,67 +749,174 @@ def get_msg_type(parameters) return parameters[:mit_msg_type] if parameters[:mit_msg_type] return 'CSTO' if parameters[:stored_credential][:initial_transaction] return unless parameters[:stored_credential][:initiator] && parameters[:stored_credential][:reason_type] - initiator = case parameters[:stored_credential][:initiator] - when 'customer' then 'C' - when 'merchant' then 'M' - end - reason = case parameters[:stored_credential][:reason_type] - when 'recurring' then 'REC' - when 'installment' then 'INS' - when 'unscheduled' then 'USE' - end + + initiator = + case parameters[:stored_credential][:initiator] + when 'cardholder', 'customer' then 'C' + when 'merchant' then 'M' + end + reason = + case parameters[:stored_credential][:reason_type] + when 'recurring' then 'REC' + when 'installment' then 'INS' + when 'unscheduled' then 'USE' + end "#{initiator}#{reason}" end - def parse(body) - response = {} - xml = REXML::Document.new(body) - root = REXML::XPath.first(xml, '//Response') || - REXML::XPath.first(xml, '//ErrorResponse') - if root - root.elements.to_a.each do |node| - recurring_parse_element(response, node) + #=====NETWORK TOKENIZATION FIELDS===== + + def add_eci(xml, credit_card, three_d_secure) + eci = if three_d_secure + three_d_secure[:eci] + elsif credit_card.is_a?(NetworkTokenizationCreditCard) + credit_card.eci + end + + xml.tag!(:AuthenticationECIInd, eci) if eci + end + + def add_dpanind(xml, credit_card, industry_type = nil) + return unless credit_card.is_a?(NetworkTokenizationCreditCard) + + xml.tag! :DPANInd, 'Y' unless industry_type == 'RC' + end + + def add_digital_token_cryptogram(xml, credit_card, three_d_secure) + return unless credit_card.is_a?(NetworkTokenizationCreditCard) || three_d_secure && credit_card.brand == 'discover' + + cryptogram = + if three_d_secure && credit_card.brand == 'discover' + three_d_secure[:cavv] + else + credit_card.payment_cryptogram end + + xml.tag!(:DigitalTokenCryptogram, cryptogram) + end + + #=====OTHER FIELDS===== + + # For Canadian transactions on PNS Tampa on New Order + # RF - First Recurring Transaction + # RS - Subsequent Recurring Transactions + def set_recurring_ind(xml, parameters) + return unless parameters[:recurring_ind] + raise 'RecurringInd must be set to either "RF" or "RS"' unless %w(RF RS).include?(parameters[:recurring_ind]) + + xml.tag! :RecurringInd, parameters[:recurring_ind] + end + + def add_managed_billing(xml, options) + return unless mb = options[:managed_billing] + + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + # default to recurring (R). Other option is deferred (D). + xml.tag! :MBType, mb[:type] || RECURRING + # default to Customer Reference Number + xml.tag! :MBOrderIdGenerationMethod, mb[:order_id_generation_method] || 'IO' + # By default use MBRecurringEndDate, set to N. + # MMDDYYYY + xml.tag! :MBRecurringStartDate, mb[:start_date].scan(/\d/).join.to_s if mb[:start_date] + # MMDDYYYY + xml.tag! :MBRecurringEndDate, mb[:end_date].scan(/\d/).join.to_s if mb[:end_date] + # By default listen to any value set in MBRecurringEndDate. + xml.tag! :MBRecurringNoEndDateFlag, mb[:no_end_date_flag] || 'N' # 'Y' || 'N' (Yes or No). + xml.tag! :MBRecurringMaxBillings, mb[:max_billings] if mb[:max_billings] + xml.tag! :MBRecurringFrequency, mb[:frequency] if mb[:frequency] + xml.tag! :MBDeferredBillDate, mb[:deferred_bill_date] if mb[:deferred_bill_date] + xml.tag! :MBMicroPaymentMaxDollarValue, mb[:max_dollar_value] if mb[:max_dollar_value] + xml.tag! :MBMicroPaymentMaxBillingDays, mb[:max_billing_days] if mb[:max_billing_days] + xml.tag! :MBMicroPaymentMaxTransactions, mb[:max_transactions] if mb[:max_transactions] + end + + def add_ews_details(xml, payment_source, parameters = {}) + split_name = payment_source.first_name.split if payment_source.first_name + xml.tag! :EWSFirstName, split_name[0] + xml.tag! :EWSMiddleName, split_name[1..-1].join(' ') + xml.tag! :EWSLastName, payment_source.last_name + xml.tag! :EWSBusinessName, parameters[:company] if payment_source.first_name.empty? && payment_source.last_name.empty? + + if (address = (parameters[:billing_address] || parameters[:address])) + xml.tag! :EWSAddressLine1, byte_limit(format_address_field(address[:address1]), 30) + xml.tag! :EWSAddressLine2, byte_limit(format_address_field(address[:address2]), 30) + xml.tag! :EWSCity, byte_limit(format_address_field(address[:city]), 20) + xml.tag! :EWSState, byte_limit(format_address_field(address[:state]), 2) + xml.tag! :EWSZip, byte_limit(format_address_field(address[:zip]), 10) end - response.delete_if { |k, _| SENSITIVE_FIELDS.include?(k) } + xml.tag! :EWSPhoneType, parameters[:phone_type] + xml.tag! :EWSPhoneNumber, parameters[:phone_number] + xml.tag! :EWSCheckSerialNumber, payment_source.account_number unless parameters[:auth_method].eql?('I') end - def recurring_parse_element(response, node) - if node.has_elements? - node.elements.each { |e| recurring_parse_element(response, e) } - else - response[node.name.underscore.to_sym] = node.text + # Adds ECP conditional attributes depending on other attribute values + def add_ecp_details(xml, payment_source, parameters = {}) + requires!(payment_source.account_number) if parameters[:auth_method]&.eql?('A') || parameters[:auth_method]&.eql?('P') + xml.tag! :ECPActionCode, parameters[:action_code] if parameters[:action_code] + xml.tag! :ECPCheckSerialNumber, payment_source.account_number if parameters[:auth_method]&.eql?('A') || parameters[:auth_method]&.eql?('P') + if parameters[:auth_method]&.eql?('P') + xml.tag! :ECPTerminalCity, parameters[:terminal_city] if parameters[:terminal_city] + xml.tag! :ECPTerminalState, parameters[:terminal_state] if parameters[:terminal_state] + xml.tag! :ECPImageReferenceNumber, parameters[:image_reference_number] if parameters[:image_reference_number] + end + if parameters[:action_code]&.eql?('W3') || parameters[:action_code]&.eql?('W5') || + parameters[:action_code]&.eql?('W7') || parameters[:action_code]&.eql?('W9') + add_ews_details(xml, payment_source, parameters) end end - def commit(order, message_type, trace_number=nil) + def add_xml_credentials(xml) + unless ip_authentication? + xml.tag! :OrbitalConnectionUsername, @options[:login] + xml.tag! :OrbitalConnectionPassword, @options[:password] + end + end + + def add_bin_merchant_and_terminal(xml, parameters) + xml.tag! :BIN, bin + xml.tag! :MerchantID, @options[:merchant_id] + xml.tag! :TerminalID, parameters[:terminal_id] || '001' + end + + #=====REQUEST/RESPONSE METHODS===== + + def commit(order, message_type, retry_logic = nil, trace_number = nil) headers = POST_HEADERS.merge('Content-length' => order.size.to_s) - if @options[:retry_logic] && trace_number + if (@options[:retry_logic] || retry_logic) && trace_number headers['Trace-number'] = trace_number.to_s headers['Merchant-Id'] = @options[:merchant_id] end request = ->(url) { parse(ssl_post(url, order, headers)) } # Failover URL will be attempted in the event of a connection error - response = begin - request.call(remote_url) - rescue ConnectionError - request.call(remote_url(:secondary)) - end + response = + begin + raise ConnectionError.new 'Should use secondary url', 500 if @use_secondary_url + + request.call(remote_url) + rescue ConnectionError + request.call(remote_url(:secondary)) + end - Response.new(success?(response, message_type), message_from(response), response, + authorization = order.include?('GT') ? response[:safetech_token] : authorization_string(response[:tx_ref_num], response[:order_id]) + + Response.new( + success?(response, message_type), + message_from(response), + response, { - :authorization => authorization_string(response[:tx_ref_num], response[:order_id]), - :test => self.test?, - :avs_result => OrbitalGateway::AVSResult.new(response[:avs_resp_code]), - :cvv_result => OrbitalGateway::CVVResult.new(response[:cvv2_resp_code]) + 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) + def remote_url(url = :primary) if url == :primary (self.test? ? self.test_url : self.live_url) else @@ -591,14 +924,38 @@ def remote_url(url=:primary) end end + def parse(body) + response = {} + xml = REXML::Document.new(strip_invalid_xml_chars(body)) + root = REXML::XPath.first(xml, '//Response') || + REXML::XPath.first(xml, '//ErrorResponse') + if root + root.elements.to_a.each do |node| + recurring_parse_element(response, node) + end + end + + response.delete_if { |k, _| SENSITIVE_FIELDS.include?(k) } + end + + def recurring_parse_element(response, node) + if node.has_elements? + node.elements.each { |e| recurring_parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text + end + end + def success?(response, message_type) - if [:refund, :void].include?(message_type) + if %i[void].include?(message_type) response[:proc_status] == SUCCESS + elsif %i[refund].include?(message_type) + response[:proc_status] == SUCCESS && response[:approval_status] == APPROVAL_SUCCESS elsif response[:customer_profile_action] response[:profile_proc_status] == SUCCESS else response[:proc_status] == SUCCESS && - APPROVED.include?(response[:resp_code]) + APPROVED.include?(response[:resp_code]) end end @@ -610,77 +967,76 @@ def ip_authentication? @options[:ip_authentication] == true end - def build_new_order_xml(action, money, creditcard, parameters = {}) + #=====BUILDER METHODS===== + + def build_new_auth_purchase_order(action, money, payment_source, options) + build_new_order_xml(action, money, payment_source, options) do |xml| + add_payment_source(xml, payment_source, options) + add_address(xml, payment_source, options) + if @options[:customer_profiles] + add_customer_data(xml, payment_source, options) + add_managed_billing(xml, options) + end + end + end + + def build_new_order_xml(action, money, payment_source, parameters = {}) requires!(parameters, :order_id) + @use_secondary_url = parameters[:use_secondary_url] if parameters[:use_secondary_url] xml = xml_envelope xml.tag! :Request do xml.tag! :NewOrder do add_xml_credentials(xml) - # EC - Ecommerce transaction - # RC - Recurring Payment transaction - # MO - Mail Order Telephone Order transaction - # IV - Interactive Voice Response - # IN - Interactive Voice Response xml.tag! :IndustryType, parameters[:industry_type] || ECOMMERCE_TRANSACTION - # A - Auth Only No Capture - # AC - Auth and Capture - # F - Force Auth No Capture and no online authorization - # FR - Force Auth No Capture and no online authorization - # FC - Force Auth and Capture no online authorization - # R - Refund and Capture no online authorization xml.tag! :MessageType, action add_bin_merchant_and_terminal(xml, parameters) yield xml if block_given? - if creditcard.is_a?(NetworkTokenizationCreditCard) - add_cdpt_eci_and_xid(xml, creditcard) - end + three_d_secure = parameters[:three_d_secure] + + add_eci(xml, payment_source, three_d_secure) + add_cavv(xml, payment_source, three_d_secure) + add_xid(xml, payment_source, three_d_secure) xml.tag! :OrderID, format_order_id(parameters[:order_id]) xml.tag! :Amount, amount(money) xml.tag! :Comments, parameters[:comments] if parameters[:comments] - add_level_2_tax(xml, parameters) - add_level_2_advice_addendum(xml, parameters) + add_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. - if creditcard.is_a?(NetworkTokenizationCreditCard) - add_cdpt_payment_cryptogram(xml, creditcard) - end + 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) - if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors) - add_soft_descriptors(xml, parameters[:soft_descriptors]) - elsif parameters[:soft_descriptors].is_a?(Hash) - add_soft_descriptors_from_hash(xml, parameters[:soft_descriptors]) - end + 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 - if action == REFUND - tx_ref_num, _ = split_authorization(parameters[:authorization]) - xml.tag! :TxRefNum, tx_ref_num - end - - add_level_2_purchase(xml, parameters) + 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_stored_credentials(xml, parameters) + add_pymt_brand_program_code(xml, payment_source, three_d_secure) + add_mastercard_fields(xml, payment_source, parameters, three_d_secure) if mastercard?(payment_source) + xml.tag! :TokenTxnType, parameters[:token_txn_type] if parameters[:token_txn_type] end end xml.target! end - # For Canadian transactions on PNS Tampa on New Order - # RF - First Recurring Transaction - # RS - Subsequent Recurring Transactions - def set_recurring_ind(xml, parameters) - if parameters[:recurring_ind] - raise 'RecurringInd must be set to either "RF" or "RS"' unless %w(RF RS).include?(parameters[:recurring_ind]) - xml.tag! :RecurringInd, parameters[:recurring_ind] - end - end - def build_mark_for_capture_xml(money, authorization, parameters = {}) tx_ref_num, order_id = split_authorization(authorization) xml = xml_envelope @@ -689,11 +1045,14 @@ def build_mark_for_capture_xml(money, authorization, parameters = {}) add_xml_credentials(xml) xml.tag! :OrderID, format_order_id(order_id) xml.tag! :Amount, amount(money) - add_level_2_tax(xml, parameters) + add_level2_tax(xml, parameters) add_bin_merchant_and_terminal(xml, parameters) xml.tag! :TxRefNum, tx_ref_num - add_level_2_purchase(xml, parameters) - add_level_2_advice_addendum(xml, parameters) + add_level2_purchase(xml, parameters) + add_level2_advice_addendum(xml, parameters) + add_level3_purchase(xml, parameters) + add_level3_tax(xml, parameters) + add_line_items(xml, parameters) if parameters[:line_items] end end xml.target! @@ -717,43 +1076,16 @@ def build_void_request_xml(authorization, parameters = {}) xml.target! end - def currency_code(currency) - CURRENCY_CODES[(currency || self.default_currency)].to_s - end - - def currency_exponents(currency) - CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s - end - - def expiry_date(credit_card) - "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" - end - - def bin - @options[:bin] || (salem_mid? ? '000001' : '000002') - end - def xml_envelope - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct!(:xml, :version => '1.0', :encoding => 'UTF-8') + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct!(:xml, version: '1.0', encoding: 'UTF-8') xml end - def add_xml_credentials(xml) - unless ip_authentication? - xml.tag! :OrbitalConnectionUsername, @options[:login] - xml.tag! :OrbitalConnectionPassword, @options[:password] - end - end - - def add_bin_merchant_and_terminal(xml, parameters) - xml.tag! :BIN, bin - xml.tag! :MerchantID, @options[:merchant_id] - xml.tag! :TerminalID, parameters[:terminal_id] || '001' - end - - def salem_mid? - @options[:merchant_id].length == 6 + # Null characters are possible in some responses (namely, the respMsg field), causing XML parsing errors + # Prevent by substituting these with a valid placeholder string + def strip_invalid_xml_chars(xml) + xml.gsub(/\u0000/, '[null]') end # The valid characters include: @@ -763,7 +1095,7 @@ def salem_mid? # 3. PINless Debit transactions can only use uppercase and lowercase alpha (A-Z, a-z) and numeric (0-9) def format_order_id(order_id) illegal_characters = /[^,$@&\- \w]/ - order_id = order_id.to_s.gsub(/\./, '-') + order_id = order_id.to_s.tr('.', '-') order_id.gsub!(illegal_characters, '') order_id.lstrip! order_id[0...22] @@ -781,14 +1113,15 @@ def byte_limit(value, byte_length) limited_value = '' value.to_s.each_char do |c| - break if((limited_value.bytesize + c.bytesize) > byte_length) + break if (limited_value.bytesize + c.bytesize) > byte_length + limited_value << c end limited_value end - def build_customer_request_xml(creditcard, options = {}) + def build_customer_request_xml(credit_card, options = {}) ActiveMerchant.deprecated 'Customer Profile support in Orbital is non-conformant to the ActiveMerchant API and will be removed in its current form in a future version. Please contact the ActiveMerchant maintainers if you have an interest in modifying it to conform to the store/unstore/update API.' xml = xml_envelope xml.tag! :Request do @@ -797,7 +1130,7 @@ def build_customer_request_xml(creditcard, options = {}) xml.tag! :OrbitalConnectionPassword, @options[:password] unless ip_authentication? xml.tag! :CustomerBin, bin xml.tag! :CustomerMerchantID, @options[:merchant_id] - xml.tag! :CustomerName, creditcard.name if creditcard + xml.tag! :CustomerName, credit_card.name if credit_card xml.tag! :CustomerRefNum, options[:customer_ref_num] if options[:customer_ref_num] add_customer_address(xml, options) @@ -825,8 +1158,8 @@ def build_customer_request_xml(creditcard, options = {}) xml.tag! :Status, options[:status] || ACTIVE # Active end - xml.tag! :CCAccountNum, creditcard.number if creditcard - xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime('%m%y') if creditcard + xml.tag! :CCAccountNum, credit_card.number if credit_card + xml.tag! :CCExpireDate, credit_card.expiry_date.expiration.strftime('%m%y') if credit_card # This has to come after CCExpireDate. add_managed_billing(xml, options) @@ -844,55 +1177,55 @@ def build_customer_request_xml(creditcard, options = {}) # class AVSResult < ActiveMerchant::Billing::AVSResult CODES = { - '1' => 'No address supplied', - '2' => 'Bill-to address did not pass Auth Host edit checks', - '3' => 'AVS not performed', - '4' => 'Issuer does not participate in AVS', - '5' => 'Edit-error - AVS data is invalid', - '6' => 'System unavailable or time-out', - '7' => 'Address information unavailable', - '8' => 'Transaction Ineligible for AVS', - '9' => 'Zip Match/Zip 4 Match/Locale match', - 'A' => 'Zip Match/Zip 4 Match/Locale no match', - 'B' => 'Zip Match/Zip 4 no Match/Locale match', - 'C' => 'Zip Match/Zip 4 no Match/Locale no match', - 'D' => 'Zip No Match/Zip 4 Match/Locale match', - 'E' => 'Zip No Match/Zip 4 Match/Locale no match', - 'F' => 'Zip No Match/Zip 4 No Match/Locale match', - 'G' => 'No match at all', - 'H' => 'Zip Match/Locale match', - 'J' => 'Issuer does not participate in Global AVS', - 'JA' => 'International street address and postal match', - 'JB' => 'International street address match. Postal code not verified', - 'JC' => 'International street address and postal code not verified', - 'JD' => 'International postal code match. Street address not verified', - 'M1' => 'Cardholder name matches', - 'M2' => 'Cardholder name, billing address, and postal code matches', - 'M3' => 'Cardholder name and billing code matches', - 'M4' => 'Cardholder name and billing address match', - 'M5' => 'Cardholder name incorrect, billing address and postal code match', - 'M6' => 'Cardholder name incorrect, billing postal code matches', - 'M7' => 'Cardholder name incorrect, billing address matches', - 'M8' => 'Cardholder name, billing address and postal code are all incorrect', - 'N3' => 'Address matches, ZIP not verified', - 'N4' => 'Address and ZIP code not verified due to incompatible formats', - 'N5' => 'Address and ZIP code match (International only)', - 'N6' => 'Address not verified (International only)', - 'N7' => 'ZIP matches, address not verified', - 'N8' => 'Address and ZIP code match (International only)', - 'N9' => 'Address and ZIP code match (UK only)', - 'R' => 'Issuer does not participate in AVS', - 'UK' => 'Unknown', - 'X' => 'Zip Match/Zip 4 Match/Address Match', - 'Z' => 'Zip Match/Locale no match', + '1' => 'No address supplied', + '2' => 'Bill-to address did not pass Auth Host edit checks', + '3' => 'AVS not performed', + '4' => 'Issuer does not participate in AVS', + '5' => 'Edit-error - AVS data is invalid', + '6' => 'System unavailable or time-out', + '7' => 'Address information unavailable', + '8' => 'Transaction Ineligible for AVS', + '9' => 'Zip Match/Zip 4 Match/Locale match', + 'A' => 'Zip Match/Zip 4 Match/Locale no match', + 'B' => 'Zip Match/Zip 4 no Match/Locale match', + 'C' => 'Zip Match/Zip 4 no Match/Locale no match', + 'D' => 'Zip No Match/Zip 4 Match/Locale match', + 'E' => 'Zip No Match/Zip 4 Match/Locale no match', + 'F' => 'Zip No Match/Zip 4 No Match/Locale match', + 'G' => 'No match at all', + 'H' => 'Zip Match/Locale match', + 'J' => 'Issuer does not participate in Global AVS', + 'JA' => 'International street address and postal match', + 'JB' => 'International street address match. Postal code not verified', + 'JC' => 'International street address and postal code not verified', + 'JD' => 'International postal code match. Street address not verified', + 'M1' => 'Cardholder name matches', + 'M2' => 'Cardholder name, billing address, and postal code matches', + 'M3' => 'Cardholder name and billing code matches', + 'M4' => 'Cardholder name and billing address match', + 'M5' => 'Cardholder name incorrect, billing address and postal code match', + 'M6' => 'Cardholder name incorrect, billing postal code matches', + 'M7' => 'Cardholder name incorrect, billing address matches', + 'M8' => 'Cardholder name, billing address and postal code are all incorrect', + 'N3' => 'Address matches, ZIP not verified', + 'N4' => 'Address and ZIP code not verified due to incompatible formats', + 'N5' => 'Address and ZIP code match (International only)', + 'N6' => 'Address not verified (International only)', + 'N7' => 'ZIP matches, address not verified', + 'N8' => 'Address and ZIP code match (International only)', + 'N9' => 'Address and ZIP code match (UK only)', + 'R' => 'Issuer does not participate in AVS', + 'UK' => 'Unknown', + 'X' => 'Zip Match/Zip 4 Match/Address Match', + 'Z' => 'Zip Match/Locale no match' } # Map vendor's AVS result code to a postal match code ORBITAL_POSTAL_MATCH_CODE = { - 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ), - 'N' => %w( D E F G M8 ), - 'X' => %w( 4 J R ), - nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UK ) + 'Y' => %w(9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z), + 'N' => %w(D E F G M8), + 'X' => %w(4 J R), + nil => %w(1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UK) }.inject({}) do |map, (type, codes)| codes.each { |code| map[code] = type } map @@ -900,10 +1233,10 @@ class AVSResult < ActiveMerchant::Billing::AVSResult # Map vendor's AVS result code to a street match code ORBITAL_STREET_MATCH_CODE = { - 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ), - 'N' => %w( A C E G M8 Z ), - 'X' => %w( 4 J R ), - nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UK ) + 'Y' => %w(9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X), + 'N' => %w(A C E G M8 Z), + 'X' => %w(4 J R), + nil => %w(1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UK) }.inject({}) do |map, (type, codes)| codes.each { |code| map[code] = type } map 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 da1d82d0ca0..5b7f4517a69 100644 --- a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +++ b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb @@ -30,13 +30,11 @@ def validate errors << [:merchant_name, 'is required'] if self.merchant_name.blank? errors << [:merchant_name, 'is required to be 25 bytes or less'] if self.merchant_name.bytesize > 25 - if(!empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2)) - errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format'] - end + 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) - [:merchant_email, :merchant_url].each do |attr| + %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) + errors << [attr, 'is required to be 13 bytes or less'] if self.send(attr).bytesize > 13 end end diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb index 952684b1943..56ab23cc774 100644 --- a/lib/active_merchant/billing/gateways/pac_net_raven.rb +++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb @@ -1,7 +1,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class PacNetRavenGateway < Gateway - AVS_ADDRESS_CODES = { 'avs_address_unavailable' => 'X', 'avs_address_not_checked' => 'X', @@ -29,7 +28,7 @@ class PacNetRavenGateway < Gateway self.test_url = self.live_url self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] self.money_format = :cents self.default_currency = 'USD' self.homepage_url = 'https://www.deepcovelabs.com/raven' @@ -122,15 +121,18 @@ def commit(action, money, parameters) test_mode = test? || message =~ /TESTMODE/ - Response.new(success?(response), message, response, - :test => test_mode, - :authorization => response['TrackingNumber'], - :fraud_review => fraud_review?(response), - :avs_result => { - :postal_match => AVS_POSTAL_CODES[response['AVSPostalResponseCode']], - :street_match => AVS_ADDRESS_CODES[response['AVSAddressResponseCode']] - }, - :cvv_result => CVV2_CODES[response['CVV2ResponseCode']] + Response.new( + success?(response), + message, + response, + test: test_mode, + authorization: response['TrackingNumber'], + fraud_review: fraud_review?(response), + avs_result: { + postal_match: AVS_POSTAL_CODES[response['AVSPostalResponseCode']], + street_match: AVS_ADDRESS_CODES[response['AVSAddressResponseCode']] + }, + cvv_result: CVV2_CODES[response['CVV2ResponseCode']] ) end @@ -140,6 +142,7 @@ def url(action) def endpoint(action) return 'void' if action == 'void' + 'submit' end @@ -149,9 +152,9 @@ def fraud_review?(response) def success?(response) if %w(cc_settle cc_debit cc_preauth cc_refund).include?(response[:action]) - !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Approved' + !response['ApprovalCode'].nil? && response['ErrorCode'].nil? && (response['Status'] == 'Approved') elsif response[:action] = 'void' - !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Voided' + !response['ApprovalCode'].nil? && response['ErrorCode'].nil? && (response['Status'] == 'Voided') end end @@ -192,13 +195,14 @@ def request_id end def signature(action, post, parameters = {}) - string = if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action) - post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency'] - elsif action == 'void' - post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber'] - else - post['UserName'] - end + string = + if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action) + post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency'] + elsif action == 'void' + post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber'] + else + post['UserName'] + end OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new(@options[:secret]), @options[:secret], string) end end diff --git a/lib/active_merchant/billing/gateways/pagarme.rb b/lib/active_merchant/billing/gateways/pagarme.rb index 26545c7c2d3..67726c8ff61 100644 --- a/lib/active_merchant/billing/gateways/pagarme.rb +++ b/lib/active_merchant/billing/gateways/pagarme.rb @@ -6,24 +6,24 @@ class PagarmeGateway < Gateway self.supported_countries = ['BR'] self.default_currency = 'BRL' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover diners_club] self.homepage_url = 'https://pagar.me/' self.display_name = 'Pagar.me' STANDARD_ERROR_CODE_MAPPING = { 'refused' => STANDARD_ERROR_CODE[:card_declined], - 'processing_error' => STANDARD_ERROR_CODE[:processing_error], + 'processing_error' => STANDARD_ERROR_CODE[:processing_error] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_key) @api_key = options[:api_key] super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = {} add_amount(post, money) add_payment_method(post, payment_method) @@ -32,7 +32,7 @@ def purchase(money, payment_method, options={}) commit(:post, 'transactions', post) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) post = {} add_amount(post, money) add_payment_method(post, payment_method) @@ -43,33 +43,27 @@ def authorize(money, payment_method, options={}) commit(:post, 'transactions', post) end - def capture(money, authorization, options={}) - if authorization.nil? - return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.') - end + def capture(money, authorization, options = {}) + return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.') if authorization.nil? post = {} commit(:post, "transactions/#{authorization}/capture", post) end - def refund(money, authorization, options={}) - if authorization.nil? - return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.') - end + def refund(money, authorization, options = {}) + return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.') if authorization.nil? void(authorization, options) end - def void(authorization, options={}) - if authorization.nil? - return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.') - end + def void(authorization, options = {}) + return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.') if authorization.nil? post = {} commit(:post, "transactions/#{authorization}/refund", post) end - def verify(payment_method, options={}) + def verify(payment_method, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(127, payment_method, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -105,7 +99,7 @@ def add_credit_card(post, credit_card) post[:card_cvv] = credit_card.verification_value end - def add_metadata(post, options={}) + def add_metadata(post, options = {}) post[:metadata] = {} post[:metadata][:order_id] = options[:order_id] post[:metadata][:ip] = options[:ip] @@ -125,6 +119,7 @@ def post_data(params) params.map do |key, value| next if value != false && value.blank? + if value.is_a?(Hash) h = {} value.each do |k, v| @@ -212,7 +207,7 @@ def message_from(response) when 'refunded' 'Transação estornada' else - "Transação com status '#{response["status"]}'" + "Transação com status '#{response['status']}'" end elsif failure_from(response) 'Transação recusada' @@ -225,9 +220,7 @@ def message_from(response) end def authorization_from(response) - if success_from(response) - response['id'] - end + response['id'] if success_from(response) end def test? diff --git a/lib/active_merchant/billing/gateways/pago_facil.rb b/lib/active_merchant/billing/gateways/pago_facil.rb index 85793fbb369..7021c057c3d 100644 --- a/lib/active_merchant/billing/gateways/pago_facil.rb +++ b/lib/active_merchant/billing/gateways/pago_facil.rb @@ -6,17 +6,17 @@ class PagoFacilGateway < Gateway self.supported_countries = ['MX'] self.default_currency = 'MXN' - self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + self.supported_cardtypes = %i[visa master american_express jcb] self.homepage_url = 'http://www.pagofacil.net/' self.display_name = 'PagoFacil' - def initialize(options={}) + def initialize(options = {}) requires!(options, :branch_id, :merchant_id, :service_id) super end - def purchase(money, credit_card, options={}) + def purchase(money, credit_card, options = {}) post = {} add_invoice(post, money, options) add_payment(post, credit_card) @@ -53,9 +53,7 @@ def add_invoice(post, money, options) def add_currency(post, money, options) currency = options.fetch(:currency, currency(money)) - unless currency == self.class.default_currency - post[:divisa] = currency - end + post[:divisa] = currency unless currency == self.class.default_currency end def add_payment(post, credit_card) diff --git a/lib/active_merchant/billing/gateways/pay_arc.rb b/lib/active_merchant/billing/gateways/pay_arc.rb new file mode 100644 index 00000000000..30a34080bda --- /dev/null +++ b/lib/active_merchant/billing/gateways/pay_arc.rb @@ -0,0 +1,392 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayArcGateway < Gateway + self.test_url = 'https://testapi.payarc.net/v1' + self.live_url = 'https://api.payarc.net/v1' + + self.supported_countries = ['US'] + self.default_currency = 'usd' + self.supported_cardtypes = %i[visa master american_express discover jcb] + + self.homepage_url = 'https://www.payarc.net/' + self.display_name = 'PAYARC Gateway' + + STANDARD_ERROR_CODE_MAPPING = {} + STANDARD_ACTIONS = { + token: + { end_point: 'tokens', + allowed_fields: %i[card_source card_number exp_month exp_year cvv card_holder_name + address_line1 address_line2 city state zip country] }, + capture: + { end_point: 'charges', + allowed_fields: %i[amount statement_description card_id currency customer_id token_id card_source tip_amount + card_level sales_tax purchase_order supplier_reference_number customer_ref_id ship_to_zip + amex_descriptor customer_vat_number summary_commodity_code shipping_charges duty_charges + ship_from_zip destination_country_code vat_invoice order_date tax_category tax_type + tax_amount tax_rate address_line1 zip terminal_id surcharge description email receipt_phone statement_descriptor ] }, + void: + { end_point: 'charges/{{chargeID}}/void', + allowed_fields: %i[reason void_description] }, + refund: + { end_point: 'charges/{{charge_id}}/refunds', + allowed_fields: %i[amount reason description] }, + credit: + { end_point: 'refunds/wo_reference', + allowed_fields: %i[amount charge_description statement_description terminal_id card_source card_number + exp_month exp_year cvv card_holder_name address_line1 address_line2 city state zip + country currency reason receipt_phone receipt_email ] } + } + + SUCCESS_STATUS = %w[ + submitted_for_settlement authorized partially_submitted_for_settlement + credit partial_refund void refunded settled + ] + + FAILURE_STATUS = %w[not_processed failed_by_gateway invalid_track_data authorization_expired] + + # The gateway must be configured with Bearer token. + # + # :api_key PAYARC's Bearer token must be passsed to initialise the gateway. + + def initialize(options = {}) + requires!(options, :api_key) + super + end + + # + # Purchase API through PAYARC. + # + # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. + # + # :creditcard CreditCard object with card details. + # + # :options Other information like address, card source etc can be passed in options + # + # ==== Options + # + # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) + # * :currency -- Three-letter ISO currency code, in lowercase (REQUIRED) + # * :card_holder_name --Name of the Card Holder (OPTIONAL) + # * :address_line1 -- Set in payment method's billing address (OPTIONAL) + # * :address_line2 -- Set in payment method's billing address (OPTIONAL) + # * :state -- State (OPTIONAL) + # * :country -- Country (OPTIONAL) + # * :statement_description -- An arbitrary string to be displayed on your costomer's credit card statement. This may be up to 22 characters. (OPTIONAL) + # * :card_level -- Commercial card level - "LEVEL2" OR "LEVEL3" (OPTIONAL) + # * :sales_tax -- A positive integer in cents representing sales tax. (OPTIONAL) + # * :terminal_id -- Optional terminal id. (OPTIONAL) + # * :tip_amount -- A positive integer in cents representing tip amount. (OPTIONAL) + # * :sales_tax -- Applicable for LEVEL2 or LEVEL3 Charge. A positive integer in cents representing sales tax. (REQUIRED for LEVEL2 0r LEVEL3) + # * :purchase_order -- Applicable for Level2 or Level3 Charge. The value used by the customer to identify an order. Issued by the buyer to the seller. (REQUIRED for LEVEL2 0r LEVEL3) + # * :order_date -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The date the order was processed. Format: Alphanumeric and Special Character |Min Length=0 Max Length=10|Allowed format: MM/DD/YYYY For example: 12/01/2016 + # * :customer_ref_id -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The reference identifier supplied by the Commercial Card cardholder. Format: Alphanumeric and Special Character |Min Length=0 Max Length=17| a-z A-Z 0-9 Space <> + # * :ship_to_zip -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 + # * :amex_descriptor -- Applicable for Level2 Charge for AMEX card only. The value of the Transaction Advice Addendum field, displays descriptive information about a transactions on a customer's AMEX card statement. Format: Alphanumeric and Special Character |Min Length=0 Max Length=40|a-z A-Z 0-9 Space <> + # * :supplier_reference_number -- Applicable for Level2 Charge for AMEX card only or Level3 charge. The value used by the customer to identify an order. Issued by the buyer to the seller. + # * :tax_amount -- Applicable for Level3 Charge. The tax amount. Format: Numeric|Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :tax_category -- Applicable for Level3 Charge. The type of tax. Formerly established through TaxCategory messages. Allowed values: SERVICE, DUTY, VAT, ALTERNATE, NATIONAL, TAX_EXEMPT + # * :customer_vat_number -- Applicable for Level3 Charge. Indicates the customer's government assigned tax identification number or the identification number assigned to their purchasing company by the tax authorities. Format: Alphanumeric and Special Character|Min Length=0 Max Length=13| a-z A-Z 0-9 Space <> + # * :summary_commodity_code -- Applicable for Level3 Charge. The international description code of the overall goods or services being supplied. Format: Alphanumeric and Special Character |Min Length=0 Max Length=4|Allowed character: a-z A-Z 0-9 Space <> + # * :shipping_charges -- Applicable for Level3 Charge. The dollar amount for shipping or freight charges applied to a product or transaction. Format: Numeric |Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :duty_charges -- Applicable for Level3 Charge. Indicates the total charges for any import or export duties included in the order. Format: Numeric |Max Length=12|Allowed characters: 0-9 . (dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :ship_from_zip -- Applicable for Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 + # * :destination_country_code -- Applicable for Level3 Charge. The destination country code indicator. Format: Alphanumeric. + # * :tax_type -- Applicable for Level3 Charge. The type of tax. For example, VAT, NATIONAL, Service Tax. Format: Alphanumeric and Special Character + # * :vat_invoice -- Applicable for Level3 Charge. The Value Added Tax (VAT) invoice number associated with the transaction. Format: Alphanumeric and Special Character |Min Length=0 Max Length=15|Allowed character: a-z A-Z 0-9 Space <> + # * :tax_rate -- Applicable for Level3 Charge. The type of tax rate. This field is used if taxCategory is not used. Default sale tax rate in percentage Must be between 0.1% - 22% ,Applicable only Level 2 AutoFill. Format: Decimal Number |Max Length=4|Allowed characters: 0-9 .(dot) Allowed range: 0.01 - 100 + # * :email -- Customer's email address sent with payment method. + + def purchase(money, creditcard, options = {}) + options[:capture] = 1 + MultiResponse.run do |r| + r.process { token(creditcard, options) } + r.process { charge(money, r.authorization, options) } + end + end + + # + # Authorize the payment API through PAYARC. + # + # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. + # + # :creditcard CreditCard object with card details. + # + # :options Other information like address, card source etc can be passed in options + # + # ==== Options + # + # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) + # * :currency -- Three-letter ISO currency code, in lowercase (REQUIRED) + # * :card_holder_name --Name of the Card Holder (OPTIONAL) + # * :address_line1 -- Set in payment method's billing address (OPTIONAL) + # * :address_line2 -- Set in payment method's billing address (OPTIONAL) + # * :state -- State (OPTIONAL) + # * :country -- Country (OPTIONAL) + # * :statement_description -- An arbitrary string to be displayed on your costomer's credit card statement. This may be up to 22 characters. (OPTIONAL) + # * :card_level -- Commercial card level - "LEVEL2" OR "LEVEL3" (OPTIONAL) + # * :sales_tax -- A positive integer in cents representing sales tax. (OPTIONAL) + # * :terminal_id -- Optional terminal id. (OPTIONAL) + # * :tip_amount -- A positive integer in cents representing tip amount. (OPTIONAL) + # * :sales_tax -- Applicable for LEVEL2 or LEVEL3 Charge. A positive integer in cents representing sales tax. (REQUIRED for LEVEL2 0r LEVEL3) + # * :purchase_order -- Applicable for Level2 or Level3 Charge. The value used by the customer to identify an order. Issued by the buyer to the seller. (REQUIRED for LEVEL2 0r LEVEL3) + # * :order_date -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The date the order was processed. Format: Alphanumeric and Special Character |Min Length=0 Max Length=10|Allowed format: MM/DD/YYYY For example: 12/01/2016 + # * :customer_ref_id -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The reference identifier supplied by the Commercial Card cardholder. Format: Alphanumeric and Special Character |Min Length=0 Max Length=17| a-z A-Z 0-9 Space <> + # * :ship_to_zip -- Applicable for Level2 Charge for AMEX card only or Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 + # * :amex_descriptor -- Applicable for Level2 Charge for AMEX card only. The value of the Transaction Advice Addendum field, displays descriptive information about a transactions on a customer's AMEX card statement. Format: Alphanumeric and Special Character |Min Length=0 Max Length=40|a-z A-Z 0-9 Space <> + # * :supplier_reference_number -- Applicable for Level2 Charge for AMEX card only or Level3 charge. The value used by the customer to identify an order. Issued by the buyer to the seller. + # * :tax_amount -- Applicable for Level3 Charge. The tax amount. Format: Numeric|Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :tax_category -- Applicable for Level3 Charge. The type of tax. Formerly established through TaxCategory messages. Allowed values: SERVICE, DUTY, VAT, ALTERNATE, NATIONAL, TAX_EXEMPT + # * :customer_vat_number -- Applicable for Level3 Charge. Indicates the customer's government assigned tax identification number or the identification number assigned to their purchasing company by the tax authorities. Format: Alphanumeric and Special Character|Min Length=0 Max Length=13| a-z A-Z 0-9 Space <> + # * :summary_commodity_code -- Applicable for Level3 Charge. The international description code of the overall goods or services being supplied. Format: Alphanumeric and Special Character |Min Length=0 Max Length=4|Allowed character: a-z A-Z 0-9 Space <> + # * :shipping_charges -- Applicable for Level3 Charge. The dollar amount for shipping or freight charges applied to a product or transaction. Format: Numeric |Max Length=12|Allowed characters: 0-9 .(dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :duty_charges -- Applicable for Level3 Charge. Indicates the total charges for any import or export duties included in the order. Format: Numeric |Max Length=12|Allowed characters: 0-9 . (dot) Note: If a decimal point is included, the amount reflects a dollar value. If a decimal point is not included, the amount reflects a cent value. + # * :ship_from_zip -- Applicable for Level3 Charge. The postal code for the address to which the goods are being shipped. Format: Alphanumeric |Min Length=2 Max Length=10 + # * :destination_country_code -- Applicable for Level3 Charge. The destination country code indicator. Format: Alphanumeric. + # * :tax_type -- Applicable for Level3 Charge. The type of tax. For example, VAT, NATIONAL, Service Tax. Format: Alphanumeric and Special Character + # * :vat_invoice -- Applicable for Level3 Charge. The Value Added Tax (VAT) invoice number associated with the transaction. Format: Alphanumeric and Special Character |Min Length=0 Max Length=15|Allowed character: a-z A-Z 0-9 Space <> + # * :tax_rate -- Applicable for Level3 Charge. The type of tax rate. This field is used if taxCategory is not used. Default sale tax rate in percentage Must be between 0.1% - 22% ,Applicable only Level 2 AutoFill. Format: Decimal Number |Max Length=4|Allowed characters: 0-9 .(dot) Allowed range: 0.01 - 100 + # * :email -- Customer's email address. + + def authorize(money, creditcard, options = {}) + options[:capture] = '0' + MultiResponse.run do |r| + r.process { token(creditcard, options) } + r.process { charge(money, r.authorization, options) } + end + end + + # + # Capture the payment of an existing, uncaptured, charge. + # This is the second half of the two-step payment flow, where first you created / authorized a charge + # with the capture option set to false. + # + # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. + # + # :tx_reference charge_id from previously created / authorized a charge + # + # :options Other information like address, card source etc can be passed in options + + def capture(money, tx_reference, options = {}) + post = {} + add_money(post, money, options) + action = "#{STANDARD_ACTIONS[:capture][:end_point]}/#{tx_reference}/capture" + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:capture][:allowed_fields]) + commit(action, post) + end + + # + # Voids the transaction / charge. + # + # :tx_reference charge_id from previously created charge + # + # :options Other information like address, card source etc can be passed in options + # + # ==== Options + # + # * :reason -- Reason for voiding transaction (REQUIRED) ( requested_by_customer, duplicate, fraudulent, other ) + + def void(tx_reference, options = {}) + post = {} + post['reason'] = options[:reason] + action = STANDARD_ACTIONS[:void][:end_point].gsub(/{{chargeID}}/, tx_reference) + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:void][:allowed_fields]) + commit(action, post) + end + + # + # Refund full / partial payment of an successful charge / capture / purchase. + # + # :money A positive integer in cents representing how much to charge. The minimum amount is 50c USD. + # + # :tx_reference charge_id from previously created / authorized a charge + # + # :options Other information like address, card source etc can be passed in options + + def refund(money, tx_reference, options = {}) + post = {} + add_money(post, money, options) + action = STANDARD_ACTIONS[:refund][:end_point].gsub(/{{charge_id}}/, tx_reference) + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:refund][:allowed_fields]) + commit(action, post) + end + + def credit(money, creditcard, options = {}) + post = {} + add_money(post, money, options) + add_creditcard(post, creditcard, options) + add_address(post, options) + add_phone(post, options) + post['receipt_email'] = options[:email] if options[:email] + action = STANDARD_ACTIONS[:credit][:end_point] + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:credit][:allowed_fields]) + commit(action, post) + end + + # + # Verify the creditcard API through PAYARC. + # + # :creditcard CreditCard object with card details. + # + # :options Other information like address, card source etc can be passed in options + # + # ==== Options + # + # * :card_source -- Source of payment (REQUIRED) ( INTERNET, SWIPE, PHONE, MAIL, MANUAL ) + # * :card_holder_name --Name of the Card Holder (OPTIONAL) + # * :address_line1 -- Set in payment method's billing address (OPTIONAL) + # * :address_line2 -- Set in payment method's billing address (OPTIONAL) + # * :state -- State (OPTIONAL) + # * :country -- Country (OPTIONAL) + + def verify(creditcard, options = {}) + token(creditcard, options) + end + + #:nodoc: + def token(creditcard, options = {}) + post = {} + post['authorize_card'] = 1 + post['card_source'] = options[:card_source] + add_creditcard(post, creditcard, options) + add_address(post, options) + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:token][:allowed_fields]) + commit(STANDARD_ACTIONS[:token][:end_point], post) + end + + def supports_scrubbing? #:nodoc: + true + end + + def scrub(transcript) + #:nodoc: + transcript. + gsub(%r((Authorization: Bearer )[^\s]+\s)i, '\1[FILTERED]\2'). + gsub(%r((&?card_number=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?cvv=)[^&]*)i, '\1[BLANK]') + end + + private + + def charge(money, authorization, options = {}) + post = {} + post['token_id'] = authorization + post['capture'] = options[:capture] || 1 + add_money(post, money, options) + add_phone(post, options) + post = filter_gateway_fields(post, options, STANDARD_ACTIONS[:capture][:allowed_fields]) + commit(STANDARD_ACTIONS[:capture][:end_point], post) + end + + def add_creditcard(post, creditcard, options) + post['card_number'] = creditcard.number + post['exp_month'] = format(creditcard.month, :two_digits) + post['exp_year'] = creditcard.year + post['cvv'] = creditcard.verification_value unless creditcard.verification_value.nil? + post['card_holder_name'] = options[:card_holder_name] || "#{creditcard.first_name} #{creditcard.last_name}" + end + + def add_address(post, options) + return unless billing_address = options[:billing_address] + + post['address_line1'] = billing_address[:address1] + post['address_line2'] = billing_address[:address2] + post['city'] = billing_address[:city] + post['state'] = billing_address[:state] + post['zip'] = billing_address[:zip] + post['country'] = billing_address[:country] + end + + def add_phone(post, options) + post['phone_number'] = options[:billing_address][:phone] if options.dig(:billing_address, :phone) + end + + def add_money(post, money, options) + post['amount'] = money + post['currency'] = currency(money) unless options[:currency] + post['statement_description'] = options[:statement_description] + end + + def headers(api_key) + { + 'Authorization' => 'Bearer ' + api_key.strip, + 'Accept' => 'application/json', + 'User-Agent' => "PayArc ActiveMerchantBindings/#{ActiveMerchant::VERSION}" + } + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + body + end + + def filter_gateway_fields(post, options, gateway_fields) + filtered_options = options.slice(*gateway_fields).compact + post.update(filtered_options) + post + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + headers = headers(@options[:api_key]) + end_point = "#{url}/#{action}" + begin + response = ssl_post(end_point, post_data(parameters), headers) + parsed_response = parse(response) + + Response.new( + success_from(parsed_response, action), + message_from(parsed_response, action), + parsed_response, + test: test?, + authorization: parse_response_id(parsed_response), + error_code: error_code_from(parsed_response, action) + ) + rescue ResponseError => e + parsed_response = parse(e.response.body) + Response.new( + false, + message_from(parsed_response, action), + parsed_response, + test: test?, + authorization: nil, + error_code: error_code_from(parsed_response, action) + ) + end + end + + def success_from(response, action) + if action == STANDARD_ACTIONS[:token][:end_point] + token = parse_response_id(response) + (!token.nil? && !token.empty?) + elsif response + return SUCCESS_STATUS.include? response['data']['status'] if response['data'] + end + end + + def message_from(response, action) + if success_from(response, action) + if action == STANDARD_ACTIONS[:token][:end_point] + return response['data']['id'] + else + return response['data']['status'] + end + else + return response['message'] + end + end + + def parse_response_id(response) + response['data']['id'] if response && response['data'] + end + + def post_data(params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def error_code_from(response, action) + response['status_code'] unless success_from(response, action) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pay_conex.rb b/lib/active_merchant/billing/gateways/pay_conex.rb index d0ae08146e8..2a2953b4248 100644 --- a/lib/active_merchant/billing/gateways/pay_conex.rb +++ b/lib/active_merchant/billing/gateways/pay_conex.rb @@ -8,36 +8,36 @@ class PayConexGateway < Gateway self.supported_countries = %w(US CA) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.homepage_url = 'http://www.bluefincommerce.com/' self.display_name = 'PayConex' - def initialize(options={}) + def initialize(options = {}) requires!(options, :account_id, :api_accesskey) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = {} add_auth_purchase_params(post, money, payment_method, options) commit('SALE', post) end - def authorize(money, payment_method, options={}) + def authorize(money, payment_method, options = {}) post = {} add_auth_purchase_params(post, money, payment_method, options) commit('AUTHORIZATION', post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} add_reference_params(post, authorization, options) add_amount(post, money, options) commit('CAPTURE', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} add_reference_params(post, authorization, options) add_amount(post, money, options) @@ -50,21 +50,19 @@ def void(authorization, options = {}) commit('REVERSAL', post) end - def credit(money, payment_method, options={}) - if payment_method.is_a?(String) - raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' - end + def credit(money, payment_method, options = {}) + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if payment_method.is_a?(String) post = {} add_auth_purchase_params(post, money, payment_method, options) commit('CREDIT', post) end - def verify(payment_method, options={}) + def verify(payment_method, options = {}) authorize(0, payment_method, options) end - def store(payment_method, options={}) + def store(payment_method, options = {}) post = {} add_credentials(post) add_payment_method(post, payment_method) @@ -81,14 +79,17 @@ def scrub(transcript) force_utf8(transcript). gsub(%r((api_accesskey=)\w+), '\1[FILTERED]'). gsub(%r((card_number=)\w+), '\1[FILTERED]'). - gsub(%r((card_verification=)\w+), '\1[FILTERED]') + gsub(%r((card_verification=)\w+), '\1[FILTERED]'). + gsub(%r((bank_account_number=)\w+), '\1[FILTERED]'). + gsub(%r((bank_routing_number=)\w+), '\1[FILTERED]') end private def force_utf8(string) return nil unless string - binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. + + binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') end @@ -209,8 +210,8 @@ def commit(action, params) message_from(response), response, authorization: response['transaction_id'], - :avs_result => AVSResult.new(code: response['avs_response']), - :cvv_result => CVVResult.new(response['cvv2_response']), + avs_result: AVSResult.new(code: response['avs_response']), + cvv_result: CVVResult.new(response['cvv2_response']), test: test? ) rescue JSON::ParserError @@ -239,7 +240,6 @@ def unparsable_response(raw_response) message += " (The raw response returned by the API was #{raw_response.inspect})" return Response.new(false, message) end - end end end diff --git a/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/lib/active_merchant/billing/gateways/pay_gate_xml.rb index 35261aaf564..a39c79f33b9 100644 --- a/lib/active_merchant/billing/gateways/pay_gate_xml.rb +++ b/lib/active_merchant/billing/gateways/pay_gate_xml.rb @@ -76,10 +76,10 @@ class PayGateXmlGateway < Gateway self.live_url = 'https://www.paygate.co.za/payxml/process.trans' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US', 'ZA'] + self.supported_countries = %w[US ZA] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] # The homepage URL of the gateway self.homepage_url = 'http://paygate.co.za/' @@ -106,7 +106,7 @@ class PayGateXmlGateway < Gateway 900002 => 'Card Expired', 900003 => 'Insufficient Funds', 900004 => 'Invalid Card Number', - 900005 => 'Bank Interface Timeout', # indicates a communications failure between the banks systems + 900005 => 'Bank Interface Timeout', # indicates a communications failure between the banks systems 900006 => 'Invalid Card', 900007 => 'Declined', 900009 => 'Lost Card', @@ -117,7 +117,7 @@ class PayGateXmlGateway < Gateway 900014 => 'Excessive Card Usage', 900015 => 'Card Blacklisted', - 900207 => 'Declined; authentication failed', # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly + 900207 => 'Declined; authentication failed', # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly 990020 => 'Auth Declined', @@ -135,15 +135,15 @@ class PayGateXmlGateway < Gateway 990053 => 'Error processing transaction', # Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0. - 900209 => 'Transaction verification failed (phase 2)', # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered - 900210 => 'Authentication complete; transaction must be restarted', # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button + 900209 => 'Transaction verification failed (phase 2)', # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered + 900210 => 'Authentication complete; transaction must be restarted', # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button 990024 => 'Duplicate Transaction Detected. Please check before submitting', - 990028 => 'Transaction cancelled' # Customer clicks the 'Cancel' button on the payment page + 990028 => 'Transaction cancelled' # Customer clicks the 'Cancel' button on the payment page } - SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 ) + SUCCESS_CODES = %w(990004 990005 990017 990012 990018 990031) TRANSACTION_CODES = { 0 => 'Not Done', @@ -183,7 +183,7 @@ def capture(money, authorization, options = {}) commit(action, build_request(action, options), authorization) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) action = 'refundtx' options[:money] = money @@ -197,11 +197,11 @@ def successful?(response) SUCCESS_CODES.include?(response[:res]) end - def build_request(action, options={}) + def build_request(action, options = {}) xml = Builder::XmlMarkup.new xml.instruct! - xml.tag! 'protocol', :ver => API_VERSION, :pgid => (test? ? TEST_ID : @options[:login]), :pwd => @options[:password] do |protocol| + xml.tag! 'protocol', ver: API_VERSION, pgid: (test? ? TEST_ID : @options[:login]), pwd: @options[:password] do |protocol| money = options.delete(:money) authorization = options.delete(:authorization) creditcard = options.delete(:creditcard) @@ -220,31 +220,31 @@ def build_request(action, options={}) xml.target! end - def build_authorization(xml, money, creditcard, options={}) + def build_authorization(xml, money, creditcard, options = {}) xml.tag! 'authtx', { - :cref => options[:order_id], - :cname => creditcard.name, - :cc => creditcard.number, - :exp => "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}", - :budp => 0, - :amt => amount(money), - :cur => (options[:currency] || currency(money)), - :cvv => creditcard.verification_value, - :email => options[:email], - :ip => options[:ip] + cref: options[:order_id], + cname: creditcard.name, + cc: creditcard.number, + exp: "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}", + budp: 0, + amt: amount(money), + cur: (options[:currency] || currency(money)), + cvv: creditcard.verification_value, + email: options[:email], + ip: options[:ip] } end - def build_capture(xml, money, authorization, options={}) + def build_capture(xml, money, authorization, options = {}) xml.tag! 'settletx', { - :tid => authorization + tid: authorization } end - def build_refund(xml, money, authorization, options={}) + def build_refund(xml, money, authorization, options = {}) xml.tag! 'refundtx', { - :tid => authorization, - :amt => amount(money) + tid: authorization, + amt: amount(money) } end @@ -255,9 +255,7 @@ def parse(action, body) response_action = action.gsub(/tx/, 'rx') root = REXML::XPath.first(xml.root, response_action) # we might have gotten an error - if root.nil? - root = REXML::XPath.first(xml.root, 'errorrx') - end + root = REXML::XPath.first(xml.root, 'errorrx') if root.nil? root.attributes.each do |name, value| hash[name.to_sym] = value end @@ -266,9 +264,12 @@ 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, - :test => test?, - :authorization => authorization || response[:tid] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: authorization || response[:tid] ) end diff --git a/lib/active_merchant/billing/gateways/pay_hub.rb b/lib/active_merchant/billing/gateways/pay_hub.rb index fdcc6c5d600..b6dbb6cb695 100644 --- a/lib/active_merchant/billing/gateways/pay_hub.rb +++ b/lib/active_merchant/billing/gateways/pay_hub.rb @@ -5,7 +5,7 @@ class PayHubGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.payhub.com/' self.display_name = 'PayHub' @@ -66,7 +66,7 @@ class PayHubGateway < Gateway '43' => STANDARD_ERROR_CODE[:pickup_card] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :orgid, :username, :password, :tid) super @@ -82,7 +82,7 @@ def authorize(amount, creditcard, options = {}) commit(post) end - def purchase(amount, creditcard, options={}) + def purchase(amount, creditcard, options = {}) post = setup_post('sale') add_creditcard(post, creditcard) add_amount(post, amount) @@ -92,7 +92,7 @@ def purchase(amount, creditcard, options={}) commit(post) end - def refund(amount, trans_id, options={}) + def refund(amount, trans_id, options = {}) # Attempt a void in case the transaction is unsettled post = setup_post('void') add_reference(post, trans_id) @@ -115,7 +115,7 @@ def capture(amount, trans_id, options = {}) # No void, as PayHub's void does not work on authorizations - def verify(creditcard, options={}) + def verify(creditcard, options = {}) authorize(100, creditcard, options) end @@ -145,6 +145,7 @@ def add_customer_data(post, options = {}) def add_address(post, address) return unless address + post[:address1] = address[:address1] post[:address2] = address[:address2] post[:zip] = address[:zip] @@ -153,7 +154,7 @@ def add_address(post, address) end def add_amount(post, amount) - post[:amount] = amount(amount) + post[:amount] = amount(amount) end def add_creditcard(post, creditcard) @@ -171,7 +172,7 @@ def commit(post) success = false begin - raw_response = ssl_post(live_url, post.to_json, {'Content-Type' => 'application/json'}) + raw_response = ssl_post(live_url, post.to_json, { 'Content-Type' => 'application/json' }) response = parse(raw_response) success = (response['RESPONSE_CODE'] == '00') rescue ResponseError => e @@ -181,11 +182,12 @@ 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']}, + 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'] diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb index 68a24d6f8fc..ce8d66fe60b 100644 --- a/lib/active_merchant/billing/gateways/pay_junction.rb +++ b/lib/active_merchant/billing/gateways/pay_junction.rb @@ -97,7 +97,7 @@ module Billing #:nodoc: # See example use above for address AVS fields # See #recurring for periodic transaction fields class PayJunctionGateway < Gateway - API_VERSION = '1.2' + API_VERSION = '1.2' class_attribute :test_url, :live_url @@ -107,7 +107,7 @@ class PayJunctionGateway < Gateway TEST_LOGIN = 'pj-ql-01' TEST_PASSWORD = 'pj-ql-01p' - SUCCESS_CODES = ['00', '85'] + SUCCESS_CODES = %w[00 85] SUCCESS_MESSAGE = 'The transaction was approved.' FAILURE_MESSAGE = 'The transaction was declined.' @@ -150,7 +150,7 @@ class PayJunctionGateway < Gateway 'AB' => 'Aborted because of an upstream system error, please try again later.' } - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.supported_countries = ['US'] self.homepage_url = 'http://www.payjunction.com/' self.display_name = 'PayJunction' @@ -165,7 +165,7 @@ def initialize(options = {}) # transaction_id that can be used later to postauthorize (capture) the funds. def authorize(money, payment_source, options = {}) parameters = { - :transaction_amount => amount(money), + transaction_amount: amount(money) } add_payment_source(parameters, payment_source) @@ -178,7 +178,7 @@ def authorize(money, payment_source, options = {}) # Execute authorization and capture in a single step. def purchase(money, payment_source, options = {}) parameters = { - :transaction_amount => amount(money), + transaction_amount: amount(money) } add_payment_source(parameters, payment_source) @@ -191,8 +191,8 @@ def purchase(money, payment_source, options = {}) # Retrieve funds that have been previously authorized with _authorization_ def capture(money, authorization, options = {}) parameters = { - :transaction_id => authorization, - :posture => 'capture' + transaction_id: authorization, + posture: 'capture' } add_optional_fields(parameters, options) @@ -203,8 +203,8 @@ def capture(money, authorization, options = {}) # _authorization_ should be the transaction id of the transaction we are returning. def refund(money, authorization, options = {}) parameters = { - :transaction_amount => amount(money), - :transaction_id => authorization + transaction_amount: amount(money), + transaction_id: authorization } commit('CREDIT', parameters) @@ -219,8 +219,8 @@ def credit(money, authorization, options = {}) # through the batch process. def void(authorization, options = {}) parameters = { - :transaction_id => authorization, - :posture => 'void' + transaction_id: authorization, + posture: 'void' } add_optional_fields(parameters, options) @@ -241,16 +241,17 @@ def void(authorization, options = {}) def recurring(money, payment_source, options = {}) ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - requires!(options, [:periodicity, :monthly, :weekly, :daily], :payments) + requires!(options, %i[periodicity monthly weekly daily], :payments) - periodic_type = case options[:periodicity] - when :monthly - 'month' - when :weekly - 'week' - when :daily - 'day' - end + periodic_type = + case options[:periodicity] + when :monthly + 'month' + when :weekly + 'week' + when :daily + 'day' + end if options[:starting_at].nil? start_date = Time.now.strftime('%Y-%m-%d') @@ -262,12 +263,12 @@ def recurring(money, payment_source, options = {}) end parameters = { - :transaction_amount => amount(money), - :schedule_periodic_type => periodic_type, - :schedule_create => 'true', - :schedule_limit => options[:payments].to_i > 1 ? options[:payments] : 1, - :schedule_periodic_number => 1, - :schedule_start => start_date + transaction_amount: amount(money), + schedule_periodic_type: periodic_type, + schedule_create: 'true', + schedule_limit: options[:payments].to_i > 1 ? options[:payments] : 1, + schedule_periodic_number: 1, + schedule_start: start_date } add_payment_source(parameters, payment_source) @@ -318,7 +319,7 @@ def add_address(params, options) address = options[:billing_address] || options[:address] if address - params[:address] = address[:address1] unless address[:address1].blank? + params[:address] = address[:address1] unless address[:address1].blank? params[:city] = address[:city] unless address[:city].blank? params[:state] = address[:state] unless address[:state].blank? params[:zipcode] = address[:zip] unless address[:zip].blank? @@ -336,9 +337,12 @@ def commit(action, parameters) response = parse(ssl_post(url, post_data(action, parameters))) - Response.new(successful?(response), message_from(response), response, - :test => test?, - :authorization => response[:transaction_id] || parameters[:transaction_id] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: response[:transaction_id] || parameters[:transaction_id] ) end @@ -366,7 +370,7 @@ def post_data(action, params) params[:version] = API_VERSION params[:transaction_type] = action - params.reject { |k, v| v.blank? }.collect { |k, v| "dc_#{k}=#{CGI.escape(v.to_s)}" }.join('&') + params.reject { |_k, v| v.blank? }.collect { |k, v| "dc_#{k}=#{CGI.escape(v.to_s)}" }.join('&') end def parse(body) diff --git a/lib/active_merchant/billing/gateways/pay_junction_v2.rb b/lib/active_merchant/billing/gateways/pay_junction_v2.rb index aed266facd6..57b237b28be 100644 --- a/lib/active_merchant/billing/gateways/pay_junction_v2.rb +++ b/lib/active_merchant/billing/gateways/pay_junction_v2.rb @@ -10,31 +10,33 @@ class PayJunctionV2Gateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] - def initialize(options={}) + def initialize(options = {}) requires!(options, :api_login, :api_password, :api_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_payment_method(post, payment_method) + add_address(post, options) commit('purchase', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} post[:status] = 'HOLD' add_invoice(post, amount, options) add_payment_method(post, payment_method) + add_address(post, options) commit('authorize', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} post[:status] = 'CAPTURE' post[:transactionId] = authorization @@ -43,7 +45,7 @@ def capture(amount, authorization, options={}) commit('capture', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} post[:status] = 'VOID' post[:transactionId] = authorization @@ -51,7 +53,7 @@ def void(authorization, options={}) commit('void', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} post[:action] = 'REFUND' post[:transactionId] = authorization @@ -60,7 +62,7 @@ def refund(amount, authorization, options={}) commit('refund', post) end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) post = {} post[:action] = 'REFUND' add_invoice(post, amount, options) @@ -69,7 +71,7 @@ def credit(amount, payment_method, options={}) commit('credit', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -110,12 +112,27 @@ def add_payment_method(post, payment_method) end end - def commit(action, params) - response = begin - parse(ssl_invoke(action, params)) - rescue ResponseError => e - parse(e.response.body) + def add_address(post, options) + if address = options[:billing_address] + post[:billingFirstName] = address[:first_name] if address[:first_name] + post[:billingLastName] = address[:last_name] if address[:last_name] + post[:billingCompanyName] = address[:company] if address[:company] + post[:billingPhone] = address[:phone_number] if address[:phone_number] + post[:billingAddress] = address[:address1] if address[:address1] + post[:billingCity] = address[:city] if address[:city] + post[:billingState] = address[:state] if address[:state] + post[:billingCountry] = address[:country] if address[:country] + post[:billingZip] = address[:zip] if address[:zip] end + end + + def commit(action, params) + response = + begin + parse(ssl_invoke(action, params)) + rescue ResponseError => e + parse(e.response.body) + end success = success_from(response) Response.new( @@ -129,7 +146,7 @@ def commit(action, params) end def ssl_invoke(action, params) - if ['purchase', 'authorize', 'refund', 'credit'].include?(action) + if %w[purchase authorize refund credit].include?(action) ssl_post(url(), post_data(params), headers) else ssl_request(:put, url(params), post_data(params), headers) @@ -140,8 +157,8 @@ def headers { 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:api_login]}:#{@options[:api_password]}").strip, 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8', - 'Accept' => 'application/json', - 'X-PJ-Application-Key' => @options[:api_key].to_s + 'Accept' => 'application/json', + 'X-PJ-Application-Key' => @options[:api_key].to_s } end @@ -149,7 +166,7 @@ def post_data(params) params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end - def url(params={}) + def url(params = {}) test? ? "#{test_url}/#{params[:transactionId]}" : "#{live_url}/#{params[:transactionId]}" end @@ -167,6 +184,7 @@ def parse(body) def success_from(response) return response['response']['approved'] if response['response'] + false end diff --git a/lib/active_merchant/billing/gateways/pay_secure.rb b/lib/active_merchant/billing/gateways/pay_secure.rb index 76c13578379..3337fa24cf3 100644 --- a/lib/active_merchant/billing/gateways/pay_secure.rb +++ b/lib/active_merchant/billing/gateways/pay_secure.rb @@ -8,10 +8,10 @@ class PaySecureGateway < Gateway # Currently Authorization and Capture is not implemented because # capturing requires the original credit card information TRANSACTIONS = { - :purchase => 'PURCHASE', - :authorization => 'AUTHORISE', - :capture => 'ADVICE', - :credit => 'REFUND' + purchase: 'PURCHASE', + authorization: 'AUTHORISE', + capture: 'ADVICE', + credit: 'REFUND' } SUCCESS = 'Accepted' @@ -20,7 +20,7 @@ class PaySecureGateway < Gateway self.supported_countries = ['AU'] self.homepage_url = 'http://www.commsecure.com.au/paysecure.shtml' self.display_name = 'PaySecure' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] def initialize(options = {}) requires!(options, :login, :password) @@ -43,7 +43,7 @@ def purchase(money, credit_card, options = {}) # Used for capturing, which is currently not supported. def add_reference(post, identification) auth, trans_id = identification.split(';') - post[:authnum] = auth + post[:authnum] = auth post[:transid] = trans_id end @@ -68,9 +68,12 @@ 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, - :test => test_response?(response), - :authorization => authorization_from(response) + Response.new( + successful?(response), + message_from(response), + response, + test: test_response?(response), + authorization: authorization_from(response) ) end @@ -79,7 +82,7 @@ def successful?(response) end def authorization_from(response) - [ response[:authnum], response[:transid] ].compact.join(';') + [response[:authnum], response[:transid]].compact.join(';') end def test_response?(response) @@ -104,7 +107,7 @@ def post_data(action, parameters = {}) parameters[:merchant_id] = @options[:login] parameters[:password] = @options[:password] - parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&') + parameters.reject { |_k, v| v.blank? }.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/pay_trace.rb b/lib/active_merchant/billing/gateways/pay_trace.rb new file mode 100644 index 00000000000..8c338687df1 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pay_trace.rb @@ -0,0 +1,459 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayTraceGateway < Gateway + self.test_url = 'https://api.paytrace.com' + self.live_url = 'https://api.paytrace.com' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://paytrace.com/' + self.display_name = 'PayTrace' + + # Response codes based on API Response Codes found here: https://developers.paytrace.com/support/home#14000041297 + STANDARD_ERROR_CODE_MAPPING = { + '1' => STANDARD_ERROR_CODE[:error_occurred], + '102' => STANDARD_ERROR_CODE[:declined], + '103' => STANDARD_ERROR_CODE[:auto_voided], + '107' => STANDARD_ERROR_CODE[:unsuccessful_refund], + '108' => STANDARD_ERROR_CODE[:test_refund], + '110' => STANDARD_ERROR_CODE[:unsuccessful_void], + '113' => STANDARD_ERROR_CODE[:unsuccessful_capture] + } + + ENDPOINTS = { + customer_id_sale: 'transactions/sale/by_customer', + keyed_sale: 'transactions/sale/keyed', + customer_id_auth: 'transactions/authorization/by_customer', + keyed_auth: 'transactions/authorization/keyed', + capture: 'transactions/authorization/capture', + transaction_refund: 'transactions/refund/for_transaction', + transaction_void: 'transactions/void', + store: 'customer/create', + redact: 'customer/delete', + level_3_visa: 'level_three/visa', + level_3_mastercard: 'level_three/mastercard', + ach_sale: 'checks/sale/by_account', + ach_customer_sale: 'checks/sale/by_customer', + ach_authorize: 'checks/hold/by_account', + ach_customer_authorize: 'checks/hold/by_customer', + ach_refund: 'checks/refund/by_transaction', + ach_capture: 'checks/manage/fund', + ach_void: 'checks/manage/void' + } + + def initialize(options = {}) + requires!(options, :username, :password, :integrator_id) + super + acquire_access_token unless options[:access_token] + end + + def purchase(money, payment_or_customer_id, options = {}) + if visa_or_mastercard?(options) + MultiResponse.run(:use_first_response) do |r| + endpoint = customer_id?(payment_or_customer_id) ? ENDPOINTS[:customer_id_sale] : ENDPOINTS[:keyed_sale] + + r.process { commit(endpoint, build_purchase_request(money, payment_or_customer_id, options)) } + r.process { commit(ENDPOINTS[:"level_3_#{options[:visa_or_mastercard]}"], send_level_3_data(r, options)) } + end + else + post = build_purchase_request(money, payment_or_customer_id, options) + endpoint = if payment_or_customer_id.kind_of?(Check) + ENDPOINTS[:ach_sale] + elsif options[:check_transaction] + ENDPOINTS[:ach_customer_sale] + elsif post[:customer_id] + ENDPOINTS[:customer_id_sale] + else + ENDPOINTS[:keyed_sale] + end + response = commit(endpoint, post) + check_token_response(response, endpoint, post, options) + end + end + + def authorize(money, payment_or_customer_id, options = {}) + post = {} + add_amount(post, money, options) + if customer_id?(payment_or_customer_id) + post[:customer_id] = payment_or_customer_id + endpoint = if options[:check_transaction] + ENDPOINTS[:ach_customer_authorize] + else + ENDPOINTS[:customer_id_auth] + end + else + add_payment(post, payment_or_customer_id) + add_address(post, payment_or_customer_id, options) + add_customer_data(post, options) + endpoint = payment_or_customer_id.kind_of?(Check) ? ENDPOINTS[:ach_authorize] : ENDPOINTS[:keyed_auth] + end + response = commit(endpoint, post) + check_token_response(response, endpoint, post, options) + end + + def capture(money, authorization, options = {}) + if visa_or_mastercard?(options) + MultiResponse.run do |r| + r.process { commit(ENDPOINTS[:capture], build_capture_request(money, authorization, options)) } + r.process { commit(ENDPOINTS[:"level_3_#{options[:visa_or_mastercard]}"], send_level_3_data(r, options)) } + end + else + post = build_capture_request(money, authorization, options) + endpoint = if options[:check_transaction] + ENDPOINTS[:ach_capture] + else + ENDPOINTS[:capture] + end + response = commit(endpoint, post) + check_token_response(response, endpoint, post, options) + end + end + + def refund(money, authorization, options = {}) + # currently only support full and partial refunds of settled transactions via a transaction ID + post = {} + add_amount(post, money, options) + if options[:check_transaction] + post[:check_transaction_id] = authorization + endpoint = ENDPOINTS[:ach_refund] + else + post[:transaction_id] = authorization + endpoint = ENDPOINTS[:transaction_refund] + end + response = commit(endpoint, post) + check_token_response(response, endpoint, post, options) + end + + def void(authorization, options = {}) + post = {} + if options[:check_transaction] + post[:check_transaction_id] = authorization + endpoint = ENDPOINTS[:ach_void] + else + post[:transaction_id] = authorization + endpoint = ENDPOINTS[:transaction_void] + end + + response = commit(endpoint, post) + check_token_response(response, endpoint, post, options) + end + + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + + # The customer_IDs that come from storing cards can be used for auth and purchase transaction types + def store(credit_card, options = {}) + post = {} + post[:customer_id] = options[:customer_id] || SecureRandom.hex(12) + add_payment(post, credit_card) + add_address(post, credit_card, options) + response = commit(ENDPOINTS[:store], post) + check_token_response(response, ENDPOINTS[:store], post, options) + end + + def unstore(customer_id) + post = {} + post[:customer_id] = customer_id + response = commit(ENDPOINTS[:redact], post) + check_token_response(response, ENDPOINTS[:redact], post, options) + end + + def supports_scrubbing? + true + end + + 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(("username\\?":\\?")\w+@+\w+.+\w+), '\1[FILTERED]'). + gsub(%r(("password\\?":\\?")\w+), '\1[FILTERED]'). + gsub(%r(("integrator_id\\?":\\?")\w+), '\1[FILTERED]') + end + + def acquire_access_token + post = {} + 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' + oauth_headers = { + 'Accept' => '*/*', + 'Content-Type' => 'application/x-www-form-urlencoded' + } + response = ssl_post(url, data, oauth_headers) + json_response = parse(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 + + def build_purchase_request(money, payment_or_customer_id, options) + post = {} + add_amount(post, money, options) + if customer_id?(payment_or_customer_id) + post[:customer_id] = payment_or_customer_id + else + add_payment(post, payment_or_customer_id) + add_address(post, payment_or_customer_id, options) + add_customer_data(post, options) + end + + post + end + + def build_capture_request(money, authorization, options) + post = {} + if options[:check_transaction] + post[:check_transaction_id] = authorization + else + post[:transaction_id] = authorization + end + add_amount(post, money, options) + + post + end + + # method can only be used to add level 3 data to any approved and unsettled sale transaction so it is built into the standard purchase workflow above + def send_level_3_data(response, options) + post = {} + post[:transaction_id] = response.authorization + add_level_3_data(post, options) + + post + end + + def visa_or_mastercard?(options) + return false unless options[:visa_or_mastercard] + + options[:visa_or_mastercard] == 'visa' || options[:visa_or_mastercard] == 'mastercard' + end + + def customer_id?(payment_or_customer_id) + payment_or_customer_id.class == String + end + + def string_literal_to_boolean(value) + return value unless value.class == String + + if value.casecmp('true').zero? + true + elsif value.casecmp('false').zero? + false + else return nil + end + end + + def add_customer_data(post, options) + return unless options[:email] + + post[:email] = options[:email] + end + + def add_address(post, creditcard, options) + return unless options[:billing_address] || options[:address] + + address = options[:billing_address] || options[:address] + post[:billing_address] = {} + 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) + post[:amount] = amount(money) + end + + def add_payment(post, payment) + if payment.kind_of?(Check) + post[:check] = {} + post[:check][:account_number] = payment.account_number + post[:check][:routing_number] = payment.routing_number + else + post[:credit_card] = {} + post[:credit_card][:number] = payment.number + post[:credit_card][:expiration_month] = payment.month + post[:credit_card][:expiration_year] = payment.year + end + end + + def add_level_3_data(post, options) + post[:invoice_id] = options[:invoice_id] if options[:invoice_id] + post[:customer_reference_id] = options[:customer_reference_id] if options[:customer_reference_id] + post[:tax_amount] = options[:tax_amount].to_i if options[:tax_amount] + post[:national_tax_amount] = options[:national_tax_amount].to_i if options[:national_tax_amount] + post[:merchant_tax_id] = options[:merchant_tax_id] if options[:merchant_tax_id] + post[:customer_tax_id] = options[:customer_tax_id] if options[:customer_tax_id] + post[:commodity_code] = options[:commodity_code] if options[:commodity_code] + post[:discount_amount] = options[:discount_amount].to_i if options[:discount_amount] + post[:freight_amount] = options[:freight_amount].to_i if options[:freight_amount] + post[:duty_amount] = options[:duty_amount].to_i if options[:duty_amount] + post[:additional_tax_amount] = options[:additional_tax_amount].to_i if options[:additional_tax_amount] + post[:additional_tax_rate] = options[:additional_tax_rate].to_i if options[:additional_tax_rate] + + add_source_address(post, options) + add_shipping_address(post, options) + add_line_items(post, options) + end + + def add_source_address(post, options) + return unless source_address = options[:source_address] || + options[:billing_address] || + options[:address] + + post[:source_address] = {} + post[:source_address][:zip] = source_address[:zip] if source_address[:zip] + end + + def add_shipping_address(post, options) + return unless shipping_address = options[:shipping_address] + + post[:shipping_address] = {} + post[:shipping_address][:name] = shipping_address[:name] if shipping_address[:name] + post[:shipping_address][:street_address] = shipping_address[:address1] if shipping_address[:address1] + post[:shipping_address][:street_address2] = shipping_address[:address2] if shipping_address[:address2] + post[:shipping_address][:city] = shipping_address[:city] if shipping_address[:city] + post[:shipping_address][:state] = shipping_address[:state] if shipping_address[:state] + post[:shipping_address][:zip] = shipping_address[:zip] if shipping_address[:zip] + post[:shipping_address][:country] = shipping_address[:country] if shipping_address[:country] + end + + def add_line_items(post, options) + return unless options[:line_items] + + line_items = [] + options[:line_items].each do |li| + obj = {} + + obj[:additional_tax_amount] = li[:additional_tax_amount].to_i if li[:additional_tax_amount] + obj[:additional_tax_included] = string_literal_to_boolean(li[:additional_tax_included]) if li[:additional_tax_included] + obj[:additional_tax_rate] = li[:additional_tax_rate].to_i if li[:additional_tax_rate] + obj[:amount] = li[:amount].to_i if li[:amount] + obj[:commodity_code] = li[:commodity_code] if li[:commodity_code] + obj[:debit_or_credit] = li[:debit_or_credit] if li[:debit_or_credit] + obj[:description] = li[:description] if li[:description] + obj[:discount_amount] = li[:discount_amount].to_i if li[:discount_amount] + obj[:discount_rate] = li[:discount_rate].to_i if li[:discount_rate] + obj[:discount_included] = string_literal_to_boolean(li[:discount_included]) if li[:discount_included] + obj[:merchant_tax_id] = li[:merchant_tax_id] if li[:merchant_tax_id] + obj[:product_id] = li[:product_id] if li[:product_id] + obj[:quantity] = li[:quantity] if li[:quantity] + obj[:transaction_id] = li[:transaction_id] if li[:transaction_id] + obj[:tax_included] = string_literal_to_boolean(li[:tax_included]) if li[:tax_included] + obj[:unit_of_measure] = li[:unit_of_measure] if li[:unit_of_measure] + obj[:unit_cost] = li[:unit_cost].to_i if li[:unit_cost] + + line_items << obj + end + post[:line_items] = line_items + end + + def check_token_response(response, endpoint, body = {}, options = {}) + return response unless response.params['error'] == 'invalid_token' + + acquire_access_token + commit(endpoint, body) + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters) + base_url = (test? ? test_url : live_url) + 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( + success, + message_from(success, response), + response, + authorization: authorization_from(action, response), + avs_result: AVSResult.new(code: response['avs_response']), + cvv_result: response['csc_response'], + test: test?, + error_code: success ? nil : error_code_from(response) + ) + end + + def unparsable_response(raw_response) + message = 'Unparsable response received from PayTrace. Please contact PayTrace if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + def headers + { + 'Content-type' => 'application/json', + 'Authorization' => 'Bearer ' + @options[:access_token] + } + end + + def success_from(response) + response['success'] + end + + def message_from(success, response) + return response['status_message'] if success + + if error = response['errors'] + message = 'Errors-' + error.each do |k, v| + message.concat(" code:#{k}, message:#{v}") + end + else + message = response['status_message'].to_s + " #{response['approval_message']}" + end + + message + end + + # store transactions do not return a transaction_id, but they return a customer_id that will then be used as the third_party_token for the stored payment method + def authorization_from(action, response) + if action == ENDPOINTS[:store] + response['customer_id'] + else + response['transaction_id'] || response['check_transaction_id'] + end + end + + def post_data(parameters = {}) + parameters[:password] = @options[:password] + parameters[:username] = @options[:username] + parameters[:integrator_id] = @options[:integrator_id] + + parameters.to_json + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['response_code']] + end + + def handle_response(response) + response.body + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/paybox_direct.rb b/lib/active_merchant/billing/gateways/paybox_direct.rb index de236330928..850eead7cac 100644 --- a/lib/active_merchant/billing/gateways/paybox_direct.rb +++ b/lib/active_merchant/billing/gateways/paybox_direct.rb @@ -12,34 +12,34 @@ class PayboxDirectGateway < Gateway # Transactions hash TRANSACTIONS = { - :authorization => '00001', - :capture => '00002', - :purchase => '00003', - :unreferenced_credit => '00004', - :void => '00005', - :refund => '00014' + authorization: '00001', + capture: '00002', + purchase: '00003', + unreferenced_credit: '00004', + void: '00005', + refund: '00014' } CURRENCY_CODES = { - 'AUD'=> '036', - 'CAD'=> '124', - 'CZK'=> '203', - 'DKK'=> '208', - 'HKD'=> '344', - 'ICK'=> '352', - 'JPY'=> '392', - 'NOK'=> '578', - 'SGD'=> '702', - 'SEK'=> '752', - 'CHF'=> '756', - 'GBP'=> '826', - 'USD'=> '840', - 'EUR'=> '978', - 'XPF'=> '953' + 'AUD' => '036', + 'CAD' => '124', + 'CZK' => '203', + 'DKK' => '208', + 'HKD' => '344', + 'ICK' => '352', + 'JPY' => '392', + 'NOK' => '578', + 'SGD' => '702', + 'SEK' => '752', + 'CHF' => '756', + 'GBP' => '826', + 'USD' => '840', + 'EUR' => '978', + 'XPF' => '953' } SUCCESS_CODES = ['00000'] - UNAVAILABILITY_CODES = ['00001', '00097', '00098'] + UNAVAILABILITY_CODES = %w[00001 00097 00098] SUCCESS_MESSAGE = 'The transaction was approved' FAILURE_MESSAGE = 'The transaction failed' @@ -51,7 +51,7 @@ class PayboxDirectGateway < Gateway self.supported_countries = ['FR'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] # The homepage URL of the gateway self.homepage_url = 'http://www.paybox.com/' @@ -64,10 +64,31 @@ def initialize(options = {}) super end + def add_3dsecure(post, options) + # ECI=02 => MasterCard success + # ECI=05 => Visa, Amex or JCB success + if options[:eci] == '02' || options[:eci] == '05' + post[:"3DSTATUS"] = 'Y' + post[:"3DENROLLED"] = 'Y' + post[:"3DSIGNVAL"] = 'Y' + post[:"3DERROR"] = '0' + else + post[:"3DSTATUS"] = 'N' + post[:"3DENROLLED"] = 'N' + post[:"3DSIGNVAL"] = 'N' + post[:"3DERROR"] = '10000' + end + post[:"3DECI"] = options[:eci] + post[:"3DXID"] = options[:xid] + post[:"3DCAVV"] = options[:cavv] + post[:"3DCAVVALGO"] = options[:cavv_algorithm] + end + def authorize(money, creditcard, options = {}) post = {} add_invoice(post, options) add_creditcard(post, creditcard) + add_3dsecure(post, options[:three_d_secure]) if options[:three_d_secure] add_amount(post, money, options) commit('authorization', money, post) @@ -77,6 +98,7 @@ def purchase(money, creditcard, options = {}) post = {} add_invoice(post, options) add_creditcard(post, creditcard) + add_3dsecure(post, options[:three_d_secure]) if options[:three_d_secure] add_amount(post, money, options) commit('purchase', money, post) @@ -95,7 +117,7 @@ def capture(money, authorization, options = {}) def void(identification, options = {}) requires!(options, :order_id, :amount) - post ={} + post = {} add_invoice(post, options) add_reference(post, identification) add_amount(post, options[:amount], options) @@ -153,12 +175,14 @@ def commit(action, money = nil, parameters = nil) request_data = post_data(action, parameters) response = parse(ssl_post(test? ? self.test_url : self.live_url, request_data)) response = parse(ssl_post(self.live_url_backup, request_data)) if service_unavailable?(response) && !test? - Response.new(success?(response), message_from(response), response.merge( - :timestamp => parameters[:dateq]), - :test => test?, - :authorization => response[:numappel].to_s + response[:numtrans].to_s, - :fraud_review => false, - :sent_params => parameters.delete_if { |key, value| ['porteur', 'dateval', 'cvv'].include?(key.to_s) } + Response.new( + success?(response), + message_from(response), + response.merge(timestamp: parameters[:dateq]), + test: test?, + authorization: response[:numappel].to_s + response[:numtrans].to_s, + fraud_review: false, + sent_params: parameters.delete_if { |key, _value| %w[porteur dateval cvv].include?(key.to_s) } ) end @@ -171,20 +195,20 @@ def service_unavailable?(response) end def message_from(response) - success?(response) ? SUCCESS_MESSAGE : (response[:commentaire] || FAILURE_MESSAGE) + success?(response) ? SUCCESS_MESSAGE : (response[:commentaire] || FAILURE_MESSAGE) end def post_data(action, parameters = {}) parameters.update( - :version => API_VERSION, - :type => TRANSACTIONS[action.to_sym], - :dateq => Time.now.strftime('%d%m%Y%H%M%S'), - :numquestion => unique_id(parameters[:order_id]), - :site => @options[:login].to_s[0, 7], - :rang => @options[:rang] || @options[:login].to_s[7..-1], - :cle => @options[:password], - :pays => '', - :archivage => parameters[:order_id] + version: API_VERSION, + type: TRANSACTIONS[action.to_sym], + dateq: Time.now.strftime('%d%m%Y%H%M%S'), + numquestion: unique_id(parameters[:order_id]), + site: @options[:login].to_s[0, 7], + rang: @options[:rang] || @options[:login].to_s[7..-1], + cle: @options[:password], + pays: '', + archivage: parameters[:order_id] ) parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&') diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb index b1ac0632f4c..8f4425cd0d5 100644 --- a/lib/active_merchant/billing/gateways/payeezy.rb +++ b/lib/active_merchant/billing/gateways/payeezy.rb @@ -11,7 +11,7 @@ class PayeezyGateway < Gateway self.money_format = :cents self.supported_countries = %w(US CA) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.homepage_url = 'https://developer.payeezy.com/' self.display_name = 'Payeezy' @@ -35,31 +35,39 @@ 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 def authorize(amount, payment_method, options = {}) - params = {transaction_type: 'authorize'} + params = { transaction_type: 'authorize' } 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 def capture(amount, authorization, options = {}) - params = {transaction_type: 'capture'} + params = { transaction_type: 'capture' } add_authorization_info(params, authorization) add_amount(params, amount, options) @@ -69,16 +77,28 @@ def capture(amount, authorization, options = {}) end def refund(amount, authorization, options = {}) - params = {transaction_type: 'refund'} + params = { transaction_type: 'refund' } add_authorization_info(params, authorization) add_amount(params, (amount || amount_from_authorization(authorization)), options) + add_soft_descriptors(params, options) + add_invoice(params, options) + + commit(params, options) + end + def credit(amount, payment_method, options = {}) + params = { transaction_type: 'refund' } + + add_amount(params, amount, options) + add_payment_method(params, payment_method, options) + add_soft_descriptors(params, options) + add_invoice(params, options) commit(params, options) end def store(payment_method, options = {}) - params = {transaction_type: 'store'} + params = { transaction_type: 'store' } add_creditcard_for_tokenization(params, payment_method, options) @@ -86,7 +106,7 @@ def store(payment_method, options = {}) end def void(authorization, options = {}) - params = {transaction_type: 'void'} + params = { transaction_type: 'void' } add_authorization_info(params, authorization, options) add_amount(params, amount_from_authorization(authorization), options) @@ -94,7 +114,7 @@ def void(authorization, options = {}) commit(params, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(0, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -111,6 +131,7 @@ def scrub(transcript) gsub(%r((Apikey: )(\w|-)+), '\1[FILTERED]'). gsub(%r((\\?"card_number\\?":\\?")\d+), '\1[FILTERED]'). gsub(%r((\\?"cvv\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r((\\?"cvv\\?":\\?)\d+), '\1[FILTERED]'). gsub(%r((\\?"account_number\\?":\\?")\d+), '\1[FILTERED]'). gsub(%r((\\?"routing_number\\?":\\?")\d+), '\1[FILTERED]'). gsub(%r((\\?card_number=)\d+(&?)), '\1[FILTERED]'). @@ -118,11 +139,33 @@ def scrub(transcript) gsub(%r((\\?apikey=)\w+(&?)), '\1[FILTERED]'). gsub(%r{(\\?"credit_card\.card_number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). gsub(%r{(\\?"credit_card\.cvv\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). - gsub(%r{(\\?"apikey\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]') + gsub(%r{(\\?"apikey\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(\\?"cavv\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(\\?"xid\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]') end 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 @@ -131,13 +174,23 @@ 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 def add_authorization_info(params, authorization, options = {}) - transaction_id, transaction_tag, method, _ = authorization.split('|') + 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] @@ -155,7 +208,7 @@ def add_creditcard_for_tokenization(params, payment_method, options) params[:auth] = 'false' end - def is_store_action?(params) + def store_action?(params) params[:transaction_type] == 'store' end @@ -164,6 +217,8 @@ def add_payment_method(params, payment_method, options) add_echeck(params, payment_method, options) elsif payment_method.is_a? String add_token(params, payment_method, options) + elsif payment_method.is_a? NetworkTokenizationCreditCard + add_network_tokenization(params, payment_method, options) else add_creditcard(params, payment_method) end @@ -176,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] @@ -219,10 +274,37 @@ def add_card_data(payment_method) card end + def add_network_tokenization(params, payment_method, options) + nt_card = {} + nt_card[:type] = 'D' + 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 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.nil? ? '5' : payment_method.eci + end + def format_exp_date(month, year) "#{format(month, :two_digits)}#{format(year, :two_digits)}" end + def name_from_address(options) + return unless address = options[:billing_address] + 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 @@ -246,17 +328,41 @@ def add_soft_descriptors(params, options) params[:soft_descriptors] = options[:soft_descriptors] if options[:soft_descriptors] end + def add_level2_data(params, options) + return unless level2_data = options[:level2] + + params[:level2] = {} + params[:level2][:customer_ref] = level2_data[:customer_ref] + end + def add_stored_credentials(params, options) - if options[:sequence] + if options[:sequence] || options[:stored_credential] params[:stored_credentials] = {} - params[:stored_credentials][:cardbrand_original_transaction_id] = options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id] - params[:stored_credentials][:sequence] = options[:sequence] - params[:stored_credentials][:initiator] = options[:initiator] if options[:initiator] - params[:stored_credentials][:is_scheduled] = options[:is_scheduled] + params[:stored_credentials][:cardbrand_original_transaction_id] = original_transaction_id(options) if original_transaction_id(options) + params[:stored_credentials][:initiator] = initiator(options) if initiator(options) + params[:stored_credentials][:sequence] = options[:sequence] || sequence(options[:stored_credential][:initial_transaction]) + params[:stored_credentials][:is_scheduled] = options[:is_scheduled] || is_scheduled(options[:stored_credential][:reason_type]) params[:stored_credentials][:auth_type_override] = options[:auth_type_override] if options[:auth_type_override] end end + def original_transaction_id(options) + return options[:cardbrand_original_transaction_id] || options.dig(:stored_credential, :network_transaction_id) + end + + def initiator(options) + return options[:initiator] if options[:initiator] + return options[:stored_credential][:initiator].upcase if options.dig(:stored_credential, :initiator) + end + + def sequence(initial_transaction) + initial_transaction ? 'FIRST' : 'SUBSEQUENT' + end + + def is_scheduled(reason_type) + reason_type == 'recurring' ? 'true' : 'false' + end + def commit(params, options) url = base_url(options) + endpoint(params) @@ -272,15 +378,16 @@ def commit(params, options) response = json_error(e.response.body) end + success = success_from(response) Response.new( - success_from(response), - handle_message(response, success_from(response)), + success, + handle_message(response, success), response, test: test?, authorization: authorization_from(params, response), - avs_result: {code: response['avs']}, + avs_result: { code: response['avs'] }, cvv_result: response['cvv2'], - error_code: error_code(response, success_from(response)) + error_code: success ? nil : error_code_from(response) ) end @@ -295,7 +402,7 @@ def base_url(options) end def endpoint(params) - is_store_action?(params) ? '/transactions/tokens' : '/transactions' + store_action?(params) ? '/transactions/tokens' : '/transactions' end def api_request(url, params) @@ -305,7 +412,8 @@ def api_request(url, params) def post_data(params) return nil unless params - params.reject { |k, v| v.blank? }.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + + params.reject { |_k, v| v.blank? }.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end def generate_hmac(nonce, current_timestamp, payload) @@ -333,9 +441,16 @@ def headers(payload) } end - def error_code(response, success) - return if success - response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ') + def error_code_from(response) + error_code = nil + if response['bank_resp_code'] == '100' + return + elsif response['bank_resp_code'] + error_code = response['bank_resp_code'] + elsif error_code = response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ') + end + + error_code end def success_from(response) @@ -371,7 +486,7 @@ def handle_message(response, success) end def authorization_from(params, response) - if is_store_action?(params) + if store_action?(params) if success_from(response) [ response['token']['type'], @@ -403,7 +518,7 @@ def response_error(raw_response) end def json_error(raw_response) - {'error' => "Unable to parse response: #{raw_response.inspect}"} + { 'error' => "Unable to parse response: #{raw_response.inspect}" } end end end diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb index c43e36bb13f..c1449672e4b 100644 --- a/lib/active_merchant/billing/gateways/payex.rb +++ b/lib/active_merchant/billing/gateways/payex.rb @@ -12,8 +12,8 @@ class PayexGateway < Gateway self.test_confined_url = 'https://test-confined.payex.com/' self.money_format = :cents - self.supported_countries = ['DK', 'FI', 'NO', 'SE'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = %w[DK FI NO SE] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://payex.com/' self.display_name = 'Payex' self.default_currency = 'EUR' @@ -25,18 +25,18 @@ class PayexGateway < Gateway authorize: '3', cancel: '4', failure: '5', - capture: '6', + capture: '6' } SOAP_ACTIONS = { initialize: { name: 'Initialize8', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, - purchasecc: { name: 'PurchaseCC', url: 'pxconfined/pxorder.asmx', xmlns: 'http://confined.payex.com/PxOrder/', confined: true}, + purchasecc: { name: 'PurchaseCC', url: 'pxconfined/pxorder.asmx', xmlns: 'http://confined.payex.com/PxOrder/', confined: true }, cancel: { name: 'Cancel2', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, capture: { name: 'Capture5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, credit: { name: 'Credit5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, create_agreement: { name: 'CreateAgreement3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, delete_agreement: { name: 'DeleteAgreement', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, - autopay: { name: 'AutoPay3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + autopay: { name: 'AutoPay3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' } } def initialize(options = {}) @@ -117,7 +117,7 @@ def capture(money, authorization, options = {}) # options - A standard ActiveMerchant options hash # # Returns an ActiveMerchant::Billing::Response object - def void(authorization, options={}) + def void(authorization, options = {}) send_cancel(authorization) end @@ -155,7 +155,7 @@ def store(creditcard, options = {}) amount = amount(1) # 1 cent for authorization MultiResponse.run(:first) do |r| r.process { send_create_agreement(options) } - r.process { send_initialize(amount, true, options.merge({agreement_ref: r.authorization})) } + r.process { send_initialize(amount, true, options.merge({ agreement_ref: r.authorization })) } order_ref = r.params['orderref'] r.process { send_purchasecc(creditcard, order_ref) } end @@ -193,9 +193,9 @@ def send_initialize(amount, is_auth, options = {}) cancelUrl: nil, clientLanguage: nil } - hash_fields = [:accountNumber, :purchaseOperation, :price, :priceArgList, :currency, :vat, :orderID, - :productNumber, :description, :clientIPAddress, :clientIdentifier, :additionalValues, - :externalID, :returnUrl, :view, :agreementRef, :cancelUrl, :clientLanguage] + hash_fields = %i[accountNumber purchaseOperation price priceArgList currency vat orderID + productNumber description clientIPAddress clientIdentifier additionalValues + externalID returnUrl view agreementRef cancelUrl clientLanguage] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:initialize] request = build_xml_request(soap_action, properties) @@ -213,8 +213,8 @@ def send_purchasecc(payment_method, order_ref) cardHolderName: payment_method.name, cardNumberCVC: payment_method.verification_value } - hash_fields = [:accountNumber, :orderRef, :transactionType, :cardNumber, :cardNumberExpireMonth, - :cardNumberExpireYear, :cardNumberCVC, :cardHolderName] + hash_fields = %i[accountNumber orderRef transactionType cardNumber cardNumberExpireMonth + cardNumberExpireYear cardNumberCVC cardHolderName] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:purchasecc] @@ -231,9 +231,9 @@ def send_autopay(amount, authorization, is_auth, options = {}) description: options[:description] || options[:order_id], orderId: options[:order_id], purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE', - currency: (options[:currency] || default_currency), + currency: (options[:currency] || default_currency) } - hash_fields = [:accountNumber, :agreementRef, :price, :productNumber, :description, :orderId, :purchaseOperation, :currency] + hash_fields = %i[accountNumber agreementRef price productNumber description orderId purchaseOperation currency] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:autopay] @@ -250,7 +250,7 @@ def send_capture(amount, transaction_number, options = {}) vatAmount: options[:vat_amount] || 0, additionalValues: '' } - hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + hash_fields = %i[accountNumber transactionNumber amount orderId vatAmount additionalValues] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:capture] @@ -267,7 +267,7 @@ def send_credit(transaction_number, amount, options = {}) vatAmount: options[:vat_amount] || 0, additionalValues: '' } - hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + hash_fields = %i[accountNumber transactionNumber amount orderId vatAmount additionalValues] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:credit] @@ -278,9 +278,9 @@ def send_credit(transaction_number, amount, options = {}) def send_cancel(transaction_number) properties = { accountNumber: @options[:account], - transactionNumber: transaction_number, + transactionNumber: transaction_number } - hash_fields = [:accountNumber, :transactionNumber] + hash_fields = %i[accountNumber transactionNumber] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:cancel] @@ -299,7 +299,7 @@ def send_create_agreement(options) startDate: options[:startDate] || '', stopDate: options[:stopDate] || '' } - hash_fields = [:accountNumber, :merchantRef, :description, :purchaseOperation, :maxAmount, :notifyUrl, :startDate, :stopDate] + hash_fields = %i[accountNumber merchantRef description purchaseOperation maxAmount notifyUrl startDate stopDate] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:create_agreement] @@ -310,9 +310,9 @@ def send_create_agreement(options) def send_delete_agreement(authorization) properties = { accountNumber: @options[:account], - agreementRef: authorization, + agreementRef: authorization } - hash_fields = [:accountNumber, :agreementRef] + hash_fields = %i[accountNumber agreementRef] add_request_hash(properties, hash_fields) soap_action = SOAP_ACTIONS[:delete_agreement] @@ -341,9 +341,9 @@ def add_request_hash(properties, fields) def build_xml_request(soap_action, properties) builder = Nokogiri::XML::Builder.new - builder.__send__('soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + builder.__send__('soap12:Envelope', { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'}) do |root| + 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope' }) do |root| root.__send__('soap12:Body') do |body| body.__send__(soap_action[:name], xmlns: soap_action[:xmlns]) do |doc| properties.each do |key, val| @@ -385,7 +385,8 @@ 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?, diff --git a/lib/active_merchant/billing/gateways/payflow.rb b/lib/active_merchant/billing/gateways/payflow.rb index 3123693a84d..23f66fd65e9 100644 --- a/lib/active_merchant/billing/gateways/payflow.rb +++ b/lib/active_merchant/billing/gateways/payflow.rb @@ -1,3 +1,4 @@ +require 'nokogiri' require 'active_merchant/billing/gateways/payflow/payflow_common_api' require 'active_merchant/billing/gateways/payflow/payflow_response' require 'active_merchant/billing/gateways/payflow_express' @@ -7,9 +8,9 @@ module Billing #:nodoc: class PayflowGateway < Gateway include PayflowCommonAPI - RECURRING_ACTIONS = Set.new([:add, :modify, :cancel, :inquiry, :reactivate, :payment]) + RECURRING_ACTIONS = Set.new(%i[add modify cancel inquiry reactivate payment]) - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express jcb discover diners_club] self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside' self.display_name = 'PayPal Payflow Pro' @@ -44,7 +45,7 @@ def refund(money, reference, options = {}) commit(build_reference_request(:credit, money, reference, options), options) end - def verify(payment, options={}) + def verify(payment, options = {}) if credit_card_type(payment) == 'Amex' MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, payment, options) } @@ -55,6 +56,10 @@ def verify(payment, options={}) end end + def store(payment, options = {}) + raise ArgumentError, 'Store is not supported on Payflow gateways' + end + def verify_credentials response = void('0') response.params['result'] != '26' @@ -78,22 +83,23 @@ def recurring(money, credit_card, options = {}) options[:name] = credit_card.name if options[:name].blank? && credit_card request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml| add_credit_card(xml, credit_card, options) if credit_card + add_stored_credential(xml, options[:stored_credential]) end - commit(request, options.merge(:request_type => :recurring)) + commit(request, options.merge(request_type: :recurring)) end def cancel_recurring(profile_id) ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - request = build_recurring_request(:cancel, 0, :profile_id => profile_id) - commit(request, options.merge(:request_type => :recurring)) + request = build_recurring_request(:cancel, 0, profile_id: profile_id) + commit(request, options.merge(request_type: :recurring)) end def recurring_inquiry(profile_id, options = {}) ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - request = build_recurring_request(:inquiry, nil, options.update(:profile_id => profile_id)) - commit(request, options.merge(:request_type => :recurring)) + request = build_recurring_request(:inquiry, nil, options.update(profile_id: profile_id)) + commit(request, options.merge(request_type: :recurring)) end def express @@ -135,11 +141,12 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti xml.tag! 'Description', options[:description] unless options[:description].blank? xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? xml.tag! 'Comment', options[:comment] unless options[:comment].blank? - xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag!('ExtData', 'Name' => 'COMMENT2', 'Value' => options[:comment2]) unless options[:comment2].blank? xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank? xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank? xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank? xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank? + xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank? billing_address = options[:billing_address] || options[:address] add_address(xml, 'BillTo', billing_address, options) if billing_address @@ -149,10 +156,12 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti end xml.tag! 'Tender' do xml.tag! 'Card' do - xml.tag! 'ExtData', 'Name' => 'ORIGID', 'Value' => reference + xml.tag! 'ExtData', 'Name' => 'ORIGID', 'Value' => reference end end + add_stored_credential(xml, options[:stored_credential]) end + xml.tag! 'ExtData', 'Name' => 'BUTTONSOURCE', 'Value' => application_id unless application_id.blank? end xml.target! end @@ -168,12 +177,13 @@ def build_credit_card_request(action, money, credit_card, options) xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? # Comment and Comment2 will show up in manager.paypal.com as Comment1 and Comment2 xml.tag! 'Comment', options[:comment] unless options[:comment].blank? - xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag!('ExtData', 'Name' => 'COMMENT2', 'Value' => options[:comment2]) unless options[:comment2].blank? xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank? xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank? xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank? xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank? xml.tag! 'EMail', options[:email] unless options[:email].nil? + xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank? billing_address = options[:billing_address] || options[:address] add_address(xml, 'BillTo', billing_address, options) if billing_address @@ -182,12 +192,78 @@ 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 + xml.tag! 'Tender' do add_credit_card(xml, credit_card, options) end + add_stored_credential(xml, options[:stored_credential]) end + xml.tag! 'ExtData', 'Name' => 'BUTTONSOURCE', 'Value' => application_id unless application_id.blank? end - xml.target! + add_level_two_three_fields(xml.target!, options) + end + + def add_mpi_3ds(xml, three_d_secure_options) + # structure as per https://developer.paypal.com/api/nvp-soap/payflow/3d-secure-mpi/ + authentication_id = three_d_secure_options[:authentication_id] + authentication_status = three_d_secure_options[:authentication_response_status] + + eci = three_d_secure_options[:eci] + cavv = three_d_secure_options[:cavv] + xid = three_d_secure_options[:xid] + version = three_d_secure_options[:version] + + # 3DS2 only + ds_transaction_id = three_d_secure_options[:ds_transaction_id] if version_2_or_newer?(three_d_secure_options) + + xml.tag!('ExtData', 'Name' => 'AUTHENTICATION_ID', 'Value' => authentication_id) unless authentication_id.blank? + xml.tag!('ExtData', 'Name' => 'AUTHENTICATION_STATUS', 'Value' => authentication_status) unless authentication_status.blank? + + xml.tag!('ExtData', 'Name' => 'CAVV', 'Value' => cavv) unless cavv.blank? + xml.tag!('ExtData', 'Name' => 'ECI', 'Value' => eci) unless eci.blank? + xml.tag!('ExtData', 'Name' => 'XID', 'Value' => xid) unless xid.blank? + xml.tag!('ExtData', 'Name' => 'THREEDSVERSION', 'Value' => version) unless version.blank? + xml.tag!('ExtData', 'Name' => 'DSTRANSACTIONID', 'Value' => ds_transaction_id) unless ds_transaction_id.blank? + end + + def add_level_two_three_fields(xml_string, options) + if options[:level_two_fields] || options[:level_three_fields] + xml_doc = Nokogiri::XML.parse(xml_string) + %i[level_two_fields level_three_fields].each do |fields| + xml_string = add_fields(xml_doc, options[fields]) if options[fields] + end + end + xml_string + end + + def check_fields(parent, fields, xml_doc) + fields.each do |k, v| + if v.is_a? String + new_node = Nokogiri::XML::Node.new(k, xml_doc) + new_node.add_child(v) + xml_doc.at_css(parent).add_child(new_node) + else + check_subparent_before_continuing(parent, k, xml_doc) + check_fields(k, v, xml_doc) + end + end + xml_doc + end + + def check_subparent_before_continuing(parent, subparent, xml_doc) + unless xml_doc.at_css(subparent) + subparent_node = Nokogiri::XML::Node.new(subparent, xml_doc) + xml_doc.at_css(parent).add_child(subparent_node) + end + end + + def add_fields(xml_doc, options_fields) + fields_to_add = JSON.parse(options_fields) + check_fields('Invoice', fields_to_add, xml_doc) + xml_doc.root.to_s end def build_check_request(action, money, check, options) @@ -199,6 +275,7 @@ def build_check_request(action, money, check, options) xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? xml.tag! 'Description', options[:description] unless options[:description].blank? xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? + xml.tag! 'MerchDescr', options[:merch_descr] unless options[:merch_descr].blank? xml.tag! 'BillTo' do xml.tag! 'Name', check.name end @@ -211,9 +288,11 @@ def build_check_request(action, money, check, options) xml.tag! 'ABA', check.routing_number end end + add_stored_credential(xml, options[:stored_credential]) end + xml.tag! 'ExtData', 'Name' => 'BUTTONSOURCE', 'Value' => application_id unless application_id.blank? end - xml.target! + add_level_two_three_fields(xml.target!, options) end def add_credit_card(xml, credit_card, options = {}) @@ -224,23 +303,76 @@ def add_credit_card(xml, credit_card, options = {}) xml.tag! 'NameOnCard', credit_card.first_name xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value? - if options[:three_d_secure] - three_d_secure = options[:three_d_secure] - xml.tag! 'BuyerAuthResult' do - xml.tag! 'Status', three_d_secure[:status] unless three_d_secure[:status].blank? - xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank? - xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank? - xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank? - xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank? - xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank? - xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank? - end + add_three_d_secure(options, xml) + + xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name + end + end + + def add_stored_credential(xml, stored_credential) + return unless stored_credential + + xml.tag! 'CardOnFile', add_card_on_file_type(stored_credential) + xml.tag! 'TxnId', stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id] + end + + def card_on_file_initiator(initator) + case initator + when 'merchant' + 'MIT' + when 'cardholder' + 'CIT' + end + end + + def card_on_file_reason(stored_credential) + return 'I' if stored_credential[:initial_transaction] && stored_credential[:reason_type] == 'unscheduled' + + case stored_credential[:reason_type] + when 'recurring', 'installment' + 'R' + when 'unscheduled' + 'U' + end + end + + def add_card_on_file_type(stored_credential) + card_on_file_initiator(stored_credential[:initiator]).to_s + card_on_file_reason(stored_credential).to_s + end + + def add_three_d_secure(options, xml) + if options[:three_d_secure] + three_d_secure = options[:three_d_secure] + xml.tag! 'BuyerAuthResult' do + authentication_status(three_d_secure, xml) + xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank? + xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank? + xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank? + xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank? + xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank? + xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank? + xml.tag! 'ThreeDSVersion', three_d_secure[:version] unless three_d_secure[:version].blank? + xml.tag! 'DSTransactionID', three_d_secure[:ds_transaction_id] unless three_d_secure[:ds_transaction_id].blank? end + end + end - xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name + def authentication_status(three_d_secure, xml) + status = if three_d_secure[:authentication_response_status].present? + three_d_secure[:authentication_response_status] + elsif three_d_secure[:directory_response_status].present? + three_d_secure[:directory_response_status] + end + if status.present? + xml.tag! 'Status', status + xml.tag! 'AuthenticationStatus', status if version_2_or_newer?(three_d_secure) end end + def version_2_or_newer?(three_d_secure) + three_d_secure[:version]&.start_with?('2') + end + def credit_card_type(credit_card) return '' if card_brand(credit_card).blank? @@ -262,15 +394,13 @@ def startdate(creditcard) end def build_recurring_request(action, money, options) - unless RECURRING_ACTIONS.include?(action) - raise StandardError, "Invalid Recurring Profile Action: #{action}" - end + raise StandardError, "Invalid Recurring Profile Action: #{action}" unless RECURRING_ACTIONS.include?(action) xml = Builder::XmlMarkup.new xml.tag! 'RecurringProfiles' do xml.tag! 'RecurringProfile' do xml.tag! action.to_s.capitalize do - unless [:cancel, :inquiry].include?(action) + unless %i[cancel inquiry].include?(action) xml.tag! 'RPData' do xml.tag! 'Name', options[:name] unless options[:name].nil? xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) @@ -281,7 +411,7 @@ def build_recurring_request(action, money, options) xml.tag! 'MaxFailPayments', options[:max_fail_payments] unless options[:max_fail_payments].nil? if initial_tx = options[:initial_transaction] - requires!(initial_tx, [:type, :authorization, :purchase]) + requires!(initial_tx, %i[type authorization purchase]) requires!(initial_tx, :amount) if initial_tx[:type] == :purchase xml.tag! 'OptionalTrans', TRANSACTIONS[initial_tx[:type]] @@ -304,9 +434,7 @@ def build_recurring_request(action, money, options) yield xml end end - if action != :add - xml.tag! 'ProfileID', options[:profile_id] - end + xml.tag! 'ProfileID', options[:profile_id] if action != :add if action == :inquiry xml.tag! 'PaymentHistory', (options[:history] ? 'Y' : 'N') end @@ -316,7 +444,7 @@ def build_recurring_request(action, money, options) end def get_pay_period(options) - requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly]) + requires!(options, %i[periodicity bimonthly monthly biweekly weekly yearly daily semimonthly quadweekly quarterly semiyearly]) case options[:periodicity] when :weekly then 'Weekly' when :biweekly then 'Bi-weekly' 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 4ad1bd00739..ef51f210ece 100644 --- a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +++ b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb @@ -10,7 +10,7 @@ def self.included(base) # Set the default partner to PayPal base.partner = 'PayPal' - base.supported_countries = ['US', 'CA', 'NZ', 'AU'] + base.supported_countries = %w[US CA NZ AU] base.class_attribute :timeout base.timeout = 60 @@ -37,20 +37,20 @@ def self.included(base) XMLNS = 'http://www.paypal.com/XMLPay' CARD_MAPPING = { - :visa => 'Visa', - :master => 'MasterCard', - :discover => 'Discover', - :american_express => 'Amex', - :jcb => 'JCB', - :diners_club => 'DinersClub', + visa: 'Visa', + master: 'MasterCard', + discover: 'Discover', + american_express: 'Amex', + jcb: 'JCB', + diners_club: 'DinersClub' } TRANSACTIONS = { - :purchase => 'Sale', - :authorization => 'Authorization', - :capture => 'Capture', - :void => 'Void', - :credit => 'Credit' + purchase: 'Sale', + authorization: 'Authorization', + capture: 'Capture', + void: 'Void', + credit: 'Credit' } CVV_CODE = { @@ -117,7 +117,8 @@ def build_reference_request(action, money, authorization, options) xml.tag!('TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)) xml.tag!('Description', options[:description]) unless options[:description].blank? xml.tag!('Comment', options[:comment]) unless options[:comment].blank? - xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag!('ExtData', 'Name' => 'COMMENT2', 'Value' => options[:comment2]) unless options[:comment2].blank? + xml.tag!('MerchDescr', options[:merch_descr]) unless options[:merch_descr].blank? xml.tag!( 'ExtData', 'Name' => 'CAPTURECOMPLETE', @@ -132,6 +133,7 @@ def build_reference_request(action, money, authorization, options) def add_address(xml, tag, address, options) return if address.nil? + xml.tag! tag do xml.tag! 'Name', address[:name] unless address[:name].blank? xml.tag! 'EMail', options[:email] unless options[:email].blank? @@ -159,9 +161,7 @@ def parse(data) # REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes tx_result = root.xpath('.//TransactionResult').first - if tx_result && tx_result.attributes['Duplicate'].to_s == 'true' - response[:duplicate] = true - end + response[:duplicate] = true if tx_result && tx_result.attributes['Duplicate'].to_s == 'true' root.xpath('.//*').each do |node| parse_element(response, node) @@ -182,7 +182,7 @@ def parse_element(response, node) node.xpath('.//*').each { |e| parse_element(payment_result_response, e) } when node.xpath('.//*').to_a.any? node.xpath('.//*').each { |e| parse_element(response, e) } - when node_name.to_s =~ /amt$/ + when /amt$/.match?(node_name.to_s) # *Amt elements don't put the value in the #text - instead they use a Currency attribute response[node_name] = node.attributes['Currency'].to_s when node_name == :ext_data diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb index 3c43642265f..b63a083cb30 100644 --- a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +++ b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb @@ -35,8 +35,7 @@ def address 'state' => @params['state'], 'country' => @params['country'], 'zip' => @params['zip'], - 'phone' => phone, - } + 'phone' => phone } end end end diff --git a/lib/active_merchant/billing/gateways/payflow_express.rb b/lib/active_merchant/billing/gateways/payflow_express.rb index 9676d2b1cf2..393d9077368 100644 --- a/lib/active_merchant/billing/gateways/payflow_express.rb +++ b/lib/active_merchant/billing/gateways/payflow_express.rb @@ -110,7 +110,7 @@ def details_for(token) private def build_get_express_details_request(token) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'GetExpressCheckout' do xml.tag! 'Authorization' do xml.tag! 'PayData' do @@ -126,7 +126,7 @@ def build_get_express_details_request(token) end def build_setup_express_sale_or_authorization_request(action, money, options = {}) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'SetExpressCheckout' do xml.tag! action do add_pay_data xml, money, options @@ -136,7 +136,7 @@ def build_setup_express_sale_or_authorization_request(action, money, options = { end def build_sale_or_authorization_request(action, money, options) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'DoExpressCheckout' do xml.tag! action do add_pay_data xml, money, options @@ -155,7 +155,7 @@ def add_pay_data(xml, money, options) # Comment, Comment2 should make it to the backend at manager.paypal.com, as with Payflow credit card transactions # but that doesn't seem to work (yet?). See: https://www.x.com/thread/51908?tstart=0 xml.tag! 'Comment', options[:comment] unless options[:comment].nil? - xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].nil? + xml.tag!('ExtData', 'Name' => 'COMMENT2', 'Value' => options[:comment2]) unless options[:comment2].nil? billing_address = options[:billing_address] || options[:address] add_address(xml, 'BillTo', billing_address, options) if billing_address diff --git a/lib/active_merchant/billing/gateways/payflow_uk.rb b/lib/active_merchant/billing/gateways/payflow_uk.rb index e963c152ef0..d44a9910a8c 100644 --- a/lib/active_merchant/billing/gateways/payflow_uk.rb +++ b/lib/active_merchant/billing/gateways/payflow_uk.rb @@ -11,7 +11,7 @@ def express @express ||= PayflowExpressUkGateway.new(@options) end - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.supported_countries = ['GB'] self.homepage_url = 'https://www.paypal.com/uk/webapps/mpp/pro' self.display_name = 'PayPal Payments Pro (UK)' diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb index ff89eb8cb08..51517b9277d 100644 --- a/lib/active_merchant/billing/gateways/payment_express.rb +++ b/lib/active_merchant/billing/gateways/payment_express.rb @@ -13,24 +13,24 @@ class PaymentExpressGateway < Gateway # VISA, Mastercard, Diners Club and Farmers cards are supported # # However, regular accounts with DPS only support VISA and Mastercard - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] - self.supported_countries = %w[ AU FJ GB HK IE MY NZ PG SG US ] + self.supported_countries = %w[AU FJ GB HK IE MY NZ PG SG US] - self.homepage_url = 'http://www.paymentexpress.com/' - self.display_name = 'PaymentExpress' + self.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' TRANSACTIONS = { - :purchase => 'Purchase', - :credit => 'Refund', - :authorization => 'Auth', - :capture => 'Complete', - :validate => 'Validate' + purchase: 'Purchase', + credit: 'Refund', + authorization: 'Auth', + capture: 'Complete', + validate: 'Validate' } # We require the DPS gateway username and password when the object is created. @@ -86,6 +86,11 @@ def credit(money, identification, options = {}) refund(money, identification, options) end + def verify(payment_source, options = {}) + request = build_purchase_or_authorization_request(100, payment_source, options) + commit(:validate, request) + end + # Token Based Billing # # Instead of storing the credit card details locally, you can store them inside the @@ -118,7 +123,7 @@ def credit(money, identification, options = {}) # # Note, once stored, PaymentExpress does not support unstoring a stored card. def store(credit_card, options = {}) - request = build_token_request(credit_card, options) + request = build_token_request(credit_card, options) commit(:validate, request) end @@ -149,7 +154,7 @@ def build_purchase_or_authorization_request(money, payment_source, options) add_credit_card(result, payment_source) end - add_amount(result, money, options) + add_amount(result, money, options) if money add_invoice(result, options) add_address_verification_data(result, options) add_optional_elements(result, options) @@ -229,8 +234,8 @@ def add_address_verification_data(xml, options) address = options[:billing_address] || options[:address] return if address.nil? - xml.add_element('EnableAvsData').text = 1 - xml.add_element('AvsAction').text = 1 + xml.add_element('EnableAvsData').text = options[:enable_avs_data] || 1 + xml.add_element('AvsAction').text = options[:avs_action] || 1 xml.add_element('AvsStreetAddress').text = address[:address1] xml.add_element('AvsPostCode').text = address[:zip] @@ -301,9 +306,12 @@ def commit(action, request) response = parse(ssl_post(url, request.to_s)) # Return a response - PaymentExpressResponse.new(response[:success] == APPROVED, message_from(response), response, - :test => response[:test_mode] == '1', - :authorization => authorization_from(action, response) + PaymentExpressResponse.new( + response[:success] == APPROVED, + message_from(response), + response, + test: response[:test_mode] == '1', + authorization: authorization_from(action, response) ) end @@ -335,7 +343,7 @@ def message_from(response) def authorization_from(action, response) case action when :validate - (response[:billing_id] || response[:dps_billing_id]) + (response[:billing_id] || response[:dps_billing_id] || response[:dps_txn_ref]) else response[:dps_txn_ref] end @@ -362,7 +370,7 @@ class PaymentExpressResponse < Response # add a method to response so we can easily get the token # for Validate transactions def token - @params['billing_id'] || @params['dps_billing_id'] + @params['billing_id'] || @params['dps_billing_id'] || @params['dps_txn_ref'] end end end diff --git a/lib/active_merchant/billing/gateways/paymentez.rb b/lib/active_merchant/billing/gateways/paymentez.rb index 50463010ea2..cc033bcddb3 100644 --- a/lib/active_merchant/billing/gateways/paymentez.rb +++ b/lib/active_merchant/billing/gateways/paymentez.rb @@ -7,9 +7,9 @@ class PaymentezGateway < Gateway #:nodoc: self.test_url = 'https://ccapi-stg.paymentez.com/v2/' self.live_url = 'https://ccapi.paymentez.com/v2/' - self.supported_countries = %w[MX EC VE CO BR CL] + self.supported_countries = %w[MX EC CO BR CL PE] self.default_currency = 'USD' - self.supported_cardtypes = %i[visa master american_express diners_club elo] + self.supported_cardtypes = %i[visa master american_express diners_club elo alia olimpica discover maestro sodexo carnet unionpay jcb] self.homepage_url = 'https://secure.paymentez.com/' self.display_name = 'Paymentez' @@ -34,12 +34,21 @@ class PaymentezGateway < Gateway #:nodoc: 28 => :card_declined }.freeze + SUCCESS_STATUS = ['success', 'pending', 1, 0] + CARD_MAPPING = { 'visa' => 'vi', 'master' => 'mc', 'american_express' => 'ax', 'diners_club' => 'di', - 'elo' => 'el' + 'elo' => 'el', + 'discover' => 'dc', + 'maestro' => 'ms', + 'sodexo' => 'sx', + 'olimpica' => 'ol', + 'carnet' => 'ct', + 'unionpay' => 'up', + 'jcb' => 'jc' }.freeze def initialize(options = {}) @@ -60,6 +69,8 @@ def purchase(money, payment, options = {}) end def authorize(money, payment, options = {}) + return purchase(money, payment, options) if options[:otp_flow] + post = {} add_invoice(post, money, options) @@ -70,18 +81,27 @@ def authorize(money, payment, options = {}) commit_transaction('authorize', post) end - def capture(money, authorization, _options = {}) + def capture(money, authorization, options = {}) post = { - transaction: { id: authorization } + transaction: { id: authorization } } - post[:order] = {amount: amount(money).to_f} if money + verify_flow = options[:type] && options[:value] + + if verify_flow + add_customer_data(post, options) + add_verify_value(post, options) + elsif money + post[:order] = { amount: amount(money).to_f } + end - commit_transaction('capture', post) + action = verify_flow ? 'verify' : 'capture' + commit_transaction(action, post) end def refund(money, authorization, options = {}) - post = {transaction: {id: authorization}} - post[:order] = {amount: amount(money).to_f} if money + post = { transaction: { id: authorization } } + post[:order] = { amount: amount(money).to_f } if money + add_more_info(post, options) commit_transaction('refund', post) end @@ -113,10 +133,14 @@ def store(credit_card, options = {}) end def unstore(identification, options = {}) - post = { card: { token: identification }, user: { id: options[:user_id] }} + post = { card: { token: identification }, user: { id: options[:user_id] } } commit_card('delete', post) end + def inquire(authorization, options = {}) + commit_transaction('inquire', authorization) + end + def supports_scrubbing? true end @@ -131,10 +155,10 @@ def scrub(transcript) private def add_customer_data(post, options) - requires!(options, :user_id, :email) + requires!(options, :user_id) post[:user] ||= {} post[:user][:id] = options[:user_id] - post[:user][:email] = options[:email] + post[:user][:email] = options[:email] if options[:email] post[:user][:ip_address] = options[:ip] if options[:ip] post[:user][:fiscal_number] = options[:fiscal_number] if options[:fiscal_number] if phone = options[:phone] || options.dig(:billing_address, :phone) @@ -171,30 +195,67 @@ def add_payment(post, payment) end end + def add_verify_value(post, options) + post[:type] = options[:type] if options[:type] + post[:value] = options[:value] if options[:value] + end + def add_extra_params(post, options) extra_params = {} extra_params.merge!(options[:extra_params]) if options[:extra_params] + add_external_mpi_fields(extra_params, options) + post['extra_params'] = extra_params unless extra_params.empty? end + def add_external_mpi_fields(extra_params, options) + three_d_secure_options = options[:three_d_secure] + return unless three_d_secure_options + + auth_data = { + cavv: three_d_secure_options[:cavv], + 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], + status: three_d_secure_options[:authentication_response_status] || three_d_secure_options[:directory_response_status] + }.compact + + return if auth_data.empty? + + extra_params[:auth_data] = auth_data + end + + def add_more_info(post, options) + post[:more_info] = options[:more_info] if options[:more_info] + end + def parse(body) JSON.parse(body) end def commit_raw(object, action, parameters) - url = "#{(test? ? test_url : live_url)}#{object}/#{action}" - - begin - raw_response = ssl_post(url, post_data(parameters), headers) - rescue ResponseError => e - raw_response = e.response.body + 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 parse(raw_response) rescue JSON::ParserError - {'status' => 'Internal server error'} + { 'status' => 'Internal server error' } end end @@ -230,20 +291,25 @@ def headers end def success_from(response) - !response.include?('error') && (response['status'] || response['transaction']['status']) == 'success' + return false if response.include?('error') + + SUCCESS_STATUS.include?(response['status'] || response['transaction']['status']) end def card_success_from(response) return false if response.include?('error') return true if response['message'] == 'card deleted' + response['card']['status'] == 'valid' end def message_from(response) + return response['detail'] if response['detail'].present? + if !success_from(response) && response['error'] response['error'] && response['error']['type'] else - response['transaction'] && response['transaction']['message'] + (response['transaction'] && response['transaction']['message']) || (response['message']) end end @@ -274,11 +340,10 @@ def post_data(parameters = {}) def error_code_from(response) return if success_from(response) + if response['transaction'] detail = response['transaction']['status_detail'] - if STANDARD_ERROR_CODE_MAPPING.include?(detail) - return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]] - end + return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]] if STANDARD_ERROR_CODE_MAPPING.include?(detail) elsif response['error'] return STANDARD_ERROR_CODE[:config_error] end diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb index caf64486d8b..e0777d56099 100644 --- a/lib/active_merchant/billing/gateways/paymill.rb +++ b/lib/active_merchant/billing/gateways/paymill.rb @@ -5,7 +5,7 @@ class PaymillGateway < Gateway GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT NL NO PL PT RO SE SI SK TR VA) - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :union_pay, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club discover union_pay jcb] self.homepage_url = 'https://paymill.com' self.display_name = 'PAYMILL' self.money_format = :cents @@ -17,7 +17,7 @@ def initialize(options = {}) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) action_with_token(:purchase, money, payment_method, options) end @@ -35,7 +35,7 @@ def capture(money, authorization, options = {}) commit(:post, 'transactions', post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} post[:amount] = amount(money) @@ -43,11 +43,16 @@ def refund(money, authorization, options={}) commit(:post, "refunds/#{transaction_id(authorization)}", post) end - def void(authorization, options={}) + def void(authorization, options = {}) commit(:delete, "preauthorizations/#{preauth(authorization)}") end - def store(credit_card, options={}) + def store(credit_card, options = {}) + # The store request requires a currency and amount of at least $1 USD. + # This is used for an authorization that is handled internally by Paymill. + options[:currency] = 'USD' + options[:money] = 100 + save_card(credit_card, options) end @@ -81,7 +86,7 @@ def add_credit_card(post, credit_card, options) post['account.expiry.year'] = sprintf('%.4i', credit_card.year) post['account.verification'] = credit_card.verification_value post['account.email'] = (options[:email] || nil) - post['presentation.amount3D'] = (options[:money] || nil) + post['presentation.amount3D'] = (options[:money] || nil) post['presentation.currency3D'] = (options[:currency] || currency(options[:money])) end @@ -89,7 +94,7 @@ def headers { 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) } end - def commit(method, action, parameters=nil) + def commit(method, action, parameters = nil) begin raw_response = ssl_request(method, live_url + action, post_data(parameters), headers) rescue ResponseError => e @@ -107,8 +112,8 @@ def commit(method, action, parameters=nil) def response_from(raw_response) parsed = JSON.parse(raw_response) options = { - :authorization => authorization_from(parsed), - :test => (parsed['mode'] == 'test'), + authorization: authorization_from(parsed), + test: (parsed['mode'] == 'test') } succeeded = (parsed['data'] == []) || (parsed['data']['response_code'].to_i == 20000) @@ -176,7 +181,7 @@ def save_card(credit_card, options) end def response_for_save_from(raw_response) - options = { :test => test? } + options = { test: test? } parser = ResponseParser.new(raw_response, options) parser.generate_response @@ -193,7 +198,7 @@ def save_card_url def post_data(params) return nil unless params - no_blanks = params.reject { |key, value| value.blank? } + no_blanks = params.reject { |_key, value| value.blank? } no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end @@ -323,7 +328,7 @@ def response_message(parsed_response) class ResponseParser attr_reader :raw_response, :parsed, :succeeded, :message, :options - def initialize(raw_response='', options={}) + def initialize(raw_response = '', options = {}) @raw_response = raw_response @options = options end @@ -352,12 +357,10 @@ def handle_response_parse_error def handle_response_correct_parsing @message = parsed['transaction']['processing']['return']['message'] - if @succeeded = is_ack? - @options[:authorization] = parsed['transaction']['identification']['uniqueId'] - end + @options[:authorization] = parsed['transaction']['identification']['uniqueId'] if @succeeded = ack? end - def is_ack? + def ack? parsed['transaction']['processing']['result'] == 'ACK' end end diff --git a/lib/active_merchant/billing/gateways/paypal.rb b/lib/active_merchant/billing/gateways/paypal.rb index 465a0dd2b24..10e42e5aaab 100644 --- a/lib/active_merchant/billing/gateways/paypal.rb +++ b/lib/active_merchant/billing/gateways/paypal.rb @@ -8,8 +8,8 @@ class PaypalGateway < Gateway include PaypalCommonAPI include PaypalRecurringApi - self.supported_cardtypes = [:visa, :master, :american_express, :discover] - self.supported_countries = ['US'] + self.supported_countries = %w[CA NZ GB US] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.paypal.com/us/webapps/mpp/paypal-payments-pro' self.display_name = 'PayPal Payments Pro (US)' @@ -55,10 +55,10 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced billing_address = options[:billing_address] || options[:address] currency_code = options[:currency] || currency(money) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do - xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:Version', api_version(options) xml.tag! 'n2:' + transaction_type + 'RequestDetails' do xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction' xml.tag! 'n2:PaymentAction', action @@ -73,6 +73,12 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced xml.target! end + def api_version(options) + return API_VERSION_3DS2 if options.dig(:three_d_secure, :version) =~ /^2/ + + API_VERSION + end + def add_credit_card(xml, credit_card, address, options) xml.tag! 'n2:CreditCard' do xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card)) @@ -90,6 +96,8 @@ def add_credit_card(xml, credit_card, address, options) xml.tag! 'n2:Payer', options[:email] add_address(xml, 'n2:Address', address) end + + add_three_d_secure(xml, options) if options[:three_d_secure] end end @@ -98,6 +106,19 @@ def add_descriptors(xml, options) xml.tag! 'n2:SoftDescriptorCity', options[:soft_descriptor_city] unless options[:soft_descriptor_city].blank? end + def add_three_d_secure(xml, options) + three_d_secure = options[:three_d_secure] + xml.tag! 'ThreeDSecureRequest' do + xml.tag! 'MpiVendor3ds', 'Y' + xml.tag! 'AuthStatus3ds', three_d_secure[:authentication_response_status] || three_d_secure[:trans_status] if three_d_secure[:authentication_response_status] || three_d_secure[:trans_status] + xml.tag! 'Cavv', three_d_secure[:cavv] unless three_d_secure[:cavv].blank? + xml.tag! 'Eci3ds', three_d_secure[:eci] unless three_d_secure[:eci].blank? + xml.tag! 'Xid', three_d_secure[:xid] unless three_d_secure[:xid].blank? + xml.tag! 'ThreeDSVersion', three_d_secure[:version] unless three_d_secure[:version].blank? + xml.tag! 'DSTransactionId', three_d_secure[:ds_transaction_id] unless three_d_secure[:ds_transaction_id].blank? + end + end + def credit_card_type(type) case type when 'visa' then 'Visa' diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb index fe3146eac55..a02d4bff1b6 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb @@ -5,6 +5,7 @@ module PaypalCommonAPI include Empty API_VERSION = '124' + API_VERSION_3DS2 = '214.0' URLS = { :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/', @@ -586,7 +587,7 @@ def add_payment_details(xml, money, currency_code, options = {}) xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code # All of the values must be included together and add up to the order total - if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) } + if [:subtotal, :shipping, :handling, :tax].all?{ |o| options[o].present? } xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb index eeb38da4117..d5f2b00a85e 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb @@ -13,6 +13,10 @@ def details (@params['PaymentDetails']||{}) end + def checkout_status + (@params['CheckoutStatus']||{}) + end + def name payer = (info['PayerName']||{}) [payer['FirstName'], payer['MiddleName'], payer['LastName']].compact.join(' ') diff --git a/lib/active_merchant/billing/gateways/paypal_ca.rb b/lib/active_merchant/billing/gateways/paypal_ca.rb index 91fb551d342..74d63ee4c9e 100644 --- a/lib/active_merchant/billing/gateways/paypal_ca.rb +++ b/lib/active_merchant/billing/gateways/paypal_ca.rb @@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: # The PayPal gateway for PayPal Website Payments Pro Canada only supports Visa and MasterCard class PaypalCaGateway < PaypalGateway - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] self.supported_countries = ['CA'] self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside' self.display_name = 'PayPal Website Payments Pro (CA)' diff --git a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb index 653a8c59015..2ed090226c1 100644 --- a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +++ b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb @@ -31,6 +31,7 @@ def redirect_url_for(token, options = {}) def build_setup_request(action, money, options) requires!(options, :items) raise ArgumentError, 'Must include at least 1 Item' unless options[:items].length > 0 + options[:items].each do |item| requires!(item, :name, :number, :quantity, :amount, :description, :category) raise ArgumentError, "Each of the items must have the category 'Digital'" unless item[:category] == 'Digital' @@ -38,7 +39,6 @@ def build_setup_request(action, money, options) super end - end end end diff --git a/lib/active_merchant/billing/gateways/paypal_express.rb b/lib/active_merchant/billing/gateways/paypal_express.rb index 60d659437f9..597cfeda9c3 100644 --- a/lib/active_merchant/billing/gateways/paypal_express.rb +++ b/lib/active_merchant/billing/gateways/paypal_express.rb @@ -73,7 +73,7 @@ def agreement_details(reference_id, options = {}) end def authorize_reference_transaction(money, options = {}) - requires!(options, :reference_id, :payment_type, :invoice_id, :description, :ip) + requires!(options, :reference_id) commit 'DoReferenceTransaction', build_reference_transaction_request('Authorization', money, options) end @@ -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 @@ -250,6 +251,8 @@ def build_reference_transaction_request(action, money, options) xml.tag! 'n2:PaymentType', options[:payment_type] || 'Any' 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/paypal_express_common.rb b/lib/active_merchant/billing/gateways/paypal_express_common.rb index 7cb72b82745..4da80ed71a2 100644 --- a/lib/active_merchant/billing/gateways/paypal_express_common.rb +++ b/lib/active_merchant/billing/gateways/paypal_express_common.rb @@ -17,7 +17,7 @@ def redirect_url end def redirect_url_for(token, options = {}) - options = {:review => true, :mobile => false}.update(options) + options = { review: true, mobile: false }.update(options) cmd = options[:mobile] ? '_express-checkout-mobile' : '_express-checkout' url = "#{redirect_url}?cmd=#{cmd}&token=#{token}" diff --git a/lib/active_merchant/billing/gateways/paysafe.rb b/lib/active_merchant/billing/gateways/paysafe.rb new file mode 100644 index 00000000000..a7cff9fe813 --- /dev/null +++ b/lib/active_merchant/billing/gateways/paysafe.rb @@ -0,0 +1,421 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaysafeGateway < Gateway + self.test_url = 'https://api.test.paysafe.com' + self.live_url = 'https://api.paysafe.com' + + self.supported_countries = %w(AL AT BE BA BG CA HR CY CZ DK EE FI FR DE GR HU IS IE IT LV LI LT LU MT ME NL MK NO PL PT RO RS SK SI ES SE CH TR GB US) + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://www.paysafe.com/' + self.display_name = 'Paysafe' + + def initialize(options = {}) + requires!(options, :username, :password, :account_id) + super + end + + def purchase(money, payment, options = {}) + post = {} + add_auth_purchase_params(post, money, payment, options) + add_airline_travel_details(post, options) + add_split_pay_details(post, options) + post[:settleWithAuth] = true + + commit(:post, 'auths', post, options) + end + + def authorize(money, payment, options = {}) + post = {} + add_auth_purchase_params(post, money, payment, options) + + commit(:post, 'auths', post, options) + end + + def capture(money, authorization, options = {}) + post = {} + add_invoice(post, money, options) + + commit(:post, "auths/#{authorization}/settlements", post, options) + end + + def refund(money, authorization, options = {}) + post = {} + add_invoice(post, money, options) + + commit(:post, "settlements/#{authorization}/refunds", post, options) + end + + def void(authorization, options = {}) + post = {} + money = options[:amount] + add_invoice(post, money, options) + + commit(:post, "auths/#{authorization}/voidauths", post, options) + end + + def credit(money, payment, options = {}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + + commit(:post, 'standalonecredits', post, options) + end + + # This is a '$0 auth' done at a specific verification endpoint at the gateway + def verify(payment, options = {}) + post = {} + add_payment(post, payment) + add_billing_address(post, options) + add_customer_data(post, payment, options) unless payment.is_a?(String) + + commit(:post, 'verifications', post, options) + end + + def store(payment, options = {}) + post = {} + add_payment(post, payment) + add_address_for_vaulting(post, options) + add_profile_data(post, payment, options) + add_store_data(post, payment, options) + + commit(:post, 'profiles', post, options) + end + + def unstore(pm_profile_id) + commit(:delete, "profiles/#{get_id_from_store_auth(pm_profile_id)}", nil, nil) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )[a-zA-Z0-9:_]+), '\1[FILTERED]'). + gsub(%r(("cardNum\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]') + end + + private + + def add_auth_purchase_params(post, money, payment, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_billing_address(post, options) + add_merchant_details(post, options) + add_customer_data(post, payment, options) unless payment.is_a?(String) + add_three_d_secure(post, payment, options) if options[:three_d_secure] + add_stored_credential(post, options) if options[:stored_credential] + add_funding_transaction(post, options) + end + + # Customer data can be included in transactions where the payment method is a credit card + # but should not be sent when the payment method is a token + def add_customer_data(post, creditcard, options) + post[:profile] = {} + post[:profile][:firstName] = creditcard.first_name + post[:profile][:lastName] = creditcard.last_name + post[:profile][:email] = options[:email] if options[:email] + post[:customerIp] = options[:ip] if options[:ip] + end + + def add_billing_address(post, options) + return unless address = options[:billing_address] || options[:address] + + post[:billingDetails] = {} + 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] = 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 + # object is formatted differently from the standard transaction billing address + def add_address_for_vaulting(post, options) + return unless address = options[:billing_address] || options[:address] + + post[:card][:billingAddress] = {} + 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] = truncate(address[:state], 40) if address[:state] + end + + # This data is specific to creating a profile at the gateway's vault level + def add_profile_data(post, payment, options) + post[:firstName] = payment.first_name + post[:lastName] = payment.last_name + post[:dateOfBirth] = {} + post[:dateOfBirth][:year] = options[:date_of_birth][:year] + post[:dateOfBirth][:month] = options[:date_of_birth][:month] + post[:dateOfBirth][:day] = options[:date_of_birth][:day] + post[:email] = options[:email] if options[:email] + post[:ip] = options[:ip] if options[:ip] + + if options[:phone] + post[:phone] = options[:phone] + elsif address = options[:billing_address] || options[:address] + post[:phone] = address[:phone] if address[:phone] + end + end + + def add_store_data(post, payment, options) + post[:merchantCustomerId] = options[:customer_id] || SecureRandom.hex(12) + post[:locale] = options[:locale] || 'en_US' + post[:card][:holderName] = payment.name + end + + # Paysafe expects minor units so we are not calling amount method on money parameter + def add_invoice(post, money, options) + post[:amount] = money + end + + def add_payment(post, payment) + if payment.is_a?(String) + post[:card] = {} + post[:card][:paymentToken] = get_pm_from_store_auth(payment) + else + post[:card] = { cardExpiry: {} } + post[:card][:cardNum] = payment.number + post[:card][:cardExpiry][:month] = payment.month + post[:card][:cardExpiry][:year] = payment.year + post[:card][:cvv] = payment.verification_value + end + end + + def add_merchant_details(post, options) + return unless options[:merchant_descriptor] + + post[:merchantDescriptor] = {} + post[:merchantDescriptor][:dynamicDescriptor] = options[:merchant_descriptor][:dynamic_descriptor] if options[:merchant_descriptor][:dynamic_descriptor] + post[:merchantDescriptor][:phone] = options[:merchant_descriptor][:phone] if options[:merchant_descriptor][:phone] + end + + def add_three_d_secure(post, payment, options) + three_d_secure = options[:three_d_secure] + + post[:authentication] = {} + post[:authentication][:eci] = three_d_secure[:eci] + post[:authentication][:cavv] = three_d_secure[:cavv] + post[:authentication][:xid] = three_d_secure[:xid] if three_d_secure[:xid] + post[:authentication][:threeDSecureVersion] = three_d_secure[:version] + post[:authentication][:directoryServerTransactionId] = three_d_secure[:ds_transaction_id] unless payment.is_a?(String) || !mastercard?(payment) + end + + def add_airline_travel_details(post, options) + return unless options[:airline_travel_details] + + post[:airlineTravelDetails] = {} + post[:airlineTravelDetails][:passengerName] = options[:airline_travel_details][:passenger_name] if options[:airline_travel_details][:passenger_name] + post[:airlineTravelDetails][:departureDate] = options[:airline_travel_details][:departure_date] if options[:airline_travel_details][:departure_date] + post[:airlineTravelDetails][:origin] = options[:airline_travel_details][:origin] if options[:airline_travel_details][:origin] + post[:airlineTravelDetails][:computerizedReservationSystem] = options[:airline_travel_details][:computerized_reservation_system] if options[:airline_travel_details][:computerized_reservation_system] + post[:airlineTravelDetails][:customerReferenceNumber] = options[:airline_travel_details][:customer_reference_number] if options[:airline_travel_details][:customer_reference_number] + + add_ticket_details(post, options) + add_travel_agency_details(post, options) + add_trip_legs(post, options) + end + + def add_ticket_details(post, options) + return unless ticket = options[:airline_travel_details][:ticket] + + post[:airlineTravelDetails][:ticket] = {} + post[:airlineTravelDetails][:ticket][:ticketNumber] = ticket[:ticket_number] if ticket[:ticket_number] + post[:airlineTravelDetails][:ticket][:isRestrictedTicket] = ticket[:is_restricted_ticket] if ticket[:is_restricted_ticket] + end + + def add_travel_agency_details(post, options) + return unless agency = options[:airline_travel_details][:travel_agency] + + post[:airlineTravelDetails][:travelAgency] = {} + post[:airlineTravelDetails][:travelAgency][:name] = agency[:name] if agency[:name] + post[:airlineTravelDetails][:travelAgency][:code] = agency[:code] if agency[:code] + end + + def add_trip_legs(post, options) + return unless trip_legs = options[:airline_travel_details][:trip_legs] + + trip_legs_hash = {} + trip_legs.each.with_index(1) do |leg, i| + my_leg = "leg#{i}".to_sym + details = add_leg_details(my_leg, leg[1]) + + trip_legs_hash[my_leg] = details + end + post[:airlineTravelDetails][:tripLegs] = trip_legs_hash + end + + def add_leg_details(obj, leg) + details = {} + add_flight_details(details, obj, leg) + details[:serviceClass] = leg[:service_class] if leg[:service_class] + details[:isStopOverAllowed] = leg[:is_stop_over_allowed] if leg[:is_stop_over_allowed] + details[:destination] = leg[:destination] if leg[:destination] + details[:fareBasis] = leg[:fare_basis] if leg[:fare_basis] + details[:departureDate] = leg[:departure_date] if leg[:departure_date] + + details + end + + def add_flight_details(details, obj, leg) + details[:flight] = {} + details[:flight][:carrierCode] = leg[:flight][:carrier_code] if leg[:flight][:carrier_code] + details[:flight][:flightNumber] = leg[:flight][:flight_number] if leg[:flight][:flight_number] + end + + def add_split_pay_details(post, options) + return unless options[:split_pay] + + split_pay = [] + options[:split_pay].each do |pmnt| + split = {} + + split[:linkedAccount] = pmnt[:linked_account] + split[:amount] = pmnt[:amount].to_i if pmnt[:amount] + split[:percent] = pmnt[:percent].to_i if pmnt[:percent] + + split_pay << split + end + post[:splitpay] = split_pay + end + + def add_funding_transaction(post, options) + return unless options[:funding_transaction] + + post[:fundingTransaction] = {} + post[:fundingTransaction][:type] = options[:funding_transaction] + post[:profile] ||= {} + post[:profile][:merchantCustomerId] = options[:customer_id] || SecureRandom.hex(12) + end + + def add_stored_credential(post, options) + return unless options[:stored_credential] + + post[:storedCredential] = {} + + case options[:stored_credential][:initial_transaction] + when true + post[:storedCredential][:occurrence] = 'INITIAL' + when false + post[:storedCredential][:occurrence] = 'SUBSEQUENT' + end + + case options[:stored_credential][:reason_type] + when 'recurring', 'installment' + post[:storedCredential][:type] = 'RECURRING' + when 'unscheduled' + if options[:stored_credential][:initiator] == 'merchant' + post[:storedCredential][:type] = 'TOPUP' + elsif options[:stored_credential][:initiator] == 'cardholder' + post[:storedCredential][:type] = 'ADHOC' + else + return + end + end + + post[:storedCredential][:initialTransactionId] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + end + + def mastercard?(payment) + return false unless payment.respond_to?(:brand) + + payment.brand == 'master' + end + + def parse(body) + return {} if body.empty? + + JSON.parse(body) + end + + def commit(method, action, parameters, options) + url = url(action) + raw_response = ssl_request(method, url, post_data(parameters, options), headers) + response = parse(raw_response) + success = success_from(response) + + Response.new( + success, + message_from(success, response), + response, + authorization: authorization_from(action, response), + avs_result: AVSResult.new(code: response['avsResponse']), + cvv_result: CVVResult.new(response['cvvVerification']), + test: test?, + error_code: success ? nil : error_code_from(response) + ) + end + + def headers + { + 'Content-Type' => 'application/json', + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:username]}:#{@options[:password]}") + } + end + + def url(action, options = {}) + base_url = (test? ? test_url : live_url) + + if action.include? 'profiles' + "#{base_url}/customervault/v1/#{action}" + else + "#{base_url}/cardpayments/v1/accounts/#{@options[:account_id]}/#{action}" + end + end + + def success_from(response) + return false if response['status'] == 'FAILED' || response['error'] + + true + end + + def message_from(success, response) + return response['status'] unless response['error'] + + "Error(s)- code:#{response['error']['code']}, message:#{response['error']['message']}" + end + + def authorization_from(action, response) + if action == 'profiles' + pm = response['cards'].first['paymentToken'] + "#{pm}|#{response['id']}" + else + response['id'] + end + end + + def get_pm_from_store_auth(authorization) + authorization.split('|')[0] + end + + def get_id_from_store_auth(authorization) + authorization.split('|')[1] + end + + def post_data(parameters = {}, options = {}) + return unless parameters.present? + + parameters[:merchantRefNum] = options[:merchant_ref_num] || options[:order_id] || SecureRandom.hex(16).to_s + + parameters.to_json + end + + def error_code_from(response) + return unless response['error'] + + response['error']['code'] + end + + def handle_response(response) + response.body + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb index d2ad18b5a16..dec04a926f6 100644 --- a/lib/active_merchant/billing/gateways/payscout.rb +++ b/lib/active_merchant/billing/gateways/payscout.rb @@ -4,7 +4,7 @@ class PayscoutGateway < Gateway self.live_url = self.test_url = 'https://secure.payscout.com/api/transact.php' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.default_currency = 'USD' self.homepage_url = 'http://www.payscout.com/' self.display_name = 'Payscout' @@ -62,7 +62,7 @@ def add_address(post, options) post[:address1] = address[:address1].to_s post[:address2] = address[:address2].to_s post[:city] = address[:city].to_s - post[:state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:state] = (address[:state].blank? ? 'n/a' : address[:state]) post[:zip] = address[:zip].to_s post[:country] = address[:country].to_s post[:phone] = address[:phone].to_s @@ -78,7 +78,7 @@ def add_address(post, options) post[:shipping_address2] = address[:address2].to_s post[:shipping_city] = address[:city].to_s post[:shipping_country] = address[:country].to_s - post[:shipping_state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:shipping_state] = (address[:state].blank? ? 'n/a' : address[:state]) post[:shipping_zip] = address[:zip].to_s post[:shipping_email] = address[:email].to_s end @@ -115,12 +115,15 @@ def commit(action, money, parameters) message = message_from(response) test_mode = (test? || message =~ /TESTMODE/) - Response.new(success?(response), message, response, - :test => test_mode, - :authorization => response['transactionid'], - :fraud_review => fraud_review?(response), - :avs_result => { :code => response['avsresponse'] }, - :cvv_result => response['cvvresponse'] + Response.new( + success?(response), + message, + response, + test: test_mode, + authorization: response['transactionid'], + fraud_review: fraud_review?(response), + avs_result: { code: response['avsresponse'] }, + cvv_result: response['cvvresponse'] ) end diff --git a/lib/active_merchant/billing/gateways/paystation.rb b/lib/active_merchant/billing/gateways/paystation.rb index 0c2afac4358..4ec37cff955 100644 --- a/lib/active_merchant/billing/gateways/paystation.rb +++ b/lib/active_merchant/billing/gateways/paystation.rb @@ -1,7 +1,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class PaystationGateway < Gateway - self.live_url = self.test_url = 'https://www.paystation.co.nz/direct/paystation.dll' # an "error code" of "0" means "No error - transaction successful" @@ -14,7 +13,7 @@ class PaystationGateway < Gateway self.supported_countries = ['NZ'] # TODO: check this with paystation (amex and diners need to be enabled) - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club ] + self.supported_cardtypes = %i[visa master american_express diners_club] self.homepage_url = 'http://paystation.co.nz' self.display_name = 'Paystation' @@ -75,7 +74,7 @@ def store(credit_card, options = {}) commit(post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = new_request add_amount(post, money, options) add_invoice(post, options) @@ -84,7 +83,7 @@ def refund(money, authorization, options={}) commit(post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) authorize(0, credit_card, options) end @@ -128,7 +127,7 @@ def add_credit_card(post, credit_card) end def add_token(post, token) - post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing + post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing post[:ft] = token end @@ -178,9 +177,12 @@ 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 @@ -195,7 +197,6 @@ def message_from(response) def format_date(month, year) "#{format(year, :two_digits)}#{format(month, :two_digits)}" end - end class PaystationResponse < Response diff --git a/lib/active_merchant/billing/gateways/payu_in.rb b/lib/active_merchant/billing/gateways/payu_in.rb index eabc32c1cd6..304c7ff0886 100644 --- a/lib/active_merchant/billing/gateways/payu_in.rb +++ b/lib/active_merchant/billing/gateways/payu_in.rb @@ -11,17 +11,17 @@ class PayuInGateway < Gateway self.supported_countries = ['IN'] self.default_currency = 'INR' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :maestro] + self.supported_cardtypes = %i[visa master american_express diners_club maestro] self.homepage_url = 'https://www.payu.in/' self.display_name = 'PayU India' - def initialize(options={}) + def initialize(options = {}) requires!(options, :key, :salt) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) requires!(options, :order_id) post = {} @@ -33,7 +33,7 @@ def purchase(money, payment, options={}) MultiResponse.run do |r| r.process { commit(url('purchase'), post) } - if(r.params['enrolled'].to_s == '0') + if r.params['enrolled'].to_s == '0' r.process { commit(r.params['post_uri'], r.params['form_post_vars']) } else r.process { handle_3dsecure(r) } @@ -41,7 +41,7 @@ def purchase(money, payment, options={}) end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) raise ArgumentError, 'Amount is required' unless money post = {} @@ -154,20 +154,21 @@ def add_payment(post, payment) def clean(value, format, maxlength) value ||= '' - value = case format - when :alphanumeric - value.gsub(/[^A-Za-z0-9]/, '') - when :name - value.gsub(/[^A-Za-z ]/, '') - when :numeric - value.gsub(/[^0-9]/, '') - when :text - value.gsub(/[^A-Za-z0-9@\-_\/\. ]/, '') - when nil - value - else - raise "Unknown format #{format} for #{value}" - end + value = + case format + when :alphanumeric + value.gsub(/[^A-Za-z0-9]/, '') + when :name + value.gsub(/[^A-Za-z ]/, '') + when :numeric + value.gsub(/[^0-9]/, '') + when :text + value.gsub(/[^A-Za-z0-9@\-_\/\. ]/, '') + when nil + value + else + raise "Unknown format #{format} for #{value}" + end value[0...maxlength] end diff --git a/lib/active_merchant/billing/gateways/payu_latam.rb b/lib/active_merchant/billing/gateways/payu_latam.rb index 1d2b995b083..3ac30eec018 100644 --- a/lib/active_merchant/billing/gateways/payu_latam.rb +++ b/lib/active_merchant/billing/gateways/payu_latam.rb @@ -9,16 +9,19 @@ class PayuLatamGateway < Gateway self.test_url = 'https://sandbox.api.payulatam.com/payments-api/4.0/service.cgi' self.live_url = 'https://api.payulatam.com/payments-api/4.0/service.cgi' - self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PA', 'PE'] + self.supported_countries = %w[AR BR CL CO MX PA PE] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club naranja cabal] BRAND_MAP = { 'visa' => 'VISA', 'master' => 'MASTERCARD', + 'maestro' => 'MASTERCARD', 'american_express' => 'AMEX', - 'diners_club' => 'DINERS' + 'diners_club' => 'DINERS', + 'naranja' => 'NARANJA', + 'cabal' => 'CABAL' } MINIMUMS = { @@ -28,24 +31,24 @@ class PayuLatamGateway < Gateway 'PEN' => 500 } - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :account_id, :api_login, :api_key, :payment_country) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} auth_or_sale(post, 'AUTHORIZATION_AND_CAPTURE', amount, payment_method, options) commit('purchase', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} auth_or_sale(post, 'AUTHORIZATION', amount, payment_method, options) commit('auth', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_credentials(post, 'SUBMIT_TRANSACTION', options) @@ -60,7 +63,7 @@ def capture(amount, authorization, options={}) commit('capture', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_credentials(post, 'SUBMIT_TRANSACTION', options) @@ -70,17 +73,24 @@ def void(authorization, options={}) commit('void', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_credentials(post, 'SUBMIT_TRANSACTION', options) - add_transaction_elements(post, 'REFUND', options) - add_reference(post, authorization) + if options[:partial_refund] + add_transaction_elements(post, 'PARTIAL_REFUND', options) + post[:transaction][:additionalValues] ||= {} + post[:transaction][:additionalValues][:TX_VALUE] = invoice_for(amount, options)[:TX_VALUE] + else + add_transaction_elements(post, 'REFUND', options) + end + + add_reference(post, authorization) commit('refund', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) minimum = MINIMUMS[options[:currency].upcase] if options[:currency] amount = options[:verify_amount] || minimum || 100 @@ -94,7 +104,7 @@ def store(payment_method, options = {}) post = {} add_credentials(post, 'CREATE_TOKEN') - add_payment_method_to_be_tokenized(post, payment_method) + add_payment_method_to_be_tokenized(post, payment_method, options) commit('store', post) end @@ -131,7 +141,7 @@ def auth_or_sale(post, transaction_type, amount, payment_method, options) add_extra_parameters(post, options) end - def add_credentials(post, command, options={}) + def add_credentials(post, command, options = {}) post[:test] = test? unless command == 'CREATE_TOKEN' post[:language] = options[:language] || 'en' post[:command] = command @@ -178,12 +188,13 @@ def add_payer(post, payment_method, options) def billing_address_fields(options) return unless address = options[:billing_address] + billing_address = {} billing_address[:street1] = address[:address1] billing_address[:street2] = address[:address2] billing_address[:city] = address[:city] billing_address[:state] = address[:state] - billing_address[:country] = address[:country] + billing_address[:country] = address[:country] unless address[:country].blank? billing_address[:postalCode] = address[:zip] if @options[:payment_country] == 'MX' billing_address[:phone] = address[:phone] billing_address @@ -195,17 +206,19 @@ def add_buyer(post, payment_method, options) buyer[:fullName] = buyer_hash[:name] buyer[:dniNumber] = buyer_hash[:dni_number] buyer[:dniType] = buyer_hash[:dni_type] + buyer[:merchantBuyerId] = buyer_hash[:merchant_buyer_id] buyer[:cnpj] = buyer_hash[:cnpj] if @options[:payment_country] == 'BR' buyer[:emailAddress] = buyer_hash[:email] - buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || '' + buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone_number] if options[:shipping_address]) || '' buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address] else buyer[:fullName] = payment_method.name.strip buyer[:dniNumber] = options[:dni_number] buyer[:dniType] = options[:dni_type] + buyer[:merchantBuyerId] = options[:merchant_buyer_id] buyer[:cnpj] = options[:cnpj] if @options[:payment_country] == 'BR' buyer[:emailAddress] = options[:email] - buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || '' + buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone_number] if options[:shipping_address]) || '' buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address] end post[:transaction][:order][:buyer] = buyer @@ -213,6 +226,7 @@ def add_buyer(post, payment_method, options) def shipping_address_fields(options) return unless address = options[:shipping_address] + shipping_address = {} shipping_address[:street1] = address[:address1] shipping_address[:street2] = address[:address2] @@ -220,7 +234,7 @@ def shipping_address_fields(options) shipping_address[:state] = address[:state] shipping_address[:country] = address[:country] shipping_address[:postalCode] = address[:zip] - shipping_address[:phone] = address[:phone] + shipping_address[:phone] = address[:phone_number] shipping_address end @@ -265,6 +279,10 @@ def signature_from(post) Digest::MD5.hexdigest(signature_string) end + def codensa_bin?(number) + number.start_with?('590712') + end + def add_payment_method(post, payment_method, options) if payment_method.is_a?(String) brand, token = split_authorization(payment_method) @@ -282,18 +300,22 @@ def add_payment_method(post, payment_method, options) credit_card[:name] = payment_method.name.strip credit_card[:processWithoutCvv2] = true if add_process_without_cvv2(payment_method, options) post[:transaction][:creditCard] = credit_card - post[:transaction][:paymentMethod] = BRAND_MAP[payment_method.brand.to_s] + post[:transaction][:paymentMethod] = codensa_bin?(payment_method.number) ? 'CODENSA' : BRAND_MAP[payment_method.brand.to_s] end end def add_process_without_cvv2(payment_method, options) return true if payment_method.verification_value.blank? && options[:cvv].blank? + false end def add_extra_parameters(post, options) extra_parameters = {} extra_parameters[:INSTALLMENTS_NUMBER] = options[:installments_number] || 1 + extra_parameters[:EXTRA1] = options[:extra_1] if options[:extra_1] + extra_parameters[:EXTRA2] = options[:extra_2] if options[:extra_2] + extra_parameters[:EXTRA3] = options[:extra_3] if options[:extra_3] post[:transaction][:extraParameters] = extra_parameters end @@ -306,15 +328,14 @@ def add_reference(post, authorization) post[:transaction][:reason] = 'n/a' end - def add_payment_method_to_be_tokenized(post, payment_method) + def add_payment_method_to_be_tokenized(post, payment_method, options) credit_card_token = {} - credit_card_token[:payerId] = generate_unique_id + credit_card_token[:payerId] = options[:payer_id] || generate_unique_id credit_card_token[:name] = payment_method.name.strip - credit_card_token[:identificationNumber] = generate_unique_id + credit_card_token[:identificationNumber] = options[:dni_number] credit_card_token[:paymentMethod] = BRAND_MAP[payment_method.brand.to_s] credit_card_token[:number] = payment_method.number credit_card_token[:expirationDate] = format(payment_method.year, :four_digits).to_s + '/' + format(payment_method.month, :two_digits).to_s - credit_card_token[:securityCode] = payment_method.verification_value post[:creditCardToken] = credit_card_token end @@ -340,8 +361,8 @@ def commit(action, params) def headers { - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' } end @@ -374,22 +395,38 @@ def success_from(action, response) def message_from(action, success, response) case action when 'store' - return response['code'] if success - error_description = response['creditCardToken']['errorDescription'] if response['creditCardToken'] - response['error'] || error_description || 'FAILED' + message_from_store(success, response) when 'verify_credentials' - return 'VERIFIED' if success - 'FAILED' + message_from_verify_credentials(success) else - if response['transactionResponse'] - response_message = response['transactionResponse']['responseMessage'] - response_code = response['transactionResponse']['responseCode'] || response['transactionResponse']['pendingReason'] - end - return response_code if success - response['error'] || response_message || response_code || 'FAILED' + message_from_transaction_response(success, response) end end + def message_from_store(success, response) + return response['code'] if success + + error_description = response['creditCardToken']['errorDescription'] if response['creditCardToken'] + response['error'] || error_description || 'FAILED' + end + + def message_from_verify_credentials(success) + return 'VERIFIED' if success + + 'FAILED' + end + + def message_from_transaction_response(success, response) + response_code = response.dig('transactionResponse', 'responseCode') || response.dig('transactionResponse', 'pendingReason') + return response_code if success + return response_code + ' | ' + response.dig('transactionResponse', 'paymentNetworkResponseErrorMessage') if response.dig('transactionResponse', 'paymentNetworkResponseErrorMessage') + return response.dig('transactionResponse', 'responseMessage') if response.dig('transactionResponse', 'responseMessage') + return response['error'] if response['error'] + return response_code if response_code + + 'FAILED' + end + def authorization_from(action, response) case action when 'store' @@ -418,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 @@ -431,7 +468,7 @@ def response_error(raw_response) false, message_from('', false, response), response, - :test => test? + test: test? ) end diff --git a/lib/active_merchant/billing/gateways/payway.rb b/lib/active_merchant/billing/gateways/payway.rb index b80e5f937a1..c48373ea608 100644 --- a/lib/active_merchant/billing/gateways/payway.rb +++ b/lib/active_merchant/billing/gateways/payway.rb @@ -3,8 +3,8 @@ module Billing class PaywayGateway < Gateway self.live_url = self.test_url = 'https://ccapi.client.qvalent.com/payway/ccapi' - self.supported_countries = [ 'AU' ] - self.supported_cardtypes = [ :visa, :master, :diners_club, :american_express, :bankcard ] + self.supported_countries = ['AU'] + self.supported_cardtypes = %i[visa master diners_club american_express bankcard] self.display_name = 'Pay Way' self.homepage_url = 'http://www.payway.com.au' self.default_currency = 'AUD' @@ -17,7 +17,7 @@ class PaywayGateway < Gateway '3' => 'Rejected' } - RESPONSE_CODES= { + RESPONSE_CODES = { '00' => 'Completed Successfully', '01' => 'Refer to card issuer', '03' => 'Invalid merchant', @@ -74,16 +74,16 @@ class PaywayGateway < Gateway 'QZ' => 'Zero value transaction' } - TRANSACTIONS = { - :authorize => 'preauth', - :purchase => 'capture', - :capture => 'captureWithoutAuth', - :status => 'query', - :refund => 'refund', - :store => 'registerAccount' + TRANSACTIONS = { + authorize: 'preauth', + purchase: 'capture', + capture: 'captureWithoutAuth', + status: 'query', + refund: 'refund', + store: 'registerAccount' } - def initialize(options={}) + def initialize(options = {}) @options = options @options[:merchant] ||= 'TEST' if test? @@ -92,7 +92,7 @@ def initialize(options={}) @options[:eci] ||= 'SSL' end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) requires!(options, :order_id) post = {} @@ -101,7 +101,7 @@ def authorize(amount, payment_method, options={}) commit(:authorize, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) requires!(options, :order_id) post = {} @@ -110,7 +110,7 @@ def capture(amount, authorization, options={}) commit(:capture, post) end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) requires!(options, :order_id) post = {} @@ -119,7 +119,7 @@ def purchase(amount, payment_method, options={}) commit(:purchase, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) requires!(options, :order_id) post = {} @@ -128,7 +128,7 @@ def refund(amount, authorization, options={}) commit(:refund, post) end - def store(credit_card, options={}) + def store(credit_card, options = {}) requires!(options, :billing_id) post = {} @@ -137,7 +137,7 @@ def store(credit_card, options={}) commit(:store, post) end - def status(options={}) + def status(options = {}) requires!(options, :order_id) commit(:status, 'customer.orderNumber' => options[:order_id]) @@ -192,15 +192,19 @@ def commit(action, post) success = (params[:summary_code] ? (params[:summary_code] == '0') : (params[:response_code] == '00')) - Response.new(success, message, params, - :test => (@options[:merchant].to_s == 'TEST'), - :authorization => post[:order_number] + Response.new( + success, + message, + params, + test: (@options[:merchant].to_s == 'TEST'), + authorization: post[:order_number] ) rescue ActiveMerchant::ResponseError => e raise unless e.response.code == '403' - return Response.new(false, 'Invalid credentials', {}, :test => test?) + + return Response.new(false, 'Invalid credentials', {}, test: test?) rescue ActiveMerchant::ClientCertificateError - return Response.new(false, 'Invalid certificate', {}, :test => test?) + return Response.new(false, 'Invalid certificate', {}, test: test?) end end end diff --git a/lib/active_merchant/billing/gateways/payway_dot_com.rb b/lib/active_merchant/billing/gateways/payway_dot_com.rb new file mode 100644 index 00000000000..995889b53bc --- /dev/null +++ b/lib/active_merchant/billing/gateways/payway_dot_com.rb @@ -0,0 +1,253 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaywayDotComGateway < Gateway + self.test_url = 'https://paywaywsdev.com/PaywayWS/Payment/CreditCard' + self.live_url = 'https://paywayws.net/PaywayWS/Payment/CreditCard' + + self.supported_countries = %w[US CA] + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.money_format = :cents + + self.homepage_url = 'http://www.payway.com' + self.display_name = 'Payway Gateway' + + STANDARD_ERROR_CODE_MAPPING = { + '5012' => STANDARD_ERROR_CODE[:card_declined], + '5035' => STANDARD_ERROR_CODE[:invalid_number], + '5037' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '5045' => STANDARD_ERROR_CODE[:incorrect_zip] + } + + # Payway to standard AVSResult codes. + AVS_MAPPING = { + 'N1' => 'I', # No address given with order + 'N2' => 'I', # Bill-to address did not pass + '““' => 'R', # AVS not performed (Blanks returned) + 'IU' => 'G', # AVS not performed by Issuer + 'ID' => 'S', # Issuer does not participate in AVS + 'IE' => 'E', # Edit Error - AVS data is invalid + 'IS' => 'R', # System unavailable or time-out + 'IB' => 'B', # Street address match. Postal code not verified due to incompatible formats (both were sent). + 'IC' => 'C', # Street address and postal code not verified due to incompatible format (both were sent). + 'IP' => 'P', # Postal code match. Street address not verified due to incompatible formats (both were sent). + 'A1' => 'K', # Accountholder name matches + 'A3' => 'V', # Accountholder name, billing address and postal code. + 'A4' => 'L', # Accountholder name and billing postal code match + 'A7' => 'O', # Accountholder name and billing address match + 'B3' => 'H', # Accountholder name incorrect, billing address and postal code match + 'B4' => 'F', # Accountholder name incorrect, billing postal code matches + 'B7' => 'T', # Accountholder name incorrect, billing address matches + 'B8' => 'N', # Accountholder name, billing address and postal code are all incorrect + '??' => 'R', # A double question mark symbol indicates an unrecognized response from association + 'I1' => 'X', # Zip code +4 and Address Match + 'I2' => 'W', # Zip code +4 Match + 'I3' => 'Y', # Zip code and Address Match + 'I4' => 'Z', # Zip code Match + 'I5' => 'M', # +4 and Address Match + 'I6' => 'W', # +4 Match + 'I7' => 'A', # Address Match + 'I8' => 'C' # No Match + } + + PAYWAY_WS_SUCCESS = '5000' + + SCRUB_PATTERNS = [ + %r(("password\\?":\\?")[^\\]+), + %r(("fsv\\?":\\?")\d+), + %r(("fsv\\?": \\?")\d+), + %r(("accountNumber\\?":\\?")\d+), + %r(("accountNumber\\?": \\?")[^\\]+), + %r(("Invalid account number: )\d+) + ].freeze + + SCRUB_REPLACEMENT = '\1[FILTERED]' + + def initialize(options = {}) + requires!(options, :login, :password, :company_id, :source_id) + super + end + + def purchase(money, payment, options = {}) + post = {} + add_common(post, options) + add_card_payment(post, payment, options) + add_card_transaction_details(post, money, options) + add_address(post, payment, options) + + commit('sale', post) + end + + def authorize(money, payment, options = {}) + post = {} + add_common(post, options) + add_card_payment(post, payment, options) + add_card_transaction_details(post, money, options) + add_address(post, payment, options) + + commit('authorize', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_common(post, options) + add_card_transaction_name(post, authorization, options) + + commit('capture', post) + end + + def credit(money, payment, options = {}) + post = {} + add_common(post, options) + add_card_payment(post, payment, options) + add_card_transaction_details(post, money, options) + add_address(post, payment, options) + + commit('credit', post) + end + + def void(authorization, options = {}) + post = {} + add_common(post, options) + add_card_transaction_name(post, authorization, options) + + commit('void', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + SCRUB_PATTERNS.inject(transcript) do |text, pattern| + text.gsub(pattern, SCRUB_REPLACEMENT) + end + end + + private + + def add_common(post, options) + post[:userName] = @options[:login] + post[:password] = @options[:password] + post[:companyId] = @options[:company_id] + post[:cardTransaction] = {} + post[:cardTransaction][:sourceId] = @options[:source_id] + end + + def add_address(post, payment, options) + post[:cardAccount] ||= {} + address = options[:billing_address] || options[:address] || {} + first_name, last_name = split_names(address[:name]) + full_address = "#{address[:address1]} #{address[:address2]}".strip + phone = address[:phone] || address[:phone_number] + + post[:cardAccount][:firstName] = first_name if first_name + post[:cardAccount][:lastName] = last_name if last_name + post[:cardAccount][:address] = full_address if full_address + post[:cardAccount][:city] = address[:city] if address[:city] + post[:cardAccount][:state] = address[:state] if address[:state] + post[:cardAccount][:zip] = address[:zip] if address[:zip] + post[:cardAccount][:phone] = phone if phone + end + + def add_card_transaction_details(post, money, options) + post[:cardTransaction][:amount] = amount(money) + eci_type = options[:eci_type].nil? ? '1' : options[:eci_type] + post[:cardTransaction][:eciType] = eci_type + post[:cardTransaction][:processorSoftDescriptor] = options[:processor_soft_descriptor] if options[:processor_soft_descriptor] + post[:cardTransaction][:tax] = options[:tax] if options[:tax] + end + + def add_card_transaction_name(post, identifier, options) + post[:cardTransaction][:name] = identifier + end + + def add_card_payment(post, payment, options) + # credit_card + post[:accountInputMode] = 'primaryAccountNumber' + + post[:cardAccount] ||= {} + post[:cardAccount][:accountNumber] = payment.number + post[:cardAccount][:fsv] = payment.verification_value + post[:cardAccount][:expirationDate] = expdate(payment) + post[:cardAccount][:email] = options[:email] if options[:email] + end + + def expdate(credit_card) + year = format(credit_card.year, :four_digits) + month = format(credit_card.month, :two_digits) + + month + year + end + + def parse(body) + body.blank? ? {} : JSON.parse(body) + end + + def commit(action, parameters) + parameters[:request] = action + + url = (test? ? test_url : live_url) + payload = parameters.to_json unless parameters.nil? + + response = + begin + parse(ssl_request(:post, url, payload, headers)) + rescue ResponseError => e + return Response.new(false, 'Invalid Login') if e.response.code == '401' + + parse(e.response.body) + end + + success = success_from(response) + avs_result_code = response['cardTransaction'].nil? || response['cardTransaction']['addressVerificationResults'].nil? ? '' : response['cardTransaction']['addressVerificationResults'] + avs_result = AVSResult.new(code: AVS_MAPPING[avs_result_code]) + cvv_result = CVVResult.new(response['cardTransaction']['fraudSecurityResults']) if response['cardTransaction'] && response['cardTransaction']['fraudSecurityResults'] + + Response.new( + success, + message_from(success, response), + response, + test: test?, + error_code: error_code_from(response), + authorization: authorization_from(response), + avs_result: avs_result, + cvv_result: cvv_result + ) + end + + def success_from(response) + response['paywayCode'] == PAYWAY_WS_SUCCESS + end + + 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] + return error + end + + def message_from(success, response) + return '' if response['paywayCode'].nil? + + return response['paywayCode'] + '-' + 'success' if success + + response['paywayCode'] + end + + def authorization_from(response) + return '' if !success_from(response) || response['cardTransaction'].nil? + + response['cardTransaction']['name'] + end + + def headers + { + 'Accept' => 'application/json', + 'Content-type' => 'application/json' + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pin.rb b/lib/active_merchant/billing/gateways/pin.rb index 89e92895980..b054ed3b7a6 100644 --- a/lib/active_merchant/billing/gateways/pin.rb +++ b/lib/active_merchant/billing/gateways/pin.rb @@ -6,8 +6,8 @@ class PinGateway < Gateway self.default_currency = 'AUD' self.money_format = :cents - self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_countries = %w(AU NZ) + self.supported_cardtypes = %i[visa master american_express diners_club discover jcb] self.homepage_url = 'http://www.pinpayments.com/' self.display_name = 'Pin Payments' @@ -30,6 +30,7 @@ def purchase(money, creditcard, options = {}) add_address(post, creditcard, options) add_capture(post, options) add_metadata(post, options) + add_3ds(post, options) commit(:post, 'charges', post, options) end @@ -45,9 +46,20 @@ def store(creditcard, options = {}) commit(:post, 'customers', post, options) end + # Unstore a customer and associated credit card. + def unstore(token) + customer_token = + if /cus_/.match?(token) + get_customer_token(token) + else + token + end + commit(:delete, "customers/#{CGI.escape(customer_token)}", {}, {}) + end + # Refund a transaction def refund(money, token, options = {}) - commit(:post, "charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options) + commit(:post, "charges/#{CGI.escape(token)}/refunds", { amount: amount(money) }, options) end # Authorize an amount on a credit card. Once authorized, you can later @@ -61,7 +73,12 @@ def authorize(money, creditcard, options = {}) # Captures a previously authorized charge. Capturing only part of the original # authorization is currently not supported. def capture(money, token, options = {}) - commit(:put, "charges/#{CGI.escape(token)}/capture", { :amount => amount(money) }, options) + commit(:put, "charges/#{CGI.escape(token)}/capture", { amount: amount(money) }, options) + end + + # Voids a previously authorized charge. + def void(token, options = {}) + commit(:put, "charges/#{CGI.escape(token)}/void", {}, options) end # Updates the credit card for the customer. @@ -101,16 +118,17 @@ def add_customer_data(post, options) def add_address(post, creditcard, options) return if creditcard.kind_of?(String) + address = (options[:billing_address] || options[:address]) return unless address post[:card] ||= {} post[:card].merge!( - :address_line1 => address[:address1], - :address_city => address[:city], - :address_postcode => address[:zip], - :address_state => address[:state], - :address_country => address[:country] + address_line1: address[:address1], + address_city: address[:city], + address_postcode: address[:zip], + address_state: address[:state], + address_country: address[:country] ) end @@ -130,14 +148,14 @@ def add_creditcard(post, creditcard) post[:card] ||= {} post[:card].merge!( - :number => creditcard.number, - :expiry_month => creditcard.month, - :expiry_year => creditcard.year, - :cvc => creditcard.verification_value, - :name => creditcard.name + number: creditcard.number, + expiry_month: creditcard.month, + expiry_year: creditcard.year, + cvc: creditcard.verification_value, + name: creditcard.name ) elsif creditcard.kind_of?(String) - if creditcard =~ /^card_/ + if /^card_/.match?(creditcard) post[:card_token] = get_card_token(creditcard) else post[:customer_token] = creditcard @@ -157,6 +175,16 @@ def add_metadata(post, options) post[:metadata] = options[:metadata] if options[:metadata] end + def add_3ds(post, options) + if options[:three_d_secure] + post[:three_d_secure] = {} + post[:three_d_secure][:version] = options[:three_d_secure][:version] if options[:three_d_secure][:version] + post[:three_d_secure][:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + post[:three_d_secure][:cavv] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + post[:three_d_secure][:transaction_id] = options[:three_d_secure][:ds_transaction_id] || options[:three_d_secure][:xid] + end + end + def headers(params = {}) result = { 'Content-Type' => 'application/json', @@ -178,7 +206,9 @@ def commit(method, action, params, options) body = parse(e.response.body) end - if body['response'] + if body.nil? + no_content_response + elsif body['response'] success_response(body) elsif body['error'] error_response(body) @@ -193,8 +223,8 @@ def success_response(body) true, response['status_message'], body, - :authorization => token(response), - :test => test? + authorization: token(response), + test: test? ) end @@ -203,8 +233,17 @@ def error_response(body) false, body['error_description'], body, - :authorization => nil, - :test => test? + authorization: nil, + test: test? + ) + end + + def no_content_response + Response.new( + true, + nil, + {}, + test: test? ) end @@ -223,7 +262,7 @@ def token(response) end def parse(body) - JSON.parse(body) + JSON.parse(body) unless body.nil? || body.length == 0 end def post_data(parameters = {}) diff --git a/lib/active_merchant/billing/gateways/plexo.rb b/lib/active_merchant/billing/gateways/plexo.rb new file mode 100644 index 00000000000..f4558e1c4df --- /dev/null +++ b/lib/active_merchant/billing/gateways/plexo.rb @@ -0,0 +1,308 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PlexoGateway < Gateway + self.test_url = 'https://api.testing.plexo.com.uy/v1/payments' + self.live_url = 'https://api.plexo.com.uy/v1/payments' + + self.supported_countries = ['UY'] + self.default_currency = 'UYU' + self.supported_cardtypes = %i[visa master american_express discover passcard edenred anda tarjeta-d] + + self.homepage_url = 'https://www.plexo.com.uy' + self.display_name = 'Plexo' + + APPENDED_URLS = %w(captures refunds cancellations verify) + AMOUNT_IN_RESPONSE = %w(authonly purchase /verify) + APPROVED_STATUS = %w(approved authorized) + + def initialize(options = {}) + requires!(options, :client_id, :api_key) + @credentials = options + super + end + + def purchase(money, payment, options = {}) + post = {} + build_auth_purchase_request(money, post, payment, options) + + commit('purchase', post, options) + end + + def authorize(money, payment, options = {}) + post = {} + build_auth_purchase_request(money, post, payment, options) + add_capture_type(post, options) + + commit('authonly', post, options) + end + + def capture(money, authorization, options = {}) + post = {} + post[:ReferenceId] = options[:reference_id] || generate_unique_id + post[:Amount] = amount(money) + + commit("/#{authorization}/captures", post, options) + end + + def refund(money, authorization, options = {}) + post = {} + post[:ReferenceId] = options[:reference_id] || generate_unique_id + post[:Type] = options[:refund_type] || 'refund' + post[:Description] = options[:description] + post[:Reason] = options[:reason] + post[:Amount] = amount(money) + + commit("/#{authorization}/refunds", post, options) + end + + def void(authorization, options = {}) + post = {} + post[:ReferenceId] = options[:reference_id] || generate_unique_id + post[:Description] = options[:description] + post[:Reason] = options[:reason] + + commit("/#{authorization}/cancellations", post, options) + end + + def verify(credit_card, options = {}) + post = {} + post[:ReferenceId] = options[:reference_id] || generate_unique_id + 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] + money = options[:verify_amount].to_i || 100 + + add_payment_method(post, credit_card, options) + add_metadata(post, options[:metadata]) + add_amount(money, post, options) + add_browser_details(post, options) + + commit('/verify', post, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + 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]') + end + + private + + def encoded_credentials + Base64.encode64("#{@credentials[:client_id]}:#{@credentials[:api_key]}").delete("\n") + end + + def build_auth_purchase_request(money, post, payment, options) + post[:ReferenceId] = options[:reference_id] || generate_unique_id + post[:MerchantId] = options[:merchant_id] || @credentials[:merchant_id] + 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] + + 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) + end + + def header(parameters = {}) + { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{encoded_credentials}" + } + end + + def add_capture_type(post, options) + post[:Capture] = {} + post[:Capture][:Method] = options.dig(:capture_type, :method) || 'manual' + end + + def add_items(post, items) + return unless items&.kind_of?(Array) + + post[:Items] = [] + + items.each do |option_item| + item = {} + item[:ReferenceId] = option_item[:reference_id] || generate_unique_id + item[:Name] = option_item[:name] if option_item[:name] + item[:Description] = option_item[:description] if option_item[:description] + item[:Quantity] = option_item[:quantity] if option_item[:quantity] + item[:Price] = option_item[:price] if option_item[:price] + item[:Discount] = option_item[:discount] if option_item[:discount] + + post[:Items].append(item) + end + end + + def add_metadata(post, metadata) + return unless metadata&.kind_of?(Hash) + + metadata.transform_keys! { |key| key.to_s.camelize.to_sym } + post[:Metadata] = metadata + end + + def add_amount(money, post, amount_options) + post[:Amount] = {} + + post[:Amount][:Currency] = amount_options[:currency] || self.default_currency + post[:Amount][:Total] = amount(money) + post[:Amount][:Details] = {} + add_amount_details(post[:Amount][:Details], amount_options[:amount_details]) if amount_options[:amount_details] + end + + def add_amount_details(amount_details, options) + return unless options + + amount_details[:TaxedAmount] = options[:taxed_amount] if options[:taxed_amount] + amount_details[:TipAmount] = options[:tip_amount] if options[:tip_amount] + amount_details[:DiscountAmount] = options[:discount_amount] if options[:discount_amount] + amount_details[:TaxableAmount] = options[:taxable_amount] if options[:taxable_amount] + add_tax(amount_details, options[:tax]) + end + + def add_tax(post, tax) + return unless tax + + post[:Tax] = {} + post[:Tax][:Type] = tax[:type] if tax[:type] + post[:Tax][:Amount] = tax[:amount] if tax[:amount] + post[:Tax][:Rate] = tax[:rate] if tax[:rate] + end + + def add_browser_details(post, browser_details) + return unless browser_details + + post[:BrowserDetails] = {} + post[:BrowserDetails][:DeviceFingerprint] = browser_details[:finger_print] if browser_details[:finger_print] + post[:BrowserDetails][:IpAddress] = browser_details[:ip] if browser_details[:ip] + end + + def add_payment_method(post, payment, options) + post[:paymentMethod] = {} + + if payment&.is_a?(CreditCard) + post[:paymentMethod][:type] = 'card' + post[:paymentMethod][:Card] = {} + post[:paymentMethod][:Card][:Number] = payment.number + post[:paymentMethod][:Card][:ExpMonth] = format(payment.month, :two_digits) if payment.month + post[:paymentMethod][:Card][:ExpYear] = format(payment.year, :two_digits) if payment.year + post[:paymentMethod][:Card][:Cvc] = payment.verification_value if payment.verification_value + + add_card_holder(post[:paymentMethod][:Card], payment, options) + end + end + + def add_card_holder(card, payment, options) + requires!(options, :email) + + cardholder = {} + cardholder[:FirstName] = payment.first_name if payment.first_name + cardholder[:LastName] = payment.last_name if payment.last_name + cardholder[:Email] = options[:email] + cardholder[:Birthdate] = options[:cardholder_birthdate] if options[:cardholder_birthdate] + cardholder[:Identification] = {} + cardholder[:Identification][:Type] = options[:identification_type] if options[:identification_type] + cardholder[:Identification][:Value] = options[:identification_value] if options[:identification_value] + add_billing_address(cardholder, options) + + card[:Cardholder] = cardholder + end + + def add_billing_address(cardholder, options) + return unless address = options[:billing_address] + + cardholder[:BillingAddress] = {} + cardholder[:BillingAddress][:City] = address[:city] + cardholder[:BillingAddress][:Country] = address[:country] + cardholder[:BillingAddress][:Line1] = address[:address1] + cardholder[:BillingAddress][:Line2] = address[:address2] + cardholder[:BillingAddress][:PostalCode] = address[:zip] + cardholder[:BillingAddress][:State] = address[:state] + end + + def parse(body) + return {} if body == '' + + JSON.parse(body) + end + + def build_url(action, base) + url = base + url += action if APPENDED_URLS.any? { |key| action.include?(key) } + url + end + + def get_authorization_from_url(url) + url.split('/')[1] + end + + def reorder_amount_fields(response) + return response unless response['amount'] + + amount_obj = response['amount'] + response['amount'] = amount_obj['total'].to_i if amount_obj['total'] + response['currency'] = amount_obj['currency'] if amount_obj['currency'] + response['amount_details'] = amount_obj['details'] if amount_obj['details'] + response + end + + def commit(action, parameters, options = {}) + base_url = (test? ? test_url : live_url) + url = build_url(action, base_url) + response = parse(ssl_post(url, parameters.to_json, header(options))) + response = reorder_amount_fields(response) if AMOUNT_IN_RESPONSE.include?(action) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response, action), + test: test?, + error_code: error_code_from(response) + ) + end + + def handle_response(response) + case response.code.to_i + when 200...300, 400, 401 + response.body + else + raise ResponseError.new(response) + end + end + + def success_from(response) + APPROVED_STATUS.include?(response['status']) + end + + def message_from(response) + response = response['transactions']&.first if response['transactions']&.is_a?(Array) + response['resultMessage'] || response['message'] + end + + def authorization_from(response, action = nil) + if action.include?('captures') + get_authorization_from_url(action) + else + response['id'] + end + end + + def error_code_from(response) + return if success_from(response) + + response = response['transactions']&.first if response['transactions']&.is_a?(Array) + response['resultCode'] || response['status'] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/plugnpay.rb b/lib/active_merchant/billing/gateways/plugnpay.rb index 36becf69ff8..e383c0d4ef7 100644 --- a/lib/active_merchant/billing/gateways/plugnpay.rb +++ b/lib/active_merchant/billing/gateways/plugnpay.rb @@ -3,8 +3,8 @@ module Billing class PlugnpayGateway < Gateway class PlugnpayPostData < PostData # Fields that will be sent even if they are blank - self.required_fields = [:publisher_name, :publisher_password, - :card_amount, :card_name, :card_number, :card_exp, :orderID] + self.required_fields = %i[publisher_name publisher_password + card_amount card_name card_number card_exp orderID] end self.live_url = self.test_url = 'https://pay1.plugnpay.com/payment/pnpremote.cgi' @@ -16,7 +16,7 @@ class PlugnpayPostData < PostData 'U' => 'Issuer was not certified for card verification' } - CARD_CODE_ERRORS = %w( N S ) + CARD_CODE_ERRORS = %w(N S) AVS_MESSAGES = { 'A' => 'Street address matches billing information, zip/postal code does not', @@ -31,10 +31,10 @@ class PlugnpayPostData < PostData 'W' => '9-digit zip/postal code matches billing information, street address does not', 'X' => 'Street address and 9-digit zip/postal code matches billing information', 'Y' => 'Street address and 5-digit zip/postal code matches billing information', - 'Z' => '5-digit zip/postal code matches billing information, street address does not', + 'Z' => '5-digit zip/postal code matches billing information, street address does not' } - AVS_ERRORS = %w( A E N R W Z ) + AVS_ERRORS = %w(A E N R W Z) PAYMENT_GATEWAY_RESPONSES = { 'P01' => 'AVS Mismatch Failure', @@ -80,20 +80,20 @@ class PlugnpayPostData < PostData } TRANSACTIONS = { - :authorization => 'auth', - :purchase => 'auth', - :capture => 'mark', - :void => 'void', - :refund => 'return', - :credit => 'newreturn' + authorization: 'auth', + purchase: 'auth', + capture: 'mark', + void: 'void', + refund: 'return', + credit: 'newreturn' } - SUCCESS_CODES = [ 'pending', 'success' ] - FAILURE_CODES = [ 'badcard', 'fraud' ] + SUCCESS_CODES = %w[pending success] + FAILURE_CODES = %w[badcard fraud] self.default_currency = 'USD' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.plugnpay.com/' self.display_name = "Plug'n Pay" @@ -178,11 +178,14 @@ def commit(action, post) success = SUCCESS_CODES.include?(response[:finalstatus]) message = success ? 'Success' : message_from(response) - Response.new(success, message, response, - :test => test?, - :authorization => response[:orderid], - :avs_result => { :code => response[:avs_code] }, - :cvv_result => response[:cvvresp] + Response.new( + success, + message, + response, + test: test?, + authorization: response[:orderid], + avs_result: { code: response[:avs_code] }, + cvv_result: response[:cvvresp] ) end @@ -219,7 +222,7 @@ def add_creditcard(post, creditcard) def add_customer_data(post, options) post[:email] = options[:email] - post[:dontsndmail] = 'yes' unless options[:send_email_confirmation] + post[:dontsndmail] = 'yes' unless options[:send_email_confirmation] post[:ipaddress] = options[:ip] end @@ -256,7 +259,7 @@ def add_addresses(post, options) post[:state] = shipping_address[:state] else post[:state] = 'ZZ' - post[:province] = shipping_address[:state] + post[:province] = shipping_address[:state] end post[:country] = shipping_address[:country] diff --git a/lib/active_merchant/billing/gateways/priority.rb b/lib/active_merchant/billing/gateways/priority.rb new file mode 100644 index 00000000000..cddde41395c --- /dev/null +++ b/lib/active_merchant/billing/gateways/priority.rb @@ -0,0 +1,392 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PriorityGateway < Gateway + # Sandbox and Production + self.test_url = 'https://sandbox.api.mxmerchant.com/checkout/v3/payment' + self.live_url = 'https://api.mxmerchant.com/checkout/v3/payment' + + class_attribute :test_url_verify, :live_url_verify, :test_auth, :live_auth, :test_env_verify, :live_env_verify, :test_url_batch, :live_url_batch, :test_url_jwt, :live_url_jwt, :merchant + + # Sandbox and Production - verify card + self.test_url_verify = 'https://sandbox-api2.mxmerchant.com/merchant/v1/bin' + self.live_url_verify = 'https://api2.mxmerchant.com/merchant/v1/bin' + + # Sandbox and Production - check batch status + self.test_url_batch = 'https://sandbox.api.mxmerchant.com/checkout/v3/batch' + self.live_url_batch = 'https://api.mxmerchant.com/checkout/v3/batch' + + # Sandbox and Production - generate jwt for verify card url + self.test_url_jwt = 'https://sandbox-api2.mxmerchant.com/security/v1/application/merchantId' + self.live_url_jwt = 'https://api2.mxmerchant.com/security/v1/application/merchantId' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://mxmerchant.com/' + self.display_name = 'Priority' + + def initialize(options = {}) + requires!(options, :merchant_id, :api_key, :secret) + super + end + + def basic_auth + Base64.strict_encode64("#{@options[:api_key]}:#{@options[:secret]}") + end + + def request_headers + { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{basic_auth}" + } + end + + def request_verify_headers(jwt) + { + 'Authorization' => "Bearer #{jwt}" + } + end + + def purchase(amount, credit_card, options = {}) + params = {} + params['authOnly'] = false + params['isSettleFunds'] = true + + add_merchant_id(params) + add_amount(params, amount, options) + add_auth_purchase_params(params, credit_card, options) + + commit('purchase', params: params) + end + + def authorize(amount, credit_card, options = {}) + params = {} + params['authOnly'] = true + params['isSettleFunds'] = false + + add_merchant_id(params) + add_amount(params, amount, options) + add_auth_purchase_params(params, credit_card, options) + + commit('purchase', params: params) + end + + def credit(amount, credit_card, options = {}) + params = {} + params['authOnly'] = false + params['isSettleFunds'] = true + amount = -amount + + add_merchant_id(params) + add_amount(params, amount, options) + add_credit_params(params, credit_card, options) + commit('credit', params: params) + end + + def refund(amount, authorization, options = {}) + params = {} + add_merchant_id(params) + params['paymentToken'] = payment_token(authorization) || options[:payment_token] + + # refund amounts must be negative + params['amount'] = ('-' + localized_amount(amount.to_f, options[:currency])).to_f + + commit('refund', params: params) + end + + def capture(amount, authorization, options = {}) + params = {} + 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' + + commit('capture', params: params) + end + + def void(authorization, options = {}) + params = {} + + commit('void', params: params, iid: payment_id(authorization)) + end + + def verify(credit_card, _options = {}) + jwt = create_jwt.params['jwtToken'] + + commit('verify', card_number: credit_card.number, jwt: jwt) + end + + def get_payment_status(batch_id) + commit('get_payment_status', params: batch_id) + end + + def close_batch(batch_id) + commit('close_batch', params: batch_id) + end + + def create_jwt + commit('create_jwt', params: @options[:merchant_id]) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r((cvv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]') + end + + private + + def add_amount(params, amount, options) + params['amount'] = localized_amount(amount.to_f, options[:currency]) + end + + def add_merchant_id(params) + params['merchantId'] = @options[:merchant_id] + end + + def add_auth_purchase_params(params, credit_card, 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) + add_additional_data(params, options) + end + + def add_credit_params(params, credit_card, options) + add_replay_id(params, options) + add_credit_card(params, credit_card, 'purchase', options) + add_additional_data(params, options) + end + + def add_replay_id(params, options) + params['replayId'] = options[:replay_id] if options[:replay_id] + end + + def add_credit_card(params, credit_card, action, options) + return unless credit_card&.is_a?(CreditCard) + + card_details = {} + card_details['expiryMonth'] = format(credit_card.month, :two_digits).to_s + card_details['expiryYear'] = format(credit_card.year, :two_digits).to_s + card_details['cardType'] = credit_card.brand + card_details['last4'] = credit_card.last_digits + card_details['cvv'] = credit_card.verification_value unless credit_card.verification_value.nil? + card_details['number'] = credit_card.number + card_details['avsStreet'] = options[:billing_address][:address1] if options[:billing_address] + card_details['avsZip'] = options[:billing_address][:zip] if !options[:billing_address].nil? && !options[:billing_address][:zip].nil? + + params['cardAccount'] = card_details + end + + def exp_date(credit_card) + "#{format(credit_card.month, :two_digits)}/#{format(credit_card.year, :two_digits)}" + end + + def add_additional_data(params, options) + params['isAuth'] = options[:is_auth].present? ? options[:is_auth] : 'true' + params['paymentType'] = options[:payment_type].present? ? options[:payment_type] : 'Sale' + params['tenderType'] = options[:tender_type].present? ? options[:tender_type] : 'Card' + params['taxExempt'] = options[:tax_exempt].present? ? options[:tax_exempt] : 'false' + params['taxAmount'] = options[:tax_amount] if options[:tax_amount] + params['shouldGetCreditCardLevel'] = options[:should_get_credit_card_level] if options[:should_get_credit_card_level] + params['source'] = options[:source] if options[:source] + params['invoice'] = options[:invoice] if options[:invoice] + params['isTicket'] = options[:is_ticket] if options[:is_ticket] + params['shouldVaultCard'] = options[:should_vault_card] if options[:should_vault_card] + params['sourceZip'] = options[:source_zip] if options[:source_zip] + params['authCode'] = options[:auth_code] if options[:auth_code] + params['achIndicator'] = options[:ach_indicator] if options[:ach_indicator] + params['bankAccount'] = options[:bank_account] if options[:bank_account] + params['meta'] = options[:meta] if options[:meta] + end + + def add_pos_data(params, options) + pos_data = {} + pos_data['cardholderPresence'] = options.dig(:pos_data, :cardholder_presence) || 'Ecom' + pos_data['deviceAttendance'] = options.dig(:pos_data, :device_attendance) || 'HomePc' + pos_data['deviceInputCapability'] = options.dig(:pos_data, :device_input_capability) || 'Unknown' + pos_data['deviceLocation'] = options.dig(:pos_data, :device_location) || 'HomePc' + pos_data['panCaptureMethod'] = options.dig(:pos_data, :pan_capture_method) || 'Manual' + pos_data['partialApprovalSupport'] = options.dig(:pos_data, :partial_approval_support) || 'NotSupported' + pos_data['pinCaptureCapability'] = options.dig(:pos_data, :pin_capture_capability) || 'Incapable' + + params['posData'] = pos_data + end + + def add_purchases_data(params, options) + return unless options[:purchases] + + params['purchases'] = [] + + options[:purchases].each do |purchase| + purchase_object = {} + + purchase_object['name'] = purchase[:name] if purchase[:name] + purchase_object['description'] = purchase[:description] if purchase[:description] + purchase_object['code'] = purchase[:code] if purchase[:code] + purchase_object['unitOfMeasure'] = purchase[:unit_of_measure] if purchase[:unit_of_measure] + purchase_object['unitPrice'] = purchase[:unit_price] if purchase[:unit_price] + purchase_object['quantity'] = purchase[:quantity] if purchase[:quantity] + purchase_object['taxRate'] = purchase[:tax_rate] if purchase[:tax_rate] + purchase_object['taxAmount'] = purchase[:tax_amount] if purchase[:tax_amount] + purchase_object['discountRate'] = purchase[:discount_rate] if purchase[:discount_rate] + purchase_object['discountAmount'] = purchase[:discount_amount] if purchase[:discount_amount] + purchase_object['extendedAmount'] = purchase[:extended_amount] if purchase[:extended_amount] + purchase_object['lineItemId'] = purchase[:line_item_id] if purchase[:line_item_id] + + params['purchases'].append(purchase_object) + end + end + + def add_shipping_data(params, options) + params['shipAmount'] = options[:ship_amount] if options[:ship_amount] + + shipping_country = shipping_country_from(options) + params['shipToCountry'] = shipping_country if shipping_country + + shipping_zip = shipping_zip_from(options) + params['shipToZip'] = shipping_zip if shipping_zip + end + + def shipping_country_from(options) + options[:ship_to_country] || options.dig(:shipping_address, :country) || options.dig(:billing_address, :country) + end + + def shipping_zip_from(options) + options[:ship_to_zip] || options.dig(:shipping_addres, :zip) || options.dig(:billing_address, :zip) + end + + def payment_token(authorization) + return unless authorization + return authorization unless authorization.include?('|') + + authorization.split('|').last + end + + def payment_id(authorization) + return unless authorization + return authorization unless authorization.include?('|') + + authorization.split('|').first + end + + def commit(action, params: '', iid: '', card_number: nil, jwt: '') + response = + begin + case action + when 'void' + parse(ssl_request(:delete, url(action, params, ref_number: iid), nil, request_headers)) + when 'verify' + parse(ssl_get(url(action, params, credit_card_number: card_number), request_verify_headers(jwt))) + when 'get_payment_status', 'create_jwt' + parse(ssl_get(url(action, params, ref_number: iid), request_headers)) + when 'close_batch' + parse(ssl_request(:put, url(action, params, ref_number: iid), nil, request_headers)) + else + parse(ssl_post(url(action, params), post_data(params), request_headers)) + end + rescue ResponseError => e + # currently Priority returns a 404 with no body on certain calls. In those cases we will substitute the response status from response.message + gateway_response = e.response.body.presence || e.response.message + parse(gateway_response) + end + + success = success_from(response, action) + Response.new( + success, + message_from(response), + response, + authorization: success ? authorization_from(response) : nil, + error_code: success || response == '' ? nil : error_from(response), + test: test? + ) + end + + def url(action, params, ref_number: '', credit_card_number: nil) + case action + when 'void' + base_url + "/#{ref_number}?force=true" + when 'verify' + (verify_url + '?search=') + credit_card_number.to_s[0..6] + when 'get_payment_status', 'close_batch' + batch_url + "/#{params}" + when 'create_jwt' + jwt_url + "/#{params}/token" + else + base_url + '?includeCustomerMatches=false&echo=true' + end + end + + def base_url + test? ? test_url : live_url + end + + def verify_url + test? ? self.test_url_verify : self.live_url_verify + end + + def jwt_url + test? ? self.test_url_jwt : self.live_url_jwt + end + + def batch_url + test? ? self.test_url_batch : self.live_url_batch + end + + def handle_response(response) + case response.code.to_i + when 204 + { status: 'Success' }.to_json + when 200...300 + response.body + else + raise ResponseError.new(response) + end + end + + def parse(body) + return {} if body.blank? + + parsed_response = JSON.parse(body) + parsed_response.is_a?(String) ? { 'message' => parsed_response } : parsed_response + rescue JSON::ParserError + message = 'Invalid JSON response received from Priority Gateway. Please contact Priority Gateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{body.inspect})" + { + 'message' => message + } + end + + def success_from(response, action) + return !response['bank'].empty? if action == 'verify' && response['bank'] + + %w[Approved Open Success Settled Voided].include?(response['status']) + end + + def message_from(response) + return response['details'][0] if response['details'] && response['details'][0] + + response['authMessage'] || response['message'] || response['status'] + end + + def authorization_from(response) + [response['id'], response['paymentToken']].join('|') + end + + def error_from(response) + response['errorCode'] || response['status'] + end + + def post_data(params) + params.to_json + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pro_pay.rb b/lib/active_merchant/billing/gateways/pro_pay.rb index dd286ad6fcb..a0dab56600f 100644 --- a/lib/active_merchant/billing/gateways/pro_pay.rb +++ b/lib/active_merchant/billing/gateways/pro_pay.rb @@ -6,10 +6,10 @@ class ProPayGateway < Gateway self.test_url = 'https://xmltest.propay.com/API/PropayAPI.aspx' self.live_url = 'https://epay.propay.com/api/propayapi.aspx' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w[US CA] self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.propay.com/' self.display_name = 'ProPay' @@ -133,12 +133,12 @@ class ProPayGateway < Gateway '99' => 'Generic decline or unable to parse issuer response code' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :cert_str) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) request = build_xml_request do |xml| add_invoice(xml, money, options) add_payment(xml, payment, options) @@ -151,7 +151,7 @@ def purchase(money, payment, options={}) commit(request) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) request = build_xml_request do |xml| add_invoice(xml, money, options) add_payment(xml, payment, options) @@ -164,7 +164,7 @@ def authorize(money, payment, options={}) commit(request) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) request = build_xml_request do |xml| add_invoice(xml, money, options) add_account(xml, options) @@ -175,7 +175,7 @@ def capture(money, authorization, options={}) commit(request) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) request = build_xml_request do |xml| add_invoice(xml, money, options) add_account(xml, options) @@ -186,11 +186,11 @@ def refund(money, authorization, options={}) commit(request) end - def void(authorization, options={}) + def void(authorization, options = {}) refund(nil, authorization, options) end - def credit(money, payment, options={}) + def credit(money, payment, options = {}) request = build_xml_request do |xml| add_invoice(xml, money, options) add_payment(xml, payment, options) @@ -201,7 +201,7 @@ def credit(money, payment, options={}) commit(request) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -253,7 +253,7 @@ def add_recurring(xml, options) end def parse(body) - results = {} + results = {} xml = Nokogiri::XML(body) resp = xml.xpath('//XMLResponse/XMLTrans') resp.children.each do |element| @@ -284,6 +284,7 @@ def success_from(response) def message_from(response) return 'Success' if success_from(response) + message = STATUS_RESPONSE_CODES[response[:status]] message += " - #{TRANSACTION_RESPONSE_CODES[response[:response_code]]}" if response[:response_code] @@ -295,9 +296,7 @@ def authorization_from(response) end def error_code_from(response) - unless success_from(response) - response[:status] - end + response[:status] unless success_from(response) end def build_xml_request diff --git a/lib/active_merchant/billing/gateways/psigate.rb b/lib/active_merchant/billing/gateways/psigate.rb index b987dd1e74b..c383ddd0cd9 100644 --- a/lib/active_merchant/billing/gateways/psigate.rb +++ b/lib/active_merchant/billing/gateways/psigate.rb @@ -38,7 +38,7 @@ class PsigateGateway < Gateway self.test_url = 'https://realtimestaging.psigate.com/xml' self.live_url = 'https://realtime.psigate.com/xml' - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.supported_countries = ['CA'] self.homepage_url = 'http://www.psigate.com/' self.display_name = 'Psigate' @@ -102,11 +102,14 @@ 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, - :test => test?, - :authorization => build_authorization(response), - :avs_result => { :code => response[:avsresult] }, - :cvv_result => response[:cardidresult] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: build_authorization(response), + avs_result: { code: response[:avsresult] }, + cvv_result: response[:cardidresult] ) end @@ -119,7 +122,7 @@ def successful?(response) end def parse(xml) - response = {:message => 'Global Error Receipt', :complete => false} + response = { message: 'Global Error Receipt', complete: false } xml = REXML::Document.new(xml) xml.elements.each('//Result/*') do |node| @@ -144,26 +147,26 @@ def post_data(money, creditcard, options) def parameters(money, creditcard, options = {}) params = { # General order parameters - :StoreID => @options[:login], - :Passphrase => @options[:password], - :TestResult => options[:test_result], - :OrderID => options[:order_id], - :UserID => options[:user_id], - :Phone => options[:phone], - :Fax => options[:fax], - :Email => options[:email], - :TransRefNumber => options[:trans_ref_number], + StoreID: @options[:login], + Passphrase: @options[:password], + TestResult: options[:test_result], + OrderID: options[:order_id], + UserID: options[:user_id], + Phone: options[:phone], + Fax: options[:fax], + Email: options[:email], + TransRefNumber: options[:trans_ref_number], # Credit Card parameters - :PaymentType => 'CC', - :CardAction => options[:CardAction], + PaymentType: 'CC', + CardAction: options[:CardAction], # Financial parameters - :CustomerIP => options[:ip], - :SubTotal => amount(money), - :Tax1 => options[:tax1], - :Tax2 => options[:tax2], - :ShippingTotal => options[:shipping_total], + CustomerIP: options[:ip], + SubTotal: amount(money), + Tax1: options[:tax1], + Tax2: options[:tax2], + ShippingTotal: options[:shipping_total] } if creditcard @@ -172,15 +175,15 @@ def parameters(money, creditcard, options = {}) card_id_code = (creditcard.verification_value.blank? ? nil : '1') params.update( - :CardNumber => creditcard.number, - :CardExpMonth => exp_month, - :CardExpYear => exp_year, - :CardIDCode => card_id_code, - :CardIDNumber => creditcard.verification_value + CardNumber: creditcard.number, + CardExpMonth: exp_month, + CardExpYear: exp_year, + CardIDCode: card_id_code, + CardIDNumber: creditcard.verification_value ) end - if(address = (options[:billing_address] || options[:address])) + if (address = (options[:billing_address] || options[:address])) params[:Bname] = address[:name] || creditcard.name params[:Baddress1] = address[:address1] unless address[:address1].blank? params[:Baddress2] = address[:address2] unless address[:address2].blank? @@ -210,6 +213,7 @@ def message_from(response) return SUCCESS_MESSAGE else return FAILURE_MESSAGE if response[:errmsg].blank? + return response[:errmsg].gsub(/[^\w]/, ' ').split.join(' ').capitalize end end diff --git a/lib/active_merchant/billing/gateways/psl_card.rb b/lib/active_merchant/billing/gateways/psl_card.rb index 77da4a123db..11f20a2a1ad 100644 --- a/lib/active_merchant/billing/gateways/psl_card.rb +++ b/lib/active_merchant/billing/gateways/psl_card.rb @@ -21,7 +21,7 @@ class PslCardGateway < Gateway # American Express, Diners Club, JCB, International Maestro, # Style, Clydesdale Financial Services, Other - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :maestro ] + self.supported_cardtypes = %i[visa master american_express diners_club jcb maestro] self.homepage_url = 'http://www.paymentsolutionsltd.com/' self.display_name = 'PSL Payment Solutions' @@ -183,7 +183,7 @@ def add_address(post, options) address = options[:billing_address] || options[:address] return if address.nil? - post[:QAAddress] = [:address1, :address2, :city, :state].collect { |a| address[a] }.reject(&:blank?).join(' ') + post[:QAAddress] = %i[address1 address2 city state].collect { |a| address[a] }.reject(&:blank?).join(' ') post[:QAPostcode] = address[:zip] end @@ -211,7 +211,7 @@ def add_amount(post, money, dispatch_type, options) def add_purchase_details(post) post[:EchoAmount] = 'YES' - post[:SCBI] = 'YES' # Return information about the transaction + post[:SCBI] = 'YES' # Return information about the transaction post[:MessageType] = MESSAGE_TYPE end @@ -259,11 +259,14 @@ def parse(body) def commit(request) response = parse(ssl_post(self.live_url, post_data(request))) - 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]] } + 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]] } ) end diff --git a/lib/active_merchant/billing/gateways/qbms.rb b/lib/active_merchant/billing/gateways/qbms.rb index 5d37f900bf4..354928930aa 100644 --- a/lib/active_merchant/billing/gateways/qbms.rb +++ b/lib/active_merchant/billing/gateways/qbms.rb @@ -11,16 +11,16 @@ class QbmsGateway < Gateway self.homepage_url = 'http://payments.intuit.com/' self.display_name = 'QuickBooks Merchant Services' self.default_currency = 'USD' - self.supported_cardtypes = [ :visa, :master, :discover, :american_express, :diners_club, :jcb ] - self.supported_countries = [ 'US' ] + self.supported_cardtypes = %i[visa master discover american_express diners_club jcb] + self.supported_countries = ['US'] TYPES = { - :authorize => 'CustomerCreditCardAuth', - :capture => 'CustomerCreditCardCapture', - :purchase => 'CustomerCreditCardCharge', - :refund => 'CustomerCreditCardTxnVoidOrRefund', - :void => 'CustomerCreditCardTxnVoid', - :query => 'MerchantAccountQuery', + authorize: 'CustomerCreditCardAuth', + capture: 'CustomerCreditCardCapture', + purchase: 'CustomerCreditCardCharge', + refund: 'CustomerCreditCardTxnVoidOrRefund', + void: 'CustomerCreditCardTxnVoid', + query: 'MerchantAccountQuery' } # Creates a new QbmsGateway @@ -51,7 +51,7 @@ def initialize(options = {}) # * options -- A hash of optional parameters. # def authorize(money, creditcard, options = {}) - commit(:authorize, money, options.merge(:credit_card => creditcard)) + commit(:authorize, money, options.merge(credit_card: creditcard)) end # Perform a purchase, which is essentially an authorization and capture in a single operation. @@ -63,7 +63,7 @@ def authorize(money, creditcard, options = {}) # * options -- A hash of optional parameters. # def purchase(money, creditcard, options = {}) - commit(:purchase, money, options.merge(:credit_card => creditcard)) + commit(:purchase, money, options.merge(credit_card: creditcard)) end # Captures the funds from an authorized transaction. @@ -74,7 +74,7 @@ def purchase(money, creditcard, options = {}) # * authorization -- The authorization returned from the previous authorize request. # def capture(money, authorization, options = {}) - commit(:capture, money, options.merge(:transaction_id => authorization)) + commit(:capture, money, options.merge(transaction_id: authorization)) end # Void a previous transaction @@ -84,7 +84,7 @@ def capture(money, authorization, options = {}) # * authorization - The authorization returned from the previous authorize request. # def void(authorization, options = {}) - commit(:void, nil, options.merge(:transaction_id => authorization)) + commit(:void, nil, options.merge(transaction_id: authorization)) end # Credit an account. @@ -105,7 +105,7 @@ def credit(money, identification, options = {}) end def refund(money, identification, options = {}) - commit(:refund, money, options.merge(:transaction_id => identification)) + commit(:refund, money, options.merge(transaction_id: identification)) end # Query the merchant account status @@ -142,12 +142,15 @@ def commit(action, money, parameters) response = parse(type, data) message = (response[:status_message] || '').strip - 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) + 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) ) end @@ -167,16 +170,16 @@ def parse(type, body) if status_code != 0 return { - :status_code => status_code, - :status_message => signon.attributes['statusMessage'], + status_code: status_code, + status_message: signon.attributes['statusMessage'] } end response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs") results = { - :status_code => response.attributes['statusCode'].to_i, - :status_message => response.attributes['statusMessage'], + status_code: response.attributes['statusCode'].to_i, + status_message: response.attributes['statusMessage'] } response.elements.each do |e| @@ -195,10 +198,10 @@ def parse(type, body) end def build_request(type, money, parameters = {}) - xml = Builder::XmlMarkup.new(:indent => 0) + xml = Builder::XmlMarkup.new(indent: 0) - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - xml.instruct!(:qbmsxml, :version => API_VERSION) + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') + xml.instruct!(:qbmsxml, version: API_VERSION) xml.tag!('QBMSXML') do xml.tag!('SignonMsgsRq') do diff --git a/lib/active_merchant/billing/gateways/quantum.rb b/lib/active_merchant/billing/gateways/quantum.rb index 4fd43ad07ca..693bcdb9b7a 100644 --- a/lib/active_merchant/billing/gateways/quantum.rb +++ b/lib/active_merchant/billing/gateways/quantum.rb @@ -13,7 +13,7 @@ class QuantumGateway < Gateway self.live_url = self.test_url = 'https://secure.quantumgateway.com/cgi/xml_requester.php' # visa, master, american_express, discover - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars @@ -95,14 +95,14 @@ def build_auth_request(money, creditcard, options) def build_capture_request(money, authorization, options) xml = Builder::XmlMarkup.new add_common_credit_card_info(xml, 'PREVIOUS_SALE') - transaction_id, _ = authorization_parts_from(authorization) + transaction_id, = authorization_parts_from(authorization) add_transaction_id(xml, transaction_id) xml.target! end def build_purchase_request(money, creditcard, options) xml = Builder::XmlMarkup.new - add_common_credit_card_info(xml, @options[:ignore_avs] || @options[:ignore_cvv] ? 'SALES' : 'AUTH_CAPTURE') + add_common_credit_card_info(xml, @options[:ignore_avs] || @options[:ignore_cvv] ? 'SALES' : 'AUTH_CAPTURE') add_address(xml, creditcard, options[:billing_address], options) add_purchase_data(xml, money) add_creditcard(xml, creditcard) @@ -116,7 +116,7 @@ def build_purchase_request(money, creditcard, options) def build_void_request(authorization, options) xml = Builder::XmlMarkup.new add_common_credit_card_info(xml, 'VOID') - transaction_id, _ = authorization_parts_from(authorization) + transaction_id, = authorization_parts_from(authorization) add_transaction_id(xml, transaction_id) xml.target! end @@ -215,11 +215,14 @@ def commit(request, options) authorization = success ? authorization_for(response) : nil end - Response.new(success, message, response, - :test => test?, - :authorization => authorization, - :avs_result => { :code => response[:AVSResponseCode] }, - :cvv_result => response[:CVV2ResponseCode] + Response.new( + success, + message, + response, + test: test?, + authorization: authorization, + avs_result: { code: response[:AVSResponseCode] }, + cvv_result: response[:CVV2ResponseCode] ) end @@ -253,7 +256,7 @@ def parse_element(reply, node) if node.has_elements? node.elements.each { |e| parse_element(reply, e) } else - if node.parent.name =~ /item/ + if /item/.match?(node.parent.name) parent = node.parent.name + (node.parent.attributes['id'] ? '_' + node.parent.attributes['id'] : '') reply[(parent + '_' + node.name).to_sym] = node.text else @@ -270,7 +273,6 @@ def authorization_for(reply) def authorization_parts_from(authorization) authorization.split(/;/) end - end end end diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb index 6759a57aee6..1876d0db3f9 100644 --- a/lib/active_merchant/billing/gateways/quickbooks.rb +++ b/lib/active_merchant/billing/gateways/quickbooks.rb @@ -6,17 +6,14 @@ class QuickbooksGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners] + self.supported_cardtypes = %i[visa master american_express discover diners] self.homepage_url = 'http://payments.intuit.com' self.display_name = 'QuickBooks Payments' - ENDPOINT = '/quickbooks/v4/payments/charges' - OAUTH_ENDPOINTS = { - site: 'https://oauth.intuit.com', - request_token_path: '/oauth/v1/get_request_token', - authorize_url: 'https://appcenter.intuit.com/Connect/Begin', - access_token_path: '/oauth/v1/get_access_token' - } + BASE = '/quickbooks/v4/payments' + ENDPOINT = "#{BASE}/charges" + VOID_ENDPOINT = "#{BASE}/txn-requests" + REFRESH_URI = 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer' # https://developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling @@ -45,13 +42,21 @@ 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 = {}) - requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm) + # Quickbooks is deprecating OAuth 1.0 on December 17, 2019. + # OAuth 2.0 requires a client_id, client_secret, access_token, and refresh_token + # To maintain backwards compatibility, check for the presence of a refresh_token (only specified for OAuth 2.0) + # When present, validate that all OAuth 2.0 options are present + if options[:refresh_token] + requires!(options, :client_id, :client_secret, :access_token, :refresh_token) + else + requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm) + end @options = options super end @@ -62,7 +67,8 @@ def purchase(money, payment, options = {}) add_charge_data(post, payment, options) post[:capture] = 'true' - commit(ENDPOINT, post) + response = commit(ENDPOINT, post) + check_token_response(response, ENDPOINT, post, options) end def authorize(money, payment, options = {}) @@ -71,30 +77,46 @@ def authorize(money, payment, options = {}) add_charge_data(post, payment, options) post[:capture] = 'false' - commit(ENDPOINT, post) + response = commit(ENDPOINT, post) + check_token_response(response, ENDPOINT, post, options) end def capture(money, authorization, options = {}) post = {} - capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture" + authorization, = split_authorization(authorization) post[:amount] = localized_amount(money, currency(money)) add_context(post, options) - commit(capture_uri, post) + response = commit(capture_uri(authorization), post) + check_token_response(response, capture_uri(authorization), post, options) end def refund(money, authorization, options = {}) post = {} post[:amount] = localized_amount(money, currency(money)) add_context(post, options) + authorization, = split_authorization(authorization) - commit(refund_uri(authorization), post) + response = commit(refund_uri(authorization), post) + check_token_response(response, refund_uri(authorization), post, options) + end + + def void(authorization, options = {}) + _, request_id = split_authorization(authorization) + + response = commit(void_uri(request_id)) + check_token_response(response, void_uri(request_id), {}, options) end def verify(credit_card, options = {}) authorize(1.00, credit_card, options) end + def refresh + response = refresh_access_token + response_object(response) + end + def supports_scrubbing? true end @@ -107,7 +129,12 @@ def scrub(transcript) 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((cvc\D+)\d{3}), '\1[FILTERED]'). + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((access_token\\?":\\?")[\w\-\.]+)i, '\1[FILTERED]'). + gsub(%r((refresh_token\\?":\\?")\w+), '\1[FILTERED]'). + gsub(%r((refresh_token=)\w+), '\1[FILTERED]'). + gsub(%r((Authorization: Bearer )[\w\-\.]+)i, '\1[FILTERED]\2') end private @@ -124,8 +151,9 @@ def add_address(post, options) if address = options[:billing_address] || options[:address] card_address[:streetAddress] = address[:address1] card_address[:city] = address[:city] - card_address[:region] = address[:state] || address[:region] - card_address[:country] = address[:country] + region = address[:state] || address[:region] + card_address[:region] = region if region.present? + card_address[:country] = address[:country] if address[:country].present? card_address[:postalCode] = address[:zip] if address[:zip] end post[:card][:address] = card_address @@ -170,30 +198,30 @@ def commit(uri, body = {}, method = :post) # The QuickBooks API returns HTTP 4xx on failed transactions, which causes a # ResponseError raise, so we have to inspect the response and discern between # a legitimate HTTP error and an actual gateway transactional error. - response = begin - case method - when :post - ssl_post(endpoint, post_data(body), headers(:post, endpoint)) - when :get - ssl_request(:get, endpoint, nil, headers(:get, endpoint)) - else - raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" + headers = {} + response = + begin + headers = headers(method, endpoint) + method == :post ? ssl_post(endpoint, post_data(body), headers) : ssl_request(:get, endpoint, nil, headers) + rescue ResponseError => e + extract_response_body_or_raise(e) end - rescue ResponseError => e - extract_response_body_or_raise(e) - end - response_object(response) + response_object(response, headers) end - def response_object(raw_response) + def response_object(raw_response, headers = {}) parsed_response = parse(raw_response) + # Include access_token and refresh_token in params for OAuth 2.0 + parsed_response['access_token'] = @options[:access_token] if @options[:refresh_token] + parsed_response['refresh_token'] = @options[:refresh_token] if @options[:refresh_token] + Response.new( success?(parsed_response), message_from(parsed_response), parsed_response, - authorization: authorization_from(parsed_response), + authorization: authorization_from(parsed_response, headers), test: test?, cvv_result: cvv_code_from(parsed_response), error_code: errors_from(parsed_response), @@ -210,7 +238,10 @@ def post_data(data = {}) end def headers(method, uri) - raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless [:post, :get].include?(method) + return oauth_v2_headers if @options[:refresh_token] + + raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless %i[post get].include?(method) + request_uri = URI.parse(uri) # Following the guidelines from http://nouncer.com/oauth/authentication.html @@ -229,7 +260,7 @@ def headers(method, uri) hmac_signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), oauth_signing_key, oauth_signature_base_string) # append signature to required OAuth parameters - oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.gsub(/\n/, '')) + 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 }] @@ -243,6 +274,45 @@ def headers(method, uri) } end + def oauth_v2_headers + { + 'Content-Type' => 'application/json', + 'Request-Id' => generate_unique_id, + 'Accept' => 'application/json', + 'Authorization' => "Bearer #{@options[:access_token]}" + } + end + + def check_token_response(response, endpoint, body = {}, options = {}) + return response unless @options[:refresh_token] + return response unless options[:allow_refresh] + return response unless response.params['code'] == 'AuthenticationFailed' + + refresh_access_token + commit(endpoint, body) + end + + def refresh_access_token + post = {} + post[:grant_type] = 'refresh_token' + post[:refresh_token] = @options[:refresh_token] + data = post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + + basic_auth = Base64.strict_encode64("#{@options[:client_id]}:#{@options[:client_secret]}") + headers = { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Accept' => 'application/json', + 'Authorization' => "Basic #{basic_auth}" + } + + response = ssl_post(REFRESH_URI, data, headers) + json_response = JSON.parse(response) + + @options[:access_token] = json_response['access_token'] if json_response['access_token'] + @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token'] + response + end + def cvv_code_from(response) if response['errors'].present? FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : '' @@ -254,7 +324,7 @@ def cvv_code_from(response) def success?(response) return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors'] - !['DECLINED', 'CANCELLED'].include?(response['status']) + !%w[DECLINED CANCELLED].include?(response['status']) && !%w[AuthenticationFailed AuthorizationFailed].include?(response['code']) end def message_from(response) @@ -262,11 +332,20 @@ def message_from(response) end def errors_from(response) - response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : '' + if %w[AuthenticationFailed AuthorizationFailed].include?(response['code']) + response['code'] + else + response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : '' + end end - def authorization_from(response) - response['id'] + def authorization_from(response, headers = {}) + [response['id'], headers['Request-Id']].join('|') + end + + def split_authorization(authorization) + authorization, request_id = authorization.split('|') + [authorization, request_id] end def fraud_review_status_from(response) @@ -279,11 +358,20 @@ def extract_response_body_or_raise(response_error) rescue JSON::ParserError raise response_error end + response_error.response.body end def refund_uri(authorization) - "#{ENDPOINT}/#{CGI.escape(authorization)}/refunds" + "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/refunds" + end + + def capture_uri(authorization) + "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/capture" + end + + def void_uri(request_id) + "#{VOID_ENDPOINT}/#{CGI.escape(request_id.to_s)}/void" end end end diff --git a/lib/active_merchant/billing/gateways/quickpay.rb b/lib/active_merchant/billing/gateways/quickpay.rb index 5c1f6fb33bb..c6cd180d2da 100644 --- a/lib/active_merchant/billing/gateways/quickpay.rb +++ b/lib/active_merchant/billing/gateways/quickpay.rb @@ -19,7 +19,6 @@ def self.new(options = {}) QuickpayV10Gateway.new(options) end end - end end end diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb index 930958e0cbb..25078bf9b5f 100644 --- a/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb @@ -1,151 +1,151 @@ module QuickpayCommon MD5_CHECK_FIELDS = { 3 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate - cvd cardtypelock testmode), + authorize: %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate + cvd cardtypelock testmode), - :capture => %w(protocol msgtype merchant amount finalize transaction), + capture: %w(protocol msgtype merchant amount finalize transaction), - :cancel => %w(protocol msgtype merchant transaction), + cancel: %w(protocol msgtype merchant transaction), - :refund => %w(protocol msgtype merchant amount transaction), + refund: %w(protocol msgtype merchant amount transaction), - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode), + subscribe: %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode), - :recurring => %w(protocol msgtype merchant ordernumber amount - currency autocapture transaction), + recurring: %w(protocol msgtype merchant ordernumber amount + currency autocapture transaction), - :status => %w(protocol msgtype merchant transaction), + status: %w(protocol msgtype merchant transaction), - :chstatus => %w(protocol msgtype merchant) + chstatus: %w(protocol msgtype merchant) }, 4 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + authorize: %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :capture => %w(protocol msgtype merchant amount finalize transaction apikey), + capture: %w(protocol msgtype merchant amount finalize transaction apikey), - :cancel => %w(protocol msgtype merchant transaction apikey), + cancel: %w(protocol msgtype merchant transaction apikey), - :refund => %w(protocol msgtype merchant amount transaction apikey), + refund: %w(protocol msgtype merchant amount transaction apikey), - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + subscribe: %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), + recurring: %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), - :status => %w(protocol msgtype merchant transaction apikey), + status: %w(protocol msgtype merchant transaction apikey), - :chstatus => %w(protocol msgtype merchant apikey) + chstatus: %w(protocol msgtype merchant apikey) }, 5 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + authorize: %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :capture => %w(protocol msgtype merchant amount finalize transaction apikey), + capture: %w(protocol msgtype merchant amount finalize transaction apikey), - :cancel => %w(protocol msgtype merchant transaction apikey), + cancel: %w(protocol msgtype merchant transaction apikey), - :refund => %w(protocol msgtype merchant amount transaction apikey), + refund: %w(protocol msgtype merchant amount transaction apikey), - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + subscribe: %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), + recurring: %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), - :status => %w(protocol msgtype merchant transaction apikey), + status: %w(protocol msgtype merchant transaction apikey), - :chstatus => %w(protocol msgtype merchant apikey) + chstatus: %w(protocol msgtype merchant apikey) }, 6 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + authorize: %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :capture => %w(protocol msgtype merchant amount finalize transaction - apikey), + capture: %w(protocol msgtype merchant amount finalize transaction + apikey), - :cancel => %w(protocol msgtype merchant transaction apikey), + cancel: %w(protocol msgtype merchant transaction apikey), - :refund => %w(protocol msgtype merchant amount transaction apikey), + refund: %w(protocol msgtype merchant amount transaction apikey), - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + subscribe: %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), + recurring: %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), - :status => %w(protocol msgtype merchant transaction apikey), + status: %w(protocol msgtype merchant transaction apikey), - :chstatus => %w(protocol msgtype merchant apikey) + chstatus: %w(protocol msgtype merchant apikey) }, 7 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - acquirers cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), + authorize: %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + acquirers cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), - :capture => %w(protocol msgtype merchant amount finalize transaction - apikey), + capture: %w(protocol msgtype merchant amount finalize transaction + apikey), - :cancel => %w(protocol msgtype merchant transaction apikey), + cancel: %w(protocol msgtype merchant transaction apikey), - :refund => %w(protocol msgtype merchant amount transaction apikey), + refund: %w(protocol msgtype merchant amount transaction apikey), - :subscribe => %w(protocol msgtype merchant ordernumber amount currency - cardnumber expirationdate cvd acquirers cardtypelock - description testmode fraud_remote_addr fraud_http_accept - fraud_http_accept_language fraud_http_accept_encoding - fraud_http_accept_charset fraud_http_referer - fraud_http_user_agent apikey), + subscribe: %w(protocol msgtype merchant ordernumber amount currency + cardnumber expirationdate cvd acquirers cardtypelock + description testmode fraud_remote_addr fraud_http_accept + fraud_http_accept_language fraud_http_accept_encoding + fraud_http_accept_charset fraud_http_referer + fraud_http_user_agent apikey), - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), + recurring: %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), - :status => %w(protocol msgtype merchant transaction apikey), + status: %w(protocol msgtype merchant transaction apikey), - :chstatus => %w(protocol msgtype merchant apikey) + chstatus: %w(protocol msgtype merchant apikey) }, 10 => { - :authorize => %w(mobile_number acquirer autofee customer_id extras - zero_auth customer_ip), - :capture => %w( extras ), - :cancel => %w( extras ), - :refund => %w( extras ), - :subscribe => %w( variables branding_id), - :authorize_subscription => %w( mobile_number acquirer customer_ip), - :recurring => %w(auto_capture autofee zero_auth) + authorize: %w(mobile_number acquirer autofee customer_id extras + zero_auth customer_ip), + capture: %w(extras), + cancel: %w(extras), + refund: %w(extras), + subscribe: %w(variables branding_id), + authorize_subscription: %w(mobile_number acquirer customer_ip), + recurring: %w(auto_capture autofee zero_auth) } } @@ -168,9 +168,9 @@ def self.included(base) base.default_currency = 'DKK' base.money_format = :cents - base.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, - :american_express, :diners_club, :jcb, :maestro] - base.supported_countries = ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'] + base.supported_countries = %w[DE DK ES FI FR FO GB IS NO SE] + base.supported_cardtypes = %i[dankort forbrugsforeningen visa master + american_express diners_club jcb maestro] base.homepage_url = 'http://quickpay.net/' base.display_name = 'QuickPay' end diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb index 565890a150e..ce71535e833 100644 --- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb @@ -24,11 +24,11 @@ def purchase(money, credit_card_or_reference, options = {}) r.process { post = authorization_params(money, credit_card_or_reference, options) add_autocapture(post, false) - commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post) + commit(synchronized_path("/payments/#{r.responses.last.params['id']}/authorize"), post) } r.process { post = capture_params(money, credit_card_or_reference, options) - commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/capture"), post) + commit(synchronized_path("/payments/#{r.responses.last.params['id']}/capture"), post) } end end @@ -42,7 +42,7 @@ def authorize(money, credit_card_or_reference, options = {}) r.process { create_payment(money, options) } r.process { post = authorization_params(money, credit_card_or_reference, options) - commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post) + commit(synchronized_path("/payments/#{r.responses.last.params['id']}/authorize"), post) } end end @@ -68,7 +68,7 @@ def refund(money, identification, options = {}) commit(synchronized_path("/payments/#{identification}/refund"), post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -153,9 +153,12 @@ def commit(action, params = {}) response = json_error(response) end - Response.new(success, message_from(success, response), response, - :test => test?, - :authorization => authorization_from(response) + Response.new( + success, + message_from(success, response), + response, + test: test?, + authorization: authorization_from(response) ) end @@ -187,15 +190,11 @@ def add_order_id(post, options) def add_invoice(post, options) add_order_id(post, options) - if options[:billing_address] - post[:invoice_address] = map_address(options[:billing_address]) - end + post[:invoice_address] = map_address(options[:billing_address]) if options[:billing_address] - if options[:shipping_address] - post[:shipping_address] = map_address(options[:shipping_address]) - end + post[:shipping_address] = map_address(options[:shipping_address]) if options[:shipping_address] - [:metadata, :branding_id, :variables].each do |field| + %i[metadata branding_id variables].each do |field| post[field] = options[field] if options[field] end end @@ -208,7 +207,7 @@ def add_additional_params(action, post, options = {}) end def add_credit_card_or_reference(post, credit_card_or_reference, options = {}) - post[:card] ||= {} + post[:card] ||= {} if credit_card_or_reference.is_a?(String) post[:card][:token] = credit_card_or_reference else @@ -219,7 +218,7 @@ def add_credit_card_or_reference(post, credit_card_or_reference, options = {}) end if options[:three_d_secure] - post[:card][:cavv]= options.dig(:three_d_secure, :cavv) + post[:card][:cavv] = options.dig(:three_d_secure, :cavv) post[:card][:eci] = options.dig(:three_d_secure, :eci) post[:card][:xav] = options.dig(:three_d_secure, :xid) end @@ -253,21 +252,22 @@ def invalid_operation_message(response) def map_address(address) return {} if address.nil? + requires!(address, :name, :address1, :city, :zip, :country) country = Country.find(address[:country]) mapped = { - :name => address[:name], - :street => address[:address1], - :city => address[:city], - :region => address[:address2], - :zip_code => address[:zip], - :country_code => country.code(:alpha3).value + name: address[:name], + street: address[:address1], + city: address[:city], + region: address[:address2], + zip_code: address[:zip], + country_code: country.code(:alpha3).value } mapped end def format_order_id(order_id) - truncate(order_id.to_s.gsub(/#/, ''), 20) + truncate(order_id.to_s.delete('#'), 20) end def headers diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb index 810d5cdefaa..c4daca39787 100644 --- a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb @@ -141,6 +141,7 @@ def add_description(post, options) def add_testmode(post) return if post[:transaction].present? + post[:testmode] = test? ? '1' : '0' end @@ -163,9 +164,12 @@ 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, - :test => test?, - :authorization => response[:transaction] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: response[:transaction] ) end diff --git a/lib/active_merchant/billing/gateways/qvalent.rb b/lib/active_merchant/billing/gateways/qvalent.rb index 10b693cd0c5..071bca09b46 100644 --- a/lib/active_merchant/billing/gateways/qvalent.rb +++ b/lib/active_merchant/billing/gateways/qvalent.rb @@ -10,78 +10,87 @@ class QvalentGateway < Gateway self.supported_countries = ['AU'] self.default_currency = 'AUD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners] + self.supported_cardtypes = %i[visa master american_express discover jcb diners] CVV_CODE_MAPPING = { 'S' => 'D' } - def initialize(options={}) - requires!(options, :username, :password, :merchant, :pem, :pem_password) + def initialize(options = {}) + requires!(options, :username, :password, :merchant, :pem) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_order_number(post, options) add_payment_method(post, payment_method) add_verification_value(post, payment_method) + add_stored_credential_data(post, payment_method, options) add_customer_data(post, options) add_soft_descriptors(post, options) + add_customer_reference(post, options) commit('capture', post) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_order_number(post, options) add_payment_method(post, payment_method) add_verification_value(post, payment_method) + add_stored_credential_data(post, payment_method, options) add_customer_data(post, options) add_soft_descriptors(post, options) + add_customer_reference(post, options) commit('preauth', post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization, options) add_customer_data(post, options) add_soft_descriptors(post, options) + add_customer_reference(post, options) commit('captureWithoutAuth', post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization, options) add_customer_data(post, options) add_soft_descriptors(post, options) + post['order.ECI'] = options[:eci] || 'SSL' + add_customer_reference(post, options) commit('refund', post) end # Credit requires the merchant account to be enabled for "Adhoc Refunds" - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) post = {} add_invoice(post, amount, options) add_order_number(post, options) add_payment_method(post, payment_method) add_customer_data(post, options) add_soft_descriptors(post, options) + add_customer_reference(post, options) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization, options) add_customer_data(post, options) add_soft_descriptors(post, options) + add_customer_reference(post, options) commit('reversal', post) end @@ -89,7 +98,7 @@ def void(authorization, options={}) def store(payment_method, options = {}) post = {} add_payment_method(post, payment_method) - add_card_reference(post) + add_card_reference(post, options) commit('registerAccount', post) end @@ -107,7 +116,7 @@ def scrub(transcript) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") } CURRENCY_CODES['AUD'] = 'AUD' CURRENCY_CODES['INR'] = 'INR' @@ -124,7 +133,6 @@ def add_soft_descriptors(post, options) def add_invoice(post, money, options) post['order.amount'] = amount(money) post['card.currency'] = CURRENCY_CODES[options[:currency] || currency(money)] - post['order.ECI'] = options[:eci] || 'SSL' end def add_payment_method(post, payment_method) @@ -134,12 +142,61 @@ def add_payment_method(post, payment_method) post['card.expiryMonth'] = format(payment_method.month, :two_digits) end + def add_stored_credential_data(post, payment_method, options) + post['order.ECI'] = options[:eci] || eci(options) + if (stored_credential = options[:stored_credential]) && %w(visa master).include?(payment_method.brand) + post['card.posEntryMode'] = stored_credential[:initial_transaction] ? 'MANUAL' : 'STORED_CREDENTIAL' + stored_credential_usage(post, payment_method, options) unless stored_credential[:initiator] && stored_credential[:initiator] == 'cardholder' + post['order.authTraceId'] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id] + end + end + + def stored_credential_usage(post, payment_method, options) + return unless payment_method.brand == 'visa' + + stored_credential = options[:stored_credential] + if stored_credential[:reason_type] == 'unscheduled' + if stored_credential[:initiator] == 'merchant' + post['card.storedCredentialUsage'] = 'UNSCHEDULED_MIT' + elsif stored_credential[:initiator] == 'customer' + post['card.storedCredentialUsage'] = 'UNSCHEDULED_CIT' + end + elsif stored_credential[:reason_type] == 'recurring' + post['card.storedCredentialUsage'] = 'RECURRING' + elsif stored_credential[:reason_type] == 'installment' + post['card.storedCredentialUsage'] = 'INSTALLMENT' + end + end + + def eci(options) + if options.dig(:stored_credential, :initial_transaction) + 'SSL' + elsif options.dig(:stored_credential, :initiator) && options[:stored_credential][:initiator] == 'cardholder' + 'MTO' + elsif options.dig(:stored_credential, :reason_type) + case options[:stored_credential][:reason_type] + when 'recurring' + 'REC' + when 'installment' + 'INS' + when 'unscheduled' + 'MTO' + end + else + 'SSL' + end + end + def add_verification_value(post, payment_method) post['card.CVN'] = payment_method.verification_value end - def add_card_reference(post) - post['customer.customerReferenceNumber'] = options[:order_id] + def add_card_reference(post, options) + post['customer.customerReferenceNumber'] = options[:customer_reference_number] || options[:order_id] + end + + def add_customer_reference(post, options) + post['customer.customerReferenceNumber'] = options[:customer_reference_number] if options[:customer_reference_number] end def add_reference(post, authorization, options) @@ -180,13 +237,14 @@ def commit(action, post) def cvv_result(succeeded, raw) return unless succeeded + code = CVV_CODE_MAPPING[raw['response.cvnResponse']] || raw['response.cvnResponse'] CVVResult.new(code) end def headers { - 'Content-Type' => 'application/x-www-form-urlencoded' + 'Content-Type' => 'application/x-www-form-urlencoded' } end @@ -236,7 +294,7 @@ def message_from(succeeded, response) '12' => STANDARD_ERROR_CODE[:card_declined], '06' => STANDARD_ERROR_CODE[:processing_error], '01' => STANDARD_ERROR_CODE[:call_issuer], - '04' => STANDARD_ERROR_CODE[:pickup_card], + '04' => STANDARD_ERROR_CODE[:pickup_card] } def error_code_from(succeeded, response) diff --git a/lib/active_merchant/billing/gateways/rapyd.rb b/lib/active_merchant/billing/gateways/rapyd.rb new file mode 100644 index 00000000000..f3936b7a33e --- /dev/null +++ b/lib/active_merchant/billing/gateways/rapyd.rb @@ -0,0 +1,393 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class RapydGateway < Gateway + self.test_url = 'https://sandboxapi.rapyd.net/v1/' + self.live_url = 'https://api.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 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 = {}) + requires!(options, :secret_key, :access_key) + super + end + + def purchase(money, payment, options = {}) + post = {} + add_auth_purchase(post, money, payment, options) + post[:capture] = true unless payment.is_a?(Check) + + commit(:post, 'payments', post) + end + + def authorize(money, payment, options = {}) + post = {} + add_auth_purchase(post, money, payment, options) + post[:capture] = false unless payment.is_a?(Check) + + commit(:post, 'payments', post) + end + + def capture(money, authorization, options = {}) + post = {} + commit(:post, "payments/#{add_reference(authorization)}/capture", post) + end + + def refund(money, authorization, options = {}) + post = {} + post[:payment] = add_reference(authorization) + add_invoice(post, money, options) + add_metadata(post, options) + add_ewallet(post, options) + + commit(:post, 'refunds', post) + end + + def void(authorization, options = {}) + post = {} + commit(:delete, "payments/#{add_reference(authorization)}", post) + end + + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + + def store(payment, options = {}) + post = {} + add_payment(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, 'store') + add_address(post, payment, options) + commit(:post, 'customers', post) + end + + def unstore(customer) + commit(:delete, "customers/#{add_reference(customer)}", {}) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Access_key: )\w+), '\1[FILTERED]'). + gsub(%r(("number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("account_number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("routing_number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]') + end + + private + + def add_reference(authorization) + return unless authorization + + authorization.split('|')[0] + end + + 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) + end + + def add_address(post, creditcard, options) + return unless address = options[:billing_address] + + post[:address] = {} + # name and line_1 are required at the gateway + post[:address][:name] = address[:name] if address[:name] + post[:address][:line_1] = address[:address1] if address[:address1] + post[:address][:line_2] = address[:address2] if address[:address2] + post[:address][:city] = address[:city] if address[:city] + post[:address][:state] = address[:state] if address[:state] + post[:address][:country] = address[:country] if address[:country] + post[:address][:zip] = address[:zip] if address[:zip] + post[:address][:phone_number] = address[:phone].gsub(/\D/, '') if address[:phone] + end + + 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] + end + + def add_payment(post, payment, options) + if payment.is_a?(CreditCard) + add_creditcard(post, payment, options) + elsif payment.is_a?(Check) + add_ach(post, payment, options) + else + add_tokens(post, payment, options) + end + end + + def add_stored_credential(post, options) + 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] + + 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) + post[:payment_method] = {} + post[:payment_method][:fields] = {} + pm_fields = post[:payment_method][:fields] + + 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[:name] = "#{payment.first_name} #{payment.last_name}" + 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] = {} + + post[:payment_method][:type] = options[:pm_type] + post[:payment_method][:fields][:proof_of_authorization] = options[:proof_of_authorization] + post[:payment_method][:fields][:first_name] = payment.first_name if payment.first_name + post[:payment_method][:fields][:last_name] = payment.last_name if payment.last_name + post[:payment_method][:fields][:routing_number] = payment.routing_number + post[:payment_method][:fields][:account_number] = payment.account_number + post[:payment_method][:fields][:payment_purpose] = options[:payment_purpose] if options[:payment_purpose] + end + + def add_tokens(post, payment, options) + return unless payment.respond_to?(:split) + + 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) + if options[:execute_threed] == true + post[:payment_method_options] = { '3d_required' => true } if options[:force_3d_secure].present? + 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) + post[:metadata] = options[:metadata] if options[:metadata] + end + + def add_ewallet(post, options) + post[:ewallet] = options[:ewallet_id] if options[:ewallet_id] + end + + def add_payment_fields(post, options) + post[:description] = options[:description] if options[:description] + post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor] + end + + 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[:email] = options[:email] unless 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 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[:addresses] = [customer_address] if customer_address + customer_data + end + + 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) + post[:customer] = options[:customer_id] if options[:customer_id] + end + + def parse(body) + return {} if body.empty? || body.nil? + + JSON.parse(body) + 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.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: avs_result(response), + cvv_result: cvv_result(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 + + def headers(rel_path, payload) + salt = SecureRandom.base64(12) + timestamp = Time.new.to_i.to_s + { + 'Content-Type' => 'application/json', + 'access_key' => @options[:access_key], + 'salt' => salt, + 'timestamp' => timestamp, + 'signature' => generate_hmac(rel_path, salt, timestamp, payload) + } + 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 + end + + def avs_result(response) + return nil unless (code = response.dig('data', 'payment_method_data', 'acs_check')) + + AVSResult.new(code: code) + end + + def cvv_result(response) + return nil unless (code = response.dig('data', 'payment_method_data', 'cvv_check')) + + CVVResult.new(code) + end + + def success_from(response) + response.dig('status', 'status') == 'SUCCESS' && response.dig('status', 'error') != 'ERR' + end + + def message_from(response) + case response.dig('status', 'status') + when 'ERROR' + response.dig('status', 'message') == '' ? response.dig('status', 'error_code') : response.dig('status', 'message') + else + response.dig('status', 'status') + end + end + + def authorization_from(response) + id = response.dig('data') ? response.dig('data', 'id') : response.dig('status', 'operation_id') + + "#{id}|#{response.dig('data', 'default_payment_method')}" + end + + def error_code_from(response) + response.dig('status', 'error_code') unless success_from(response) + end + + def handle_response(response) + case response.code.to_i + when 200...300, 400, 401, 404 + response.body + else + raise ResponseError.new(response) + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/reach.rb b/lib/active_merchant/billing/gateways/reach.rb new file mode 100644 index 00000000000..41c2c6c9926 --- /dev/null +++ b/lib/active_merchant/billing/gateways/reach.rb @@ -0,0 +1,284 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ReachGateway < Gateway + self.test_url = 'https://checkout.rch.how/' + self.live_url = 'https://checkout.rch.io/' + + 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] + + self.homepage_url = 'https://www.withreach.com/' + self.display_name = 'Reach' + self.currencies_without_fractions = %w(BIF BYR CLF CLP CVE DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF IDR MGA MRO) + + API_VERSION = 'v2.22'.freeze + STANDARD_ERROR_CODE_MAPPING = {} + PAYMENT_METHOD_MAP = { + american_express: 'AMEX', + cabal: 'CABAL', + check: 'ACH', + dankort: 'DANKORT', + diners_club: 'DINERS', + discover: 'DISC', + elo: 'ELO', + jcb: 'JCB', + maestro: 'MAESTRO', + master: 'MC', + naranja: 'NARANJA', + union_pay: 'UNIONPAY', + visa: 'VISA' + } + + def initialize(options = {}) + requires!(options, :merchant_id, :secret) + super + end + + def authorize(money, payment, options = {}) + request = build_checkout_request(money, payment, options) + add_custom_fields_data(request, options) + add_customer_data(request, options, payment) + add_stored_credentials(request, options) + post = { request: request, card: add_payment(payment, options) } + if options[:stored_credential] + MultiResponse.run(:use_first_response) do |r| + r.process { commit('checkout', post) } + r.process do + r2 = get_network_payment_reference(r.responses[0]) + r.params[:network_transaction_id] = r2.message + r2 + end + end + else + commit('checkout', post) + end + end + + def purchase(money, payment, options = {}) + options[:capture] = true + authorize(money, payment, options) + end + + def capture(money, authorization, options = {}) + post = { request: { MerchantId: @options[:merchant_id], OrderId: authorization } } + commit('capture', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + 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') + end + + def refund(amount, authorization, options = {}) + currency = options[:currency] || currency(options[:amount]) + post = { + request: { + MerchantId: @options[:merchant_id], + OrderId: authorization, + ReferenceId: options[:order_id] || options[:reference_id], + Amount: localized_amount(amount, currency) + } + } + commit('refund', post) + end + + def void(authorization, options = {}) + post = { + request: { + MerchantId: @options[:merchant_id], + OrderId: authorization + } + } + + commit('cancel', post) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + def build_checkout_request(amount, payment, options) + currency = options[:currency] || currency(options[:amount]) + { + MerchantId: @options[:merchant_id], + ReferenceId: options[:order_id], + ConsumerCurrency: currency, + Capture: options[:capture] || false, + PaymentMethod: PAYMENT_METHOD_MAP.fetch(payment.brand.to_sym, 'unsupported'), + Items: [ + Sku: options[:item_sku] || SecureRandom.alphanumeric, + ConsumerPrice: localized_amount(amount, currency), + Quantity: (options[:item_quantity] || 1) + ] + } + end + + def add_payment(payment, options) + ntid = options.dig(:stored_credential, :network_transaction_id) + cvv_or_previos_reference = (ntid ? { PreviousNetworkPaymentReference: ntid } : { VerificationCode: payment.verification_value }) + { + Name: payment.name, + Number: payment.number, + Expiry: { Month: payment.month, Year: payment.year } + }.merge!(cvv_or_previos_reference) + end + + def add_customer_data(request, options, payment) + address = options[:billing_address] || options[:address] + + return if address.blank? + + request[:Consumer] = { + Name: payment.respond_to?(:name) ? payment.name : address[:name], + Email: options[:email], + Address: address[:address1], + City: address[:city], + Country: address[:country] + }.compact + end + + def add_stored_credentials(request, options) + request[:PaymentModel] = payment_model(options) || '' + request[:DeviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint] + end + + def payment_model(options) + stored_credential = options[:stored_credential] + return options[:payment_model] if options[:payment_model] + return 'CIT-One-Time' unless stored_credential + + payment_model_options = { + initial_transaction: { + 'cardholder' => { + 'installment' => 'CIT-Setup-Scheduled', + 'unscheduled' => 'CIT-Setup-Unscheduled-MIT', + 'recurring' => 'CIT-Setup-Unscheduled' + } + }, + no_initial_transaction: { + 'cardholder' => { + 'unscheduled' => 'CIT-Subsequent-Unscheduled' + }, + 'merchant' => { + 'recurring' => 'MIT-Subsequent-Scheduled', + 'unscheduled' => 'MIT-Subsequent-Unscheduled' + } + } + } + initial = stored_credential[:initial_transaction] ? :initial_transaction : :no_initial_transaction + payment_model_options[initial].dig(stored_credential[:initiator], stored_credential[:reason_type]) + end + + def add_custom_fields_data(request, options) + add_shipping_data(request, options) if options[:taxes].present? + request[:RateOfferId] = options[:rate_offer_id] if options[:rate_offer_id].present? + request[:Items] = options[:items] if options[:items].present? + end + + def add_shipping_data(request, options) + request[:Shipping] = { + ConsumerPrice: options[:price], + ConsumerTaxes: options[:taxes], + ConsumerDuty: options[:duty] + } + request[:Consignee] = { + Name: options[:consignee_name], + Address: options[:consignee_address], + City: options[:consignee_city], + Country: options[:consignee_country] + } + end + + def sign_body(body) + Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', @options[:secret].encode('utf-8'), body.encode('utf-8'))) + end + + def parse(body) + hash_response = URI.decode_www_form(body).to_h + hash_response['response'] = JSON.parse(hash_response['response']) + + hash_response + end + + def format_and_sign(post) + post[:request] = post[:request].to_json + post[:card] = post[:card].to_json if post[:card].present? + post[:signature] = sign_body(post[:request]) + post + end + + def get_network_payment_reference(response) + parameters = { request: { MerchantId: @options[:merchant_id], OrderId: response.params['response']['OrderId'] } } + body = post_data format_and_sign(parameters) + + raw_response = ssl_request :post, url('query'), body, {} + response = parse(raw_response) + message = response.dig('response', 'Payment', 'NetworkPaymentReference') + Response.new(true, message, {}) + end + + def commit(action, parameters) + body = post_data format_and_sign(parameters) + raw_response = ssl_post url(action), body + response = parse(raw_response) + + Response.new( + success_from(response), + message_from(response) || '', + response, + authorization: authorization_from(response['response']), + # avs_result: AVSResult.new(code: response['some_avs_response_key']), + # cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(response) + ) + rescue ActiveMerchant::ResponseError => e + Response.new(false, (e.response.body.present? ? e.response.body : e.response.msg), {}, test: test?) + end + + def success_from(response) + response.dig('response', 'Error').blank? + end + + def message_from(response) + success_from(response) ? '' : response.dig('response', 'Error', 'ReasonCode') + end + + def authorization_from(response) + response['OrderId'] + end + + def post_data(params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def error_code_from(response) + response['response']['Error']['Code'] unless success_from(response) + end + + def url(action) + "#{test? ? test_url : live_url}#{API_VERSION}/#{action}" + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/realex.rb b/lib/active_merchant/billing/gateways/realex.rb index ca0b91517b3..d639b04d91a 100644 --- a/lib/active_merchant/billing/gateways/realex.rb +++ b/lib/active_merchant/billing/gateways/realex.rb @@ -31,7 +31,7 @@ class RealexGateway < Gateway self.money_format = :cents self.default_currency = 'EUR' - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club ] + self.supported_cardtypes = %i[visa master american_express diners_club] self.supported_countries = %w(IE GB FR BE NL LU IT US CA ES) self.homepage_url = 'http://www.realexpayments.com/' self.display_name = 'Realex' @@ -42,7 +42,8 @@ class RealexGateway < Gateway def initialize(options = {}) requires!(options, :login, :password) - options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret) + options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options[:rebate_secret].present? + options[:credit_hash] = Digest::SHA1.hexdigest(options[:refund_secret]) if options[:refund_secret].present? super end @@ -70,9 +71,9 @@ def refund(money, authorization, options = {}) commit(request) end - def credit(money, authorization, options = {}) - ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, authorization, options) + def credit(money, creditcard, options = {}) + request = build_credit_request(money, creditcard, options) + commit(request) end def void(authorization, options = {}) @@ -106,8 +107,8 @@ def commit(request) (response[:result] == '00'), message_from(response), response, - :test => (response[:message] =~ %r{\[ test system \]}), - :authorization => authorization_from(response), + test: (response[:message] =~ %r{\[ test system \]}), + authorization: authorization_from(response), avs_result: AVSResult.new(code: response[:avspostcoderesponse]), cvv_result: CVVResult.new(response[:cvnresult]) ) @@ -137,7 +138,7 @@ def authorization_from(parsed) def build_purchase_or_authorization_request(action, money, credit_card, options) timestamp = new_timestamp - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'auth' do add_merchant_details(xml, options) xml.tag! 'orderid', sanitize_order_id(options[:order_id]) @@ -145,7 +146,12 @@ def build_purchase_or_authorization_request(action, money, credit_card, options) add_card(xml, credit_card) xml.tag! 'autosettle', 'flag' => auto_settle_flag(action) add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number) - add_network_tokenization_card(xml, credit_card) if credit_card.is_a?(NetworkTokenizationCreditCard) + if credit_card.is_a?(NetworkTokenizationCreditCard) + add_network_tokenization_card(xml, credit_card) + else + add_three_d_secure(xml, options) + end + add_stored_credential(xml, options) add_comments(xml, options) add_address_and_customer_info(xml, options) end @@ -154,7 +160,7 @@ def build_purchase_or_authorization_request(action, money, credit_card, options) def build_capture_request(money, authorization, options) timestamp = new_timestamp - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do add_merchant_details(xml, options) add_amount(xml, money, options) @@ -167,7 +173,7 @@ def build_capture_request(money, authorization, options) def build_refund_request(money, authorization, options) timestamp = new_timestamp - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'rebate' do add_merchant_details(xml, options) add_transaction_identifiers(xml, authorization, options) @@ -180,9 +186,25 @@ def build_refund_request(money, authorization, options) xml.target! end + def build_credit_request(money, credit_card, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new indent: 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'credit' do + add_merchant_details(xml, options) + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + add_amount(xml, money, options) + add_card(xml, credit_card) + xml.tag! 'refundhash', @options[:credit_hash] if @options[:credit_hash] + xml.tag! 'autosettle', 'flag' => 1 + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number) + end + xml.target! + end + def build_void_request(authorization, options) timestamp = new_timestamp - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'void' do add_merchant_details(xml, options) add_transaction_identifiers(xml, authorization, options) @@ -195,7 +217,7 @@ def build_void_request(authorization, options) # Verify initiates an OTB (Open To Buy) request def build_verify_request(credit_card, options) timestamp = new_timestamp - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'otb' do add_merchant_details(xml, options) xml.tag! 'orderid', sanitize_order_id(options[:order_id]) @@ -209,13 +231,14 @@ def build_verify_request(credit_card, options) def add_address_and_customer_info(xml, options) billing_address = options[:billing_address] || options[:address] shipping_address = options[:shipping_address] + ipv4_address = ipv4?(options[:ip]) ? options[:ip] : nil - return unless billing_address || shipping_address || options[:customer] || options[:invoice] || options[:ip] + return unless billing_address || shipping_address || options[:customer] || options[:invoice] || ipv4_address xml.tag! 'tssinfo' do xml.tag! 'custnum', options[:customer] if options[:customer] xml.tag! 'prodid', options[:invoice] if options[:invoice] - xml.tag! 'custipaddress', options[:ip] if options[:ip] + xml.tag! 'custipaddress', options[:ip] if ipv4_address if billing_address xml.tag! 'address', 'type' => 'billing' do @@ -235,9 +258,7 @@ def add_address_and_customer_info(xml, options) def add_merchant_details(xml, options) xml.tag! 'merchantid', @options[:login] - if options[:account] || @options[:account] - xml.tag! 'account', (options[:account] || @options[:account]) - end + xml.tag! 'account', (options[:account] || @options[:account]) if options[:account] || @options[:account] end def add_transaction_identifiers(xml, authorization, options) @@ -249,6 +270,7 @@ def add_transaction_identifiers(xml, authorization, options) def add_comments(xml, options) return unless options[:description] + xml.tag! 'comments' do xml.tag! 'comment', options[:description], 'id' => 1 end @@ -279,11 +301,46 @@ def add_network_tokenization_card(xml, payment) end xml.tag! 'supplementarydata' do xml.tag! 'item', 'type' => 'mobile' do - xml.tag! 'field01', payment.source.to_s.gsub('_', '-') + xml.tag! 'field01', payment.source.to_s.tr('_', '-') end end end + def add_three_d_secure(xml, options) + return unless three_d_secure = options[:three_d_secure] + + version = three_d_secure.fetch(:version, '') + xml.tag! 'mpi' do + if /^2/.match?(version) + xml.tag! 'authentication_value', three_d_secure[:cavv] + xml.tag! 'ds_trans_id', three_d_secure[:ds_transaction_id] + else + xml.tag! 'cavv', three_d_secure[:cavv] + xml.tag! 'xid', three_d_secure[:xid] + version = '1' + end + xml.tag! 'eci', three_d_secure[:eci] + xml.tag! 'message_version', version + end + end + + def add_stored_credential(xml, options) + return unless stored_credential = options[:stored_credential] + + xml.tag! 'storedcredential' do + xml.tag! 'type', stored_credential_type(stored_credential[:reason_type]) + xml.tag! 'initiator', stored_credential[:initiator] + xml.tag! 'sequence', stored_credential[:initial_transaction] ? 'first' : 'subsequent' + xml.tag! 'srd', stored_credential[:network_transaction_id] + end + end + + def stored_credential_type(reason) + return 'oneoff' if reason == 'unscheduled' + + reason + end + def format_address_code(address) code = [address[:zip].to_s, address[:address1].to_s + address[:address2].to_s] code.collect { |e| e.gsub(/\D/, '') }.reject(&:empty?).join('|') @@ -332,6 +389,12 @@ def message_from(response) def sanitize_order_id(order_id) order_id.to_s.gsub(/[^a-zA-Z0-9\-_]/, '') end + + def ipv4?(ip_address) + return false if ip_address.nil? + + !ip_address.match(/\A\d+\.\d+\.\d+\.\d+\z/).nil? + end end end end diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb index 792b935d557..260c4d7d83f 100644 --- a/lib/active_merchant/billing/gateways/redsys.rb +++ b/lib/active_merchant/billing/gateways/redsys.rb @@ -36,15 +36,15 @@ module Billing #:nodoc: # # class RedsysGateway < Gateway - self.live_url = 'https://sis.sermepa.es/sis/operaciones' + 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 # Not all card types may be activated by the bank! - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express jcb diners_club unionpay] self.homepage_url = 'http://www.redsys.es/' self.display_name = 'Redsys' @@ -91,11 +91,11 @@ class RedsysGateway < Gateway # More operations are supported by the gateway itself, but # are not supported in this library. SUPPORTED_TRANSACTIONS = { - :purchase => 'A', - :authorize => '1', - :capture => '2', - :refund => '3', - :cancel => '9' + purchase: '0', + authorize: '1', + capture: '2', + refund: '3', + cancel: '9' } # These are the text meanings sent back by the acquirer when @@ -126,6 +126,7 @@ class RedsysGateway < Gateway 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', @@ -170,6 +171,10 @@ class RedsysGateway < Gateway 9914 => 'KO Confirmation' } + # Expected values as per documentation + THREE_DS_V1 = '1.0.2' + THREE_DS_V2 = '2.1.0' + # Creates a new instance # # Redsys requires a login and secret_key, and optionally also accepts a @@ -193,67 +198,81 @@ def purchase(money, payment, options = {}) requires!(options, :order_id) data = {} - add_action(data, :purchase) + add_action(data, :purchase, options) add_amount(data, money, options) add_order(data, options[:order_id]) add_payment(data, payment) + add_external_mpi_fields(data, options) + add_three_ds_data(data, options) if options[:execute_threed] + add_stored_credential_options(data, options) data[:description] = options[:description] data[:store_in_vault] = options[:store] + data[:sca_exemption] = options[:sca_exemption] + data[:sca_exemption_direct_payment_enabled] = options[:sca_exemption_direct_payment_enabled] - commit data + commit data, options end def authorize(money, payment, options = {}) requires!(options, :order_id) data = {} - add_action(data, :authorize) + add_action(data, :authorize, options) add_amount(data, money, options) add_order(data, options[:order_id]) add_payment(data, payment) + add_external_mpi_fields(data, options) + add_three_ds_data(data, options) if options[:execute_threed] + add_stored_credential_options(data, options) data[:description] = options[:description] data[:store_in_vault] = options[:store] + data[:sca_exemption] = options[:sca_exemption] + data[:sca_exemption_direct_payment_enabled] = options[:sca_exemption_direct_payment_enabled] - commit data + commit data, options end def capture(money, authorization, options = {}) data = {} add_action(data, :capture) add_amount(data, money, options) - order_id, _, _ = split_authorization(authorization) + order_id, = split_authorization(authorization) add_order(data, order_id) data[:description] = options[:description] - commit data + commit data, options end def void(authorization, options = {}) data = {} add_action(data, :cancel) order_id, amount, currency = split_authorization(authorization) - add_amount(data, amount, :currency => currency) + add_amount(data, amount, currency: currency) add_order(data, order_id) data[:description] = options[:description] - commit data + commit data, options end def refund(money, authorization, options = {}) data = {} add_action(data, :refund) add_amount(data, money, options) - order_id, _, _ = split_authorization(authorization) + order_id, = split_authorization(authorization) add_order(data, order_id) data[:description] = options[:description] - commit data + commit data, options end def verify(creditcard, options = {}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, creditcard, options) } - r.process(:ignore_result) { void(r.authorization, options) } + 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 end @@ -266,8 +285,10 @@ def scrub(transcript) gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r((%3CDS_MERCHANT_PAN%3E)\d+(%3C%2FDS_MERCHANT_PAN%3E))i, '\1[FILTERED]\2'). gsub(%r((%3CDS_MERCHANT_CVV2%3E)\d+(%3C%2FDS_MERCHANT_CVV2%3E))i, '\1[FILTERED]\2'). + gsub(%r((<DS_MERCHANT_PAN>)\d+(</DS_MERCHANT_PAN>))i, '\1[FILTERED]\2'). gsub(%r(()\d+())i, '\1[FILTERED]\2'). gsub(%r(()\d+())i, '\1[FILTERED]\2'). + gsub(%r((<DS_MERCHANT_CVV2>)\d+(</DS_MERCHANT_CVV2>))i, '\1[FILTERED]\2'). gsub(%r((DS_MERCHANT_CVV2)%2F%3E%0A%3C%2F)i, '\1[BLANK]'). gsub(%r((DS_MERCHANT_CVV2)%2F%3E%3C)i, '\1[BLANK]'). gsub(%r((DS_MERCHANT_CVV2%3E)(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2'). @@ -278,8 +299,8 @@ def scrub(transcript) private - def add_action(data, action) - data[:action] = transaction_code(action) + def add_action(data, action, options = {}) + data[:action] = options[:execute_threed].present? ? '0' : transaction_code(action) end def add_amount(data, money, options) @@ -295,6 +316,10 @@ def url test? ? test_url : live_url end + def webservice_url + test? ? 'https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2' : 'https://sis.redsys.es/sis/services/SerClsWSEntradaV2' + end + def add_payment(data, card) if card.is_a?(String) data[:credit_card_token] = card @@ -303,37 +328,120 @@ def add_payment(data, card) year = sprintf('%.4i', card.year) month = sprintf('%.2i', card.month) data[:card] = { - :name => name, - :pan => card.number, - :date => "#{year[2..3]}#{month}", - :cvv => card.verification_value + name: name, + pan: card.number, + date: "#{year[2..3]}#{month}", + cvv: card.verification_value } end end - def commit(data) - parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers)) + def add_external_mpi_fields(data, options) + return unless options[:three_d_secure] + + if options[:three_d_secure][:version] == THREE_DS_V2 + data[:threeDSServerTransID] = options[:three_d_secure][:three_ds_server_trans_id] if options[:three_d_secure][:three_ds_server_trans_id] + data[:dsTransID] = options[:three_d_secure][:ds_transaction_id] if options[:three_d_secure][:ds_transaction_id] + data[:authenticacionValue] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + data[:protocolVersion] = options[:three_d_secure][:version] if options[:three_d_secure][:version] + data[:authenticacionMethod] = options[:authentication_method] if options[:authentication_method] + data[:authenticacionType] = options[:authentication_type] if options[:authentication_type] + data[:authenticacionFlow] = options[:authentication_flow] if options[:authentication_flow] + data[:eci_v2] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + elsif options[:three_d_secure][:version] == THREE_DS_V1 + data[:txid] = options[:three_d_secure][:xid] if options[:three_d_secure][:xid] + data[:cavv] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + data[:eci_v1] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + end end - def headers - { - 'Content-Type' => 'application/x-www-form-urlencoded' - } + def add_stored_credential_options(data, options) + return unless options[:stored_credential] + + case options[:stored_credential][:initial_transaction] + when true + data[:DS_MERCHANT_COF_INI] = 'S' + when false + data[:DS_MERCHANT_COF_INI] = 'N' + data[:DS_MERCHANT_COF_TXNID] = options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + end + + case options[:stored_credential][:reason_type] + when 'recurring' + data[:DS_MERCHANT_COF_TYPE] = 'R' + when 'installment' + data[:DS_MERCHANT_COF_TYPE] = 'I' + when 'unscheduled' + return + end + end + + def add_three_ds_data(data, options) + data[:three_ds_data] = { threeDSInfo: 'CardData' } if options[:execute_threed] == true end - def xml_request_from(data) + def determine_peticion_type(data) + three_ds_info = data.dig(:three_ds_data, :threeDSInfo) + return 'iniciaPeticion' if three_ds_info == 'CardData' + return 'trataPeticion' if three_ds_info == 'AuthenticationData' || + three_ds_info == 'ChallengeResponse' || + data[:sca_exemption] == 'MIT' + end + + def use_webservice_endpoint?(data, options) + options[:use_webservice_endpoint].to_s == 'true' || data[:three_ds_data] || data[:sca_exemption] == 'MIT' + end + + def commit(data, options = {}) + xmlreq = xml_request_from(data, options) + + if use_webservice_endpoint?(data, options) + peticion_type = determine_peticion_type(data) + + request = <<-REQUEST + + + + + + + + + + + REQUEST + parse(ssl_post(webservice_url, request, headers(peticion_type)), peticion_type) + else + parse(ssl_post(url, "entrada=#{CGI.escape(xmlreq)}", headers), peticion_type) + end + end + + def headers(peticion_type = nil) + if peticion_type + { + 'Content-Type' => 'text/xml', + 'SOAPAction' => peticion_type + } + else + { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + end + end + + def xml_request_from(data, options = {}) if sha256_authentication? - build_sha256_xml_request(data) + build_sha256_xml_request(data, options) else - build_sha1_xml_request(data) + build_sha1_xml_request(data, options) end end def build_signature(data) str = data[:amount] + - data[:order_id].to_s + - @options[:login].to_s + - data[:currency] + data[:order_id].to_s + + @options[:login].to_s + + data[:currency] if card = data[:card] str << card[:pan] @@ -351,30 +459,32 @@ def build_signature(data) Digest::SHA1.hexdigest(str) end - def build_sha256_xml_request(data) + def build_sha256_xml_request(data, options = {}) xml = Builder::XmlMarkup.new xml.instruct! xml.REQUEST do - build_merchant_data(xml, data) + build_merchant_data(xml, data, options) xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1' - xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id]) + xml.DS_SIGNATURE sign_request(merchant_data_xml(data, options), data[:order_id]) end xml.target! end - def build_sha1_xml_request(data) - xml = Builder::XmlMarkup.new :indent => 2 - build_merchant_data(xml, data) + def build_sha1_xml_request(data, options = {}) + xml = Builder::XmlMarkup.new indent: 2 + build_merchant_data(xml, data, options) xml.target! end - def merchant_data_xml(data) + def merchant_data_xml(data, options = {}) xml = Builder::XmlMarkup.new - build_merchant_data(xml, data) + build_merchant_data(xml, data, options) xml.target! end - def build_merchant_data(xml, data) + def build_merchant_data(xml, data, options = {}) + # See https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2/wsdl/SerClsWSEntradaV2.wsdl + # (which results from calling #webservice_url + '?WSDL', https://sis-t.redsys.es:25443/sis/services/SerClsWSEntradaV2?WSDL) xml.DATOSENTRADA do # Basic elements xml.DS_Version 0.1 @@ -382,45 +492,109 @@ def build_merchant_data(xml, data) xml.DS_MERCHANT_AMOUNT data[:amount] xml.DS_MERCHANT_ORDER data[:order_id] xml.DS_MERCHANT_TRANSACTIONTYPE data[:action] - xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description] - xml.DS_MERCHANT_TERMINAL @options[:terminal] + if data[:description] && use_webservice_endpoint?(data, options) + xml.DS_MERCHANT_PRODUCTDESCRIPTION CGI.escape(data[:description]) + else + xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description] + end + xml.DS_MERCHANT_TERMINAL options[:terminal] || @options[:terminal] xml.DS_MERCHANT_MERCHANTCODE @options[:login] xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication? + peticion_type = determine_peticion_type(data) if data[:three_ds_data] + if peticion_type == 'iniciaPeticion' && data[:sca_exemption] + xml.DS_MERCHANT_EXCEP_SCA 'Y' + else + xml.DS_MERCHANT_EXCEP_SCA data[:sca_exemption] if data[:sca_exemption] + xml.DS_MERCHANT_DIRECTPAYMENT data[:sca_exemption_direct_payment_enabled] || 'true' if data[:sca_exemption] == 'MIT' + end + # Only when card is present if data[:card] - xml.DS_MERCHANT_TITULAR data[:card][:name] + if data[:card][:name] && use_webservice_endpoint?(data, options) + xml.DS_MERCHANT_TITULAR CGI.escape(data[:card][:name]) + else + xml.DS_MERCHANT_TITULAR data[:card][:name] + end xml.DS_MERCHANT_PAN data[:card][:pan] xml.DS_MERCHANT_EXPIRYDATE data[:card][:date] xml.DS_MERCHANT_CVV2 data[:card][:cvv] xml.DS_MERCHANT_IDENTIFIER 'REQUIRED' if data[:store_in_vault] + + build_merchant_mpi_external(xml, data) + elsif data[:credit_card_token] xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token] xml.DS_MERCHANT_DIRECTPAYMENT 'true' end + + # Set moto flag only if explicitly requested via moto field + # Requires account configuration to be able to use + xml.DS_MERCHANT_DIRECTPAYMENT 'moto' if options.dig(:moto) && options.dig(:metadata, :manual_entry) + + xml.DS_MERCHANT_EMV3DS data[:three_ds_data].to_json if data[:three_ds_data] + + if options[:stored_credential] + 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 - def parse(data) + def build_merchant_mpi_external(xml, data) + return unless data[:txid] || data[:threeDSServerTransID] + + ds_merchant_mpi_external = {} + ds_merchant_mpi_external[:TXID] = data[:txid] if data[:txid] + ds_merchant_mpi_external[:CAVV] = data[:cavv] if data[:cavv] + ds_merchant_mpi_external[:ECI] = data[:eci_v1] if data[:eci_v1] + + ds_merchant_mpi_external[:threeDSServerTransID] = data[:threeDSServerTransID] if data[:threeDSServerTransID] + ds_merchant_mpi_external[:dsTransID] = data[:dsTransID] if data[:dsTransID] + ds_merchant_mpi_external[:authenticacionValue] = data[:authenticacionValue] if data[:authenticacionValue] + ds_merchant_mpi_external[:protocolVersion] = data[:protocolVersion] if data[:protocolVersion] + ds_merchant_mpi_external[:Eci] = data[:eci_v2] if data[:eci_v2] + ds_merchant_mpi_external[:authenticacionMethod] = data[:authenticacionMethod] if data[:authenticacionMethod] + ds_merchant_mpi_external[:authenticacionType] = data[:authenticacionType] if data[:authenticacionType] + ds_merchant_mpi_external[:authenticacionFlow] = data[:authenticacionFlow] if data[:authenticacionFlow] + + xml.DS_MERCHANT_MPIEXTERNAL ds_merchant_mpi_external.to_json unless ds_merchant_mpi_external.empty? + xml.target! + end + + def parse(data, action) params = {} success = false message = '' - options = @options.merge(:test => test?) + options = @options.merge(test: test?) xml = Nokogiri::XML(data) code = xml.xpath('//RETORNOXML/CODIGO').text - if code == '0' + + if code == '0' && xml.xpath('//RETORNOXML/OPERACION').present? op = xml.xpath('//RETORNOXML/OPERACION') op.children.each do |element| params[element.name.downcase.to_sym] = element.text end - if validate_signature(params) message = response_text(params[:ds_response]) options[:authorization] = build_authorization(params) - success = is_success_response?(params[:ds_response]) + success = success_response?(params[:ds_response]) else message = 'Response failed validation check' end + elsif %w[iniciaPeticion trataPeticion].include?(action) + vxml = Nokogiri::XML(data).remove_namespaces!.xpath("//Envelope/Body/#{action}Response/#{action}Return").inner_text + xml = Nokogiri::XML(vxml) + node = (action == 'iniciaPeticion' ? 'INFOTARJETA' : 'OPERACION') + op = xml.xpath("//RETORNOXML/#{node}") + op.children.each do |element| + params[element.name.downcase.to_sym] = element.text + end + message = response_text_3ds(xml, params) + options[:authorization] = build_authorization(params) + success = params.size > 0 && success_response?(params[:ds_response]) else # Some kind of programmer error with the request! message = "#{code} ERROR" @@ -435,14 +609,14 @@ def validate_signature(data) sig.casecmp(data[:ds_signature].to_s).zero? else str = data[:ds_amount] + - data[:ds_order].to_s + - data[:ds_merchantcode] + - data[:ds_currency] + - data[:ds_response] + - data[:ds_cardnumber].to_s + - data[:ds_transactiontype].to_s + - data[:ds_securepayment].to_s + - @options[:secret_key] + data[:ds_order].to_s + + data[:ds_merchantcode] + + data[:ds_currency] + + data[:ds_response] + + data[:ds_cardnumber].to_s + + data[:ds_transactiontype].to_s + + data[:ds_securepayment].to_s + + @options[:secret_key] sig = Digest::SHA1.hexdigest(str) data[:ds_signature].to_s.downcase == sig @@ -461,6 +635,7 @@ def split_authorization(authorization) def currency_code(currency) return currency if currency =~ /^\d+$/ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency] + CURRENCY_CODES[currency] end @@ -471,16 +646,30 @@ def transaction_code(type) def response_text(code) code = code.to_i code = 0 if code < 100 - RESPONSE_TEXTS[code] || 'Unkown code, please check in manual' + RESPONSE_TEXTS[code] || 'Unknown code, please check in manual' end - def is_success_response?(code) + def response_text_3ds(xml, params) + code = xml.xpath('//RETORNOXML/CODIGO').text + message = '' + if code != '0' + message = "#{code} ERROR" + elsif params[:ds_emv3ds] + three_ds_data = JSON.parse(params[:ds_emv3ds]) + message = three_ds_data['threeDSInfo'] + elsif params[:ds_response] + message = response_text(params[:ds_response]) + end + message + end + + def success_response?(code) (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i) end def clean_order_id(order_id) cleansed = order_id.gsub(/[^\da-zA-Z]/, '') - if cleansed =~ /^\d{4}/ + if /^\d{4}/.match?(cleansed) cleansed[0..11] else '%04d%s' % [rand(0..9999), cleansed[0...8]] @@ -501,7 +690,7 @@ def encrypt(key, order_id) cipher = OpenSSL::Cipher.new('DES3') cipher.encrypt - cipher.key = Base64.strict_decode64(key) + cipher.key = Base64.urlsafe_decode64(key) # The OpenSSL default of an all-zeroes ("\\0") IV is used. cipher.padding = 0 @@ -517,11 +706,11 @@ def mac256(key, data) def xml_signed_fields(data) xml_signed_fields = data[:ds_amount] + data[:ds_order] + data[:ds_merchantcode] + - data[:ds_currency] + data[:ds_response] + data[:ds_currency] + data[:ds_response] - if data[:ds_cardnumber] - xml_signed_fields += data[:ds_cardnumber] - end + xml_signed_fields += data[:ds_cardnumber] if data[:ds_cardnumber] + + xml_signed_fields += data[:ds_emv3ds] if data[:ds_emv3ds] xml_signed_fields + data[:ds_transactiontype] + data[:ds_securepayment] end diff --git a/lib/active_merchant/billing/gateways/s5.rb b/lib/active_merchant/billing/gateways/s5.rb index 9f36a91e54e..4dc62423313 100644 --- a/lib/active_merchant/billing/gateways/s5.rb +++ b/lib/active_merchant/billing/gateways/s5.rb @@ -8,7 +8,7 @@ class S5Gateway < Gateway self.supported_countries = ['DK'] self.default_currency = 'EUR' - self.supported_cardtypes = [:visa, :master, :maestro] + self.supported_cardtypes = %i[visa master maestro] self.homepage_url = 'http://www.s5.dk/' self.display_name = 'S5' @@ -22,12 +22,12 @@ class S5Gateway < Gateway 'store' => 'CC.RG' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :sender, :channel, :login, :password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) request = build_xml_request do |xml| add_identification(xml, options) add_payment(xml, money, 'sale', options) @@ -39,7 +39,7 @@ def purchase(money, payment, options={}) commit(request) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) request = build_xml_request do |xml| add_identification(xml, options, authorization) add_payment(xml, money, 'refund', options) @@ -48,7 +48,7 @@ def refund(money, authorization, options={}) commit(request) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) request = build_xml_request do |xml| add_identification(xml, options) add_payment(xml, money, 'authonly', options) @@ -60,7 +60,7 @@ def authorize(money, payment, options={}) commit(request) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) request = build_xml_request do |xml| add_identification(xml, options, authorization) add_payment(xml, money, 'capture', options) @@ -69,7 +69,7 @@ def capture(money, authorization, options={}) commit(request) end - def void(authorization, options={}) + def void(authorization, options = {}) request = build_xml_request do |xml| add_identification(xml, options, authorization) add_payment(xml, nil, 'void', options) @@ -89,7 +89,7 @@ def store(payment, options = {}) commit(request) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -143,6 +143,7 @@ def add_account(xml, payment_method) def add_customer(xml, creditcard, options) return unless creditcard.respond_to?(:number) + address = options[:billing_address] xml.Customer do xml.Contact do @@ -180,7 +181,7 @@ def add_recurrence_mode(xml, options) end def parse(body) - results = {} + results = {} xml = Nokogiri::XML(body) resp = xml.xpath('//Response/Transaction/Identification') resp.children.each do |element| diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb index 8d3cccd3cc6..a4be0c6fcd1 100644 --- a/lib/active_merchant/billing/gateways/safe_charge.rb +++ b/lib/active_merchant/billing/gateways/safe_charge.rb @@ -6,24 +6,35 @@ class SafeChargeGateway < Gateway self.test_url = 'https://process.sandbox.safecharge.com/service.asmx/Process' self.live_url = 'https://process.safecharge.com/service.asmx/Process' - self.supported_countries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'GR', 'ES', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB', 'US'] + self.supported_countries = %w[AT BE BG CY CZ DE DK EE GR ES FI FR GI HK HR HU IE IS IT LI LT LU LV MT MX NL NO PL PT RO SE SG SI SK GB US] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] self.homepage_url = 'https://www.safecharge.com' self.display_name = 'SafeCharge' VERSION = '4.1.0' - def initialize(options={}) + def initialize(options = {}) requires!(options, :client_login_id, :client_password) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = {} - post[:sg_APIType] = 1 if options[:three_d_secure] - trans_type = options[:three_d_secure] ? 'Sale3D' : 'Sale' + + # Determine if 3DS is requested, or there is standard external MPI data + if options[:three_d_secure] + if options[:three_d_secure].is_a?(Hash) + add_external_mpi_data(post, options) + else + post[:sg_APIType] = 1 + trans_type = 'Sale3D' + end + end + + trans_type ||= 'Sale' + add_transaction_data(trans_type, post, money, options) add_payment(post, payment, options) add_customer_details(post, payment, options) @@ -31,8 +42,10 @@ def purchase(money, payment, options={}) commit(post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} + + add_external_mpi_data(post, options) if options[:three_d_secure]&.is_a?(Hash) add_transaction_data('Auth', post, money, options) add_payment(post, payment, options) add_customer_details(post, payment, options) @@ -40,46 +53,50 @@ def authorize(money, payment, options={}) commit(post) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) post = {} auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|') - add_transaction_data('Settle', post, money, options.merge!({currency: original_currency})) + add_transaction_data('Settle', post, money, options.merge!({ currency: original_currency })) post[:sg_AuthCode] = auth post[:sg_TransactionID] = transaction_id post[:sg_CCToken] = token post[:sg_ExpMonth] = exp_month post[:sg_ExpYear] = exp_year + post[:sg_Email] = options[:email] commit(post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|') - add_transaction_data('Credit', post, money, options.merge!({currency: original_currency})) + 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 - def credit(money, payment, options={}) + def credit(money, payment, options = {}) post = {} + add_payment(post, payment, options) add_transaction_data('Credit', post, money, options) - post[:sg_CreditType] = 1 + add_customer_details(post, payment, options) + + options[:unreferenced_refund].to_s == 'true' ? post[:sg_CreditType] = 2 : post[:sg_CreditType] = 1 commit(post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} auth, transaction_id, token, exp_month, exp_year, original_amount, original_currency = authorization.split('|') - add_transaction_data('Void', post, (original_amount.to_f * 100), options.merge!({currency: original_currency})) + add_transaction_data('Void', post, (original_amount.to_f * 100), options.merge!({ currency: original_currency })) post[:sg_CreditType] = 2 post[:sg_AuthCode] = auth post[:sg_TransactionID] = transaction_id @@ -90,11 +107,8 @@ def void(authorization, options={}) commit(post) end - def verify(credit_card, options={}) - MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options) } - end + def verify(credit_card, options = {}) + authorize(0, credit_card, options) end def supports_scrubbing? @@ -111,9 +125,11 @@ def scrub(transcript) private def add_transaction_data(trans_type, post, money, options) + currency = options[:currency] || currency(money) + post[:sg_TransType] = trans_type - post[:sg_Currency] = (options[:currency] || currency(money)) - post[:sg_Amount] = amount(money) + post[:sg_Currency] = currency + post[:sg_Amount] = localized_amount(money, currency) post[:sg_ClientLoginID] = @options[:client_login_id] post[:sg_ClientPassword] = @options[:client_password] post[:sg_ResponseFormat] = '4' @@ -128,15 +144,25 @@ def add_transaction_data(trans_type, post, money, options) post[:sg_Descriptor] = options[:merchant_descriptor] if options[:merchant_descriptor] post[:sg_MerchantPhoneNumber] = options[:merchant_phone_number] if options[:merchant_phone_number] post[:sg_MerchantName] = options[:merchant_name] if options[:merchant_name] + post[:sg_ProductID] = options[:product_id] if options[:product_id] + post[:sg_NotUseCVV] = options[:not_use_cvv].to_s == 'true' ? 1 : 0 unless options[:not_use_cvv].nil? end - def add_payment(post, payment, options={}) - post[:sg_NameOnCard] = payment.name - post[:sg_CardNumber] = payment.number + def add_payment(post, payment, options = {}) post[:sg_ExpMonth] = format(payment.month, :two_digits) post[:sg_ExpYear] = format(payment.year, :two_digits) - post[:sg_CVV2] = payment.verification_value - post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0) + 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) + end end def add_customer_details(post, payment, options) @@ -146,14 +172,25 @@ def add_customer_details(post, payment, options) post[:sg_Address] = address[:address1] if address[:address1] post[:sg_City] = address[:city] if address[:city] post[:sg_State] = address[:state] if address[:state] - post[:sg_Zip] = address[:zip] if address[:zip] - post[:sg_Country] = address[:country] if address[:country] - post[:sg_Phone] = address[:phone] if address[:phone] + post[:sg_Zip] = address[:zip] if address[:zip] + post[:sg_Country] = address[:country] if address[:country] + post[:sg_Phone] = address[:phone] if address[:phone] end post[:sg_Email] = options[:email] end + def add_external_mpi_data(post, options) + post[:sg_ECI] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + post[:sg_CAVV] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + post[:sg_dsTransID] = options[:three_d_secure][:ds_transaction_id] if options[:three_d_secure][:ds_transaction_id] + post[:sg_threeDSProtocolVersion] = options[:three_d_secure][:ds_transaction_id] ? '2' : '1' + post[:sg_Xid] = options[:three_d_secure][:xid] + post[:sg_IsExternalMPI] = 1 + post[:sg_EnablePartialApproval] = options[:is_partial_approval] + post[:sg_challengePreference] = options[:three_d_secure][:challenge_preference] if options[:three_d_secure][:challenge_preference] + end + def parse(xml) response = {} @@ -207,6 +244,7 @@ def success_from(response) def message_from(response) return 'Success' if success_from(response) + response[:reason_codes] || response[:reason] end @@ -240,14 +278,13 @@ def post_data(params) params.map do |key, value| next if value != false && value.blank? + "#{key}=#{CGI.escape(value.to_s)}" end.compact.join('&') end def error_code_from(response) - unless success_from(response) - response[:ex_err_code] || response[:err_code] - end + response[:ex_err_code] || response[:err_code] unless success_from(response) end def underscore(camel_cased_word) diff --git a/lib/active_merchant/billing/gateways/sage.rb b/lib/active_merchant/billing/gateways/sage.rb index 3f3c9a96bcf..25817aa99f9 100644 --- a/lib/active_merchant/billing/gateways/sage.rb +++ b/lib/active_merchant/billing/gateways/sage.rb @@ -7,20 +7,20 @@ class SageGateway < Gateway self.homepage_url = 'Sage Payment Solutions' self.live_url = 'https://www.sagepayments.net/cgi-bin' - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] TRANSACTIONS = { - :purchase => '01', - :authorization => '02', - :capture => '11', - :void => '04', - :credit => '06', - :refund => '10' + purchase: '01', + authorization: '02', + capture: '11', + void: '04', + credit: '06', + refund: '10' } SOURCE_CARD = 'bankcard' - SOURCE_ECHECK = 'virtual_check' + SOURCE_ECHECK = 'virtual_check' def initialize(options = {}) requires!(options, :login, :password) @@ -77,7 +77,7 @@ def credit(money, payment_method, options = {}) commit(:credit, post, source) end - def refund(money, reference, options={}) + def refund(money, reference, options = {}) post = {} add_reference(post, reference) add_transaction_data(post, money, options) @@ -115,7 +115,8 @@ def scrub(transcript) # use the same method as in pay_conex def force_utf8(string) return nil unless string - binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. + + binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') end @@ -214,7 +215,7 @@ def add_invoice(post, options) end def add_reference(post, reference) - ref, _ = reference.to_s.split(';') + ref, = reference.to_s.split(';') post[:T_reference] = ref end @@ -227,7 +228,7 @@ def add_customer_data(post, options) end def add_addresses(post, options) - billing_address = options[:billing_address] || options[:address] || {} + billing_address = options[:billing_address] || options[:address] || {} post[:C_address] = billing_address[:address1] post[:C_city] = billing_address[:city] @@ -259,11 +260,14 @@ def commit(action, params, source) url = url(params, source) response = parse(ssl_post(url, post_data(action, params)), source) - Response.new(success?(response), response[:message], response, - :test => test?, - :authorization => authorization_from(response, source), - :avs_result => { :code => response[:avs_result] }, - :cvv_result => response[:cvv_result] + Response.new( + success?(response), + response[:message], + response, + test: test?, + authorization: authorization_from(response, source), + avs_result: { code: response[:avs_result] }, + cvv_result: response[:cvv_result] ) end @@ -296,7 +300,6 @@ def vault end class SageVault - def initialize(options, gateway) @live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx' @options = options @@ -381,7 +384,10 @@ def commit(action, request) message = success ? 'Succeeded' : 'Failed' end - Response.new(success, message, response, + Response.new( + success, + message, + response, authorization: response[:guid] ) end diff --git a/lib/active_merchant/billing/gateways/sage_pay.rb b/lib/active_merchant/billing/gateways/sage_pay.rb index a3cd5f7b657..0f062f8caab 100644 --- a/lib/active_merchant/billing/gateways/sage_pay.rb +++ b/lib/active_merchant/billing/gateways/sage_pay.rb @@ -13,26 +13,26 @@ class SagePayGateway < Gateway APPROVED = 'OK' TRANSACTIONS = { - :purchase => 'PAYMENT', - :credit => 'REFUND', - :authorization => 'DEFERRED', - :capture => 'RELEASE', - :void => 'VOID', - :abort => 'ABORT', - :store => 'TOKEN', - :unstore => 'REMOVETOKEN', - :repeat => 'REPEAT' + purchase: 'PAYMENT', + credit: 'REFUND', + authorization: 'DEFERRED', + capture: 'RELEASE', + void: 'VOID', + abort: 'ABORT', + store: 'TOKEN', + unstore: 'REMOVETOKEN', + repeat: 'REPEAT' } CREDIT_CARDS = { - :visa => 'VISA', - :master => 'MC', - :delta => 'DELTA', - :maestro => 'MAESTRO', - :american_express => 'AMEX', - :electron => 'UKE', - :diners_club => 'DC', - :jcb => 'JCB' + visa: 'VISA', + master: 'MC', + delta: 'DELTA', + maestro: 'MAESTRO', + american_express: 'AMEX', + electron: 'UKE', + diners_club: 'DC', + jcb: 'JCB' } AVS_CODE = { @@ -69,8 +69,8 @@ class SagePayGateway < Gateway recipient_dob: :FIRecipientDoB } - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :diners_club] - self.supported_countries = ['GB', 'IE'] + self.supported_countries = %w[GB IE] + self.supported_cardtypes = %i[visa master american_express discover jcb maestro diners_club] self.default_currency = 'GBP' self.homepage_url = 'http://www.sagepay.com' @@ -162,7 +162,7 @@ def unstore(token, options = {}) commit(:unstore, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -213,18 +213,18 @@ def add_related_reference(post, identification) def add_amount(post, money, options) currency = options[:currency] || currency(money) - add_pair(post, :Amount, localized_amount(money, currency), :required => true) - add_pair(post, :Currency, currency, :required => true) + add_pair(post, :Amount, localized_amount(money, currency), required: true) + add_pair(post, :Currency, currency, required: true) end def add_currency(post, money, options) currency = options[:currency] || currency(money) - add_pair(post, :Currency, currency, :required => true) + add_pair(post, :Currency, currency, required: true) end # doesn't actually use the currency -- dodgy! def add_release_amount(post, money, options) - add_pair(post, :ReleaseAmount, amount(money), :required => true) + add_pair(post, :ReleaseAmount, amount(money), required: true) end def add_customer_data(post, options) @@ -248,7 +248,7 @@ def add_address(post, options) add_pair(post, :BillingAddress1, truncate(billing_address[:address1], 100)) add_pair(post, :BillingAddress2, truncate(billing_address[:address2], 100)) add_pair(post, :BillingCity, truncate(billing_address[:city], 40)) - add_pair(post, :BillingState, truncate(billing_address[:state], 2)) if is_usa(billing_address[:country]) + add_pair(post, :BillingState, truncate(billing_address[:state], 2)) if usa?(billing_address[:country]) add_pair(post, :BillingCountry, truncate(billing_address[:country], 2)) add_pair(post, :BillingPhone, sanitize_phone(billing_address[:phone])) add_pair(post, :BillingPostCode, truncate(billing_address[:zip], 10)) @@ -261,7 +261,7 @@ def add_address(post, options) add_pair(post, :DeliveryAddress1, truncate(shipping_address[:address1], 100)) add_pair(post, :DeliveryAddress2, truncate(shipping_address[:address2], 100)) add_pair(post, :DeliveryCity, truncate(shipping_address[:city], 40)) - add_pair(post, :DeliveryState, truncate(shipping_address[:state], 2)) if is_usa(shipping_address[:country]) + add_pair(post, :DeliveryState, truncate(shipping_address[:state], 2)) if usa?(shipping_address[:country]) add_pair(post, :DeliveryCountry, truncate(shipping_address[:country], 2)) add_pair(post, :DeliveryPhone, sanitize_phone(shipping_address[:phone])) add_pair(post, :DeliveryPostCode, truncate(shipping_address[:zip], 10)) @@ -269,7 +269,7 @@ def add_address(post, options) end def add_invoice(post, options) - add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), :required => true) + add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), required: true) add_pair(post, :Description, truncate(options[:description] || options[:order_id], 100)) end @@ -286,10 +286,10 @@ def add_payment_method(post, payment_method, options) end def add_credit_card(post, credit_card) - add_pair(post, :CardHolder, truncate(credit_card.name, 50), :required => true) - add_pair(post, :CardNumber, credit_card.number, :required => true) + add_pair(post, :CardHolder, truncate(credit_card.name, 50), required: true) + add_pair(post, :CardNumber, credit_card.number, required: true) - add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), :required => true) + add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), required: true) add_pair(post, :CardType, map_card_type(credit_card)) add_pair(post, :CV2, credit_card.verification_value) @@ -312,11 +312,12 @@ def sanitize_order_id(order_id) def sanitize_phone(phone) return nil unless phone + cleansed = phone.to_s.gsub(/[^0-9+]/, '') truncate(cleansed, 20) end - def is_usa(country) + def usa?(country) truncate(country, 2) == 'US' end @@ -345,14 +346,17 @@ 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, - :test => test?, - :authorization => authorization_from(response, parameters, action), - :avs_result => { - :street_match => AVS_CODE[response['AddressResult']], - :postal_match => AVS_CODE[response['PostCodeResult']], + 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 @@ -361,11 +365,11 @@ def authorization_from(response, params, action) when :store response['Token'] else - [ params[:VendorTxCode], - response['VPSTxId'] || params[:VPSTxId], - response['TxAuthNo'], - response['SecurityKey'] || params[:SecurityKey], - action ].join(';') + [params[:VendorTxCode], + response['VPSTxId'] || params[:VPSTxId], + response['TxAuthNo'], + response['SecurityKey'] || params[:SecurityKey], + action].join(';') end end @@ -379,33 +383,32 @@ def url_for(action) end def build_url(action) - endpoint = case action - when :purchase, :authorization then 'vspdirect-register' - when :store then 'directtoken' - else TRANSACTIONS[action].downcase - end + endpoint = + case action + when :purchase, :authorization then 'vspdirect-register' + when :store then 'directtoken' + else TRANSACTIONS[action].downcase + end "#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp" end def build_simulator_url(action) - endpoint = [ :purchase, :authorization ].include?(action) ? 'VSPDirectGateway.asp' : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx" + endpoint = %i[purchase authorization].include?(action) ? 'VSPDirectGateway.asp' : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx" "#{self.simulator_url}/#{endpoint}" end def message_from(response) - response['Status'] == APPROVED ? 'Success' : (response['StatusDetail'] || 'Unspecified error') # simonr 20080207 can't actually get non-nil blanks, so this is shorter + response['Status'] == APPROVED ? 'Success' : (response['StatusDetail'] || 'Unspecified error') # simonr 20080207 can't actually get non-nil blanks, so this is shorter end def post_data(action, parameters = {}) parameters.update( - :Vendor => @options[:login], - :TxType => TRANSACTIONS[action], - :VPSProtocol => @options.fetch(:protocol_version, '3.00') + Vendor: @options[:login], + TxType: TRANSACTIONS[action], + VPSProtocol: @options.fetch(:protocol_version, '3.00') ) - if(application_id && (application_id != Gateway.application_id)) - parameters.update(:ReferrerID => application_id) - end + parameters.update(ReferrerID: application_id) if application_id && (application_id != Gateway.application_id) parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end @@ -427,7 +430,8 @@ def add_pair(post, key, value, options = {}) def past_purchase_reference?(payment_method) return false unless payment_method.is_a?(String) - payment_method.split(';').last == 'purchase' + + %w(purchase repeat).include?(payment_method.split(';').last) end end end diff --git a/lib/active_merchant/billing/gateways/sallie_mae.rb b/lib/active_merchant/billing/gateways/sallie_mae.rb index 7e15f08e232..fa7f1f9f8c3 100644 --- a/lib/active_merchant/billing/gateways/sallie_mae.rb +++ b/lib/active_merchant/billing/gateways/sallie_mae.rb @@ -7,7 +7,7 @@ class SallieMaeGateway < Gateway self.supported_countries = ['US'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'http://www.salliemae.com/' @@ -120,9 +120,12 @@ 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, - :test => test?, - :authorization => response['refcode'] + Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: response['refcode'] ) end diff --git a/lib/active_merchant/billing/gateways/secure_net.rb b/lib/active_merchant/billing/gateways/secure_net.rb index f3abdf29935..a82a8bc2ec4 100644 --- a/lib/active_merchant/billing/gateways/secure_net.rb +++ b/lib/active_merchant/billing/gateways/secure_net.rb @@ -1,25 +1,24 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class SecureNetGateway < Gateway - API_VERSION = '4.0' TRANSACTIONS = { - :auth_only => '0000', - :auth_capture => '0100', - :prior_auth_capture => '0200', - :void => '0400', - :credit => '0500' + auth_only: '0000', + auth_capture: '0100', + prior_auth_capture: '0200', + void: '0400', + credit: '0500' } XML_ATTRIBUTES = { - 'xmlns' => 'http://gateway.securenet.com/API/Contracts', - 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance' - } + 'xmlns' => 'http://gateway.securenet.com/API/Contracts', + 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance' + } NIL_ATTRIBUTE = { 'i:nil' => 'true' } self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.securenet.com/' self.display_name = 'SecureNet' @@ -28,8 +27,8 @@ class SecureNetGateway < Gateway APPROVED, DECLINED = 1, 2 - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) + CARD_CODE_ERRORS = %w(N S) + AVS_ERRORS = %w(A E N R W Z) def initialize(options = {}) requires!(options, :login, :password) @@ -80,11 +79,14 @@ def commit(request) data = ssl_post(url, xml, 'Content-Type' => 'text/xml') response = parse(data) - 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] + 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] ) end @@ -136,13 +138,9 @@ def add_credit_card(xml, creditcard) end def add_customer_data(xml, options) - if options.has_key? :customer - xml.tag! 'CUSTOMERID', options[:customer] - end + xml.tag! 'CUSTOMERID', options[:customer] if options.has_key? :customer - if options.has_key? :ip - xml.tag! 'CUSTOMERIP', options[:ip] - end + xml.tag! 'CUSTOMERIP', options[:ip] if options.has_key? :ip end def add_address(xml, creditcard, options) @@ -161,7 +159,7 @@ def add_address(xml, creditcard, options) xml.tag! 'FIRSTNAME', creditcard.first_name xml.tag! 'LASTNAME', creditcard.last_name xml.tag! 'PHONE', address[:phone].to_s - xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] + xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] xml.tag! 'ZIP', address[:zip].to_s end end @@ -182,7 +180,7 @@ def add_address(xml, creditcard, options) xml.tag! 'LASTNAME', address[:last_name].to_s end - xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] + xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] xml.tag! 'ZIP', address[:zip].to_s end else @@ -261,7 +259,6 @@ def split_authorization(authorization) def build_authorization(response) [response[:transactionid], response[:transactionamount], response[:last4_digits]].join('|') end - end end end diff --git a/lib/active_merchant/billing/gateways/secure_pay.rb b/lib/active_merchant/billing/gateways/secure_pay.rb index 4cd0c8a63d3..faddf42c301 100644 --- a/lib/active_merchant/billing/gateways/secure_pay.rb +++ b/lib/active_merchant/billing/gateways/secure_pay.rb @@ -17,12 +17,12 @@ class SecurePayGateway < Gateway self.default_currency = 'USD' self.supported_countries = %w(US CA GB AU) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.securepay.com/' self.display_name = 'SecurePay' - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) + CARD_CODE_ERRORS = %w(N S) + AVS_ERRORS = %w(A E N R W Z) AVS_REASON_CODES = %w(27 45) TRANSACTION_ALREADY_ACTIONED = %w(310 311) @@ -56,12 +56,15 @@ def commit(action, money, parameters) message = message_from(response) - Response.new(success?(response), message, response, - :test => test?, - :authorization => response[:transaction_id], - :fraud_review => fraud_review?(response), - :avs_result => { :code => response[:avs_result_code] }, - :cvv_result => response[:card_code] + Response.new( + success?(response), + message, + response, + test: test?, + authorization: response[:transaction_id], + fraud_review: fraud_review?(response), + avs_result: { code: response[:avs_result_code] }, + cvv_result: response[:card_code] ) end @@ -77,14 +80,14 @@ def parse(body) fields = split(body) results = { - :response_code => fields[RESPONSE_CODE].to_i, - :response_reason_code => fields[RESPONSE_REASON_CODE], - :response_reason_text => fields[RESPONSE_REASON_TEXT], - :avs_result_code => fields[AVS_RESULT_CODE], - :transaction_id => fields[TRANSACTION_ID], - :card_code => fields[CARD_CODE_RESPONSE_CODE], - :authorization_code => fields[AUTHORIZATION_CODE], - :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE] + response_code: fields[RESPONSE_CODE].to_i, + response_reason_code: fields[RESPONSE_REASON_CODE], + response_reason_text: fields[RESPONSE_REASON_TEXT], + avs_result_code: fields[AVS_RESULT_CODE], + transaction_id: fields[TRANSACTION_ID], + card_code: fields[CARD_CODE_RESPONSE_CODE], + authorization_code: fields[AUTHORIZATION_CODE], + cardholder_authentication_code: fields[CARDHOLDER_AUTH_CODE] } results end @@ -115,7 +118,7 @@ def add_invoice(post, options) post[:description] = options[:description] end - def add_creditcard(post, creditcard, options={}) + def add_creditcard(post, creditcard, options = {}) post[:card_num] = creditcard.number post[:card_code] = creditcard.verification_value if creditcard.verification_value? post[:exp_date] = expdate(creditcard) @@ -123,7 +126,7 @@ def add_creditcard(post, creditcard, options={}) post[:last_name] = creditcard.last_name end - def add_payment_source(params, source, options={}) + def add_payment_source(params, source, options = {}) add_creditcard(params, source, options) end @@ -137,17 +140,11 @@ def add_customer_data(post, options) post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil end - if options.has_key? :ip - post[:customer_ip] = options[:ip] - end + post[:customer_ip] = options[:ip] if options.has_key? :ip - if options.has_key? :cardholder_authentication_value - post[:cardholder_authentication_value] = options[:cardholder_authentication_value] - end + post[:cardholder_authentication_value] = options[:cardholder_authentication_value] if options.has_key? :cardholder_authentication_value - if options.has_key? :authentication_indicator - post[:authentication_indicator] = options[:authentication_indicator] - end + post[:authentication_indicator] = options[:authentication_indicator] if options.has_key? :authentication_indicator end # x_duplicate_window won't be sent by default, because sending it changes the response. @@ -165,7 +162,7 @@ def add_address(post, options) post[:zip] = address[:zip].to_s post[:city] = address[:city].to_s post[:country] = address[:country].to_s - post[:state] = address[:state].blank? ? 'n/a' : address[:state] + post[:state] = address[:state].blank? ? 'n/a' : address[:state] end if address = options[:shipping_address] @@ -177,16 +174,14 @@ def add_address(post, options) post[:ship_to_zip] = address[:zip].to_s post[:ship_to_city] = address[:city].to_s post[:ship_to_country] = address[:country].to_s - post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] end end def message_from(results) if results[:response_code] == DECLINED return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code]) - if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) - return AVSResult.messages[results[:avs_result_code]] - end + return AVSResult.messages[results[:avs_result_code]] if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) end (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb index 37a2845bbd5..13f37c96254 100644 --- a/lib/active_merchant/billing/gateways/secure_pay_au.rb +++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb @@ -8,14 +8,14 @@ class SecurePayAuGateway < Gateway class_attribute :test_periodic_url, :live_periodic_url - self.test_url = 'https://api.securepay.com.au/test/payment' + self.test_url = 'https://test.api.securepay.com.au/xmlapi/payment' self.live_url = 'https://api.securepay.com.au/xmlapi/payment' self.test_periodic_url = 'https://test.securepay.com.au/xmlapi/periodic' self.live_periodic_url = 'https://api.securepay.com.au/xmlapi/periodic' self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] # The homepage URL of the gateway self.homepage_url = 'http://securepay.com.au' @@ -35,26 +35,26 @@ class SecurePayAuGateway < Gateway # 10 Preauthorise # 11 Preauth Complete (Advice) TRANSACTIONS = { - :purchase => 0, - :authorization => 10, - :capture => 11, - :void => 6, - :refund => 4 + purchase: 0, + authorization: 10, + capture: 11, + void: 6, + refund: 4 } PERIODIC_ACTIONS = { - :add_triggered => 'add', - :remove_triggered => 'delete', - :trigger => 'trigger' + add_triggered: 'add', + remove_triggered: 'delete', + trigger: 'trigger' } PERIODIC_TYPES = { - :add_triggered => 4, - :remove_triggered => nil, - :trigger => nil + add_triggered: 4, + remove_triggered: nil, + trigger: nil } - SUCCESS_CODES = [ '00', '08', '11', '16', '77' ] + SUCCESS_CODES = %w[00 08 11 16 77] def initialize(options = {}) requires!(options, :login, :password) @@ -183,9 +183,12 @@ 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, - :test => test?, - :authorization => authorization_from(response) + Response.new( + success?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(response) ) end @@ -240,9 +243,12 @@ 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, - :test => test?, - :authorization => authorization_from(response) + Response.new( + success?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(response) ) end diff --git a/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/lib/active_merchant/billing/gateways/secure_pay_tech.rb index b6980d4cf99..80f26087b9c 100644 --- a/lib/active_merchant/billing/gateways/secure_pay_tech.rb +++ b/lib/active_merchant/billing/gateways/secure_pay_tech.rb @@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class SecurePayTechGateway < Gateway class SecurePayTechPostData < PostData - self.required_fields = [ :OrderReference, :CardNumber, :CardExpiry, :CardHolderName, :CardType, :MerchantID, :MerchantKey, :Amount, :Currency ] + self.required_fields = %i[OrderReference CardNumber CardExpiry CardHolderName CardType MerchantID MerchantKey Amount Currency] end self.live_url = self.test_url = 'https://tx.securepaytech.com/web/HttpPostPurchase' @@ -21,7 +21,7 @@ class SecurePayTechPostData < PostData self.default_currency = 'NZD' self.supported_countries = ['NZ'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.supported_cardtypes = %i[visa master american_express diners_club] self.homepage_url = 'http://www.securepaytech.com/' self.display_name = 'SecurePayTech' @@ -84,9 +84,12 @@ 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, - :test => test?, - :authorization => response[:merchant_transaction_reference] + Response.new( + response[:result_code] == 1, + message_from(response), + response, + test: test?, + authorization: response[:merchant_transaction_reference] ) end diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb index c59370bad4d..5699451b1eb 100644 --- a/lib/active_merchant/billing/gateways/securion_pay.rb +++ b/lib/active_merchant/billing/gateways/securion_pay.rb @@ -4,12 +4,12 @@ class SecurionPayGateway < Gateway self.test_url = 'https://api.securionpay.com/' self.live_url = 'https://api.securionpay.com/' - self.supported_countries = %w(AL AD AT BY BE BG HR CY CZ RE DK EE IS FI FR DE GI GR HU IS IE IT IL LV LI LT LU - MK MT MD MC NL NO PL PT RO RU MA RS SK SI ES SE CH UA GB KI CI ME) + self.supported_countries = %w(AD BE BG CH CY CZ DE DK EE ES FI FO FR GI GL GR GS GT HR HU IE IS IT LI LR LT + LU LV MC MT MU MV MW NL NO PL RO SE SI) self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.homepage_url = 'https://securionpay.com/' self.display_name = 'SecurionPay' @@ -31,17 +31,17 @@ class SecurionPayGateway < Gateway 'expired_token' => STANDARD_ERROR_CODE[:card_declined] } - def initialize(options={}) + def initialize(options = {}) requires!(options, :secret_key) super end - def purchase(money, payment, options={}) + def purchase(money, payment, options = {}) post = create_post_for_auth_or_purchase(money, payment, options) commit('charges', post, options) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = create_post_for_auth_or_purchase(money, payment, options) post[:captured] = 'false' commit('charges', post, options) @@ -63,7 +63,7 @@ def void(authorization, options = {}) commit("charges/#{CGI.escape(authorization)}/refund", {}, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -133,6 +133,7 @@ def create_post_for_auth_or_purchase(money, payment, options) add_creditcard(post, payment, options) add_customer(post, payment, options) add_customer_data(post, options) + add_external_three_ds(post, options) if options[:email] post[:metadata] = {} post[:metadata][:email] = options[:email] @@ -140,6 +141,40 @@ def create_post_for_auth_or_purchase(money, payment, options) post end + def add_external_three_ds(post, options) + return if options[:three_d_secure].blank? + + post[:threeDSecure] = { + external: { + version: options[:three_d_secure][:version], + authenticationValue: options[:three_d_secure][:cavv], + acsTransactionId: options[:three_d_secure][:acs_transaction_id], + status: options[:three_d_secure][:authentication_response_status], + eci: options[:three_d_secure][:eci] + }.merge(xid_or_ds_trans_id(options[:three_d_secure])) + } + end + + def xid_or_ds_trans_id(three_ds) + if three_ds[:version].to_f >= 2.0 + { dsTransactionId: three_ds[:ds_transaction_id] } + else + { xid: three_ds[:xid] } + end + end + + def validate_three_ds_params(three_ds) + errors = {} + supported_version = %w{1.0.2 2.1.0 2.2.0}.include?(three_ds[:version]) + supported_auth_response = ['Y', 'N', 'U', 'R', 'E', 'A', nil].include?(three_ds[:status]) + + errors[:three_ds_version] = 'ThreeDs version not supported' unless supported_version + errors[:auth_response] = 'Authentication response value not supported' unless supported_auth_response + errors.compact! + + errors.present? ? Response.new(false, 'ThreeDs data is invalid', errors) : nil + end + def add_amount(post, money, options, include_currency = false) currency = (options[:currency] || default_currency) post[:amount] = localized_amount(money, currency) @@ -166,6 +201,7 @@ def add_creditcard(post, creditcard, options) def add_address(post, options) return unless post[:card]&.kind_of?(Hash) + if address = options[:billing_address] post[:card][:addressLine1] = address[:address1] if address[:address1] post[:card][:addressLine2] = address[:address2] if address[:address2] @@ -181,18 +217,36 @@ def parse(body) end def commit(url, parameters = nil, options = {}, method = nil) + if parameters.present? && parameters[:threeDSecure].present? + three_ds_errors = validate_three_ds_params(parameters[:threeDSecure][:external]) + return three_ds_errors if three_ds_errors + 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']), + 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] @@ -214,6 +268,7 @@ def post_data(params) params.map do |key, value| next if value.blank? + if value.is_a?(Hash) h = {} value.each do |k, v| @@ -246,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' => { diff --git a/lib/active_merchant/billing/gateways/shift4.rb b/lib/active_merchant/billing/gateways/shift4.rb new file mode 100644 index 00000000000..ca6feaa5eaf --- /dev/null +++ b/lib/active_merchant/billing/gateways/shift4.rb @@ -0,0 +1,345 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Shift4Gateway < Gateway + self.test_url = 'https://utgapi.shift4test.com/api/rest/v1/' + self.live_url = 'https://utg.shift4api.net/api/rest/v1/' + + self.supported_countries = %w(US CA CU HT DO PR JM TT GP MQ BS BB LC CW AW VC VI GD AG DM KY KN SX TC MF VG BQ AI BL MS) + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://shift4.com' + self.display_name = 'Shift4' + + RECURRING_TYPE_TRANSACTIONS = %w(recurring installment) + TRANSACTIONS_WITHOUT_RESPONSE_CODE = %w(accesstoken add) + SUCCESS_TRANSACTION_STATUS = %w(A) + DISALLOWED_ENTRY_MODE_ACTIONS = %w(capture refund add verify) + URL_POSTFIX_MAPPING = { + 'accesstoken' => 'credentials', + '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) + @client_guid = options[:client_guid] + @auth_token = options[:auth_token] + @access_token = options[:access_token] + super + end + + def purchase(money, payment_method, options = {}) + post = {} + action = 'sale' + + payment_method = get_card_token(payment_method) if payment_method.is_a?(String) + add_datetime(post, options) + add_invoice(post, money, options) + add_clerk(post, options) + add_transaction(post, options) + add_card(action, post, payment_method, options) + add_card_present(post, options) + add_customer(post, payment_method, options) + + commit(action, post, options) + end + + def authorize(money, payment_method, options = {}) + post = {} + action = 'authorization' + + payment_method = get_card_token(payment_method) if payment_method.is_a?(String) + add_datetime(post, options) + add_invoice(post, money, options) + add_clerk(post, options) + add_transaction(post, options) + add_card(action, post, payment_method, options) + add_card_present(post, options) + add_customer(post, payment_method, options) + + commit(action, post, options) + end + + def capture(money, authorization, options = {}) + post = {} + action = 'capture' + options[:invoice] = get_invoice(authorization) + + add_datetime(post, options) + add_invoice(post, money, options) + add_clerk(post, options) + add_transaction(post, options) + add_card(action, post, get_card_token(authorization), options) + + commit(action, post, options) + end + + def refund(money, payment_method, options = {}) + post = {} + action = 'refund' + + add_datetime(post, options) + add_invoice(post, money, options) + add_clerk(post, options) + add_transaction(post, 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) + end + + def verify(credit_card, options = {}) + post = {} + action = 'verify' + post[:transaction] = {} + + add_datetime(post, options) + add_card(action, post, credit_card, options) + add_customer(post, credit_card, options) + add_card_on_file(post[:transaction], options) + + commit(action, post, options) + end + + def store(credit_card, options = {}) + post = {} + action = 'add' + + add_datetime(post, options) + add_card(action, post, credit_card, options) + add_customer(post, credit_card, options) + + commit(action, post, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(("Number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + 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]') + end + + def setup_access_token + post = {} + add_credentials(post, options) + 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 + + private + + def add_credentials(post, options) + post[:credential] = {} + post[:credential][:clientGuid] = @client_guid + post[:credential][:authToken] = @auth_token + end + + def add_clerk(post, options) + post[:clerk] = {} + post[:clerk][:numericId] = options[:clerk_id] || '1' + end + + def add_invoice(post, money, options) + post[:amount] = {} + post[:amount][:total] = amount(money.to_f) + post[:amount][:tax] = options[:tax].to_f || 0.0 + end + + def add_datetime(post, options) + post[:dateTime] = options[:date_time] || current_date_time(options) + end + + 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) + end + + def add_card(action, post, payment_method, options) + post[:card] = {} + post[:card][:entryMode] = options[:entry_mode] || 'M' unless DISALLOWED_ENTRY_MODE_ACTIONS.include?(action) + if payment_method.is_a?(CreditCard) + post[:card][:expirationDate] = "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}" + post[:card][:number] = payment_method.number + post[:card][:securityCode] = {} + post[:card][:securityCode][:indicator] = 1 + post[:card][:securityCode][:value] = payment_method.verification_value + else + post[:card] = {} if post[:card].nil? + post[:card][:token] = {} + post[:card][:token][:value] = payment_method + post[:card][:expirationDate] = options[:expiration_date] if options[:expiration_date] + end + end + + def add_card_present(post, options) + post[:card] = {} unless post[:card].present? + + post[:card][:present] = options[:card_present] || 'N' + end + + def add_customer(post, card, options) + address = options[:billing_address] || {} + + post[:customer] = {} + post[:customer][:addressLine1] = address[:address1] if address[:address1] + post[:customer][:postalCode] = address[:zip] if address[:zip] && !address[:zip]&.to_s&.empty? + post[:customer][:firstName] = card.first_name if card.is_a?(CreditCard) && card.first_name + post[:customer][:lastName] = card.last_name if card.is_a?(CreditCard) && card.last_name + post[:customer][:emailAddress] = options[:email] if options[:email] + post[:customer][:ipAddress] = options[:ip] if options[:ip] + end + + def add_purchase_card(post, options) + return unless options[:customer_reference] || options[:destination_postal_code] || options[:product_descriptors] + + post[:purchaseCard] = {} + post[:purchaseCard][:customerReference] = options[:customer_reference] if options[:customer_reference] + post[:purchaseCard][:destinationPostalCode] = options[:destination_postal_code] if options[:destination_postal_code] + post[:purchaseCard][:productDescriptors] = options[:product_descriptors] if options[:product_descriptors] + end + + def add_card_on_file(post, options) + return unless options[:stored_credential] || options[:usage_indicator] || options[:indicator] || options[:scheduled_indicator] || options[:transaction_id] + + stored_credential = options[:stored_credential] || {} + post[:cardOnFile] = {} + post[:cardOnFile][:usageIndicator] = options[:usage_indicator] || (stored_credential[:initial_transaction] ? '01' : '02') + post[:cardOnFile][:indicator] = options[:indicator] || '01' + post[:cardOnFile][:scheduledIndicator] = options[:scheduled_indicator] || (RECURRING_TYPE_TRANSACTIONS.include?(stored_credential[:reason_type]) ? '01' : '02') + post[:cardOnFile][:transactionId] = options[:transaction_id] || stored_credential[:network_transaction_id] if options[:transaction_id] || stored_credential[:network_transaction_id] + end + + def commit(action, parameters, option) + url_postfix = URL_POSTFIX_MAPPING[action] || 'transactions' + url = (test? ? "#{test_url}#{url_postfix}/#{action}" : "#{live_url}#{url_postfix}/#{action}") + if action == 'invoice' + response = parse(ssl_request(:delete, url, parameters.to_json, request_headers(action, option))) + else + response = parse(ssl_post(url, parameters.to_json, request_headers(action, option))) + end + + 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 handle_response(response) + case response.code.to_i + when 200...300, 400, 401, 500 + response.body + else + raise ResponseError.new(response) + end + end + + def parse(body) + return {} if body == '' + + JSON.parse(body) + end + + def message_from(action, response) + success_from(action, response) ? 'Transaction successful' : (error(response)&.dig('longText') || 'Transaction declined') + end + + def error_code_from(action, response) + return unless success_from(action, response) + + STANDARD_ERROR_CODE_MAPPING[response['primaryCode']] + end + + def authorization_from(action, response) + return unless success_from(action, response) + + authorization = response.dig('result', 0, 'card', 'token', 'value').to_s + invoice = response.dig('result', 0, 'transaction', 'invoice') + authorization += "|#{invoice}" if invoice + authorization + end + + def get_card_token(authorization) + authorization.is_a?(CreditCard) ? authorization : authorization.split('|')[0] + end + + def get_invoice(authorization) + authorization.is_a?(CreditCard) ? authorization : authorization.split('|')[1] + end + + def request_headers(action, options) + headers = { + 'Content-Type' => 'application/json' + } + headers['AccessToken'] = @access_token + headers['Invoice'] = options[:invoice] if action != 'capture' && options[:invoice].present? + headers['InterfaceVersion'] = '1' + headers['InterfaceName'] = 'Spreedly' + headers['CompanyName'] = 'Spreedly' + headers + end + + def success_from(action, response) + success = error(response).nil? + success &&= SUCCESS_TRANSACTION_STATUS.include?(response['result'].first['transaction']['responseCode']) unless TRANSACTIONS_WITHOUT_RESPONSE_CODE.include?(action) + success + end + + def error(response) + server_error = { 'longText' => response['error'] } if response['error'] + server_error || response['result'].first['error'] + end + + def current_date_time(options = {}) + time_zone = options[:merchant_time_zone] || 'Pacific Time (US & Canada)' + time = Time.now.in_time_zone(time_zone) + offset = Time.now.in_time_zone(time_zone).formatted_offset + + time.strftime('%Y-%m-%dT%H:%M:%S.%3N') + offset + end + end + end +end 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..f06208c2883 --- /dev/null +++ b/lib/active_merchant/billing/gateways/shift4_v2.rb @@ -0,0 +1,58 @@ +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 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 + end + end +end diff --git a/lib/active_merchant/billing/gateways/simetrik.rb b/lib/active_merchant/billing/gateways/simetrik.rb new file mode 100644 index 00000000000..f3b0863eef8 --- /dev/null +++ b/lib/active_merchant/billing/gateways/simetrik.rb @@ -0,0 +1,374 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SimetrikGateway < Gateway + self.test_url = 'https://payments.sta.simetrik.com/v1' + self.live_url = 'https://payments.simetrik.com/v1' + + class_attribute :test_auth_url, :live_auth_url, :test_audience, :live_audience + self.test_auth_url = 'https://tenant-payments-dev.us.auth0.com/oauth/token' + self.live_auth_url = 'https://tenant-payments-prod.us.auth0.com/oauth/token' + + self.test_audience = 'https://tenant-payments-dev.us.auth0.com/api/v2/' + self.live_audience = 'https://tenant-payments-prod.us.auth0.com/api/v2/' + + self.supported_countries = %w(PE AR) + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express discover] + + self.homepage_url = 'https://www.simetrik.com' + self.display_name = 'Simetrik' + + STANDARD_ERROR_CODE_MAPPING = { + 'R101' => STANDARD_ERROR_CODE[:incorrect_number], + 'R102' => STANDARD_ERROR_CODE[:invalid_number], + 'R103' => STANDARD_ERROR_CODE[:invalid_expiry_date], + 'R104' => STANDARD_ERROR_CODE[:invalid_cvc], + 'R105' => STANDARD_ERROR_CODE[:expired_card], + 'R106' => STANDARD_ERROR_CODE[:incorrect_cvc], + 'R107' => STANDARD_ERROR_CODE[:incorrect_pin], + 'R201' => STANDARD_ERROR_CODE[:incorrect_zip], + 'R202' => STANDARD_ERROR_CODE[:incorrect_address], + 'R301' => STANDARD_ERROR_CODE[:card_declined], + 'R302' => STANDARD_ERROR_CODE[:processing_error], + 'R303' => STANDARD_ERROR_CODE[:call_issuer], + 'R304' => STANDARD_ERROR_CODE[:pick_up_card], + 'R305' => STANDARD_ERROR_CODE[:processing_error], + 'R306' => STANDARD_ERROR_CODE[:processing_error], + 'R307' => STANDARD_ERROR_CODE[:processing_error], + 'R401' => STANDARD_ERROR_CODE[:config_error], + 'R402' => STANDARD_ERROR_CODE[:test_mode_live_card], + 'R403' => STANDARD_ERROR_CODE[:unsupported_feature] + + } + + def initialize(options = {}) + requires!(options, :client_id, :client_secret) + super + @access_token = options[:access_token] || {} + sign_access_token() + end + + def authorize(money, payment, options = {}) + requires!(options, :token_acquirer) + + post = {} + add_forward_route(post, options) + add_forward_payload(post, money, payment, options) + add_stored_credential(post, options) + commit('authorize', post, { token_acquirer: options[:token_acquirer] }) + end + + def capture(money, authorization, options = {}) + requires!(options, :token_acquirer) + post = { + forward_payload: { + amount: { + total_amount: amount(money).to_f, + currency: (options[:currency] || currency(money)) + }, + transaction: { + id: authorization + }, + acquire_extra_options: options[:acquire_extra_options] || {} + } + } + post[:forward_payload][:amount][:vat] = options[:vat].to_f / 100 if options[:vat] + + add_forward_route(post, options) + commit('capture', post, { token_acquirer: options[:token_acquirer] }) + end + + def refund(money, authorization, options = {}) + requires!(options, :token_acquirer) + post = { + forward_payload: { + amount: { + total_amount: amount(money).to_f, + currency: (options[:currency] || currency(money)) + }, + transaction: { + id: authorization + }, + acquire_extra_options: options[:acquire_extra_options] || {} + } + } + post[:forward_payload][:transaction][:comment] = options[:comment] if options[:comment] + + add_forward_route(post, options) + commit('refund', post, { token_acquirer: options[:token_acquirer] }) + end + + def void(authorization, options = {}) + requires!(options, :token_acquirer) + post = { + forward_payload: { + transaction: { + id: authorization + }, + acquire_extra_options: options[:acquire_extra_options] || {} + } + } + add_forward_route(post, options) + commit('void', post, { token_acquirer: options[:token_acquirer] }) + end + + def purchase(money, payment, options = {}) + requires!(options, :token_acquirer) + + post = {} + add_forward_route(post, options) + add_forward_payload(post, money, payment, options) + + add_stored_credential(post, options) + commit('charge', post, { token_acquirer: options[:token_acquirer] }) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Bearer ).+), '\1[FILTERED]'). + gsub(%r(("client_secret\\?":\\?")\w+), '\1[FILTERED]'). + gsub(%r(("number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("security_code\\?":\\?")\d+), '\1[FILTERED]') + end + + private + + def add_forward_route(post, options) + forward_route = {} + forward_route[:trace_id] = options[:trace_id] if options[:trace_id] + + forward_route[:psp_extra_fields] = options[:psp_extra_fields] || {} + post[:forward_route] = forward_route + end + + def add_forward_payload(post, money, payment, options) + forward_payload = {} + add_user(forward_payload, options[:user]) if options[:user] + add_order(forward_payload, money, options) + add_payment_method(forward_payload, payment, options[:payment_method]) if options[:payment_method] || payment + + forward_payload[:payment_method] = {} unless forward_payload[:payment_method] + forward_payload[:payment_method][:card] = {} unless forward_payload[:payment_method][:card] + add_address('billing_address', forward_payload[:payment_method][:card], options[:billing_address]) if options[:billing_address] + + add_three_ds_fields(forward_payload[:authentication] = {}, options[:three_ds_fields]) if options[:three_ds_fields] + add_sub_merchant(forward_payload, options[:sub_merchant]) if options[:sub_merchant] + forward_payload[:acquire_extra_options] = options[:acquire_extra_options] || {} + post[:forward_payload] = forward_payload + end + + def add_sub_merchant(post, sub_merchant_options) + sub_merchant = {} + sub_merchant[:merchant_id] = sub_merchant_options[:merchant_id] if sub_merchant_options[:merchant_id] + sub_merchant[:extra_params] = sub_merchant_options[:extra_params] if sub_merchant_options[:extra_params] + sub_merchant[:mcc] = sub_merchant_options[:mcc] if sub_merchant_options[:mcc] + sub_merchant[:name] = sub_merchant_options[:name] if sub_merchant_options[:name] + sub_merchant[:address] = sub_merchant_options[:address] if sub_merchant_options[:address] + sub_merchant[:postal_code] = sub_merchant_options[:postal_code] if sub_merchant_options[:postal_code] + sub_merchant[:url] = sub_merchant_options[:url] if sub_merchant_options[:url] + sub_merchant[:phone_number] = sub_merchant_options[:phone_number] if sub_merchant_options[:phone_number] + + post[:sub_merchant] = sub_merchant + end + + def add_payment_method(post, payment, payment_method_options) + payment_method = {} + opts = nil + opts = payment_method_options[:card] if payment_method_options + add_card(payment_method, payment, opts) if opts || payment + + post[:payment_method] = payment_method + end + + def add_three_ds_fields(post, three_ds_options) + three_ds = {} + three_ds[:version] = three_ds_options[:version] if three_ds_options[:version] + three_ds[:eci] = three_ds_options[:eci] if three_ds_options[:eci] + three_ds[:cavv] = three_ds_options[:cavv] if three_ds_options[:cavv] + three_ds[:ds_transaction_id] = three_ds_options[:ds_transaction_id] if three_ds_options[:ds_transaction_id] + three_ds[:acs_transaction_id] = three_ds_options[:acs_transaction_id] if three_ds_options[:acs_transaction_id] + three_ds[:xid] = three_ds_options[:xid] if three_ds_options[:xid] + three_ds[:enrolled] = three_ds_options[:enrolled] if three_ds_options[:enrolled] + three_ds[:cavv_algorithm] = three_ds_options[:cavv_algorithm] if three_ds_options[:cavv_algorithm] + three_ds[:directory_response_status] = three_ds_options[:directory_response_status] if three_ds_options[:directory_response_status] + three_ds[:authentication_response_status] = three_ds_options[:authentication_response_status] if three_ds_options[:authentication_response_status] + three_ds[:three_ds_server_trans_id] = three_ds_options[:three_ds_server_trans_id] if three_ds_options[:three_ds_server_trans_id] + + post[:three_ds_fields] = three_ds + end + + def add_card(post, card, card_options = {}) + card_hash = {} + card_hash[:number] = card.number + card_hash[:exp_month] = card.month + card_hash[:exp_year] = card.year + card_hash[:security_code] = card.verification_value + card_hash[:type] = card.brand + card_hash[:holder_first_name] = card.first_name + card_hash[:holder_last_name] = card.last_name + post[:card] = card_hash + end + + def add_user(post, user_options) + user = {} + user[:id] = user_options[:id] if user_options[:id] + user[:email] = user_options[:email] if user_options[:email] + + post[:user] = user + end + + def add_stored_credential(post, options) + return unless options[:stored_credential] + + check_initiator = %w[merchant cardholder].any? { |item| item == options[:stored_credential][:initiator] } + check_reason_type = %w[recurring installment unscheduled].any? { |item| item == options[:stored_credential][:reason_type] } + post[:forward_payload][:authentication] = {} unless post[:forward_payload].key?(:authentication) + post[:forward_payload][:authentication][:stored_credential] = options[:stored_credential] if check_initiator && check_reason_type + end + + def add_order(post, money, options) + return unless options[:order] || money + + order = {} + order_options = options[:order] || {} + order[:id] = options[:order_id] if options[:order_id] + order[:description] = options[:description] if options[:description] + order[:installments] = order_options[:installments].to_i if order_options[:installments] + order[:datetime_local_transaction] = order_options[:datetime_local_transaction] if order_options[:datetime_local_transaction] + + add_amount(order, money, options) + add_address('shipping_address', order, options) + + post[:order] = order + end + + def add_amount(post, money, options) + amount_obj = {} + amount_obj[:total_amount] = amount(money).to_f + amount_obj[:currency] = (options[:currency] || currency(money)) + amount_obj[:vat] = options[:vat].to_f / 100 if options[:vat] + + post[:amount] = amount_obj + end + + def add_address(tag, post, options) + return unless address_options = options[:shipping_address] + + address = {} + address[:name] = address_options[:name] if address_options[:name] + address[:address1] = address_options[:address1] if address_options[:address1] + address[:address2] = address_options[:address2] if address_options[:address2] + address[:company] = address_options[:company] if address_options[:company] + address[:city] = address_options[:city] if address_options[:city] + address[:state] = address_options[:state] if address_options[:state] + address[:zip] = address_options[:zip] if address_options[:zip] + address[:country] = address_options[:country] if address_options[:country] + address[:phone] = address_options[:phone] if address_options[:phone] + address[:fax] = address_options[:fax] if address_options[:fax] + + post[tag] = address + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters, url_params = {}) + begin + response = JSON.parse ssl_post(url(action, url_params), post_data(parameters), authorized_headers()) + rescue ResponseError => e + case e.response.code.to_i + when 400...499 + response = JSON.parse e.response.body + else + raise e + end + end + + Response.new( + success_from(response['code']), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: avs_code_from(response)), + cvv_result: CVVResult.new(cvv_code_from(response)), + test: test?, + error_code: error_code_from(response) + ) + end + + def avs_code_from(response) + response['avs_result'] + end + + def cvv_code_from(response) + response['cvv_result'] + end + + def success_from(code) + code == 'S001' + end + + def message_from(response) + response['message'] + end + + def url(action, url_params) + "#{(test? ? test_url : live_url)}/#{url_params[:token_acquirer]}/#{action}" + end + + def post_data(data = {}) + data.to_json + end + + def authorization_from(response) + response['simetrik_authorization_id'] + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['code']] unless success_from(response['code']) + end + + def authorized_headers + { + 'content-Type' => 'application/json', + 'Authorization' => "Bearer #{sign_access_token()}" + } + end + + # if this method is refactored, ensure that the client_secret is properly scrubbed + def sign_access_token + fetch_access_token() if Time.new.to_i > (@access_token[:expires_at] || 0) + 10 + @access_token[:access_token] + end + + def auth_url + (test? ? test_auth_url : live_auth_url) + end + + def fetch_access_token + login_info = {} + login_info[:client_id] = @options[:client_id] + login_info[:client_secret] = @options[:client_secret] + login_info[:audience] = test? ? test_audience : live_audience + login_info[:grant_type] = 'client_credentials' + + 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 +end diff --git a/lib/active_merchant/billing/gateways/skip_jack.rb b/lib/active_merchant/billing/gateways/skip_jack.rb index 158742d58f7..effe78d837b 100644 --- a/lib/active_merchant/billing/gateways/skip_jack.rb +++ b/lib/active_merchant/billing/gateways/skip_jack.rb @@ -12,16 +12,16 @@ class SkipJackGateway < Gateway ADVANCED_PATH = '/evolvcc/evolvcc.aspx' ACTIONS = { - :authorization => 'AuthorizeAPI', - :change_status => 'SJAPI_TransactionChangeStatusRequest', - :get_status => 'SJAPI_TransactionStatusRequest' + authorization: 'AuthorizeAPI', + change_status: 'SJAPI_TransactionChangeStatusRequest', + get_status: 'SJAPI_TransactionStatusRequest' } SUCCESS_MESSAGE = 'The transaction was successful.' MONETARY_CHANGE_STATUSES = ['SETTLE', 'AUTHORIZE', 'AUTHORIZE ADDITIONAL', 'CREDIT', 'SPLITSETTLE'] - CARD_CODE_ERRORS = %w( N S "" ) + CARD_CODE_ERRORS = %w(N S "") CARD_CODE_MESSAGES = { 'M' => 'Card verification number matched', @@ -32,7 +32,7 @@ class SkipJackGateway < Gateway '' => 'Transaction failed because incorrect card verification number was entered or no number was entered' } - AVS_ERRORS = %w( A B C E I N O P R W Z ) + AVS_ERRORS = %w(A B C E I N O P R W Z) AVS_MESSAGES = { 'A' => 'Street address matches billing information, zip/postal code does not', @@ -51,7 +51,7 @@ class SkipJackGateway < Gateway 'W' => '9-digit zip/postal code matches billing information, street address does not', 'X' => 'Street address and 9-digit zip/postal code matches billing information', 'Y' => 'Street address and 5-digit zip/postal code matches billing information', - 'Z' => '5-digit zip/postal code matches billing information, street address does not', + 'Z' => '5-digit zip/postal code matches billing information, street address does not' } CHANGE_STATUS_ERROR_MESSAGES = { @@ -160,8 +160,8 @@ class SkipJackGateway < Gateway '-117' => 'POS Check Invalid Cashier Number' } - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express jcb discover diners_club] self.homepage_url = 'http://www.skipjack.com/' self.display_name = 'SkipJack' @@ -213,7 +213,7 @@ def purchase(money, creditcard, options = {}) # # * :force_settlement -- Force the settlement to occur as soon as possible. This option is not supported by other gateways. See the SkipJack API reference for more details def capture(money, authorization, options = {}) - post = { } + post = {} add_status_action(post, 'SETTLE') add_forced_settlement(post, options) add_transaction_id(post, authorization) @@ -242,7 +242,7 @@ def credit(money, identification, options = {}) end def status(order_id) - commit(:get_status, nil, :szOrderNumber => order_id) + commit(:get_status, nil, szOrderNumber: order_id) end private @@ -263,11 +263,14 @@ 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, - :test => test?, - :authorization => response[:szTransactionFileName] || parameters[:szTransactionId], - :avs_result => { :code => response[:szAVSResponseCode] }, - :cvv_result => response[:szCVV2ResponseCode] + 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] ) end @@ -300,9 +303,9 @@ def parse(body, action) when :authorization parse_authorization_response(body) when :get_status - parse_status_response(body, [ :SerialNumber, :TransactionAmount, :TransactionStatusCode, :TransactionStatusMessage, :OrderNumber, :TransactionDateTime, :TransactionID, :ApprovalCode, :BatchNumber ]) + parse_status_response(body, %i[SerialNumber TransactionAmount TransactionStatusCode TransactionStatusMessage OrderNumber TransactionDateTime TransactionID ApprovalCode BatchNumber]) else - parse_status_response(body, [ :SerialNumber, :TransactionAmount, :DesiredStatus, :StatusResponse, :StatusResponseMessage, :OrderNumber, :AuditID ]) + parse_status_response(body, %i[SerialNumber TransactionAmount DesiredStatus StatusResponse StatusResponseMessage OrderNumber AuditID]) end end @@ -329,7 +332,7 @@ def parse_authorization_response(body) def parse_status_response(body, response_keys) lines = split_lines(body) - keys = [ :szSerialNumber, :szErrorCode, :szNumberRecords] + keys = %i[szSerialNumber szErrorCode szNumberRecords] values = split_line(lines[0])[0..2] result = Hash[*keys.zip(values).flatten] @@ -375,7 +378,7 @@ def add_invoice(post, options) end def add_creditcard(post, creditcard) - post[:AccountNumber] = creditcard.number + post[:AccountNumber] = creditcard.number post[:Month] = creditcard.month post[:Year] = creditcard.year post[:CVV2] = creditcard.verification_value if creditcard.verification_value? @@ -434,6 +437,7 @@ def message_from_authorization(response) return CARD_CODE_MESSAGES[response[:szCVV2ResponseCode]] if CARD_CODE_ERRORS.include?(response[:szCVV2ResponseCode]) return AVS_MESSAGES[response[:szAVSResponseMessage]] if AVS_ERRORS.include?(response[:szAVSResponseCode]) return RETURN_CODE_MESSAGES[response[:szReturnCode]] if response[:szReturnCode] != '1' + return response[:szAuthorizationDeclinedMessage] end end diff --git a/lib/active_merchant/billing/gateways/smart_ps.rb b/lib/active_merchant/billing/gateways/smart_ps.rb index 0e1b7c0a699..79d116be921 100644 --- a/lib/active_merchant/billing/gateways/smart_ps.rb +++ b/lib/active_merchant/billing/gateways/smart_ps.rb @@ -3,7 +3,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class SmartPs < Gateway #:nodoc: - ## # This is the base gateway for processors who use the smartPS processing system @@ -48,13 +47,13 @@ def purchase(money, payment_source, options = {}) end def capture(money, authorization, options = {}) - post ={} + post = {} post[:transactionid] = authorization commit('capture', money, post) end def void(authorization, options = {}) - post ={} + post = {} post[:transactionid] = authorization commit('void', nil, post) end @@ -118,37 +117,33 @@ def delete(vault_id) def store(payment_source, options = {}) post = {} billing_id = options.delete(:billing_id).to_s || true - add_payment_source(post, payment_source, :store => billing_id) + add_payment_source(post, payment_source, store: billing_id) add_address(post, options[:billing_address] || options[:address]) add_customer_data(post, options) commit(nil, nil, post) end - alias_method :unstore, :delete + alias unstore delete private def add_customer_data(post, options) - if options.has_key? :email - post[:email] = options[:email] - end + post[:email] = options[:email] if options.has_key? :email - if options.has_key? :ip - post[:ipaddress] = options[:ip] - end + post[:ipaddress] = options[:ip] if options.has_key? :ip end - def add_address(post, address, prefix='') - prefix +='_' unless prefix.blank? - unless address.blank? or address.values.blank? - post[prefix+'address1'] = address[:address1].to_s - post[prefix+'address2'] = address[:address2].to_s unless address[:address2].blank? - post[prefix+'company'] = address[:company].to_s - post[prefix+'phone'] = address[:phone].to_s - post[prefix+'zip'] = address[:zip].to_s - post[prefix+'city'] = address[:city].to_s - post[prefix+'country'] = address[:country].to_s - post[prefix+'state'] = address[:state].blank? ? 'n/a' : address[:state] + def add_address(post, address, prefix = '') + prefix += '_' unless prefix.blank? + unless address.blank? || address.values.blank? + post[prefix + 'address1'] = address[:address1].to_s + post[prefix + 'address2'] = address[:address2].to_s unless address[:address2].blank? + post[prefix + 'company'] = address[:company].to_s + post[prefix + 'phone'] = address[:phone].to_s + post[prefix + 'zip'] = address[:zip].to_s + post[prefix + 'city'] = address[:city].to_s + post[prefix + 'country'] = address[:country].to_s + post[prefix + 'state'] = address[:state].blank? ? 'n/a' : address[:state] end end @@ -168,7 +163,7 @@ def add_invoice(post, options) post[:orderid] = options[:order_id].to_s.gsub(/[^\w.]/, '') end - def add_payment_source(params, source, options={}) + def add_payment_source(params, source, options = {}) case determine_funding_source(source) when :vault then add_customer_vault_id(params, source) when :credit_card then add_creditcard(params, source, options) @@ -185,9 +180,9 @@ def add_creditcard(post, creditcard, options) post[:customer_vault] = 'add_customer' post[:customer_vault_id] = options[:store] unless options[:store] == true end - post[:ccnumber] = creditcard.number + post[:ccnumber] = creditcard.number post[:cvv] = creditcard.verification_value if creditcard.verification_value? - post[:ccexp] = expdate(creditcard) + post[:ccexp] = expdate(creditcard) post[:firstname] = creditcard.first_name post[:lastname] = creditcard.last_name end @@ -229,13 +224,16 @@ def parse(body) end def commit(action, money, parameters) - parameters[:amount] = localized_amount(money, parameters[:currency] || default_currency) if money + parameters[:amount] = localized_amount(money, parameters[:currency] || default_currency) if money response = parse(ssl_post(self.live_url, post_data(action, parameters))) - Response.new(response['response'] == '1', message_from(response), response, - :authorization => (response['transactionid'] || response['customer_vault_id']), - :test => test?, - :cvv_result => response['cvvresponse'], - :avs_result => { :code => response['avsresponse'] } + 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'] } ) end @@ -259,7 +257,7 @@ def message_from(response) def post_data(action, parameters = {}) post = {} - post[:username] = @options[:login] + post[:username] = @options[:login] post[:password] = @options[:password] post[:type] = action if action diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb index f0fa4523322..f13a3e6d4cc 100644 --- a/lib/active_merchant/billing/gateways/so_easy_pay.rb +++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb @@ -4,13 +4,13 @@ class SoEasyPayGateway < Gateway self.live_url = self.test_url = 'https://secure.soeasypay.com/gateway.asmx' self.money_format = :cents - self.supported_countries = [ - 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', - 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', - 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', - 'IS', 'NO', 'CH' + self.supported_countries = %w[ + US CA AT BE BG HR CY CZ DK EE + FI FR DE GR HU IE IT LV LT LU + MT NL PL PT RO SK SI ES SE GB + IS NO CH ] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover maestro jcb diners_club] self.homepage_url = 'http://www.soeasypay.com/' self.display_name = 'SoEasyPay' @@ -39,11 +39,11 @@ def capture(money, authorization, options = {}) commit('CaptureTransaction', do_capture(money, authorization, options), options) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) commit('RefundTransaction', do_refund(money, authorization, options), options) end - def void(authorization, options={}) + def void(authorization, options = {}) commit('CancelTransaction', do_void(authorization, options), options) end @@ -139,7 +139,7 @@ def fill_card(soap, card) soap.tag!('cardExpireYear', card.year.to_s) end - def fill_order_info(soap, money, options, skip_currency=false) + def fill_order_info(soap, money, options, skip_currency = false) soap.tag!('orderID', options[:order_id].to_s) soap.tag!('orderDescription', "Order #{options[:order_id]}") soap.tag!('amount', amount(money).to_s) @@ -149,7 +149,7 @@ def fill_order_info(soap, money, options, skip_currency=false) def parse(response, action) result = {} document = REXML::Document.new(response) - response_element = document.root.get_elements("//[@xsi:type='tns:#{action}Response']").first + response_element = document.root.get_elements("//*[@xsi:type='tns:#{action}Response']").first response_element.elements.each do |element| result[element.name.underscore] = element.text end @@ -157,30 +157,33 @@ def parse(response, action) end def commit(soap_action, soap, options) - headers = {'SOAPAction' => "\"urn:Interface##{soap_action}\"", - 'Content-Type' => 'text/xml; charset=utf-8'} + headers = { 'SOAPAction' => "\"urn:Interface##{soap_action}\"", + 'Content-Type' => 'text/xml; charset=utf-8' } response_string = ssl_post(test? ? self.test_url : self.live_url, soap, headers) response = parse(response_string, soap_action) - return Response.new(response['errorcode'] == '000', + return Response.new( + response['errorcode'] == '000', response['errormessage'], response, - :test => test?, - :authorization => response['transaction_id']) + test: test?, + authorization: response['transaction_id'] + ) end def build_soap(request) - retval = Builder::XmlMarkup.new(:indent => 2) - retval.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + retval = Builder::XmlMarkup.new(indent: 2) + retval.instruct!(:xml, version: '1.0', encoding: 'utf-8') retval.tag!('soap:Envelope', { - 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', - 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:soapenc' => 'http://schemas.xmlsoap.org/soap/encoding/', - 'xmlns:tns' => 'urn:Interface', - 'xmlns:types' => 'urn:Interface/encodedTypes', - 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'}) do - retval.tag!('soap:Body', {'soap:encodingStyle'=>'http://schemas.xmlsoap.org/soap/encoding/'}) do + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soapenc' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:tns' => 'urn:Interface', + 'xmlns:types' => 'urn:Interface/encodedTypes', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' + }) do + retval.tag!('soap:Body', { 'soap:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' }) do retval.tag!("tns:#{request}") do - retval.tag!("#{request}Request", {'xsi:type'=>"tns:#{request}Request"}) do + retval.tag!("#{request}Request", { 'xsi:type' => "tns:#{request}Request" }) do yield retval end end @@ -188,7 +191,6 @@ def build_soap(request) end retval.target! end - end end end diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb index d3d3e91af87..a286892086f 100644 --- a/lib/active_merchant/billing/gateways/spreedly_core.rb +++ b/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -14,7 +14,7 @@ class SpreedlyCoreGateway < Gateway MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM TR TT UM US VA VN ZA) - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://spreedly.com' self.display_name = 'Spreedly' self.money_format = :cents @@ -35,19 +35,13 @@ def initialize(options = {}) # Public: Run a purchase transaction. # # money - The monetary amount of the transaction in cents. - # payment_method - The CreditCard or the Spreedly payment method token. + # payment_method - The CreditCard or Check or the Spreedly payment method token. # options - A hash of options: # :store - Retain the payment method if the purchase # succeeds. Defaults to false. (optional) def purchase(money, payment_method, options = {}) - if payment_method.is_a?(String) - purchase_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(options[:store], payment_method, options) } - r.process { purchase_with_token(money, r.authorization, options) } - end - end + request = build_transaction_request(money, payment_method, options) + commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request) end # Public: Run an authorize transaction. @@ -58,17 +52,11 @@ def purchase(money, payment_method, options = {}) # :store - Retain the payment method if the authorize # succeeds. Defaults to false. (optional) def authorize(money, payment_method, options = {}) - if payment_method.is_a?(String) - authorize_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(options[:store], payment_method, options) } - r.process { authorize_with_token(money, r.authorization, options) } - end - end + request = build_transaction_request(money, payment_method, options) + commit("gateways/#{@options[:gateway_token]}/authorize.xml", request) end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) request = build_xml_request('transaction') do |doc| add_invoice(doc, money, options) end @@ -76,15 +64,16 @@ def capture(money, authorization, options={}) commit("transactions/#{authorization}/capture.xml", request) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) request = build_xml_request('transaction') do |doc| add_invoice(doc, money, options) + add_extra_options(:gateway_specific_fields, doc, options) end commit("transactions/#{authorization}/credit.xml", request) end - def void(authorization, options={}) + def void(authorization, options = {}) commit("transactions/#{authorization}/void.xml", '') end @@ -109,7 +98,7 @@ def verify(payment_method, options = {}) # # credit_card - The CreditCard to store # options - A standard ActiveMerchant options hash - def store(credit_card, options={}) + def store(credit_card, options = {}) retain = (options.has_key?(:retain) ? options[:retain] : true) save_card(retain, credit_card, options) end @@ -119,7 +108,7 @@ def store(credit_card, options={}) # # credit_card - The CreditCard to store # options - A standard ActiveMerchant options hash - def unstore(authorization, options={}) + def unstore(authorization, options = {}) commit("payment_methods/#{authorization}/redact.xml", '', :put) end @@ -128,7 +117,7 @@ def find(transaction_token) commit("transactions/#{transaction_token}.xml", nil, :get) end - alias_method :status, :find + alias status find def supports_scrubbing? true @@ -155,32 +144,25 @@ def save_card(retain, credit_card, options) end def purchase_with_token(money, payment_method_token, options) - request = auth_purchase_request(money, payment_method_token, options) + request = build_transaction_request(money, payment_method_token, options) commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request) end def authorize_with_token(money, payment_method_token, options) - request = auth_purchase_request(money, payment_method_token, options) + request = build_transaction_request(money, payment_method_token, options) commit("gateways/#{@options[:gateway_token]}/authorize.xml", request) end def verify_with_token(payment_method_token, options) - request = build_xml_request('transaction') do |doc| - add_invoice(doc, nil, options) - doc.payment_method_token(payment_method_token) - doc.retain_on_success(true) if options[:store] - add_extra_options(:gateway_specific_fields, doc, options) - end - + request = build_transaction_request(nil, payment_method_token, options) commit("gateways/#{@options[:gateway_token]}/verify.xml", request) end - def auth_purchase_request(money, payment_method_token, options) + def build_transaction_request(money, payment_method, options) build_xml_request('transaction') do |doc| add_invoice(doc, money, options) + add_payment_method(doc, payment_method, options) add_extra_options(:gateway_specific_fields, doc, options) - doc.payment_method_token(payment_method_token) - doc.retain_on_success(true) if options[:store] end end @@ -190,6 +172,23 @@ def add_invoice(doc, money, options) doc.order_id(options[:order_id]) doc.ip(options[:ip]) if options[:ip] doc.description(options[:description]) if options[:description] + + doc.merchant_name_descriptor(options[:merchant_name_descriptor]) if options[:merchant_name_descriptor] + doc.merchant_location_descriptor(options[:merchant_location_descriptor]) if options[:merchant_location_descriptor] + end + + def add_payment_method(doc, payment_method, options) + doc.retain_on_success(true) if options[:store] + + if payment_method.is_a?(String) + doc.payment_method_token(payment_method) + elsif payment_method.is_a?(CreditCard) + add_credit_card(doc, payment_method, options) + elsif payment_method.is_a?(Check) + add_bank_account(doc, payment_method, options) + else + raise TypeError, 'Payment method not supported' + end end def add_credit_card(doc, credit_card, options) @@ -210,6 +209,17 @@ def add_credit_card(doc, credit_card, options) end end + def add_bank_account(doc, bank_account, options) + doc.bank_account do + doc.first_name(bank_account.first_name) + doc.last_name(bank_account.last_name) + doc.bank_routing_number(bank_account.routing_number) + doc.bank_account_number(bank_account.account_number) + doc.bank_account_type(bank_account.account_type) + doc.bank_account_holder_type(bank_account.account_holder_type) + end + end + def add_extra_options(type, doc, options) doc.send(type) do extra_options_to_doc(doc, options[type]) @@ -218,6 +228,7 @@ def add_extra_options(type, doc, options) def extra_options_to_doc(doc, value) return doc.text value unless value.kind_of? Hash + value.each do |k, v| doc.send(k) do extra_options_to_doc(doc, v) @@ -243,11 +254,20 @@ def parse(xml) end def childnode_to_response(response, node, childnode) - name = "#{node.name.downcase}_#{childnode.name.downcase}" - if name == 'payment_method_data' && !childnode.elements.empty? - response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first + node_name = node.name.downcase + childnode_name = childnode.name.downcase + composed_name = "#{node_name}_#{childnode_name}" + + childnodes_present = !childnode.elements.empty? + + if childnodes_present && composed_name == 'payment_method_data' + response[composed_name.to_sym] = Hash.from_xml(childnode.to_s).values.first + elsif childnodes_present && node_name == 'gateway_specific_response_fields' + response[node_name.to_sym] = { + childnode_name => Hash.from_xml(childnode.to_s).values.first + } else - response[name.to_sym] = childnode.text + response[composed_name.to_sym] = childnode.text end end @@ -272,10 +292,10 @@ def commit(relative_url, request, method = :post, authorization_field = :token) def response_from(raw_response, authorization_field) parsed = parse(raw_response) options = { - :authorization => parsed[authorization_field], - :test => (parsed[:on_test_gateway] == 'true'), - :avs_result => { :code => parsed[:response_avs_code] }, - :cvv_result => parsed[:response_cvv_code] + authorization: parsed[authorization_field], + test: (parsed[:on_test_gateway] == 'true'), + avs_result: { code: parsed[:response_avs_code] }, + cvv_result: parsed[:response_cvv_code] } Response.new(parsed[:succeeded] == 'true', parsed[:message] || parsed[:error], parsed, options) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 4d152fe1939..da0eee50312 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -2,6 +2,8 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: + # This gateway uses an older version of the Stripe API. + # To utilize the updated {Payment Intents API}[https://stripe.com/docs/api/payment_intents], integrate with the StripePaymentIntents gateway class StripeGateway < Gateway self.live_url = 'https://api.stripe.com/v1/' @@ -21,10 +23,12 @@ class StripeGateway < Gateway 'unchecked' => 'P' } - self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US) + DEFAULT_API_VERSION = '2020-08-27' + + self.supported_countries = %w(AE AT AU BE BG BR CA CH CY CZ DE DK EE ES FI FR GB GR HK HU IE IN IT JP LT LU LV MT MX MY NL NO NZ PL PT RO SE SG SI SK US) self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] + self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro unionpay] self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF VND VUV XAF XOF XPF UGX) self.homepage_url = 'https://stripe.com/' @@ -44,12 +48,13 @@ class StripeGateway < Gateway 'processing_error' => STANDARD_ERROR_CODE[:processing_error], 'incorrect_pin' => STANDARD_ERROR_CODE[:incorrect_pin], 'test_mode_live_card' => STANDARD_ERROR_CODE[:test_mode_live_card], - 'pickup_card' => STANDARD_ERROR_CODE[:pickup_card] + 'pickup_card' => STANDARD_ERROR_CODE[:pickup_card], + 'amount_too_small' => STANDARD_ERROR_CODE[:invalid_amount] } BANK_ACCOUNT_HOLDER_TYPE_MAPPING = { 'personal' => 'individual', - 'business' => 'company', + 'business' => 'company' } MINIMUM_AUTHORIZE_AMOUNTS = { @@ -76,6 +81,11 @@ def initialize(options = {}) end def authorize(money, payment, options = {}) + if ach?(payment) + direct_bank_error = 'Direct bank account transactions are not supported for authorize.' + return Response.new(false, direct_bank_error) + end + MultiResponse.run do |r| if payment.is_a?(ApplePayPaymentToken) r.process { tokenize_apple_pay_token(payment) } @@ -83,11 +93,8 @@ def authorize(money, payment, options = {}) end r.process do post = create_post_for_auth_or_purchase(money, payment, options) - if emv_payment?(payment) - add_application_fee(post, options) - else - post[:capture] = 'false' - end + add_application_fee(post, options) if emv_payment?(payment) + post[:capture] = 'false' commit(:post, 'charges', post, options) end end.responses.last @@ -123,18 +130,22 @@ def capture(money, authorization, options = {}) post = {} if emv_tc_response = options.delete(:icc_data) - post[:card] = { emv_approval_data: emv_tc_response } - commit(:post, "charges/#{CGI.escape(authorization)}", post, options) + # update the charge with emv data if card present + update = {} + update[:card] = { emv_approval_data: emv_tc_response } + commit(:post, "charges/#{CGI.escape(authorization)}", update, options) else add_application_fee(post, options) add_amount(post, money, options) add_exchange_rate(post, options) - commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) end + + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) end def void(identification, options = {}) post = {} + post[:reverse_transfer] = options[:reverse_transfer] if options[:reverse_transfer] post[:metadata] = options[:metadata] if options[:metadata] post[:reason] = options[:reason] if options[:reason] post[:expand] = [:charge] @@ -150,14 +161,21 @@ def refund(money, identification, options = {}) post[:reason] = options[:reason] if options[:reason] post[:expand] = [:charge] - MultiResponse.run(:first) do |r| - r.process { commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) } + response = commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) + + if response.success? && options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0' + charge = api_request(:get, "charges/#{CGI.escape(identification)}", nil, options) - if options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0' - r.process { fetch_application_fee(identification, options) } - r.process { refund_application_fee(options[:refund_fee_amount].to_i, application_fee_from_response(r.responses.last), options) } + if application_fee = charge['application_fee'] + fee_refund_options = { + currency: options[:currency], # currency isn't used by Stripe here, but we need it for #add_amount + key: @fee_refund_api_key + } + refund_application_fee(options[:refund_fee_amount].to_i, application_fee, fee_refund_options) end end + + response end def verify(payment, options = {}) @@ -168,21 +186,10 @@ def verify(payment, options = {}) end end - def application_fee_from_response(response) - return unless response.success? - response.params['application_fee'] unless response.params['application_fee'].empty? - end - def refund_application_fee(money, identification, options = {}) - return Response.new(false, 'Application fee id could not be found') unless identification - post = {} add_amount(post, money, options) - options[:key] = @fee_refund_api_key if @fee_refund_api_key - options.delete(:stripe_account) - - refund_fee = commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options) - application_fee_response!(refund_fee, "Application fee could not be refunded: #{refund_fee.message}") + commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options) end # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true) @@ -198,6 +205,7 @@ def store(payment, options = {}) elsif payment.is_a?(Check) bank_token_response = tokenize_bank_account(payment) return bank_token_response unless bank_token_response.success? + params = { source: bank_token_response.params['token']['id'] } else add_creditcard(params, payment, options) @@ -215,15 +223,12 @@ def store(payment, options = {}) # The /cards endpoint does not update other customer parameters. r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", params, options) } - if options[:set_default] and r.success? and !r.params['id'].blank? - post[:default_card] = r.params['id'] - end + post[:default_card] = r.params['id'] if options[:set_default] && r.success? && !r.params['id'].blank? - if post.count > 0 - r.process { update_customer(options[:customer], post) } - end + r.process { update_customer(options[:customer], post.merge(expand: [:sources])) } if post.count > 0 end else + post[:expand] = [:sources] commit(:post, 'customers', post.merge(params), options) end end @@ -276,21 +281,35 @@ def supports_scrubbing? def scrub(transcript) transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]'). gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]'). - gsub(%r((card\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\2'). - gsub(%r((card\[number\]=)\d+), '\1[FILTERED]'). - gsub(%r((card\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\2') + gsub(%r(((\[card\]|card)\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\3'). + gsub(%r(((\[card\]|card)\[cvc\]=)\d+), '\1[FILTERED]'). + gsub(%r(((\[card\]|card)\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\3'). + gsub(%r(((\[card\]|card)\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\3'). + gsub(%r(((\[card\]|card)\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\3'). + gsub(%r(((\[card\]|card)\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\3'). + 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') end def supports_network_tokenization? true end + # Helper method to prevent hitting the external_account limit from remote test runs + def delete_latest_test_external_account(account) + return unless test? + + auth_header = { 'Authorization' => 'Basic ' + Base64.strict_encode64(options[:login].to_s + ':').strip } + url = "#{live_url}accounts/#{CGI.escape(account)}/external_accounts" + accounts_response = JSON.parse(ssl_get("#{url}?limit=100", auth_header)) + to_delete = accounts_response['data'].reject { |ac| ac['default_for_currency'] } + ssl_request(:delete, "#{url}/#{to_delete.first['id']}", nil, auth_header) + end + private class StripePaymentToken < PaymentToken @@ -304,19 +323,25 @@ def create_source(money, payment, type, options = {}) add_amount(post, money, options, true) post[:type] = type if type == 'card' - add_creditcard(post, payment, options) - post[:card].delete(:name) + add_creditcard(post, payment, options, true) + add_source_owner(post, payment, options) elsif type == 'three_d_secure' - post[:three_d_secure] = {card: payment} - post[:redirect] = {return_url: options[:redirect_url]} + post[:three_d_secure] = { card: payment } + post[:redirect] = { return_url: options[:redirect_url] } end commit(:post, 'sources', post, options) end + def show_source(source_id, options) + commit(:get, "sources/#{source_id}", nil, options) + end + def create_webhook_endpoint(options, events) post = {} post[:url] = options[:callback_url] post[:enabled_events] = events + post[:connect] = true if options[:stripe_account] + options.delete(:stripe_account) commit(:post, 'webhook_endpoints', post, options) end @@ -324,6 +349,18 @@ def delete_webhook_endpoint(options) commit(:delete, "webhook_endpoints/#{options[:webhook_id]}", {}, options) end + def show_webhook_endpoint(options) + options.delete(:stripe_account) + commit(:get, "webhook_endpoints/#{options[:webhook_id]}", nil, options) + end + + def list_webhook_endpoints(options) + params = {} + params[:limit] = options[:limit] if options[:limit] + options.delete(:stripe_account) + commit(:get, "webhook_endpoints?#{post_data(params)}", nil, options) + end + def create_post_for_auth_or_purchase(money, payment, options) post = {} @@ -333,6 +370,12 @@ def create_post_for_auth_or_purchase(money, payment, options) add_creditcard(post, payment, options) end + add_charge_details(post, money, payment, options) + post + end + + # Used internally by Spreedly to populate the charge object for 3DS 1.0 transactions + def add_charge_details(post, money, payment, options) if emv_payment?(payment) add_statement_address(post, options) add_emv_metadata(post, payment) @@ -341,16 +384,20 @@ def create_post_for_auth_or_purchase(money, payment, options) add_customer_data(post, options) post[:description] = options[:description] post[:statement_descriptor] = options[:statement_description] + post[:statement_descriptor_suffix] = options[:statement_descriptor_suffix] if options[:statement_descriptor_suffix] post[:receipt_email] = options[:receipt_email] if options[:receipt_email] add_customer(post, payment, options) add_flags(post, options) end add_metadata(post, options) + add_shipping_address(post, payment, options) add_application_fee(post, options) add_exchange_rate(post, options) add_destination(post, options) add_level_three(post, options) + add_connected_account(post, options) + add_radar_data(post, options) post end @@ -386,9 +433,7 @@ def add_level_three(post, options) copy_when_present(level_three, [:shipping_amount], options) copy_when_present(level_three, [:line_items], options) - unless level_three.empty? - post[:level3] = level_three - end + post[:level3] = level_three unless level_three.empty? end def add_expand_parameters(post, options) @@ -398,13 +443,13 @@ def add_expand_parameters(post, options) def add_external_account(post, card_params, payment) external_account = {} - external_account[:object] ='card' + external_account[:object] = 'card' external_account[:currency] = (options[:currency] || currency(payment)).downcase post[:external_account] = external_account.merge(card_params[:card]) end def add_customer_data(post, options) - metadata_options = [:description, :ip, :user_agent, :referrer] + metadata_options = %i[description ip user_agent referrer] post.update(options.slice(*metadata_options)) post[:external_id] = options[:order_id] @@ -413,6 +458,7 @@ def add_customer_data(post, options) def add_address(post, options) return unless post[:card]&.kind_of?(Hash) + if address = options[:billing_address] || options[:address] post[:card][:address_line1] = address[:address1] if address[:address1] post[:card][:address_line2] = address[:address2] if address[:address2] @@ -425,7 +471,7 @@ def add_address(post, options) def add_statement_address(post, options) return unless statement_address = options[:statement_address] - return unless [:address1, :city, :zip, :state].all? { |key| statement_address[key].present? } + return unless %i[address1 city zip state].all? { |key| statement_address[key].present? } post[:statement_address] = {} post[:statement_address][:line1] = statement_address[:address1] @@ -435,7 +481,7 @@ def add_statement_address(post, options) post[:statement_address][:state] = statement_address[:state] end - def add_creditcard(post, creditcard, options) + def add_creditcard(post, creditcard, options, use_sources = false) card = {} if emv_payment?(creditcard) add_emv_creditcard(post, creditcard.icc_data) @@ -458,7 +504,7 @@ def add_creditcard(post, creditcard, options) card[:exp_month] = creditcard.month card[:exp_year] = creditcard.year card[:cvc] = creditcard.verification_value if creditcard.verification_value? - card[:name] = creditcard.name if creditcard.name + card[:name] = creditcard.name if creditcard.name && !use_sources end if creditcard.is_a?(NetworkTokenizationCreditCard) @@ -468,7 +514,7 @@ def add_creditcard(post, creditcard, options) end post[:card] = card - add_address(post, options) + add_address(post, options) unless use_sources elsif creditcard.kind_of?(String) if options[:track_data] card[:swipe_data] = options[:track_data] @@ -492,10 +538,7 @@ def add_payment_token(post, token, options = {}) end def add_customer(post, payment, options) - if options[:customer] && !payment.respond_to?(:number) - ActiveMerchant.deprecated 'Passing the customer in the options is deprecated. Just use the response.authorization instead.' - post[:customer] = options[:customer] - end + post[:customer] = options[:customer] if options[:customer] && !payment.respond_to?(:number) end def add_flags(post, options) @@ -508,7 +551,6 @@ def add_metadata(post, options = {}) post[:metadata].merge!(options[:metadata]) if options[:metadata] post[:metadata][:email] = options[:email] if options[:email] post[:metadata][:order_id] = options[:order_id] if options[:order_id] - post.delete(:metadata) if post[:metadata].empty? end def add_emv_metadata(post, creditcard) @@ -516,15 +558,59 @@ def add_emv_metadata(post, creditcard) post[:metadata][:card_read_method] = creditcard.read_method if creditcard.respond_to?(:read_method) end - def fetch_application_fee(identification, options = {}) - options[:key] = @fee_refund_api_key + def add_shipping_address(post, payment, options = {}) + return unless shipping = options[:shipping_address] + return unless shipping_name = shipping[:name] - fetch_charge = commit(:get, "charges/#{CGI.escape(identification)}", nil, options) - application_fee_response!(fetch_charge, "Application fee id could not be retrieved: #{fetch_charge.message}") + post[:shipping] = {} + + post[:shipping][:name] = shipping_name + post[:shipping][:address] = {} + post[:shipping][:address][:line1] = shipping[:address1] + post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2] + post[:shipping][:address][:city] = shipping[:city] if shipping[:city] + post[:shipping][:address][:country] = shipping[:country] if shipping[:country] + post[:shipping][:address][:state] = shipping[:state] if shipping[:state] + post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip] + post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number] end - def application_fee_response!(response, message) - response.success? ? response : Response.new(false, message) + def add_source_owner(post, creditcard, options) + post[:owner] = {} + post[:owner][:name] = creditcard.name if creditcard.name + post[:owner][:email] = options[:email] if options[:email] + + if address = options[:billing_address] || options[:address] + owner_address = {} + owner_address[:line1] = address[:address1] if address[:address1] + owner_address[:line2] = address[:address2] if address[:address2] + owner_address[:country] = address[:country] if address[:country] + owner_address[:postal_code] = address[:zip] if address[:zip] + owner_address[:state] = address[:state] if address[:state] + owner_address[:city] = address[:city] if address[:city] + + post[:owner][:phone] = address[:phone] if address[:phone] + post[:owner][:address] = owner_address + end + end + + def add_connected_account(post, options = {}) + post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of] + + return unless options[:transfer_destination] + + post[:transfer_data] = { destination: options[:transfer_destination] } + post[:transfer_data][:amount] = options[:transfer_amount] if options[:transfer_amount] + post[:transfer_group] = options[:transfer_group] if options[:transfer_group] + post[:application_fee_amount] = options[:application_fee_amount] if options[:application_fee_amount] + end + + def add_radar_data(post, options = {}) + radar_options = {} + radar_options[:session] = options[:radar_session_id] if options[:radar_session_id] + radar_options[:skip_rules] = ['all'] if options[:skip_radar_rules] + + post[:radar_options] = radar_options unless radar_options.empty? end def parse(body) @@ -533,12 +619,14 @@ def parse(body) def post_data(params) return nil unless params + flatten_params([], params).join('&') end def flatten_params(flattened, params, prefix = nil) params.each do |key, value| next if value != false && value.blank? + flattened_key = prefix.nil? ? key : "#{prefix}[#{key}]" if value.is_a?(Hash) flatten_params(flattened, value, flattened_key) @@ -564,29 +652,31 @@ 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.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 + '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 def stripe_client_user_agent(options) return user_agent unless options[:application] - JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]})) + + JSON.dump(JSON.parse(user_agent).merge!({ application: options[:application] })) end def api_version(options) - options[:version] || @options[:version] || '2015-04-07' + options[:version] || @options[:version] || self.class::DEFAULT_API_VERSION end def api_request(method, endpoint, parameters = nil, options = {}) @@ -605,32 +695,47 @@ 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) + 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"]}"] + 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, + Response.new( + success, message_from(success, response), response, - :test => response_is_test?(response), - :authorization => authorization_from(success, url, method, response), - :avs_result => { :code => avs_code }, - :cvv_result => cvc_code, - :emv_authorization => emv_authorization_from_response(response), - :error_code => success ? nil : error_code_from(response) + test: response_is_test?(response), + authorization: authorization_from(success, url, method, response), + avs_result: { code: avs_code }, + cvv_result: cvc_code, + emv_authorization: emv_authorization_from_response(response), + error_code: success ? nil : error_code_from(response) ) end + def key_valid?(options) + return true unless test? + + %w(sk rk).each do |k| + if key(options).start_with?(k) + return false unless key(options).start_with?("#{k}_test") + end + end + + true + end + def authorization_from(success, url, method, response) return response.fetch('error', {})['charge'] unless success if url == 'customers' - [response['id'], response['sources']['data'].first['id']].join('|') - elsif method == :post && url.match(/customers\/.*\/cards/) + [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|') + elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/)) [response['customer'], response['id']].join('|') else response['id'] @@ -638,10 +743,10 @@ def authorization_from(success, url, method, response) end def message_from(success, response) - success ? 'Transaction approved' : response.fetch('error', {'message' => 'No error details'})['message'] + success ? 'Transaction approved' : response.fetch('error', { 'message' => 'No error details' })['message'] end - def success_from(response) + def success_from(response, options) !response.key?('error') && response['status'] != 'failed' end @@ -709,8 +814,8 @@ def tokenize_bank_account(bank_account, options = {}) country: 'US', currency: 'usd', routing_number: bank_account.routing_number, - name: bank_account.name, - account_holder_type: account_holder_type, + account_holder_name: bank_account.name, + account_holder_type: account_holder_type } } @@ -735,6 +840,7 @@ def ach?(payment_method) def auth_minimum_amount(options) return 100 unless options[:currency] + return MINIMUM_AUTHORIZE_AMOUNTS[options[:currency].upcase] || 100 end @@ -742,6 +848,7 @@ def copy_when_present(dest, dest_path, source, source_path = nil) source_path ||= dest_path source_path.each do |key| return nil unless source[key] + source = source[key] end diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb new file mode 100644 index 00000000000..f423ff39aa5 --- /dev/null +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -0,0 +1,651 @@ +require 'active_support/core_ext/hash/slice' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents]. + # For the legacy API, see the Stripe gateway + class StripePaymentIntentsGateway < StripeGateway + ALLOWED_METHOD_STATES = %w[automatic manual].freeze + ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze + CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor_suffix statement_descriptor receipt_email save_payment_method] + CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session] + UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor_suffix statement_descriptor receipt_email setup_future_usage] + DEFAULT_API_VERSION = '2020-08-27' + + def create_intent(money, payment_method, options = {}) + MultiResponse.run do |r| + if payment_method.is_a?(NetworkTokenizationCreditCard) && digital_wallet_payment_method?(payment_method) + r.process { tokenize_apple_google(payment_method, options) } + payment_method = (r.params['token']['id']) if r.success? + end + r.process do + post = {} + add_amount(post, money, options, true) + add_capture_method(post, 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) + + 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_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) + post[:expand] = ['charges.data.balance_transaction'] + + CREATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + commit(:post, 'payment_intents', post, options) + end + end + end + + 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) + + add_payment_method_types(post, options) + CONFIRM_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, "payment_intents/#{intent_id}/confirm", post, options) + end + + def create_payment_method(payment_method, options = {}) + post_data = add_payment_method_data(payment_method, options) + options = format_idempotency_key(options, 'pm') + commit(:post, 'payment_methods', post_data, options) + end + + def add_payment_method_data(payment_method, options = {}) + 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) + post_data.merge!({ + payment_method_types: ['card'], + payment_method_data: { type: 'card', card: { token: payment_method } } + }) + end + + 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) + + add_payment_method_types(post, options) + add_customer(post, options) + add_metadata(post, options) + add_shipping_address(post, options) + add_connected_account(post, options) + add_fulfillment_date(post, options) + + UPDATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + commit(:post, "payment_intents/#{intent_id}", post, options) + end + + def create_setup_intent(payment_method, options = {}) + MultiResponse.run do |r| + r.process do + post = {} + add_customer(post, options) + result = add_payment_method_token(post, payment_method, options, r) + return result if result.is_a?(ActiveMerchant::Billing::Response) + + 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] + + commit(:post, 'setup_intents', post, options) + end + end + end + + def retrieve_setup_intent(setup_intent_id, options = {}) + # Retrieving a setup_intent passing 'expand[]=latest_attempt' allows the caller to + # check for a network_transaction_id and ds_transaction_id + # eg (latest_attempt -> payment_method_details -> card -> network_transaction_id) + # + # Being able to retrieve these fields enables payment flows that rely on MIT exemptions, e.g: off_session + commit(:post, "setup_intents/#{setup_intent_id}", { + 'expand[]': 'latest_attempt' + }, options) + end + + def authorize(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual')) + end + + def purchase(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic')) + end + + def capture(money, intent_id, options = {}) + post = {} + currency = options[:currency] || currency(money) + post[:amount_to_capture] = localized_amount(money, currency) + if options[:transfer_amount] + post[:transfer_data] = {} + post[:transfer_data][:amount] = options[:transfer_amount] + end + post[:application_fee_amount] = options[:application_fee] if options[:application_fee] + options = format_idempotency_key(options, 'capture') + commit(:post, "payment_intents/#{intent_id}/capture", post, options) + end + + def void(intent_id, options = {}) + post = {} + post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason]) + commit(:post, "payment_intents/#{intent_id}/cancel", post, options) + end + + def refund(money, intent_id, options = {}) + if intent_id.include?('pi_') + intent = api_request(:get, "payment_intents/#{intent_id}", nil, options) + + return Response.new(false, intent['error']['message'], intent) if intent['error'] + + charge_id = intent.try(:[], 'charges').try(:[], 'data').try(:[], 0).try(:[], 'id') + + if charge_id.nil? + error_message = "No associated charge for #{intent['id']}" + error_message << "; payment_intent has a status of #{intent['status']}" if intent.try(:[], 'status') && intent.try(:[], 'status') != 'succeeded' + return Response.new(false, error_message, intent) + end + else + charge_id = intent_id + end + + super(money, charge_id, options) + end + + # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods] + # Current implementation will create a PaymentMethod object if the method is a token or credit card + # All other types will default to legacy Stripe store + def store(payment_method, options = {}) + params = {} + post = {} + # If customer option is provided, create a payment method and attach to customer id + # Otherwise, create a customer, then attach + if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + result = add_payment_method_token(params, payment_method, options) + return result if result.is_a?(ActiveMerchant::Billing::Response) + + customer_id = options[:customer] || customer(post, 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? + commit(:post, "payment_methods/#{params[:payment_method]}/attach", attach_parameters, options) + else + super(payment_method, options) + end + end + + def customer(post, payment, options) + 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('|') + commit(:post, "payment_methods/#{payment_method}/detach", nil, options) + else + super(identification, options, deprecated_options) + end + end + + def verify(payment_method, options = {}) + create_setup_intent(payment_method, options.merge!({ confirm: true, verify: true })) + end + + def setup_purchase(money, options = {}) + requires!(options, :payment_method_types) + post = {} + add_currency(post, options, money) + add_amount(post, money, options) + add_payment_method_types(post, options) + add_metadata(post, 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 + + def add_connected_account(post, options = {}) + super(post, options) + post[:application_fee_amount] = options[:application_fee] if options[:application_fee] + end + + def add_whitelisted_attribute(post, options, attribute) + post[attribute] = options[attribute] if options[attribute] + end + + def add_capture_method(post, options) + capture_method = options[:capture_method].to_s + post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method) + end + + def add_confirmation_method(post, options) + confirmation_method = options[:confirmation_method].to_s + post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method) + end + + def add_customer(post, options) + customer = options[:customer].to_s + post[:customer] = customer if customer.start_with?('cus_') + end + + def add_fulfillment_date(post, options) + post[:fulfillment_date] = options[:fulfillment_date].to_i if options[:fulfillment_date] + end + + def add_metadata(post, options = {}) + super + + post[:metadata][:event_type] = options[:event_type] if options[:event_type] + end + + def add_level_three(post, options = {}) + level_three = {} + + level_three[:merchant_reference] = options[:merchant_reference] if options[:merchant_reference] + level_three[:customer_reference] = options[:customer_reference] if options[:customer_reference] + level_three[:shipping_address_zip] = options[:shipping_address_zip] if options[:shipping_address_zip] + level_three[:shipping_from_zip] = options[:shipping_from_zip] if options[:shipping_from_zip] + level_three[:shipping_amount] = options[:shipping_amount] if options[:shipping_amount] + level_three[:line_items] = options[:line_items] if options[:line_items] + + post[:level3] = level_three unless level_three.empty? + end + + def add_return_url(post, options) + return unless options[:confirm] + + post[:confirm] = options[:confirm] + post[:return_url] = options[:return_url] if options[:return_url] + end + + def add_payment_method_token(post, payment_method, options, responses = []) + case payment_method + when StripePaymentToken + post[:payment_method_data] = { + type: 'card', + card: { + token: payment_method.payment_data['id'] || payment_method.payment_data + } + } + post[:payment_method] = payment_method.payment_data['id'] || payment_method.payment_data + when String + extract_token_from_string_and_maybe_add_customer_id(post, payment_method) + when ActiveMerchant::Billing::CreditCard + 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 extract_token_from_string_and_maybe_add_customer_id(post, payment_method) + if payment_method.include?('|') + customer_id, payment_method = payment_method.split('|') + post[:customer] = customer_id + end + + if payment_method.include?('tok_') + add_payment_method_card_data_token(post, payment_method) + else + post[:payment_method] = payment_method + end + end + + def tokenize_apple_google(payment, options = {}) + tokenization_method = payment.source == :google_pay ? :android_pay : payment.source + post = { + card: { + number: payment.number, + exp_month: payment.month, + exp_year: payment.year, + tokenization_method: tokenization_method, + eci: payment.eci, + 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'] + Response.new(success, nil, token: token_response) + elsif token_response['error']['message'] + Response.new(false, "The tokenization process fails. #{token_response['error']['message']}") + else + Response.new(false, "The tokenization process fails. #{token_response}") + end + end + + 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, 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 + + def add_payment_method_types(post, options) + payment_method_types = options[:payment_method_types] if options[:payment_method_types] + return if payment_method_types.nil? + + post[:payment_method_types] = Array(payment_method_types) + end + + def add_exemption(post, options = {}) + return unless options[:confirm] + + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:moto] = true if options[:moto] + end + + # 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 = {}) + stored_credential = options[:stored_credential] + return unless stored_credential && !stored_credential.values.all?(&:nil?) + + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + + 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. + card_options[:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id] + unless options[:setup_future_usage] == 'off_session' + card_options[:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id] + end + + 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 = {}) + return unless options[:network_transaction_id] + + post[:payment_method_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] + end + + def add_claim_without_transaction_id(post, options = {}) + return if options[:stored_credential] || options[:network_transaction_id] || options[:ds_transaction_id] + return unless options[:claim_without_transaction_id] + + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:mit_exemption] = {} + + # Stripe PI accepts claim_without_transaction_id for transactions without transaction ids. + # Gateway validation for this field occurs through a different service, before the transaction request is sent to the gateway. + post[:payment_method_options][:card][:mit_exemption][:claim_without_transaction_id] = options[:claim_without_transaction_id] + end + + def add_error_on_requires_action(post, options = {}) + return unless options[:confirm] + + post[:error_on_requires_action] = true if options[:error_on_requires_action] + end + + def request_three_d_secure(post, options = {}) + return unless options[:request_three_d_secure] && %w(any automatic).include?(options[:request_three_d_secure]) + + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:request_three_d_secure] = options[:request_three_d_secure] + end + + def add_external_three_d_secure_auth_data(post, options = {}) + return unless options[:three_d_secure]&.is_a?(Hash) + + three_d_secure = options[:three_d_secure] + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:three_d_secure] ||= {} + post[:payment_method_options][:card][:three_d_secure][:version] = three_d_secure[:version] || (three_d_secure[:ds_transaction_id] ? '2.2.0' : '1.0.2') + post[:payment_method_options][:card][:three_d_secure][:electronic_commerce_indicator] = three_d_secure[:eci] if three_d_secure[:eci] + post[:payment_method_options][:card][:three_d_secure][:cryptogram] = three_d_secure[:cavv] if three_d_secure[:cavv] + post[:payment_method_options][:card][:three_d_secure][:transaction_id] = three_d_secure[:ds_transaction_id] || three_d_secure[:xid] + end + + def setup_future_usage(post, options = {}) + post[:setup_future_usage] = options[:setup_future_usage] if %w(on_session off_session).include?(options[:setup_future_usage]) + post[:off_session] = options[:off_session] if off_session_request?(options) + post + 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_shipping_address(post, options = {}) + return unless shipping = options[:shipping_address] + + 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] + + options.merge(idempotency_key: "#{options[:idempotency_key]}-#{suffix}") + end + + def success_from(response, options) + if response['status'] == 'requires_action' && !options[:execute_threed] + response['error'] = {} + response['error']['message'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.' + return false + end + + super(response, options) + end + + def add_currency(post, options, money) + post[:currency] = options[:currency] || currency(money) + end + end + end +end 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..c2824c9f39f --- /dev/null +++ b/lib/active_merchant/billing/gateways/sum_up.rb @@ -0,0 +1,205 @@ +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 void(authorization, options = {}) + checkout_id = authorization.split('#')[0] + commit('checkouts/' + checkout_id, {}, :delete) + end + + def refund(money, authorization, options = {}) + transaction_id = authorization.split('#')[-1] + payment_currency = options[:currency] || currency(money) + post = money ? { amount: localized_amount(money, payment_currency) } : {} + 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) + + 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) + payment_currency = options[:currency] || currency(money) + post[:checkout_reference] = options[:order_id] + post[:amount] = localized_amount(money, payment_currency) + post[:currency] = payment_currency + 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 commit(action, post, method = :post) + response = api_request(action, post.compact, method) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def api_request(action, post, method) + begin + raw_response = ssl_request(method, live_url + action, post.to_json, auth_headers) + rescue ResponseError => e + raw_response = e.response.body + end + + response = parse(raw_response) + # Multiple invalid parameters + response = format_multiple_errors(response) if raw_response.include?('error_code') && response.is_a?(Array) + + return response.symbolize_keys + end + + def parse(body) + JSON.parse(body) + end + + def success_from(response) + return false unless %w(PENDING EXPIRED PAID).include?(response[:status]) + + response[:transactions].each do |transaction| + return false unless %w(PENDING CANCELLED SUCCESSFUL).include?(transaction.symbolize_keys[:status]) + end + + true + end + + def message_from(response) + return response[:status] if success_from(response) + + response[:message] || response[:error_message] + end + + def authorization_from(response) + 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(response) + response[:error_code] unless success_from(response) + end + + def format_multiple_errors(responses) + errors = responses.map do |response| + { error_code: response['error_code'], param: response['param'] } + end + + { + error_code: STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], + message: 'Validation error', + errors: errors + } + 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 aaf4002d355..e274ce2d918 100644 --- a/lib/active_merchant/billing/gateways/swipe_checkout.rb +++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb @@ -11,9 +11,9 @@ class SwipeCheckoutGateway < Gateway TRANSACTION_API = '/createShopifyTransaction.php' - self.supported_countries = %w[ NZ CA ] + self.supported_countries = %w[NZ CA] self.default_currency = 'NZD' - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = %i[visa master] self.homepage_url = 'https://www.swipehq.com/checkout' self.display_name = 'Swipe Checkout' self.money_format = :dollars @@ -58,7 +58,7 @@ def add_customer_data(post, creditcard, options) post[:address] = "#{address[:address1]}, #{address[:address2]}" post[:city] = address[:city] post[:country] = address[:country] - post[:mobile] = address[:phone] # API only has a "mobile" field, no "phone" + post[:mobile] = address[:phone] # API only has a "mobile" field, no "phone" end def add_invoice(post, options) @@ -104,12 +104,13 @@ 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) @@ -125,7 +126,7 @@ def commit(action, money, parameters) end end - def call_api(api, params=nil) + def call_api(api, params = nil) params ||= {} params[:merchant_id] = @options[:login] params[:api_key] = @options[:api_key] @@ -139,12 +140,12 @@ def url(api) (test? ? self.test_url : self.live_url) + api end - def build_error_response(message, params={}) + def build_error_response(message, params = {}) Response.new( false, message, params, - :test => test? + test: test? ) end end diff --git a/lib/active_merchant/billing/gateways/telr.rb b/lib/active_merchant/billing/gateways/telr.rb index c39b0a17e34..76c47c1dba4 100644 --- a/lib/active_merchant/billing/gateways/telr.rb +++ b/lib/active_merchant/billing/gateways/telr.rb @@ -8,16 +8,16 @@ class TelrGateway < Gateway self.live_url = 'https://secure.telr.com/gateway/remote.xml' - self.supported_countries = ['AE', 'IN', 'SA'] + self.supported_countries = %w[AE IN SA] self.default_currency = 'AED' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :maestro, :jcb] + self.supported_cardtypes = %i[visa master american_express maestro jcb] CVC_CODE_TRANSLATOR = { 'Y' => 'M', 'N' => 'N', 'X' => 'P', - 'E' => 'U', + 'E' => 'U' } AVS_CODE_TRANSLATOR = { @@ -28,12 +28,12 @@ class TelrGateway < Gateway 'E' => 'R' } - def initialize(options={}) + def initialize(options = {}) requires!(options, :merchant_id, :api_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) commit(:purchase, amount, options[:currency]) do |doc| add_invoice(doc, 'sale', amount, payment_method, options) add_payment_method(doc, payment_method, options) @@ -41,7 +41,7 @@ def purchase(amount, payment_method, options={}) end end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) commit(:authorize, amount, options[:currency]) do |doc| add_invoice(doc, 'auth', amount, payment_method, options) add_payment_method(doc, payment_method, options) @@ -49,26 +49,26 @@ def authorize(amount, payment_method, options={}) end end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) commit(:capture) do |doc| add_invoice(doc, 'capture', amount, authorization, options) end end - def void(authorization, options={}) + def void(authorization, options = {}) _, amount, currency = split_authorization(authorization) commit(:void) do |doc| add_invoice(doc, 'void', amount.to_i, authorization, options.merge(currency: currency)) end end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) commit(:refund) do |doc| add_invoice(doc, 'refund', amount, authorization, options) end end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) commit(:verify) do |doc| add_invoice(doc, 'verify', 100, credit_card, options) add_payment_method(doc, credit_card, options) @@ -78,7 +78,7 @@ def verify(credit_card, options={}) def verify_credentials response = void('0') - !['01', '04'].include?(response.error_code) + !%w[01 04].include?(response.error_code) end def supports_scrubbing? @@ -109,6 +109,7 @@ def add_invoice(doc, action, money, payment_method, options) def add_payment_method(doc, payment_method, options) return if payment_method.is_a?(String) + doc.card do doc.number(payment_method.number) doc.cvv(payment_method.verification_value) @@ -121,6 +122,7 @@ def add_payment_method(doc, payment_method, options) def add_customer_data(doc, payment_method, options) return if payment_method.is_a?(String) + doc.billing do doc.name do doc.first(payment_method.first_name) @@ -140,15 +142,14 @@ def add_address(doc, options) doc.city(address[:city] || 'City') doc.line1(address[:address1] || 'Address') return unless address + doc.line2(address[:address2]) if address[:address2] doc.zip(address[:zip]) if address[:zip] doc.region(address[:state]) if address[:state] end def add_ref(doc, action, payment_method) - if ['capture', 'refund', 'void'].include?(action) || payment_method.is_a?(String) - doc.ref(split_authorization(payment_method)[0]) - end + doc.ref(split_authorization(payment_method)[0]) if %w[capture refund void].include?(action) || payment_method.is_a?(String) end def add_authentication(doc) @@ -161,7 +162,7 @@ def lookup_country_code(code) country.code(:alpha2) end - def commit(action, amount=nil, currency=nil) + def commit(action, amount = nil, currency = nil) currency = default_currency if currency == nil request = build_xml_request { |doc| yield(doc) } response = ssl_post(live_url, request, headers) @@ -251,9 +252,7 @@ def message_from(succeeded, response) end def error_code_from(succeeded, response) - unless succeeded - response[:code] - end + response[:code] unless succeeded end def cvv_result(parsed) diff --git a/lib/active_merchant/billing/gateways/tns.rb b/lib/active_merchant/billing/gateways/tns.rb index 25ed79306a9..b84da916ef5 100644 --- a/lib/active_merchant/billing/gateways/tns.rb +++ b/lib/active_merchant/billing/gateways/tns.rb @@ -3,20 +3,21 @@ module Billing class TnsGateway < Gateway include MastercardGateway - class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url + class_attribute :live_na_url, :live_ap_url, :live_eu_url, :test_na_url, :test_ap_url, :test_eu_url - self.live_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/' - self.test_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/' + VERSION = '52' - self.live_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/' - self.test_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/' + self.live_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.live_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/' self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US) self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] - + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro] end end end diff --git a/lib/active_merchant/billing/gateways/trans_first.rb b/lib/active_merchant/billing/gateways/trans_first.rb index 8b2c579683f..55215e8c0e0 100644 --- a/lib/active_merchant/billing/gateways/trans_first.rb +++ b/lib/active_merchant/billing/gateways/trans_first.rb @@ -5,7 +5,7 @@ class TransFirstGateway < Gateway self.live_url = 'https://webservices.primerchants.com' self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.transfirst.com/' self.display_name = 'TransFirst' @@ -18,7 +18,7 @@ class TransFirstGateway < Gateway purchase_echeck: 'ACHDebit', refund: 'CreditCardCredit', refund_echeck: 'ACHVoidTransaction', - void: 'CreditCardAutoRefundorVoid', + void: 'CreditCardAutoRefundorVoid' } ENDPOINTS = { @@ -46,7 +46,7 @@ def purchase(money, payment, options = {}) commit((payment.is_a?(Check) ? :purchase_echeck : :purchase), post) end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) post = {} transaction_id, payment_type = split_authorization(authorization) @@ -57,10 +57,10 @@ def refund(money, authorization, options={}) commit((payment_type == 'check' ? :refund_echeck : :refund), post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} - transaction_id, _ = split_authorization(authorization) + transaction_id, = split_authorization(authorization) add_pair(post, :TransID, transaction_id) commit(:void, post) @@ -133,6 +133,7 @@ def add_echeck(post, payment) def add_or_use_default(payment_data, default_value) return payment_data.capitalize if payment_data + return default_value end @@ -174,10 +175,10 @@ def commit(action, params) success_from(response), message_from(response), response, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response[:avs_code] }, - :cvv_result => response[:cvv2_code] + test: test?, + authorization: authorization_from(response), + avs_result: { code: response[:avs_code] }, + cvv_result: response[:cvv2_code] ) end diff --git a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb index fe94ffe357b..36a5d43084d 100644 --- a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +++ b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb @@ -12,7 +12,7 @@ class TransFirstTransactionExpressGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + self.supported_cardtypes = %i[visa master american_express discover diners_club] V1_NAMESPACE = 'http://postilion/realtime/merchantframework/xsd/v1/' SOAPENV_NAMESPACE = 'http://schemas.xmlsoap.org/soap/envelope/' @@ -152,7 +152,7 @@ class TransFirstTransactionExpressGateway < Gateway 'R1' => 'The transaction was declined or returned, because the cardholder requested that payment of all recurring or installment payment transactions for a specific merchant account be stopped/ Reserved for client-specific use (declined)', 'Q1' => 'Card Authentication failed/ Reserved for client-specific use (declined)', 'XA' => 'Forward to Issuer/ Reserved for client-specific use (declined)', - 'XD' => 'Forward to Issuer/ Reserved for client-specific use (declined)', + 'XD' => 'Forward to Issuer/ Reserved for client-specific use (declined)' } EXTENDED_RESPONSE_MESSAGES = { @@ -179,15 +179,15 @@ class TransFirstTransactionExpressGateway < Gateway refund_echeck: 16, void_echeck: 16, - wallet_sale: 14, + wallet_sale: 14 } - def initialize(options={}) + def initialize(options = {}) requires!(options, :gateway_id, :reg_key) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) if credit_card?(payment_method) action = :purchase request = build_xml_transaction_request do |doc| @@ -216,7 +216,7 @@ def purchase(amount, payment_method, options={}) commit(action, request) end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) if credit_card?(payment_method) request = build_xml_transaction_request do |doc| add_credit_card(doc, payment_method) @@ -234,7 +234,7 @@ def authorize(amount, payment_method, options={}) commit(:authorize, request) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) transaction_id = split_authorization(authorization)[1] request = build_xml_transaction_request do |doc| add_amount(doc, amount) @@ -244,7 +244,7 @@ def capture(amount, authorization, options={}) commit(:capture, request) end - def void(authorization, options={}) + def void(authorization, options = {}) action, transaction_id = split_authorization(authorization) request = build_xml_transaction_request do |doc| @@ -254,7 +254,7 @@ def void(authorization, options={}) commit(void_type(action), request) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) action, transaction_id = split_authorization(authorization) request = build_xml_transaction_request do |doc| @@ -265,7 +265,7 @@ def refund(amount, authorization, options={}) commit(refund_type(action), request) end - def credit(amount, payment_method, options={}) + def credit(amount, payment_method, options = {}) request = build_xml_transaction_request do |doc| add_pan(doc, payment_method) add_amount(doc, amount) @@ -274,7 +274,7 @@ def credit(amount, payment_method, options={}) commit(:credit, request) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) request = build_xml_transaction_request do |doc| add_credit_card(doc, credit_card) add_contact(doc, credit_card.name, options) @@ -283,7 +283,7 @@ def verify(credit_card, options={}) commit(:verify, request) end - def store(payment_method, options={}) + def store(payment_method, options = {}) store_customer_request = build_xml_payment_storage_request do |doc| store_customer_details(doc, payment_method.name, options) end @@ -291,6 +291,7 @@ def store(payment_method, options={}) MultiResponse.run do |r| r.process { commit(:store, store_customer_request) } return r unless r.success? && r.params['custId'] + customer_id = r.params['custId'] store_payment_method_request = build_xml_payment_storage_request do |doc| @@ -316,12 +317,13 @@ def scrub(transcript) gsub(%r((<[^>]+pan>)[^<]+(<))i, '\1[FILTERED]\2'). gsub(%r((<[^>]+sec>)[^<]+(<))i, '\1[FILTERED]\2'). gsub(%r((<[^>]+id>)[^<]+(<))i, '\1[FILTERED]\2'). - gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2') + gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<[^>]+acctNr>)[^<]+(<))i, '\1[FILTERED]\2') end private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") } CURRENCY_CODES['USD'] = '840' def headers @@ -333,11 +335,12 @@ def headers def commit(action, request) request = add_transaction_code_to_request(request, action) - raw_response = begin - ssl_post(url, request, headers) - rescue ActiveMerchant::ResponseError => e - e.response.body - end + raw_response = + begin + ssl_post(url, request, headers) + rescue ActiveMerchant::ResponseError => e + e.response.body + end response = parse(raw_response) @@ -383,6 +386,7 @@ def success_from(response) def error_code_from(succeeded, response) return if succeeded + response['errorCode'] || response['rspCode'] end @@ -459,7 +463,7 @@ def build_xml_payment_search_request end end - def build_xml_request(wrapper, merchant_product_type=nil) + def build_xml_request(wrapper, merchant_product_type = nil) Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| xml['soapenv'].Envelope('xmlns:soapenv' => SOAPENV_NAMESPACE) do xml['soapenv'].Body do @@ -478,11 +482,11 @@ def add_transaction_code_to_request(request, action) doc = Nokogiri::XML::Document.parse(request) merc_nodeset = doc.xpath('//v1:merc', 'v1' => V1_NAMESPACE) - merc_nodeset.after "#{TRANSACTION_CODES[action]}" + merc_nodeset.after "#{TRANSACTION_CODES[action]}" doc.root.to_xml end - def add_merchant(doc, product_type=nil) + def add_merchant(doc, product_type = nil) doc['v1'].merc do doc['v1'].id @options[:gateway_id] doc['v1'].regKey @options[:reg_key] @@ -544,10 +548,10 @@ def add_contact(doc, fullname, options) end end doc['v1'].addrLn1 billing_address[:address1] if billing_address[:address1] - doc['v1'].addrLn2 billing_address[:address2] if billing_address[:address2] + doc['v1'].addrLn2 billing_address[:address2] unless billing_address[:address2].blank? doc['v1'].city billing_address[:city] if billing_address[:city] doc['v1'].state billing_address[:state] if billing_address[:state] - doc['v1'].zipCode billing_address[:zip] if billing_address[:zip] + doc['v1'].zipCode billing_address[:zip].delete('-') if billing_address[:zip] doc['v1'].ctry 'US' end @@ -559,10 +563,10 @@ def add_contact(doc, fullname, options) doc['v1'].ship do doc['v1'].fullName fullname unless fullname.blank? doc['v1'].addrLn1 shipping_address[:address1] if shipping_address[:address1] - doc['v1'].addrLn2 shipping_address[:address2] if shipping_address[:address2] + doc['v1'].addrLn2 shipping_address[:address2] unless shipping_address[:address2].blank? doc['v1'].city shipping_address[:city] if shipping_address[:city] doc['v1'].state shipping_address[:state] if shipping_address[:state] - doc['v1'].zipCode shipping_address[:zip] if shipping_address[:zip] + doc['v1'].zipCode shipping_address[:zip].delete('-') if shipping_address[:zip] doc['v1'].phone shipping_address[:phone].gsub(/\D/, '') if shipping_address[:phone] doc['v1'].email shipping_address[:email] if shipping_address[:email] end diff --git a/lib/active_merchant/billing/gateways/transact_pro.rb b/lib/active_merchant/billing/gateways/transact_pro.rb index 5aaa36d756d..bda2602c49d 100644 --- a/lib/active_merchant/billing/gateways/transact_pro.rb +++ b/lib/active_merchant/billing/gateways/transact_pro.rb @@ -12,17 +12,17 @@ class TransactProGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.transactpro.lv/business/online-payments-acceptance' self.display_name = 'Transact Pro' - def initialize(options={}) + def initialize(options = {}) requires!(options, :guid, :password, :terminal) super end - def purchase(amount, payment, options={}) + def purchase(amount, payment, options = {}) post = PostData.new add_invoice(post, amount, options) add_payment(post, payment) @@ -44,7 +44,7 @@ def purchase(amount, payment, options={}) end end - def authorize(amount, payment, options={}) + def authorize(amount, payment, options = {}) post = PostData.new add_invoice(post, amount, options) add_payment(post, payment) @@ -66,11 +66,9 @@ def authorize(amount, payment, options={}) end end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) identifier, original_amount = split_authorization(authorization) - if amount && (amount != original_amount) - raise ArgumentError.new("Partial capture is not supported, and #{amount.inspect} != #{original_amount.inspect}") - end + raise ArgumentError.new("Partial capture is not supported, and #{amount.inspect} != #{original_amount.inspect}") if amount && (amount != original_amount) post = PostData.new add_credentials(post) @@ -80,7 +78,7 @@ def capture(amount, authorization, options={}) commit('charge_hold', post, original_amount) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) identifier, original_amount = split_authorization(authorization) post = PostData.new @@ -91,7 +89,7 @@ def refund(amount, authorization, options={}) commit('refund', post) end - def void(authorization, options={}) + def void(authorization, options = {}) identifier, amount = split_authorization(authorization) post = PostData.new @@ -101,7 +99,7 @@ def void(authorization, options={}) commit('cancel_dms', post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -158,13 +156,13 @@ def add_payment_cc(post, credit_card) post[:expire] = "#{month}/#{year[2..3]}" end - def add_credentials(post, key=:guid) + def add_credentials(post, key = :guid) post[key] = @options[:guid] post[:pwd] = Digest::SHA1.hexdigest(@options[:password]) end def parse(body) - if body =~ /^ID:/ + if /^ID:/.match?(body) body.split('~').reduce(Hash.new) { |h, v| m = v.match('(.*?):(.*)') h.merge!(m[1].underscore.to_sym => m[2]) @@ -178,7 +176,7 @@ def parse(body) end end - def commit(action, parameters, amount=nil) + def commit(action, parameters, amount = nil) url = (test? ? test_url : live_url) response = parse(ssl_post(url, post_data(action, parameters))) @@ -199,7 +197,7 @@ def authorization_from(parameters, response, amount) end def split_authorization(authorization) - if authorization =~ /|/ + if /|/.match?(authorization) identifier, amount = authorization.split('|') [identifier, amount.to_i] else diff --git a/lib/active_merchant/billing/gateways/transax.rb b/lib/active_merchant/billing/gateways/transax.rb index 336a7fb31c3..7a40b9e2c00 100644 --- a/lib/active_merchant/billing/gateways/transax.rb +++ b/lib/active_merchant/billing/gateways/transax.rb @@ -9,14 +9,13 @@ class TransaxGateway < SmartPs self.supported_countries = ['US'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] # The homepage URL of the gateway self.homepage_url = 'https://www.nelixtransax.com/' # The name of the gateway self.display_name = 'NELiX TransaX' - end end end diff --git a/lib/active_merchant/billing/gateways/trexle.rb b/lib/active_merchant/billing/gateways/trexle.rb index 42451539b2c..00ab2578c66 100644 --- a/lib/active_merchant/billing/gateways/trexle.rb +++ b/lib/active_merchant/billing/gateways/trexle.rb @@ -10,7 +10,7 @@ class TrexleGateway < Gateway GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM TR TT UM US VA VN ZA) - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.homepage_url = 'https://trexle.com' self.display_name = 'Trexle' @@ -106,6 +106,7 @@ def add_customer_data(post, options) def add_address(post, creditcard, options) return if creditcard.kind_of?(String) + address = (options[:billing_address] || options[:address]) return unless address @@ -136,7 +137,7 @@ def add_creditcard(post, creditcard) name: creditcard.name ) elsif creditcard.kind_of?(String) - if creditcard =~ /^token_/ + if /^token_/.match?(creditcard) post[:card_token] = creditcard else post[:customer_token] = creditcard @@ -181,6 +182,7 @@ def success_response(body) def error_response(body) return invalid_response unless body['error'] + Response.new( false, body['error'], @@ -207,6 +209,7 @@ def token(response) def parse(body) return {} if body.blank? + JSON.parse(body) end diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb index 767fe06ccb9..88529d90c5d 100644 --- a/lib/active_merchant/billing/gateways/trust_commerce.rb +++ b/lib/active_merchant/billing/gateways/trust_commerce.rb @@ -67,7 +67,7 @@ module Billing #:nodoc: class TrustCommerceGateway < Gateway self.live_url = self.test_url = 'https://vault.trustcommerce.com/trans/' - SUCCESS_TYPES = ['approved', 'accepted'] + SUCCESS_TYPES = %w[approved accepted] DECLINE_CODES = { 'decline' => 'The credit card was declined', @@ -104,8 +104,10 @@ class TrustCommerceGateway < Gateway TEST_LOGIN = 'TestMerchant' TEST_PASSWORD = 'password' + VOIDABLE_ACTIONS = %w(preauth sale postauth credit) + self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master discover american_express diners_club jcb] self.supported_countries = ['US'] self.homepage_url = 'http://www.trustcommerce.com/' self.display_name = 'TrustCommerce' @@ -149,7 +151,7 @@ def test? def authorize(money, creditcard_or_billing_id, options = {}) parameters = { - :amount => amount(money), + amount: amount(money) } add_order_id(parameters, options) @@ -157,6 +159,8 @@ def authorize(money, creditcard_or_billing_id, options = {}) add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('preauth', parameters) end @@ -164,7 +168,7 @@ def authorize(money, creditcard_or_billing_id, options = {}) # to process a purchase are an amount in cents or a money object and a creditcard object or billingid string. def purchase(money, creditcard_or_billing_id, options = {}) parameters = { - :amount => amount(money), + amount: amount(money) } add_order_id(parameters, options) @@ -172,6 +176,8 @@ def purchase(money, creditcard_or_billing_id, options = {}) add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('sale', parameters) end @@ -179,11 +185,13 @@ def purchase(money, creditcard_or_billing_id, options = {}) # postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process # a postauthorization with TC, you need an amount in cents or a money object, and a TC transid. def capture(money, authorization, options = {}) + transaction_id, = split_authorization(authorization) parameters = { - :amount => amount(money), - :transid => authorization, + amount: amount(money), + transid: transaction_id } add_aggregator(parameters, options) + add_custom_fields(parameters, options) commit('postauth', parameters) end @@ -191,11 +199,15 @@ def capture(money, authorization, options = {}) # refund() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object, # that you want to refund, and a TC transid for the transaction that you are refunding. def refund(money, identification, options = {}) + transaction_id, = split_authorization(identification) + parameters = { - :amount => amount(money), - :transid => identification + amount: amount(money), + transid: transaction_id } + add_aggregator(parameters, options) + add_custom_fields(parameters, options) commit('credit', parameters) end @@ -214,18 +226,32 @@ def credit(money, identification, options = {}) # TrustCommerce to allow for reversal transactions before you can use this # method. # + # void() is also used to to cancel a capture (postauth), purchase (sale), + # or refund (credit) or a before it is sent for settlement. + # # NOTE: AMEX preauth's cannot be reversed. If you want to clear it more # quickly than the automatic expiration (7-10 days), you will have to # capture it and then immediately issue a credit for the same amount # which should clear the customers credit card with 48 hours according to # TC. def void(authorization, options = {}) + transaction_id, original_action = split_authorization(authorization) + action = (VOIDABLE_ACTIONS - ['preauth']).include?(original_action) ? 'void' : 'reversal' + parameters = { - :transid => authorization, + transid: transaction_id } + add_aggregator(parameters, options) + add_custom_fields(parameters, options) + + commit(action, parameters) + end - commit('reversal', parameters) + 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 @@ -242,29 +268,30 @@ def void(authorization, options = {}) def recurring(money, creditcard, options = {}) ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE - requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily]) - - cycle = case options[:periodicity] - when :monthly - '1m' - when :bimonthly - '2m' - when :weekly - '1w' - when :biweekly - '2w' - when :yearly - '1y' - when :daily - '1d' - end + requires!(options, %i[periodicity bimonthly monthly biweekly weekly yearly daily]) + + cycle = + case options[:periodicity] + when :monthly + '1m' + when :bimonthly + '2m' + when :weekly + '1w' + when :biweekly + '2w' + when :yearly + '1y' + when :daily + '1d' + end parameters = { - :amount => amount(money), - :cycle => cycle, - :verify => options[:verify] || 'y', - :billingid => options[:billingid] || nil, - :payments => options[:payments] || nil, + amount: amount(money), + cycle: cycle, + verify: options[:verify] || 'y', + billingid: options[:billingid] || nil, + payments: options[:payments] || nil } add_creditcard(parameters, creditcard) @@ -278,12 +305,14 @@ def recurring(money, creditcard, options = {}) def store(creditcard, options = {}) parameters = { - :verify => options[:verify] || 'y', - :billingid => options[:billingid] || options[:billing_id] || nil, + verify: options[:verify] || 'y', + billingid: options[:billingid] || options[:billing_id] || nil } add_creditcard(parameters, creditcard) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('store', parameters) end @@ -291,9 +320,11 @@ def store(creditcard, options = {}) # unstore() the information will be removed and a Response object will be returned indicating the success of the action. def unstore(identification, options = {}) parameters = { - :billingid => identification, + billingid: identification } + add_custom_fields(parameters, options) + commit('unstore', parameters) end @@ -306,11 +337,18 @@ def scrub(transcript) gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r((&?cc=)\d*(&?)), '\1[FILTERED]\2'). gsub(%r((&?password=)[^&]+(&?)), '\1[FILTERED]\2'). - gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2') + gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?account=)\d*(&?)), '\1[FILTERED]\2') end private + def add_custom_fields(params, options) + options[:custom_fields]&.each do |key, value| + params[key.to_sym] = value + end + end + def add_aggregator(params, options) if @options[:aggregator_id] || application_id != Gateway.application_id params[:aggregators] = 1 @@ -386,9 +424,7 @@ def clean_and_stringify_params(parameters) # symbol keys. Before sending our input to TCLink, we convert all our keys to strings and dump the symbol keys. # We also remove any pairs with nil values, as these confuse TCLink. parameters.keys.reverse_each do |key| - if parameters[key] - parameters[key.to_s] = parameters[key] - end + parameters[key.to_s] = parameters[key] if parameters[key] parameters.delete(key) end end @@ -409,16 +445,19 @@ def commit(action, parameters) TCLink.send(parameters) else parse(ssl_post(self.live_url, post_data(parameters))) - end + end # 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, - :test => test?, - :authorization => data['transid'], - :cvv_result => data['cvv'], - :avs_result => { :code => data['avs'] } + Response.new( + success, + message, + data, + test: test?, + authorization: authorization_from(action, data), + cvv_result: data['cvv'], + avs_result: { code: data['avs'] } ) end @@ -446,6 +485,20 @@ def message_from(data) end end + def authorization_from(action, data) + case action + when 'store' + data['billingid'] + when *VOIDABLE_ACTIONS + "#{data['transid']}|#{action}" + else + data['transid'] + end + end + + def split_authorization(authorization) + authorization&.split('|') + end end end end diff --git a/lib/active_merchant/billing/gateways/usa_epay.rb b/lib/active_merchant/billing/gateways/usa_epay.rb index 0558311bc11..8e69dd08f4c 100644 --- a/lib/active_merchant/billing/gateways/usa_epay.rb +++ b/lib/active_merchant/billing/gateways/usa_epay.rb @@ -5,7 +5,6 @@ module Billing #:nodoc: # depending on options passed to new. # class UsaEpayGateway < Gateway - self.abstract_class = true ## @@ -13,7 +12,7 @@ class UsaEpayGateway < Gateway # :software id or :live_url are passed in the options hash it will # create an instance of UsaEpayAdvancedGateway. # - def self.new(options={}) + def self.new(options = {}) if options.has_key?(:software_id) || options.has_key?(:live_url) UsaEpayAdvancedGateway.new(options) else diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index 23077a0fd87..dd16d383583 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -73,34 +73,34 @@ class UsaEpayAdvancedGateway < Gateway FAILURE_MESSAGE = 'Default Failure' #:nodoc: self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.supported_cardtypes = %i[visa master american_express discover diners_club jcb] self.homepage_url = 'http://www.usaepay.com/' self.display_name = 'USA ePay Advanced SOAP Interface' CUSTOMER_PROFILE_OPTIONS = { - :id => [:string, 'CustomerID'], # merchant assigned number - :notes => [:string, 'Notes'], - :data => [:string, 'CustomData'], - :url => [:string, 'URL'] + id: [:string, 'CustomerID'], # merchant assigned number + notes: [:string, 'Notes'], + data: [:string, 'CustomData'], + url: [:string, 'URL'] } #:nodoc: CUSTOMER_RECURRING_BILLING_OPTIONS = { - :enabled => [:boolean, 'Enabled'], - :schedule => [:string, 'Schedule'], - :number_left => [:integer, 'NumLeft'], - :currency => [:string, 'Currency'], - :description => [:string, 'Description'], - :order_id => [:string, 'OrderID'], - :user => [:string, 'User'], - :source => [:string, 'Source'], - :send_receipt => [:boolean, 'SendReceipt'], - :receipt_note => [:string, 'ReceiptNote'] + enabled: [:boolean, 'Enabled'], + schedule: [:string, 'Schedule'], + number_left: [:integer, 'NumLeft'], + currency: [:string, 'Currency'], + description: [:string, 'Description'], + order_id: [:string, 'OrderID'], + user: [:string, 'User'], + source: [:string, 'Source'], + send_receipt: [:boolean, 'SendReceipt'], + receipt_note: [:string, 'ReceiptNote'] } #:nodoc: CUSTOMER_POINT_OF_SALE_OPTIONS = { - :price_tier => [:string, 'PriceTier'], - :tax_class => [:string, 'TaxClass'], - :lookup_code => [:string, 'LookupCode'] + price_tier: [:string, 'PriceTier'], + tax_class: [:string, 'TaxClass'], + lookup_code: [:string, 'LookupCode'] } #:nodoc: CUSTOMER_OPTIONS = [ @@ -110,23 +110,23 @@ class UsaEpayAdvancedGateway < Gateway ].inject(:merge) #:nodoc: COMMON_ADDRESS_OPTIONS = { - :first_name => [:string, 'FirstName'], - :last_name => [:string, 'LastName'], - :city => [:string, 'City'], - :state => [:string, 'State'], - :zip => [:string, 'Zip'], - :country => [:string, 'Country'], - :phone => [:string, 'Phone'], - :email => [:string, 'Email'], - :fax => [:string, 'Fax'], - :company => [:string, 'Company'] + first_name: [:string, 'FirstName'], + last_name: [:string, 'LastName'], + city: [:string, 'City'], + state: [:string, 'State'], + zip: [:string, 'Zip'], + country: [:string, 'Country'], + phone: [:string, 'Phone'], + email: [:string, 'Email'], + fax: [:string, 'Fax'], + company: [:string, 'Company'] } #:nodoc: ADDRESS_OPTIONS = [ COMMON_ADDRESS_OPTIONS, { - :address1 => [:string, 'Street'], - :address2 => [:string, 'Street2'], + address1: [:string, 'Street'], + address2: [:string, 'Street2'] } ].inject(:merge) #:nodoc @@ -135,116 +135,116 @@ class UsaEpayAdvancedGateway < Gateway CUSTOMER_RECURRING_BILLING_OPTIONS, COMMON_ADDRESS_OPTIONS, { - :address1 => [:string, 'Address'], - :address2 => [:string, 'Address2'], + address1: [:string, 'Address'], + address2: [:string, 'Address2'] }, { - :card_number => [:string, 'CardNumber'], - :card_exp => [:string, 'CardExp'], - :account => [:string, 'Account'], - :routing => [:string, 'Routing'], - :check_format => [:string, 'CheckFormat'], - :record_type => [:string, 'RecordType'], + card_number: [:string, 'CardNumber'], + card_exp: [:string, 'CardExp'], + account: [:string, 'Account'], + routing: [:string, 'Routing'], + check_format: [:string, 'CheckFormat'], + record_type: [:string, 'RecordType'] } ].inject(:merge) #:nodoc CUSTOMER_TRANSACTION_REQUEST_OPTIONS = { - :command => [:string, 'Command'], - :ignore_duplicate => [:boolean, 'IgnoreDuplicate'], - :client_ip => [:string, 'ClientIP'], - :customer_receipt => [:boolean, 'CustReceipt'], - :customer_email => [:boolean, 'CustReceiptEmail'], - :customer_template => [:boolean, 'CustReceiptName'], - :merchant_receipt => [:boolean, 'MerchReceipt'], - :merchant_email => [:boolean, 'MerchReceiptEmail'], - :merchant_template => [:boolean, 'MerchReceiptName'], - :recurring => [:boolean, 'isRecurring'], - :verification_value => [:string, 'CardCode'], - :software => [:string, 'Software'] + command: [:string, 'Command'], + ignore_duplicate: [:boolean, 'IgnoreDuplicate'], + client_ip: [:string, 'ClientIP'], + customer_receipt: [:boolean, 'CustReceipt'], + customer_email: [:boolean, 'CustReceiptEmail'], + customer_template: [:boolean, 'CustReceiptName'], + merchant_receipt: [:boolean, 'MerchReceipt'], + merchant_email: [:boolean, 'MerchReceiptEmail'], + merchant_template: [:boolean, 'MerchReceiptName'], + recurring: [:boolean, 'isRecurring'], + verification_value: [:string, 'CardCode'], + software: [:string, 'Software'] } #:nodoc: TRANSACTION_REQUEST_OBJECT_OPTIONS = { - :command => [:string, 'Command'], - :ignore_duplicate => [:boolean, 'IgnoreDuplicate'], - :authorization_code => [:string, 'AuthCode'], - :reference_number => [:string, 'RefNum'], - :account_holder => [:string, 'AccountHolder'], - :client_ip => [:string, 'ClientIP'], - :customer_id => [:string, 'CustomerID'], - :customer_receipt => [:boolean, 'CustReceipt'], - :customer_template => [:boolean, 'CustReceiptName'], - :software => [:string, 'Software'] + command: [:string, 'Command'], + ignore_duplicate: [:boolean, 'IgnoreDuplicate'], + authorization_code: [:string, 'AuthCode'], + reference_number: [:string, 'RefNum'], + account_holder: [:string, 'AccountHolder'], + client_ip: [:string, 'ClientIP'], + customer_id: [:string, 'CustomerID'], + customer_receipt: [:boolean, 'CustReceipt'], + customer_template: [:boolean, 'CustReceiptName'], + software: [:string, 'Software'] } #:nodoc: TRANSACTION_DETAIL_OPTIONS = { - :invoice => [:string, 'Invoice'], - :po_number => [:string, 'PONum'], - :order_id => [:string, 'OrderID'], - :clerk => [:string, 'Clerk'], - :terminal => [:string, 'Terminal'], - :table => [:string, 'Table'], - :description => [:string, 'Description'], - :comments => [:string, 'Comments'], - :allow_partial_auth => [:boolean, 'AllowPartialAuth'], - :currency => [:string, 'Currency'], - :non_tax => [:boolean, 'NonTax'], + invoice: [:string, 'Invoice'], + po_number: [:string, 'PONum'], + order_id: [:string, 'OrderID'], + clerk: [:string, 'Clerk'], + terminal: [:string, 'Terminal'], + table: [:string, 'Table'], + description: [:string, 'Description'], + comments: [:string, 'Comments'], + allow_partial_auth: [:boolean, 'AllowPartialAuth'], + currency: [:string, 'Currency'], + non_tax: [:boolean, 'NonTax'] } #:nodoc: TRANSACTION_DETAIL_MONEY_OPTIONS = { - :amount => [:double, 'Amount'], - :tax => [:double, 'Tax'], - :tip => [:double, 'Tip'], - :non_tax => [:boolean, 'NonTax'], - :shipping => [:double, 'Shipping'], - :discount => [:double, 'Discount'], - :subtotal => [:double, 'Subtotal'] + amount: [:double, 'Amount'], + tax: [:double, 'Tax'], + tip: [:double, 'Tip'], + non_tax: [:boolean, 'NonTax'], + shipping: [:double, 'Shipping'], + discount: [:double, 'Discount'], + subtotal: [:double, 'Subtotal'] } #:nodoc: CREDIT_CARD_DATA_OPTIONS = { - :magnetic_stripe => [:string, 'MagStripe'], - :dukpt => [:string, 'DUKPT'], - :signature => [:string, 'Signature'], - :terminal_type => [:string, 'TermType'], - :magnetic_support => [:string, 'MagSupport'], - :xid => [:string, 'XID'], - :cavv => [:string, 'CAVV'], - :eci => [:integer, 'ECI'], - :internal_card_authorization => [:boolean, 'InternalCardAuth'], - :pares => [:string, 'Pares'] + magnetic_stripe: [:string, 'MagStripe'], + dukpt: [:string, 'DUKPT'], + signature: [:string, 'Signature'], + terminal_type: [:string, 'TermType'], + magnetic_support: [:string, 'MagSupport'], + xid: [:string, 'XID'], + cavv: [:string, 'CAVV'], + eci: [:integer, 'ECI'], + internal_card_authorization: [:boolean, 'InternalCardAuth'], + pares: [:string, 'Pares'] } #:nodoc: CHECK_DATA_OPTIONS = { - :drivers_license => [:string, 'DriversLicense'], - :drivers_license_state => [:string, 'DriversLicenseState'], - :record_type => [:string, 'RecordType'], - :aux_on_us => [:string, 'AuxOnUS'], - :epc_code => [:string, 'EpcCode'], - :front_image => [:string, 'FrontImage'], - :back_image => [:string, 'BackImage'] + drivers_license: [:string, 'DriversLicense'], + drivers_license_state: [:string, 'DriversLicenseState'], + record_type: [:string, 'RecordType'], + aux_on_us: [:string, 'AuxOnUS'], + epc_code: [:string, 'EpcCode'], + front_image: [:string, 'FrontImage'], + back_image: [:string, 'BackImage'] } #:nodoc: RECURRING_BILLING_OPTIONS = { - :schedule => [:string, 'Schedule'], - :number_left => [:integer, 'NumLeft'], - :enabled => [:boolean, 'Enabled'] + schedule: [:string, 'Schedule'], + number_left: [:integer, 'NumLeft'], + enabled: [:boolean, 'Enabled'] } #:nodoc: AVS_RESULTS = { - 'Y' => %w( YYY Y YYA YYD ), - 'Z' => %w( NYZ Z ), - 'A' => %w( YNA A YNY ), - 'N' => %w( NNN N NN ), - 'X' => %w( YYX X ), - 'W' => %w( NYW W ), - 'XXW' => %w( XXW ), - 'XXU' => %w( XXU ), - 'R' => %w( XXR R U E ), - 'S' => %w( XXS S ), - 'XXE' => %w( XXE ), - 'G' => %w( XXG G C I ), - 'B' => %w( YYG B M ), - 'D' => %w( GGG D ), - 'P' => %w( YGG P ) + 'Y' => %w(YYY Y YYA YYD), + 'Z' => %w(NYZ Z), + 'A' => %w(YNA A YNY), + 'N' => %w(NNN N NN), + 'X' => %w(YYX X), + 'W' => %w(NYW W), + 'XXW' => %w(XXW), + 'XXU' => %w(XXU), + 'R' => %w(XXR R U E), + 'S' => %w(XXS S), + 'XXE' => %w(XXE), + 'G' => %w(XXG G C I), + 'B' => %w(YYG B M), + 'D' => %w(GGG D), + 'P' => %w(YGG P) }.inject({}) do |map, (type, codes)| codes.each { |code| map[code] = type } map @@ -289,43 +289,43 @@ def initialize(options = {}) # # Note: See run_transaction for additional options. # - def purchase(money, creditcard, options={}) - run_sale(options.merge!(:amount => money, :payment_method => creditcard)) + def purchase(money, creditcard, options = {}) + run_sale(options.merge!(amount: money, payment_method: creditcard)) end # Authorize an amount on a credit card or account. # # Note: See run_transaction for additional options. # - def authorize(money, creditcard, options={}) - run_auth_only(options.merge!(:amount => money, :payment_method => creditcard)) + def authorize(money, creditcard, options = {}) + run_auth_only(options.merge!(amount: money, payment_method: creditcard)) end # Capture an authorized transaction. # # Note: See run_transaction for additional options. # - def capture(money, identification, options={}) - capture_transaction(options.merge!(:amount => money, :reference_number => identification)) + def capture(money, identification, options = {}) + capture_transaction(options.merge!(amount: money, reference_number: identification)) end # Void a previous transaction that has not been settled. # # Note: See run_transaction for additional options. # - def void(identification, options={}) - void_transaction(options.merge!(:reference_number => identification)) + def void(identification, options = {}) + void_transaction(options.merge!(reference_number: identification)) end # Refund a previous transaction. # # Note: See run_transaction for additional options. # - def refund(money, identification, options={}) - refund_transaction(options.merge!(:amount => money, :reference_number => identification)) + def refund(money, identification, options = {}) + refund_transaction(options.merge!(amount: money, reference_number: identification)) end - def credit(money, identification, options={}) + def credit(money, identification, options = {}) ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -368,7 +368,7 @@ def credit(money, identification, options={}) # ==== Response # * #message -- customer number assigned by gateway # - def add_customer(options={}) + def add_customer(options = {}) request = build_request(__method__, options) commit(__method__, request) end @@ -381,7 +381,7 @@ def add_customer(options={}) # ==== Options # * Same as add_customer # - def update_customer(options={}) + def update_customer(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -429,7 +429,7 @@ def update_customer(options={}) # ==== Response # * #message -- boolean; Returns true if successful. Exception thrown all failures. # - def quick_update_customer(options={}) + def quick_update_customer(options = {}) requires! options, :customer_number requires! options, :update_data @@ -444,7 +444,7 @@ def quick_update_customer(options={}) # ==== Required # * :customer_number # - def enable_customer(options={}) + def enable_customer(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -456,7 +456,7 @@ def enable_customer(options={}) # ==== Required # * :customer_number # - def disable_customer(options={}) + def disable_customer(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -479,7 +479,7 @@ def disable_customer(options={}) # ==== Response # * #message -- method_id of new customer payment method # - def add_customer_payment_method(options={}) + def add_customer_payment_method(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -494,7 +494,7 @@ def add_customer_payment_method(options={}) # ==== Response # * #message -- either a single hash or an array of hashes of payment methods # - def get_customer_payment_methods(options={}) + def get_customer_payment_methods(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -510,7 +510,7 @@ def get_customer_payment_methods(options={}) # ==== Response # * #message -- hash of payment method # - def get_customer_payment_method(options={}) + def get_customer_payment_method(options = {}) requires! options, :customer_number, :method_id request = build_request(__method__, options) @@ -531,7 +531,7 @@ def get_customer_payment_method(options={}) # ==== Response # * #message -- hash of payment method # - def update_customer_payment_method(options={}) + def update_customer_payment_method(options = {}) requires! options, :method_id request = build_request(__method__, options) @@ -544,7 +544,7 @@ def update_customer_payment_method(options={}) # * :customer_number # * :method_id # - def delete_customer_payment_method(options={}) + def delete_customer_payment_method(options = {}) requires! options, :customer_number, :method_id request = build_request(__method__, options) @@ -556,7 +556,7 @@ def delete_customer_payment_method(options={}) # ==== Required # * :customer_number # - def delete_customer(options={}) + def delete_customer(options = {}) requires! options, :customer_number request = build_request(__method__, options) @@ -607,7 +607,7 @@ def delete_customer(options={}) # ==== Response # * #message -- transaction response hash # - def run_customer_transaction(options={}) + def run_customer_transaction(options = {}) requires! options, :customer_number, :command, :amount request = build_request(__method__, options) @@ -671,14 +671,14 @@ def run_customer_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def run_transaction(options={}) + def run_transaction(options = {}) request = build_request(__method__, options) commit(__method__, request) end - TRANSACTION_METHODS = [ - :run_sale, :run_auth_only, :run_credit, - :run_check_sale, :run_check_credit + TRANSACTION_METHODS = %i[ + run_sale run_auth_only run_credit + run_check_sale run_check_credit ] #:nodoc: TRANSACTION_METHODS.each do |method| @@ -699,7 +699,7 @@ def run_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def post_auth(options={}) + def post_auth(options = {}) requires! options, :authorization_code request = build_request(__method__, options) @@ -721,7 +721,7 @@ def post_auth(options={}) # ==== Response # * #message -- transaction response hash # - def capture_transaction(options={}) + def capture_transaction(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -738,7 +738,7 @@ def capture_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def void_transaction(options={}) + def void_transaction(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -757,7 +757,7 @@ def void_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def refund_transaction(options={}) + def refund_transaction(options = {}) requires! options, :reference_number, :amount request = build_request(__method__, options) @@ -777,7 +777,7 @@ def refund_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def override_transaction(options={}) + def override_transaction(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -820,7 +820,7 @@ def override_transaction(options={}) # ==== Response # * #message -- transaction response hash # - def run_quick_sale(options={}) + def run_quick_sale(options = {}) requires! options, :reference_number, :amount request = build_request(__method__, options) @@ -858,7 +858,7 @@ def run_quick_sale(options={}) # ==== Response # * #message -- transaction response hash # - def run_quick_credit(options={}) + def run_quick_credit(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -875,7 +875,7 @@ def run_quick_credit(options={}) # ==== Response # * #message -- transaction hash # - def get_transaction(options={}) + def get_transaction(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -892,7 +892,7 @@ def get_transaction(options={}) # * response.message -- message of the referenced transaction # * response.authorization -- same as :reference_number in options # - def get_transaction_status(options={}) + def get_transaction_status(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -990,7 +990,7 @@ def get_transaction_status(options={}) # ==== Response # * #message -- hash; keys are the field values # - def get_transaction_custom(options={}) + def get_transaction_custom(options = {}) requires! options, :reference_number, :fields request = build_request(__method__, options) @@ -1005,7 +1005,7 @@ def get_transaction_custom(options={}) # ==== Response # * #message -- check trace hash # - def get_check_trace(options={}) + def get_check_trace(options = {}) requires! options, :reference_number request = build_request(__method__, options) @@ -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 @@ -1078,7 +1080,7 @@ def build_add_customer(soap, options) end end - def build_customer(soap, options, type, add_customer_data=false) + def build_customer(soap, options, type, add_customer_data = false) soap.tag! "ns1:#{type}" do build_token soap, options build_tag soap, :integer, 'CustNum', options[:customer_number] @@ -1310,7 +1312,7 @@ def build_customer_payments(soap, options) if options[:payment_methods] length = options[:payment_methods].length soap.PaymentMethods 'SOAP-ENC:arrayType' => "ns1:PaymentMethod[#{length}]", - 'xsi:type' =>'ns1:PaymentMethodArray' do + 'xsi:type' => 'ns1:PaymentMethodArray' do build_customer_payment_methods soap, options end 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] @@ -1345,9 +1346,7 @@ def build_credit_card_or_check(soap, payment_method) when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check) build_tag soap, :string, 'Account', payment_method[:method].account_number build_tag soap, :string, 'Routing', payment_method[:method].routing_number - unless payment_method[:method].account_type.nil? - build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize - end + build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize unless payment_method[:method].account_type.nil? build_tag soap, :string, 'DriversLicense', options[:drivers_license] build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state] build_tag soap, :string, 'RecordType', options[:record_type] @@ -1380,7 +1379,7 @@ def build_customer_transaction(soap, options) # Transaction Helpers =========================================== - def build_transaction_request_object(soap, options, name='Params') + def build_transaction_request_object(soap, options, name = 'Params') soap.tag! name, 'xsi:type' => 'ns1:TransactionRequestObject' do TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] @@ -1434,9 +1433,7 @@ def build_credit_card_data(soap, options) def build_card_expiration(options) month = options[:payment_method].month year = options[:payment_method].year - unless month.nil? || year.nil? - "#{"%02d" % month}#{year.to_s[-2..-1]}" - end + "#{'%02d' % month}#{year.to_s[-2..-1]}" unless month.nil? || year.nil? end def build_check_data(soap, options) @@ -1476,9 +1473,7 @@ def build_transaction_field_array(soap, options) def build_billing_address(soap, options) if options[:billing_address] - if options[:billing_address][:name] - options[:billing_address][:first_name], options[:billing_address][:last_name] = split_names(options[:billing_address][:name]) - end + options[:billing_address][:first_name], options[:billing_address][:last_name] = split_names(options[:billing_address][:name]) if options[:billing_address][:name] soap.BillingAddress 'xsi:type' => 'ns1:Address' do ADDRESS_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[:billing_address][k] @@ -1489,9 +1484,7 @@ def build_billing_address(soap, options) def build_shipping_address(soap, options) if options[:shipping_address] - if options[:shipping_address][:name] - options[:shipping_address][:first_name], options[:shipping_address][:last_name] = split_names(options[:shipping_address][:name]) - end + options[:shipping_address][:first_name], options[:shipping_address][:last_name] = split_names(options[:shipping_address][:name]) if options[:shipping_address][:name] soap.ShippingAddress 'xsi:type' => 'ns1:Address' do ADDRESS_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[:shipping_address][k] @@ -1528,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) @@ -1544,15 +1537,15 @@ def build_response(action, soap) success, message, response_params, - :test => test?, - :authorization => authorization, - :avs_result => avs_from(avs), - :cvv_result => cvv + test: test?, + authorization: authorization, + avs_result: avs_from(avs), + cvv_result: cvv ) end def avs_from(avs) - avs_params = { :code => avs } + avs_params = { code: avs } avs_params[:message] = AVS_CUSTOM_MESSAGES[avs] if AVS_CUSTOM_MESSAGES.key?(avs) avs_params end @@ -1560,7 +1553,7 @@ def avs_from(avs) def parse(action, soap) xml = REXML::Document.new(soap) root = REXML::XPath.first(xml, '//SOAP-ENV:Body') - response = root ? parse_element(root[0]) : { :response => soap } + response = root ? parse_element(root[0]) : { response: soap } success, message, authorization, avs, cvv = false, FAILURE_MESSAGE, nil, nil, nil @@ -1576,15 +1569,16 @@ def parse(action, soap) else success = true end - message = case action - when :get_customer_payment_methods - p['item'] - when :get_transaction_custom - items = p['item'].kind_of?(Array) ? p['item'] : [p['item']] - items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash } - else - p - end + message = + case action + when :get_customer_payment_methods + p['item'] + when :get_transaction_custom + items = p['item'].kind_of?(Array) ? p['item'] : [p['item']] + items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash } + else + p + end elsif response.respond_to?(:[]) && p = response[:response] message = p # when response is html end @@ -1614,7 +1608,6 @@ def parse_element(node) response end - end end end diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb index 5b1931fb3a5..1faa8291fb2 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb @@ -4,19 +4,20 @@ class UsaEpayTransactionGateway < Gateway self.live_url = 'https://www.usaepay.com/gate' self.test_url = 'https://sandbox.usaepay.com/gate' - self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_cardtypes = %i[visa master american_express] self.supported_countries = ['US'] self.homepage_url = 'http://www.usaepay.com/' self.display_name = 'USA ePay' TRANSACTIONS = { - :authorization => 'cc:authonly', - :purchase => 'cc:sale', - :capture => 'cc:capture', - :refund => 'cc:refund', - :void => 'cc:void', - :void_release => 'cc:void:release', - :check_purchase => 'check:sale' + authorization: 'cc:authonly', + purchase: 'cc:sale', + capture: 'cc:capture', + refund: 'cc:refund', + void: 'cc:void', + void_release: 'cc:void:release', + check_purchase: 'check:sale', + store: 'cc:save' } STANDARD_ERROR_CODE_MAPPING = { @@ -33,9 +34,9 @@ class UsaEpayTransactionGateway < Gateway '10110' => STANDARD_ERROR_CODE[:incorrect_address], '10111' => STANDARD_ERROR_CODE[:incorrect_address], '10127' => STANDARD_ERROR_CODE[:card_declined], - '10128' => STANDARD_ERROR_CODE[:processing_error], - '10132' => STANDARD_ERROR_CODE[:processing_error], - '00043' => STANDARD_ERROR_CODE[:call_issuer] + '00043' => STANDARD_ERROR_CODE[:call_issuer], + '10205' => STANDARD_ERROR_CODE[:card_declined], + '10204' => STANDARD_ERROR_CODE[:pickup_card] } def initialize(options = {}) @@ -43,14 +44,14 @@ def initialize(options = {}) super end - def authorize(money, credit_card, options = {}) + def authorize(money, payment, options = {}) post = {} add_amount(post, money) add_invoice(post, options) - add_payment(post, credit_card) - unless credit_card.track_data.present? - add_address(post, credit_card, options) + add_payment(post, payment) + unless payment.is_a?(CreditCard) && payment.track_data.present? + add_address(post, payment, options) add_customer_data(post, options) end add_split_payments(post, options) @@ -82,7 +83,7 @@ def purchase(money, payment, options = {}) end def capture(money, authorization, options = {}) - post = { :refNum => authorization } + post = { refNum: authorization } add_amount(post, money) add_test_mode(post, options) @@ -90,13 +91,19 @@ def capture(money, authorization, options = {}) end def refund(money, authorization, options = {}) - post = { :refNum => authorization } + post = { refNum: authorization } add_amount(post, money) add_test_mode(post, options) commit(:refund, post) end + def store(payment, options = {}) + post = {} + add_payment(post, payment, options) + commit(:store, post) + end + def verify(creditcard, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(1, creditcard, options) } @@ -107,7 +114,7 @@ def verify(creditcard, options = {}) # Pass `no_release: true` to keep the void from immediately settling def void(authorization, options = {}) command = (options[:no_release] ? :void : :void_release) - post = { :refNum => authorization } + post = { refNum: authorization } add_test_mode(post, options) commit(command, post) end @@ -153,13 +160,9 @@ def add_customer_data(post, options) end end - if options.has_key? :customer - post[:custid] = options[:customer] - end + post[:custid] = options[:customer] if options.has_key? :customer - if options.has_key? :ip - post[:ip] = options[:ip] - end + post[:ip] = options[:ip] if options.has_key? :ip end def add_address(post, payment, options) @@ -202,12 +205,13 @@ def add_invoice(post, options) post[:description] = options[:description] end - def add_payment(post, payment, options={}) + def add_payment(post, payment, options = {}) if payment.respond_to?(:routing_number) post[:checkformat] = options[:check_format] if options[:check_format] if payment.account_type account_type = payment.account_type.to_s.capitalize raise ArgumentError, 'account_type must be checking or savings' unless %w(Checking Savings).include?(account_type) + post[:accounttype] = account_type end post[:account] = payment.account_number @@ -216,6 +220,8 @@ def add_payment(post, payment, options={}) elsif payment.respond_to?(:track_data) && payment.track_data.present? post[:magstripe] = payment.track_data post[:cardpresent] = true + elsif payment.is_a?(String) + post[:card] = payment else post[:card] = payment.number post[:cvv2] = payment.verification_value if payment.verification_value? @@ -232,6 +238,7 @@ def add_test_mode(post, options) # see: http://wiki.usaepay.com/developer/transactionapi#split_payments def add_split_payments(post, options) return unless options[:split_payments].is_a?(Array) + options[:split_payments].each_with_index do |payment, index| prefix = '%02d' % (index + 2) post["#{prefix}key"] = payment[:key] @@ -245,6 +252,7 @@ def add_split_payments(post, options) def add_recurring_fields(post, options) return unless options[:recurring_fields].is_a?(Hash) + options[:recurring_fields].each do |key, value| if value == true value = 'yes' @@ -252,9 +260,7 @@ def add_recurring_fields(post, options) next end - if key == :bill_amount - value = amount(value) - end + value = amount(value) if key == :bill_amount post[key.to_s.delete('_')] = value end @@ -274,6 +280,7 @@ def add_custom_fields(post, options) # see: https://wiki.usaepay.com/developer/transactionapi#line_item_details def add_line_items(post, options) return unless options[:line_items].is_a?(Array) + options[:line_items].each_with_index do |line_item, index| %w(product_ref_num sku qty name description taxable tax_rate tax_amount commodity_code discount_rate discount_amount).each do |key| post["line#{index}#{key.delete('_')}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym) @@ -281,7 +288,7 @@ def add_line_items(post, options) { quantity: 'qty', - unit: 'um', + unit: 'um' }.each do |key, umkey| post["line#{index}#{umkey}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym) end @@ -298,32 +305,39 @@ def parse(body) end { - :status => fields['UMstatus'], - :auth_code => fields['UMauthCode'], - :ref_num => fields['UMrefNum'], - :batch => fields['UMbatch'], - :avs_result => fields['UMavsResult'], - :avs_result_code => fields['UMavsResultCode'], - :cvv2_result => fields['UMcvv2Result'], - :cvv2_result_code => fields['UMcvv2ResultCode'], - :vpas_result_code => fields['UMvpasResultCode'], - :result => fields['UMresult'], - :error => fields['UMerror'], - :error_code => fields['UMerrorcode'], - :acs_url => fields['UMacsurl'], - :payload => fields['UMpayload'] - }.delete_if { |k, v| v.nil? } + status: fields['UMstatus'], + auth_code: fields['UMauthCode'], + ref_num: fields['UMrefNum'], + card_ref: fields['UMcardRef'], + batch: fields['UMbatch'], + avs_result: fields['UMavsResult'], + avs_result_code: fields['UMavsResultCode'], + cvv2_result: fields['UMcvv2Result'], + cvv2_result_code: fields['UMcvv2ResultCode'], + vpas_result_code: fields['UMvpasResultCode'], + result: fields['UMresult'], + error: fields['UMerror'], + error_code: fields['UMerrorcode'], + acs_url: fields['UMacsurl'], + payload: fields['UMpayload'] + }.delete_if { |_k, v| v.nil? } end def commit(action, parameters) url = (test? ? self.test_url : self.live_url) response = parse(ssl_post(url, post_data(action, parameters))) - Response.new(response[:status] == 'Approved', message_from(response), response, - :test => test?, - :authorization => response[:ref_num], - :cvv_result => response[:cvv2_result_code], - :avs_result => { :code => response[:avs_result_code] }, - :error_code => STANDARD_ERROR_CODE_MAPPING[response[:error_code]] + approved = response[:status] == 'Approved' + error_code = nil + error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved + Response.new( + approved, + message_from(response), + response, + test: test?, + authorization: authorization_from(action, response), + cvv_result: response[:cvv2_result_code], + avs_result: { code: response[:avs_result_code] }, + error_code: error_code ) end @@ -332,17 +346,22 @@ def message_from(response) return 'Success' else return 'Unspecified error' if response[:error].blank? + return response[:error] end end + def authorization_from(action, response) + return (action == :store ? response[:card_ref] : response[:ref_num]) + end + def post_data(action, parameters = {}) parameters[:command] = TRANSACTIONS[action] parameters[:key] = @options[:login] parameters[:software] = 'Active Merchant' parameters[:testmode] = (@options[:test] ? 1 : 0) unless parameters.has_key?(:testmode) seed = SecureRandom.hex(32).upcase - hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}") + hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:pin] || @options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}") parameters[:hash] = "s/#{seed}/#{hash}/n" parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join('&') diff --git a/lib/active_merchant/billing/gateways/vanco/vanco.rb b/lib/active_merchant/billing/gateways/vanco/vanco.rb index 40c40864a91..acc791c49fd 100644 --- a/lib/active_merchant/billing/gateways/vanco/vanco.rb +++ b/lib/active_merchant/billing/gateways/vanco/vanco.rb @@ -10,24 +10,33 @@ class VancoGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://vancopayments.com/' self.display_name = 'Vanco Payment Solutions' - def initialize(options={}) + SECONDS_PER_DAY = 3600 * 24 + BUFFER_TIME_IN_SECS = 60 * 3 + + def initialize(options = {}) requires!(options, :user_id, :password, :client_id) super end - def purchase(money, payment_method, options={}) - MultiResponse.run do |r| - r.process { login } - r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) } + def purchase(money, payment_method, options = {}) + moment_less_than_24_hours_ago = Time.now - SECONDS_PER_DAY - BUFFER_TIME_IN_SECS + + if options[:session_id] && options[:session_id][:created_at] >= moment_less_than_24_hours_ago + commit(purchase_request(money, payment_method, options[:session_id][:id], options), :response_transactionref) + else + MultiResponse.run do |r| + r.process { login } + r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) } + end end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) MultiResponse.run do |r| r.process { login } r.process { commit(refund_request(money, authorization, r.params['response_sessionid']), :response_creditrequestreceived) } @@ -108,6 +117,7 @@ def success_from(response, success_field_name) def message_from(succeeded, response) return 'Success' if succeeded + response[:error_message] end @@ -180,7 +190,7 @@ def add_amount(doc, money, options) doc.Amount(amount(money)) elsif options[:fund_id].respond_to?(:each_pair) doc.Funds do - options[:fund_id].each_pair do |k,v| + options[:fund_id].each_pair do |k, v| doc.Fund do doc.FundID(k) doc.FundAmount(amount(v)) @@ -299,7 +309,6 @@ def headers 'Content-Type' => 'text/xml' } end - end end end diff --git a/lib/active_merchant/billing/gateways/vanco/vanco_common.rb b/lib/active_merchant/billing/gateways/vanco/vanco_common.rb index 6842c74fa19..610dccea62d 100644 --- a/lib/active_merchant/billing/gateways/vanco/vanco_common.rb +++ b/lib/active_merchant/billing/gateways/vanco/vanco_common.rb @@ -2,220 +2,220 @@ module ActiveMerchant module Billing module VancoCommon VANCO_ERROR_CODE = { - '10' => "Invalid UserID/password combination", - '11' => "Session expired", - '25' => "All default address fields are required", - '32' => "Name is required", - '33' => "Unknown bank/bankpk", - '34' => "Valid PaymentType is required", - '35' => "Valid Routing Number Is Required", - '63' => "Invalid StartDate", - '65' => "Specified fund reference is not valid.", - '66' => "Invalid End Date", - '67' => "Transaction must have at least one transaction fund.", - '68' => "User is Inactive", - '69' => "Expiration Date Invalid", - '70' => "Account Type must be “C”, “S' for ACH and must be blank for Credit Card", - '71' => "Class Code must be PPD, CCD, TEL, WEB, RCK or blank.", - '72' => "Missing Client Data: Client ID", - '73' => "Missing Customer Data: Customer ID or Name or Last Name & First Name", - '74' => "PaymentMethod is required.", - '76' => "Transaction Type is required", - '77' => "Missing Credit Card Data: Card # or Expiration Date", - '78' => "Missing ACH Data: Routing # or Account #", - '79' => "Missing Transaction Data: Amount or Start Date", - '80' => "Account Number has invalid characters in it", - '81' => "Account Number has too many characters in it", - '82' => "Customer name required", - '83' => "Customer ID has not been set", + '10' => 'Invalid UserID/password combination', + '11' => 'Session expired', + '25' => 'All default address fields are required', + '32' => 'Name is required', + '33' => 'Unknown bank/bankpk', + '34' => 'Valid PaymentType is required', + '35' => 'Valid Routing Number Is Required', + '63' => 'Invalid StartDate', + '65' => 'Specified fund reference is not valid.', + '66' => 'Invalid End Date', + '67' => 'Transaction must have at least one transaction fund.', + '68' => 'User is Inactive', + '69' => 'Expiration Date Invalid', + '70' => 'Account Type must be “C”, “S" for ACH and must be blank for Credit Card', + '71' => 'Class Code must be PPD, CCD, TEL, WEB, RCK or blank.', + '72' => 'Missing Client Data: Client ID', + '73' => 'Missing Customer Data: Customer ID or Name or Last Name & First Name', + '74' => 'PaymentMethod is required.', + '76' => 'Transaction Type is required', + '77' => 'Missing Credit Card Data: Card # or Expiration Date', + '78' => 'Missing ACH Data: Routing # or Account #', + '79' => 'Missing Transaction Data: Amount or Start Date', + '80' => 'Account Number has invalid characters in it', + '81' => 'Account Number has too many characters in it', + '82' => 'Customer name required', + '83' => 'Customer ID has not been set', '86' => "NextSettlement does not fall in today's processing dates", - '87' => "Invalid FrequencyPK", - '88' => "Processed yesterday", - '89' => "Duplicate Transaction (matches another with PaymentMethod and NextSettlement)", - '91' => "Dollar amount for transaction is over the allowed limit", - '92' => "Invalid client reference occurred. - Transaction WILL NOT process", - '94' => "Customer ID already exists for this client", - '95' => "Payment Method is missing Account Number", - '101' => "Dollar Amount for transaction cannot be negative", + '87' => 'Invalid FrequencyPK', + '88' => 'Processed yesterday', + '89' => 'Duplicate Transaction (matches another with PaymentMethod and NextSettlement)', + '91' => 'Dollar amount for transaction is over the allowed limit', + '92' => 'Invalid client reference occurred. - Transaction WILL NOT process', + '94' => 'Customer ID already exists for this client', + '95' => 'Payment Method is missing Account Number', + '101' => 'Dollar Amount for transaction cannot be negative', '102' => "Updated transaction's dollar amount violates amount limit", - '105' => "PaymentMethod Date not valid yet.", - '125' => "Email Address is required.", - '127' => "User Is Not Proofed", - '134' => "User does not have access to specified client.", - '157' => "Client ID is required", - '158' => "Specified Client is invalid", - '159' => "Customer ID required", - '160' => "Customer ID is already in use", - '161' => "Customer name required", - '162' => "Invalid Date Format", - '163' => "Transaction Type is required", - '164' => "Transaction Type is invalid", - '165' => "Fund required", - '166' => "Customer Required", - '167' => "Payment Method Not Found", - '168' => "Amount Required", - '169' => "Amount Exceeds Limit. Set up manually.", - '170' => "Start Date Required", - '171' => "Invalid Start Date", - '172' => "End Date earlier than Start Date", - '173' => "Cannot Prenote a Credit Card", - '174' => "Cannot Prenote processed account", - '175' => "Transaction pending for Prenote account", - '176' => "Invalid Account Type", - '177' => "Account Number Required", - '178' => "Invalid Routing Number", + '105' => 'PaymentMethod Date not valid yet.', + '125' => 'Email Address is required.', + '127' => 'User Is Not Proofed', + '134' => 'User does not have access to specified client.', + '157' => 'Client ID is required', + '158' => 'Specified Client is invalid', + '159' => 'Customer ID required', + '160' => 'Customer ID is already in use', + '161' => 'Customer name required', + '162' => 'Invalid Date Format', + '163' => 'Transaction Type is required', + '164' => 'Transaction Type is invalid', + '165' => 'Fund required', + '166' => 'Customer Required', + '167' => 'Payment Method Not Found', + '168' => 'Amount Required', + '169' => 'Amount Exceeds Limit. Set up manually.', + '170' => 'Start Date Required', + '171' => 'Invalid Start Date', + '172' => 'End Date earlier than Start Date', + '173' => 'Cannot Prenote a Credit Card', + '174' => 'Cannot Prenote processed account', + '175' => 'Transaction pending for Prenote account', + '176' => 'Invalid Account Type', + '177' => 'Account Number Required', + '178' => 'Invalid Routing Number', '179' => "Client doesn't accept Credit Card Transactions", - '180' => "Client is in test mode for Credit Cards", - '181' => "Client is cancelled for Credit Cards", - '182' => "Name on Credit Card is Required", - '183' => "Invalid Expiration Date", - '184' => "Complete Billing Address is Required", - '185' => "Fund ID is Required", - '187' => "Fund Name Required", - '195' => "Transaction Cannot Be Deleted", - '196' => "Recurring Telephone Entry Transaction NOT Allowed", - '197' => "Cannot delete default fund", - '198' => "Invalid State", - '199' => "Start Date Is Later Than Expiration date", - '201' => "Frequency Required", - '202' => "Account Cannot Be Deleted, Active Transaction Exists", - '203' => "Client Does Not Accept ACH Transactions", - '204' => "Duplicate Transaction", - '210' => "Recurring Credits NOT Allowed", - '211' => "ONHold/Cancelled Customer", - '217' => "End Date Cannot Be Earlier Than The Last Settlement Date", - '218' => "Fund ID Cannot Be W, P, T, or C", - '223' => "Customer ID not on file", - '224' => "Credit Card Credits NOT Allowed - Must Be Refunded", - '231' => "Customer Not Found For Client", - '232' => "Invalid Account Number", - '233' => "Invalid Country Code", - '234' => "Transactions Are Not Allow From This Country", - '242' => "Valid State Required", - '251' => "Transactionref Required", - '284' => "User Has Been Deleted", - '286' => "Client not set up for International Credit Card Processing", - '296' => "Client Is Cancelled", - '328' => "Credit Pending - Cancel Date cannot be earlier than Today", - '329' => "Credit Pending - Account cannot be placed on hold until Tomorrow", - '341' => "Cancel Date Cannot be Greater Than Today", - '344' => "Phone Number Must be 10 Digits Long", - '365' => "Invalid Email Address", - '378' => "Invalid Loginkey", - '379' => "Requesttype Unavailable", - '380' => "Invalid Sessionid", - '381' => "Invalid Clientid for Session", - '383' => "Internal Handler Error. Contact Vanco Payment Solutions.", - '384' => "Invalid Requestid", - '385' => "Duplicate Requestid", - '390' => "Requesttype Not Authorized For User", - '391' => "Requesttype Not Authorized For Client", - '392' => "Invalid Value Format", - '393' => "Blocked IP", - '395' => "Transactions cannot be processed on Weekends", - '404' => "Invalid Date", - '410' => "Credits Cannot Be WEB or TEL", - '420' => "Transaction Not Found", - '431' => "Client Does Not Accept International Credit Cards", - '432' => "Can not process credit card", - '434' => "Credit Card Processor Error", - '445' => "Cancel Date Cannot Be Prior to the Last Settlement Date", - '446' => "End Date Cannot Be In The Past", - '447' => "Masked Account", - '469' => "Card Number Not Allowed", - '474' => "MasterCard Not Accepted", - '475' => "Visa Not Accepted", - '476' => "American Express Not Accepted", - '477' => "Discover Not Accepted", - '478' => "Invalid Account Number", - '485' => "Client Must Have One Default Fund", - '489' => "Customer ID Exceeds 15 Characters", - '490' => "Too Many Results, Please Narrow Search", - '495' => "Field Contains Invalid Characters", - '496' => "Field contains Too Many Characters", - '497' => "Invalid Zip Code", - '498' => "Invalid City", - '499' => "Invalid Canadian Postal Code", - '500' => "Invalid Canadian Province", - '506' => "User Not Found", - '511' => "Amount Exceeds Limit", - '512' => "Client Not Set Up For Credit Card Processing", - '515' => "Transaction Already Refunded", - '516' => "Can Not Refund a Refund", - '517' => "Invalid Customer", - '518' => "Invalid Payment Method", - '519' => "Client Only Accepts Debit Cards", - '520' => "Transaction Max for Account Number Reached", - '521' => "Thirty Day Max for Client Reached", - '522' => "Service Not Allowed from Outside the Company (if using IP whitelist)", - '523' => "Invalid Login Request", - '527' => "Change in account/routing# or type", - '535' => "SSN Required", - '549' => "CVV2 Number is Required", - '550' => "Invalid Client ID", - '556' => "Invalid Banking Information", - '569' => "Please Contact This Organization for Assistance with Processing This Transaction", - '570' => "City Required", - '571' => "Zip Code Required", - '572' => "Canadian Province Required", - '573' => "Canadian Postal Code Required", - '574' => "Country Code Required", - '578' => "Unable to Read Card Information. Please Click “Click to Swipe” Button and Try Again.", - '610' => "Invalid Banking Information. Previous Notification of Change Received for this Account", - '629' => "Invalid CVV2", - '641' => "Fund ID Not Found", - '642' => "Request Amount Exceeds Total Transaction Amount", - '643' => "Phone Extension Required", - '645' => "Invalid Zip Code", - '652' => "Invalid SSN", - '653' => "SSN Required", - '657' => "Billing State Required", - '659' => "Phone Number Required", - '663' => "Version Not Supported", - '665' => "Invalid Billing Address", - '666' => "Customer Not On Hold", - '667' => "Account number for fund is invalid", - '678' => "Password Expired", - '687' => "Fund Name is currently in use. Please choose another name. If you would like to use this Fund Name, go to the other fund and change the Fund Name to something different.", - '688' => "Fund ID is currently in use. Please choose another number. If you would like to use this Fund ID, go to the other fund and change the Fund ID to something different.", - '705' => "Please Limit Your Date Range To 30 Days", - '706' => "Last Digits of Account Number Required", - '721' => "MS Transaction Amount Cannot Be Greater Than $50,000.", - '725' => "User ID is for Web Services Only", - '730' => "Start Date Required", - '734' => "Date Range Cannot Be Greater Than One Year", - '764' => "Start Date Cannot Occur In The Past", - '800' => "The CustomerID Does Not Match The Given CustomerRef", - '801' => "Default Payment Method Not Found", - '838' => "Transaction Cannot Be Processed. Please contact your organization.", - '842' => "Invalid Pin", - '844' => "Phone Number Must be 10 Digits Long", - '850' => "Invalid Authentication Signature", - '857' => "Fund Name Can Not Be Greater Than 30 Characters", - '858' => "Fund ID Can Not Be Greater Than 20 Characters", - '859' => "Customer Is Unproofed", - '862' => "Invalid Start Date", - '866' => "Invalid Track Data", - '956' => "Amount Must Be Greater Than $0.00", - '960' => "Date of Birth Required", - '963' => "Missing Field", - '973' => "No match found for these credentials.", - '974' => "Recurring Return Fee Not Allowed", - '992' => "No Transaction Returned Within the Past 45 Days", - '993' => "Return Fee Must Be Collected Within 45 Days", - '994' => "Return Fee Is Greater Than the Return Fee Allowed", - '1005' => "Phone Extension Must Be All Digits", - '1008' => "We are sorry. This organization does not accept online credit card transactions. Please try again using a debit card.", - '1047' => "Invalid nvpvar variables", - '1054' => "Invalid Debit Card Only field", - '1059' => "No Matching Customer Found", - '1067' => "Invalid Original Request ID", - '1070' => "Transaction Cannot Be Voided", - '1073' => "Transaction Processed More Than 25 Minutes Ago", - '1127' => "Declined - Tran Not Permitted", - '1128' => "Unable To Process, Please Try Again", - '1150' => "Cannot determine which fund(s) to credit", + '180' => 'Client is in test mode for Credit Cards', + '181' => 'Client is cancelled for Credit Cards', + '182' => 'Name on Credit Card is Required', + '183' => 'Invalid Expiration Date', + '184' => 'Complete Billing Address is Required', + '185' => 'Fund ID is Required', + '187' => 'Fund Name Required', + '195' => 'Transaction Cannot Be Deleted', + '196' => 'Recurring Telephone Entry Transaction NOT Allowed', + '197' => 'Cannot delete default fund', + '198' => 'Invalid State', + '199' => 'Start Date Is Later Than Expiration date', + '201' => 'Frequency Required', + '202' => 'Account Cannot Be Deleted, Active Transaction Exists', + '203' => 'Client Does Not Accept ACH Transactions', + '204' => 'Duplicate Transaction', + '210' => 'Recurring Credits NOT Allowed', + '211' => 'ONHold/Cancelled Customer', + '217' => 'End Date Cannot Be Earlier Than The Last Settlement Date', + '218' => 'Fund ID Cannot Be W, P, T, or C', + '223' => 'Customer ID not on file', + '224' => 'Credit Card Credits NOT Allowed - Must Be Refunded', + '231' => 'Customer Not Found For Client', + '232' => 'Invalid Account Number', + '233' => 'Invalid Country Code', + '234' => 'Transactions Are Not Allow From This Country', + '242' => 'Valid State Required', + '251' => 'Transactionref Required', + '284' => 'User Has Been Deleted', + '286' => 'Client not set up for International Credit Card Processing', + '296' => 'Client Is Cancelled', + '328' => 'Credit Pending - Cancel Date cannot be earlier than Today', + '329' => 'Credit Pending - Account cannot be placed on hold until Tomorrow', + '341' => 'Cancel Date Cannot be Greater Than Today', + '344' => 'Phone Number Must be 10 Digits Long', + '365' => 'Invalid Email Address', + '378' => 'Invalid Loginkey', + '379' => 'Requesttype Unavailable', + '380' => 'Invalid Sessionid', + '381' => 'Invalid Clientid for Session', + '383' => 'Internal Handler Error. Contact Vanco Payment Solutions.', + '384' => 'Invalid Requestid', + '385' => 'Duplicate Requestid', + '390' => 'Requesttype Not Authorized For User', + '391' => 'Requesttype Not Authorized For Client', + '392' => 'Invalid Value Format', + '393' => 'Blocked IP', + '395' => 'Transactions cannot be processed on Weekends', + '404' => 'Invalid Date', + '410' => 'Credits Cannot Be WEB or TEL', + '420' => 'Transaction Not Found', + '431' => 'Client Does Not Accept International Credit Cards', + '432' => 'Can not process credit card', + '434' => 'Credit Card Processor Error', + '445' => 'Cancel Date Cannot Be Prior to the Last Settlement Date', + '446' => 'End Date Cannot Be In The Past', + '447' => 'Masked Account', + '469' => 'Card Number Not Allowed', + '474' => 'MasterCard Not Accepted', + '475' => 'Visa Not Accepted', + '476' => 'American Express Not Accepted', + '477' => 'Discover Not Accepted', + '478' => 'Invalid Account Number', + '485' => 'Client Must Have One Default Fund', + '489' => 'Customer ID Exceeds 15 Characters', + '490' => 'Too Many Results, Please Narrow Search', + '495' => 'Field Contains Invalid Characters', + '496' => 'Field contains Too Many Characters', + '497' => 'Invalid Zip Code', + '498' => 'Invalid City', + '499' => 'Invalid Canadian Postal Code', + '500' => 'Invalid Canadian Province', + '506' => 'User Not Found', + '511' => 'Amount Exceeds Limit', + '512' => 'Client Not Set Up For Credit Card Processing', + '515' => 'Transaction Already Refunded', + '516' => 'Can Not Refund a Refund', + '517' => 'Invalid Customer', + '518' => 'Invalid Payment Method', + '519' => 'Client Only Accepts Debit Cards', + '520' => 'Transaction Max for Account Number Reached', + '521' => 'Thirty Day Max for Client Reached', + '522' => 'Service Not Allowed from Outside the Company (if using IP whitelist)', + '523' => 'Invalid Login Request', + '527' => 'Change in account/routing# or type', + '535' => 'SSN Required', + '549' => 'CVV2 Number is Required', + '550' => 'Invalid Client ID', + '556' => 'Invalid Banking Information', + '569' => 'Please Contact This Organization for Assistance with Processing This Transaction', + '570' => 'City Required', + '571' => 'Zip Code Required', + '572' => 'Canadian Province Required', + '573' => 'Canadian Postal Code Required', + '574' => 'Country Code Required', + '578' => 'Unable to Read Card Information. Please Click “Click to Swipe” Button and Try Again.', + '610' => 'Invalid Banking Information. Previous Notification of Change Received for this Account', + '629' => 'Invalid CVV2', + '641' => 'Fund ID Not Found', + '642' => 'Request Amount Exceeds Total Transaction Amount', + '643' => 'Phone Extension Required', + '645' => 'Invalid Zip Code', + '652' => 'Invalid SSN', + '653' => 'SSN Required', + '657' => 'Billing State Required', + '659' => 'Phone Number Required', + '663' => 'Version Not Supported', + '665' => 'Invalid Billing Address', + '666' => 'Customer Not On Hold', + '667' => 'Account number for fund is invalid', + '678' => 'Password Expired', + '687' => 'Fund Name is currently in use. Please choose another name. If you would like to use this Fund Name, go to the other fund and change the Fund Name to something different.', + '688' => 'Fund ID is currently in use. Please choose another number. If you would like to use this Fund ID, go to the other fund and change the Fund ID to something different.', + '705' => 'Please Limit Your Date Range To 30 Days', + '706' => 'Last Digits of Account Number Required', + '721' => 'MS Transaction Amount Cannot Be Greater Than $50,000.', + '725' => 'User ID is for Web Services Only', + '730' => 'Start Date Required', + '734' => 'Date Range Cannot Be Greater Than One Year', + '764' => 'Start Date Cannot Occur In The Past', + '800' => 'The CustomerID Does Not Match The Given CustomerRef', + '801' => 'Default Payment Method Not Found', + '838' => 'Transaction Cannot Be Processed. Please contact your organization.', + '842' => 'Invalid Pin', + '844' => 'Phone Number Must be 10 Digits Long', + '850' => 'Invalid Authentication Signature', + '857' => 'Fund Name Can Not Be Greater Than 30 Characters', + '858' => 'Fund ID Can Not Be Greater Than 20 Characters', + '859' => 'Customer Is Unproofed', + '862' => 'Invalid Start Date', + '866' => 'Invalid Track Data', + '956' => 'Amount Must Be Greater Than $0.00', + '960' => 'Date of Birth Required', + '963' => 'Missing Field', + '973' => 'No match found for these credentials.', + '974' => 'Recurring Return Fee Not Allowed', + '992' => 'No Transaction Returned Within the Past 45 Days', + '993' => 'Return Fee Must Be Collected Within 45 Days', + '994' => 'Return Fee Is Greater Than the Return Fee Allowed', + '1005' => 'Phone Extension Must Be All Digits', + '1008' => 'We are sorry. This organization does not accept online credit card transactions. Please try again using a debit card.', + '1047' => 'Invalid nvpvar variables', + '1054' => 'Invalid Debit Card Only field', + '1059' => 'No Matching Customer Found', + '1067' => 'Invalid Original Request ID', + '1070' => 'Transaction Cannot Be Voided', + '1073' => 'Transaction Processed More Than 25 Minutes Ago', + '1127' => 'Declined - Tran Not Permitted', + '1128' => 'Unable To Process, Please Try Again', + '1150' => 'Cannot determine which fund(s) to credit' } end end -end \ No newline at end of file +end diff --git a/lib/active_merchant/billing/gateways/vanco/vanco_nvp.rb b/lib/active_merchant/billing/gateways/vanco/vanco_nvp.rb index a75b86f3b53..346d88b0807 100644 --- a/lib/active_merchant/billing/gateways/vanco/vanco_nvp.rb +++ b/lib/active_merchant/billing/gateways/vanco/vanco_nvp.rb @@ -14,36 +14,36 @@ class VancoNvpGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://vancopayments.com/' self.display_name = 'Vanco Payment Solutions' - def initialize(options={}) + def initialize(options = {}) requires!(options, :user_id, :password, :client_id, :client_key) super end - def funds(session_id, options={}) - results = MultiResponse.run do |r| + def funds(session_id, options = {}) + MultiResponse.run do |r| r.process { commit('funds', vanco_fund_list(session_id, options)) } end end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) MultiResponse.run do |r| r.process { login } r.process { commit('purchase', purchase_request(money, payment_method, r.params['sessionid'], options)) } end end - def vanco_purchase(session_id, customer_ref, payment_method_ref, options={}) + def vanco_purchase(session_id, customer_ref, payment_method_ref, options = {}) MultiResponse.run do |r| r.process { commit('purchase', vanco_purchase_request(customer_ref, payment_method_ref, session_id, options)) } end end - def refund(money, authorization, options={}) + def refund(money, authorization, options = {}) MultiResponse.run do |r| r.process { login } r.process { commit('refund', refund_request(money, authorization, r.params['sessionid'])) } @@ -83,14 +83,13 @@ def decrypt(value) c.key = @options[:client_key] c.padding = 0 decrypted = c.update(encrypted) + c.final - inflated = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(decrypted) + Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(decrypted) end def parse(body) - results = body.split(/\r?\n/).inject({}) do |acc, pair| + results = body.split(/\r?\n/).each_with_object({}) do |pair, acc| key, value = pair.split('=') acc[key] = CGI.unescape(value) - acc end if !results.include?('errorlist') && results.include?('nvpvar') results['nvpvar'] = decrypt(results['nvpvar']) @@ -116,13 +115,15 @@ def commit(request_type, params) def success_from(request_type, response) return (response['creditrequestreceived'] == 'yes') if request_type == 'refund' + empty?(response['errorlist']) end def message_from(succeeded, response) return 'Success' if succeeded - response['errorlist'].split(',').map do |no| - ((no == '434' && response['ccauthdesc']) ? response['ccauthdesc'] : VANCO_ERROR_CODE[no]) + + response['errorlist'].split(',').map do |no| + (no == '434' && response['ccauthdesc'] ? response['ccauthdesc'] : VANCO_ERROR_CODE[no]) end.join(', ') + '.' end @@ -168,7 +169,7 @@ def vanco_purchase_request(customer_ref, payment_method_ref, session_id, options add_amount(doc, 0, options) doc['nvpvar']['customerref'] = customer_ref unless customer_ref.blank? doc['nvpvar']['paymentmethodref'] = payment_method_ref unless payment_method_ref.blank? -# doc['accounttype'] = 'CC' + # doc['accounttype'] = 'CC' doc['nvpvar']['isdebitcardonly'] = 'No' add_options(doc, options) add_purchase_noise(doc) @@ -208,13 +209,13 @@ def add_amount(doc, money, options) if empty?(options[:fund_id]) doc['amount'] = amount(money) elsif options[:fund_id].respond_to?(:each_with_index) - options[:fund_id].each_with_index do |(k,v), i| + options[:fund_id].each_with_index do |(k, v), i| doc['nvpvar']["fundid_#{i}"] = k doc['nvpvar']["fundamount_#{i}"] = amount(v) end else - doc['nvpvar']["fundid_0"] = options[:fund_id] - doc['nvpvar']["fundamount_0"] = amount(money) + doc['nvpvar']['fundid_0'] = options[:fund_id] + doc['nvpvar']['fundamount_0'] = amount(money) end end @@ -306,15 +307,13 @@ def url end def headers - { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } end def post_data(doc) if doc.include?('nvpvar') - nvpvar = doc['nvpvar'].map { |k, v| "#{k.to_s}=#{v.to_s}" }.join('&') - if doc['nvpvar']['requesttype'] && doc['nvpvar']['requesttype'] != 'login' - nvpvar = encrypt(nvpvar) - end + nvpvar = doc['nvpvar'].map { |k, v| "#{k}=#{v}" }.join('&') + nvpvar = encrypt(nvpvar) if doc['nvpvar']['requesttype'] && doc['nvpvar']['requesttype'] != 'login' params = doc.merge({ 'nvpvar' => nvpvar }) else params = doc @@ -331,7 +330,7 @@ def encrypt(nvpvar) c.key = @options[:client_key] c.padding = 0 encrypted = c.update(padded) + c.final - nvpvar = Base64.urlsafe_encode64(encrypted) + Base64.urlsafe_encode64(encrypted) end end end diff --git a/lib/active_merchant/billing/gateways/verifi.rb b/lib/active_merchant/billing/gateways/verifi.rb index e435505d8be..5df036a5a77 100644 --- a/lib/active_merchant/billing/gateways/verifi.rb +++ b/lib/active_merchant/billing/gateways/verifi.rb @@ -5,8 +5,8 @@ module Billing #:nodoc: class VerifiGateway < Gateway class VerifiPostData < PostData # Fields that will be sent even if they are blank - self.required_fields = [:amount, :type, :ccnumber, :ccexp, :firstname, :lastname, - :company, :address1, :address2, :city, :state, :zip, :country, :phone] + self.required_fields = %i[amount type ccnumber ccexp firstname lastname + company address1 address2 city state zip country phone] end self.live_url = self.test_url = 'https://secure.verifi.com/gw/api/transact.php' @@ -50,16 +50,16 @@ class VerifiPostData < PostData SUCCESS = 1 TRANSACTIONS = { - :authorization => 'auth', - :purchase => 'sale', - :capture => 'capture', - :void => 'void', - :credit => 'credit', - :refund => 'refund' + authorization: 'auth', + purchase: 'sale', + capture: 'capture', + void: 'void', + credit: 'credit', + refund: 'refund' } self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'http://www.verifi.com/' self.display_name = 'Verifi' @@ -194,11 +194,14 @@ 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, - :test => test?, - :authorization => response[:transactionid], - :avs_result => { :code => response[:avsresponse] }, - :cvv_result => response[:cvvresponse] + 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] ) end diff --git a/lib/active_merchant/billing/gateways/viaklix.rb b/lib/active_merchant/billing/gateways/viaklix.rb index b68a8ec3ac9..dd49530a77f 100644 --- a/lib/active_merchant/billing/gateways/viaklix.rb +++ b/lib/active_merchant/billing/gateways/viaklix.rb @@ -8,13 +8,13 @@ class ViaklixGateway < Gateway self.delimiter = "\r\n" self.actions = { - :purchase => 'SALE', - :credit => 'CREDIT' + purchase: 'SALE', + credit: 'CREDIT' } APPROVED = '0' - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] self.supported_countries = ['US'] self.display_name = 'ViaKLIX' self.homepage_url = 'http://viaklix.com' @@ -49,9 +49,7 @@ def purchase(money, creditcard, options = {}) # Make a credit to a card (Void can only be done from the virtual terminal) # Viaklix does not support credits by reference. You must pass in the credit card def credit(money, creditcard, options = {}) - if creditcard.is_a?(String) - raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card' - end + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card' if creditcard.is_a?(String) form = {} add_invoice(form, options) @@ -109,9 +107,7 @@ def add_creditcard(form, creditcard) form[:card_number] = creditcard.number form[:exp_date] = expdate(creditcard) - if creditcard.verification_value? - add_verification_value(form, creditcard) - end + add_verification_value(form, creditcard) if creditcard.verification_value? form[:first_name] = creditcard.first_name.to_s.slice(0, 20) form[:last_name] = creditcard.last_name.to_s.slice(0, 30) @@ -140,11 +136,14 @@ 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, - :test => @options[:test] || test?, - :authorization => authorization_from(response), - :avs_result => { :code => response['avs_response'] }, - :cvv_result => response['cvv2_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'] ) end diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb index 70999dc58ee..6bd87e406c1 100644 --- a/lib/active_merchant/billing/gateways/visanet_peru.rb +++ b/lib/active_merchant/billing/gateways/visanet_peru.rb @@ -8,24 +8,24 @@ class VisanetPeruGateway < Gateway self.test_url = 'https://devapi.vnforapps.com/api.tokenization/api/v2/merchant' self.live_url = 'https://api.vnforapps.com/api.tokenization/api/v2/merchant' - self.supported_countries = ['US', 'PE'] + self.supported_countries = %w[US PE] self.default_currency = 'PEN' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_cardtypes = %i[visa master american_express discover] - def initialize(options={}) + def initialize(options = {}) requires!(options, :access_key_id, :secret_access_key, :merchant_id) super end - def purchase(amount, payment_method, options={}) + def purchase(amount, payment_method, options = {}) MultiResponse.run() do |r| r.process { authorize(amount, payment_method, options) } - r.process { capture(r.authorization, options) } + r.process { capture(amount, r.authorization, options) } end end - def authorize(amount, payment_method, options={}) + def authorize(amount, payment_method, options = {}) params = {} add_invoice(params, amount, options) @@ -37,32 +37,35 @@ def authorize(amount, payment_method, options={}) commit('authorize', params, options) end - def capture(authorization, options={}) + def capture(amount, authorization, options = {}) params = {} options[:id_unico] = split_authorization(authorization)[1] add_auth_order_id(params, authorization, options) commit('deposit', params, options) end - def void(authorization, options={}) + def void(authorization, options = {}) params = {} add_auth_order_id(params, authorization, options) commit('void', params, options) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) params = {} params[:amount] = amount(amount) if amount add_auth_order_id(params, authorization, options) response = commit('cancelDeposit', params, options) return response if response.success? || split_authorization(authorization).length == 1 || !options[:force_full_refund_if_unsettled] - # Attempt RefundSingleTransaction if unsettled + # Attempt RefundSingleTransaction if unsettled (and stash the original + # response message so it will be included it in the follow-up response + # message) + options[:error_message] = response.message prepare_refund_data(params, authorization, options) commit('refund', params, options) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -82,20 +85,20 @@ def scrub(transcript) private - CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") } CURRENCY_CODES['USD'] = 840 CURRENCY_CODES['PEN'] = 604 def add_invoice(params, money, options) - # Visanet Peru expects a 9-digit numeric purchaseNumber - params[:purchaseNumber] = (SecureRandom.random_number(900_000_000) + 100_000_000).to_s + # Visanet Peru expects a 12-digit alphanumeric purchaseNumber + params[:purchaseNumber] = generate_purchase_number_stamp params[:externalTransactionId] = options[:order_id] params[:amount] = amount(money) params[:currencyId] = CURRENCY_CODES[options[:currency] || currency(money)] end def add_auth_order_id(params, authorization, options) - purchase_number, _ = split_authorization(authorization) + purchase_number, = split_authorization(authorization) params[:purchaseNumber] = purchase_number params[:externalTransactionId] = options[:order_id] end @@ -139,7 +142,11 @@ def split_authorization(authorization) authorization.split('|') end - def commit(action, params, options={}) + def generate_purchase_number_stamp + 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) rescue ResponseError => e @@ -152,9 +159,9 @@ def commit(action, params, options={}) success_from(response), message_from(response, options, action), response, - :test => test?, - :authorization => authorization_from(params, response, options), - :error_code => response['errorCode'] + test: test?, + authorization: authorization_from(params, response, options), + error_code: response['errorCode'] ) end @@ -165,7 +172,7 @@ def headers } end - def url(action, params, options={}) + def url(action, params, options = {}) if action == 'authorize' "#{base_url}/#{@options[:merchant_id]}" elsif action == 'refund' @@ -197,15 +204,25 @@ def success_from(response) end def message_from(response, options, action) - if empty?(response['errorMessage']) || response['errorMessage'] == '[ ]' - action == 'refund' ? "#{response['data']['DSC_COD_ACCION']}, #{options[:error_message]}" : response['data']['DSC_COD_ACCION'] - elsif action == 'refund' - message = "#{response['errorMessage']}, #{options[:error_message]}" - options[:error_message] = response['errorMessage'] - message - else - response['errorMessage'] - end + message_from_messages( + response['errorMessage'], + action_code_description(response), + options[:error_message] + ) + end + + def message_from_messages(*args) + args.reject { |m| error_message_empty?(m) }.join(' | ') + end + + def action_code_description(response) + return nil unless response['data'] + + response['data']['DSC_COD_ACCION'] + end + + def error_message_empty?(error_message) + empty?(error_message) || error_message == '[ ]' end def response_error(raw_response, options, action) @@ -217,9 +234,9 @@ def response_error(raw_response, options, action) false, message_from(response, options, action), response, - :test => test?, - :authorization => response['transactionUUID'], - :error_code => response['errorCode'] + test: test?, + authorization: response['transactionUUID'], + error_code: response['errorCode'] ) end diff --git a/lib/active_merchant/billing/gateways/vpos.rb b/lib/active_merchant/billing/gateways/vpos.rb new file mode 100644 index 00000000000..25280eee8c3 --- /dev/null +++ b/lib/active_merchant/billing/gateways/vpos.rb @@ -0,0 +1,223 @@ +require 'digest' +require 'jwe' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class VposGateway < Gateway + self.test_url = 'https://vpos.infonet.com.py:8888' + self.live_url = 'https://vpos.infonet.com.py' + + self.supported_countries = ['PY'] + self.default_currency = 'PYG' + self.supported_cardtypes = %i[visa master panal] + + self.homepage_url = 'https://comercios.bancard.com.py' + self.display_name = 'vPOS' + + self.money_format = :dollars + + ENDPOINTS = { + pci_encryption_key: '/vpos/api/0.3/application/encryption-key', + pay_pci_buy_encrypted: '/vpos/api/0.3/pci/encrypted', + pci_buy_rollback: '/vpos/api/0.3/pci_buy/rollback', + refund: '/vpos/api/0.3/refunds' + } + + def initialize(options = {}) + requires!(options, :private_key, :public_key) + @private_key = options[:private_key] + @public_key = options[:public_key] + @encryption_key = OpenSSL::PKey::RSA.new(options[:encryption_key]) if options[:encryption_key] + @shop_process_id = options[:shop_process_id] || SecureRandom.random_number(10**15) + super + end + + def purchase(money, payment, options = {}) + commerce = options[:commerce] || @options[:commerce] + commerce_branch = options[:commerce_branch] || @options[:commerce_branch] + shop_process_id = options[:shop_process_id] || @shop_process_id + + token = generate_token(shop_process_id, 'pay_pci', commerce, commerce_branch, amount(money), currency(money)) + + post = {} + post[:token] = token + post[:commerce] = commerce.to_s + post[:commerce_branch] = commerce_branch.to_s + post[:shop_process_id] = shop_process_id + post[:number_of_payments] = options[:number_of_payments] || 1 + post[:recursive] = options[:recursive] || false + + add_invoice(post, money, options) + add_card_data(post, payment) + add_customer_data(post, options) + + commit(:pay_pci_buy_encrypted, post) + end + + def void(authorization, options = {}) + _, shop_process_id = authorization.to_s.split('#') + token = generate_token(shop_process_id, 'rollback', '0.00') + post = { + token: token, + shop_process_id: shop_process_id + } + commit(:pci_buy_rollback, post) + end + + def credit(money, payment, options = {}) + # Not permitted for foreign cards. + commerce = options[:commerce] || @options[:commerce] + commerce_branch = options[:commerce_branch] || @options[:commerce_branch] + + token = generate_token(@shop_process_id, 'refund', commerce, commerce_branch, amount(money), currency(money)) + post = {} + post[:token] = token + post[:commerce] = commerce.to_i + post[:commerce_branch] = commerce_branch.to_i + post[:shop_process_id] = @shop_process_id + add_invoice(post, money, options) + add_card_data(post, payment) + add_customer_data(post, options) + post[:origin_shop_process_id] = options[:original_shop_process_id] if options[:original_shop_process_id] + commit(:refund, post) + end + + def refund(money, authorization, options = {}) + commerce = options[:commerce] || @options[:commerce] + commerce_branch = options[:commerce_branch] || @options[:commerce_branch] + shop_process_id = options[:shop_process_id] || @shop_process_id + _, original_shop_process_id = authorization.to_s.split('#') + + token = generate_token(shop_process_id, 'refund', commerce, commerce_branch, amount(money), currency(money)) + post = {} + post[:token] = token + post[:commerce] = commerce.to_i + post[:commerce_branch] = commerce_branch.to_i + post[:shop_process_id] = shop_process_id + add_invoice(post, money, options) + add_customer_data(post, options) + post[:origin_shop_process_id] = original_shop_process_id || options[:original_shop_process_id] + commit(:refund, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + clean_transcript = remove_invalid_utf_8_byte_sequences(transcript) + clean_transcript. + gsub(/(token\\":\\")[.\-\w]+/, '\1[FILTERED]'). + gsub(/(card_encrypted_data\\":\\")[.\-\w]+/, '\1[FILTERED]') + end + + def remove_invalid_utf_8_byte_sequences(transcript) + transcript.encode('UTF-8', 'binary', undef: :replace, replace: '') + end + + # Required to encrypt PAN data. + def one_time_public_key + token = generate_token('get_encription_public_key', @public_key) + response = commit(:pci_encryption_key, token: token) + response.params['encryption_key'] + end + + private + + def generate_token(*elements) + Digest::MD5.hexdigest(@private_key + elements.join) + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) + end + + def add_card_data(post, payment) + card_number = payment.number + cvv = payment.verification_value + + payload = { card_number: card_number, 'cvv': cvv }.to_json + + encryption_key = @encryption_key || OpenSSL::PKey::RSA.new(one_time_public_key) + + post[:card_encrypted_data] = JWE.encrypt(payload, encryption_key) + post[:card_month_expiration] = format(payment.month, :two_digits) + post[:card_year_expiration] = format(payment.year, :two_digits) + end + + def add_customer_data(post, options) + post[:additional_data] = options[:additional_data] || '' # must be passed even if empty + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters) + url = build_request_url(action) + begin + response = parse(ssl_post(url, post_data(parameters))) + rescue ResponseError => e + # Errors are returned with helpful data, + # but get filtered out by `ssl_post` because of their HTTP status. + response = parse(e.response.body) + end + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: nil, + cvv_result: nil, + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + if code = response.dig('confirmation', 'response_code') + code == '00' + else + response['status'] == 'success' + end + end + + def message_from(response) + %w(confirmation refund).each do |m| + message = + response.dig(m, 'extended_response_description') || + response.dig(m, 'response_description') || + response.dig(m, 'response_details') + return message if message + end + [response.dig('messages', 0, 'key'), response.dig('messages', 0, 'dsc')].join(':') + end + + def authorization_from(response) + response_body = response.dig('confirmation') || response.dig('refund') + return unless response_body + + authorization_number = response_body.dig('authorization_number') || response_body.dig('authorization_code') + shop_process_id = response_body.dig('shop_process_id') + + "#{authorization_number}##{shop_process_id}" + end + + def error_code_from(response) + response.dig('confirmation', 'response_code') unless success_from(response) + end + + def build_request_url(action) + base_url = (test? ? test_url : live_url) + base_url + ENDPOINTS[action] + end + + def post_data(data) + { public_key: @public_key, + operation: data }.compact.to_json + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb index 29636897892..492fa8fa5ac 100644 --- a/lib/active_merchant/billing/gateways/webpay.rb +++ b/lib/active_merchant/billing/gateways/webpay.rb @@ -8,7 +8,7 @@ class WebpayGateway < StripeGateway self.supported_countries = ['JP'] self.default_currency = 'JPY' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] + self.supported_cardtypes = %i[visa master american_express jcb diners_club] self.homepage_url = 'https://webpay.jp/' self.display_name = 'WebPay' @@ -51,9 +51,9 @@ def store(creditcard, options = {}) MultiResponse.run(:first) do |r| r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/", post, options) } - return r unless options[:set_default] and r.success? and !r.params['id'].blank? + return r unless options[:set_default] && r.success? && !r.params['id'].blank? - r.process { update_customer(options[:customer], :default_card => r.params['id']) } + r.process { update_customer(options[:customer], default_card: r.params['id']) } end else commit(:post, 'customers', post, options) @@ -89,7 +89,7 @@ def headers(options = {}) 'Authorization' => 'Basic ' + Base64.encode64(@api_key.to_s + ':').strip, 'User-Agent' => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", 'X-Webpay-Client-User-Agent' => user_agent, - 'X-Webpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json + 'X-Webpay-Client-User-Metadata' => { ip: options[:ip] }.to_json } end end diff --git a/lib/active_merchant/billing/gateways/wepay.rb b/lib/active_merchant/billing/gateways/wepay.rb index 9ec05da7f7f..10804147ec5 100644 --- a/lib/active_merchant/billing/gateways/wepay.rb +++ b/lib/active_merchant/billing/gateways/wepay.rb @@ -4,8 +4,8 @@ class WepayGateway < Gateway self.test_url = 'https://stage.wepayapi.com/v2' self.live_url = 'https://wepayapi.com/v2' - self.supported_countries = ['US', 'CA'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = %w[US CA] + self.supported_cardtypes = %i[visa master american_express discover] self.homepage_url = 'https://www.wepay.com/' self.default_currency = 'USD' self.display_name = 'WePay' @@ -48,9 +48,7 @@ def capture(money, identifier, options = {}) post = {} post[:checkout_id] = checkout_id - if(money && (original_amount != amount(money))) - post[:amount] = amount(money) - end + post[:amount] = amount(money) if money && (original_amount != amount(money)) commit('/checkout/capture', post, options) end @@ -66,9 +64,7 @@ def refund(money, identifier, options = {}) post = {} post[:checkout_id] = checkout_id - if(money && (original_amount != amount(money))) - post[:amount] = amount(money) - end + post[:amount] = amount(money) if money && (original_amount != amount(money)) post[:refund_reason] = (options[:description] || 'Refund') post[:payer_email_message] = options[:payer_email_message] if options[:payer_email_message] post[:payee_email_message] = options[:payee_email_message] if options[:payee_email_message] @@ -85,12 +81,12 @@ def store(creditcard, options = {}) post[:expiration_month] = creditcard.month post[:expiration_year] = creditcard.year - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:address] = {} post[:address]['address1'] = billing_address[:address1] if billing_address[:address1] post[:address]['city'] = billing_address[:city] if billing_address[:city] post[:address]['country'] = billing_address[:country] if billing_address[:country] - post[:address]['region'] = billing_address[:state] if billing_address[:state] + post[:address]['region'] = billing_address[:state] if billing_address[:state] post[:address]['postal_code'] = billing_address[:zip] end @@ -172,13 +168,15 @@ def parse(response) JSON.parse(response) end - def commit(action, params, options={}) + def commit(action, params, options = {}) begin - response = parse(ssl_post( - ((test? ? test_url : live_url) + action), - params.to_json, - headers(options) - )) + response = parse( + ssl_post( + ((test? ? test_url : live_url) + action), + params.to_json, + headers(options) + ) + ) rescue ResponseError => e response = parse(e.response.body) end diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb index 21218896b6a..60f1aa1eca8 100644 --- a/lib/active_merchant/billing/gateways/wirecard.rb +++ b/lib/active_merchant/billing/gateways/wirecard.rb @@ -13,9 +13,9 @@ class WirecardGateway < Gateway 'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd' } - PERMITTED_TRANSACTIONS = %w[ PREAUTHORIZATION CAPTURE PURCHASE ] + PERMITTED_TRANSACTIONS = %w[PREAUTHORIZATION CAPTURE PURCHASE] - RETURN_CODES = %w[ ACK NOK ] + RETURN_CODES = %w[ACK NOK] # Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where: # xxx = Country code @@ -26,7 +26,7 @@ class WirecardGateway < Gateway # number 5551234 within area code 202 (country code 1). VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/ - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ] + self.supported_cardtypes = %i[visa master american_express diners_club jcb] self.supported_countries = %w(AD CY GI IM MT RO CH AT DK GR IT MC SM TR BE EE HU LV NL SK GB BG FI IS LI NO SI VA FR IL LT PL ES CZ DE IE LU PT SE) self.homepage_url = 'http://www.wirecard.com' self.display_name = 'Wirecard' @@ -179,11 +179,14 @@ def commit(action, money, options) message = response[:Message] authorization = response[:GuWID] - Response.new(success, message, response, - :test => test?, - :authorization => authorization, - :avs_result => { :code => avs_code(response, options) }, - :cvv_result => response[:CVCResponseCode] + Response.new( + success, + message, + response, + test: test?, + authorization: authorization, + avs_result: { code: avs_code(response, options) }, + cvv_result: response[:CVCResponseCode] ) rescue ResponseError => e if e.response.code == '401' @@ -197,7 +200,7 @@ def commit(action, money, options) def build_request(action, money, options) options = prepare_options_hash(options) options[:action] = action - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.tag! 'WIRECARD_BXML' do xml.tag! 'W_REQUEST' do @@ -263,6 +266,7 @@ def add_amount(xml, money, options) # Includes the credit-card data to the transaction-xml def add_creditcard(xml, creditcard) raise 'Creditcard must be supplied!' if creditcard.nil? + xml.tag! 'CREDIT_CARD_DATA' do xml.tag! 'CreditCardNumber', creditcard.number xml.tag! 'CVC2', creditcard.verification_value @@ -275,6 +279,7 @@ def add_creditcard(xml, creditcard) # Includes the IP address of the customer to the transaction-xml def add_customer_data(xml, options) return unless options[:ip] + xml.tag! 'CONTACT_DATA' do xml.tag! 'IPAddress', options[:ip] end @@ -283,6 +288,7 @@ def add_customer_data(xml, options) # Includes the address to the transaction-xml def add_address(xml, address) return if address.nil? + xml.tag! 'CORPTRUSTCENTER_DATA' do xml.tag! 'ADDRESS' do xml.tag! 'Address1', address[:address1] @@ -290,9 +296,7 @@ def add_address(xml, address) xml.tag! 'City', address[:city] xml.tag! 'ZipCode', address[:zip] - if address[:state] =~ /[A-Za-z]{2}/ && address[:country] =~ /^(us|ca)$/i - xml.tag! 'State', address[:state].upcase - end + xml.tag! 'State', address[:state].upcase if address[:state] =~ /[A-Za-z]{2}/ && address[:country] =~ /^(us|ca)$/i xml.tag! 'Country', address[:country] xml.tag! 'Phone', address[:phone] if address[:phone] =~ VALID_PHONE_FORMAT @@ -331,9 +335,7 @@ def parse_response(response, root) status = nil root.elements.to_a.each do |node| - if node.name =~ /FNC_CC_/ - status = REXML::XPath.first(node, 'CC_TRANSACTION/PROCESSING_STATUS') - end + status = REXML::XPath.first(node, 'CC_TRANSACTION/PROCESSING_STATUS') if node.name =~ /FNC_CC_/ end message = '' @@ -392,7 +394,7 @@ def errors_to_string(root) string << error[:Message] if error[:Message] error[:Advice].each_with_index do |advice, index| string << ' (' if index == 0 - string << "#{index+1}. #{advice}" + string << "#{index + 1}. #{advice}" string << ' and ' if index < error[:Advice].size - 1 string << ')' if index == error[:Advice].size - 1 end @@ -407,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 new file mode 100644 index 00000000000..ed0f4536039 --- /dev/null +++ b/lib/active_merchant/billing/gateways/wompi.rb @@ -0,0 +1,197 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WompiGateway < Gateway + self.test_url = 'https://sync.sandbox.wompi.co/v1' + self.live_url = 'https://sync.production.wompi.co/v1' + + self.supported_countries = ['CO'] + self.default_currency = 'COP' + self.supported_cardtypes = %i[visa master american_express] + + self.homepage_url = 'https://wompi.co/' + self.display_name = 'Wompi' + + self.money_format = :cents + + def initialize(options = {}) + ## Sandbox keys have prefix pub_test_ and prv_test_ + ## Production keys have prefix pub_prod_ and prv_prod_ + begin + requires!(options, :prod_private_key, :prod_public_key) + rescue ArgumentError + begin + requires!(options, :test_private_key, :test_public_key) + rescue ArgumentError + raise ArgumentError, 'Gateway requires both test_private_key and test_public_key, or both prod_private_key and prod_public_key' + end + end + super + end + + def purchase(money, payment, options = {}) + post = { + reference: options[:reference] || generate_reference, + public_key: public_key + } + add_invoice(post, money, options) + add_card(post, payment, options) + + commit('sale', post, '/transactions_sync') + end + + def authorize(money, payment, options = {}) + post = { + public_key: public_key, + type: 'CARD', + financial_operation: 'PREAUTHORIZATION' + } + add_auth_params(post, money, payment, options) + + commit('authorize', post, '/payment_sources_sync') + end + + def capture(money, authorization, options = {}) + post = { + reference: options[:reference] || generate_reference, + public_key: public_key, + payment_source_id: authorization.to_i + } + add_invoice(post, money, options) + commit('capture', post, '/transactions_sync') + end + + def refund(money, authorization, options = {}) + # post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s } + # commit('refund', post, '/refunds_sync') + + # All refunds will instead be voided. This is temporary. + void(authorization, options, money) + end + + def void(authorization, options = {}, money = nil) + post = money ? { amount_in_cents: amount(money).to_i } : {} + commit('void', post, "/transactions/#{authorization}/void_sync") + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript.gsub(/(Bearer )\w+/, '\1[REDACTED]'). + gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]'). + gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]'). + gsub(/(\\\"phone_number\\\":\\\")\+?\d+/, '\1[REDACTED]'). + gsub(/(\\\"email\\\":\\\")\S+\\\",/, '\1[REDACTED]\",'). + gsub(/(\\\"legal_id\\\":\\\")\d+/, '\1[REDACTED]') + end + + private + + def headers + { + 'Authorization' => "Bearer #{private_key}", + 'Content-Type' => 'application/json' + } + end + + def generate_reference + SecureRandom.alphanumeric(12) + end + + def private_key + test? ? options[:test_private_key] : options[:prod_private_key] + end + + def public_key + test? ? options[:test_public_key] : options[:prod_public_key] + end + + def add_invoice(post, money, options) + post[:amount_in_cents] = amount(money).to_i + post[:currency] = (options[:currency] || currency(money)) + end + + def add_card(post, card, options) + payment_method = { + type: 'CARD' + } + add_basic_card_info(payment_method, card, options) + post[:payment_method] = payment_method + end + + def add_auth_params(post, money, card, options) + data = { + amount_in_cents: amount(money).to_i, + currency: (options[:currency] || currency(money)) + } + add_basic_card_info(data, card, options) + post[:data] = data + end + + def add_basic_card_info(post, card, options) + installments = options[:installments] ? options[:installments].to_i : 1 + cvc = card.verification_value || nil + + post[:number] = card.number + post[:exp_month] = card.month.to_s.rjust(2, '0') + post[:exp_year] = card.year.to_s[2..3] + post[:installments] = installments + post[:card_holder] = card.name + post[:cvc] = cvc if cvc && !cvc.empty? + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters, endpoint) + url = (test? ? test_url : live_url) + endpoint + response = parse(ssl_post(url, post_data(action, parameters), headers)) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: nil, + cvv_result: nil, + test: test?, + error_code: error_code_from(response) + ) + end + + def handle_response(response) + case response.code.to_i + when 200...300, 401, 404, 422 + response.body + else + raise ResponseError.new(response) + end + end + + def success_from(response) + success_statuses.include? response.dig('data', 'status') + end + + def success_statuses + %w(APPROVED AVAILABLE) + end + + def message_from(response) + response.dig('data', 'status_message') || response.dig('error', 'reason') || response.dig('error', 'messages').to_json + end + + def authorization_from(response) + response.dig('data', 'transaction_id') || response.dig('data', 'id') || response.dig('data', 'transaction', 'id') + end + + def post_data(action, parameters = {}) + parameters.to_json + end + + def error_code_from(response) + response.dig('error', 'type') unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/world_net.rb b/lib/active_merchant/billing/gateways/world_net.rb index 70699b1a139..a0ad9df6fef 100644 --- a/lib/active_merchant/billing/gateways/world_net.rb +++ b/lib/active_merchant/billing/gateways/world_net.rb @@ -128,6 +128,7 @@ def add_customer_data(post, options) def add_address(post, _creditcard, options) address = options[:billing_address] || options[:address] return unless address + post[:address1] = address[:address1] post[:address2] = address[:address2] post[:city] = address[:city] @@ -278,49 +279,49 @@ def fields(action) # Gateway expects fields in fixed order below. case action when 'PAYMENT', 'PREAUTH' - [ - :orderid, - :terminalid, - :amount, - :datetime, - :cardnumber, :cardtype, :cardexpiry, :cardholdername, - :hash, - :currency, - :terminaltype, - :transactiontype, - :email, - :cvv, - :address1, :address2, - :postcode, - :description, - :city, :country, - :ipaddress + %i[ + orderid + terminalid + amount + datetime + cardnumber cardtype cardexpiry cardholdername + hash + currency + terminaltype + transactiontype + email + cvv + address1 address2 + postcode + description + city country + ipaddress ] when 'PREAUTHCOMPLETION' - [:uniqueref, :terminalid, :amount, :datetime, :hash] + %i[uniqueref terminalid amount datetime hash] when 'REFUND' - [:uniqueref, :terminalid, :amount, :datetime, :hash, - :operator, :reason] + %i[uniqueref terminalid amount datetime hash + operator reason] when 'VOID' [:uniqueref] when 'SECURECARDREGISTRATION' - [ - :merchantref, - :terminalid, - :datetime, - :cardnumber, :cardexpiry, :cardtype, :cardholdername, - :hash, - :dontchecksecurity, - :cvv, - :issueno + %i[ + merchantref + terminalid + datetime + cardnumber cardexpiry cardtype cardholdername + hash + dontchecksecurity + cvv + issueno ] when 'SECURECARDREMOVAL' - [ - :merchantref, - :cardreference, - :terminalid, - :datetime, - :hash + %i[ + merchantref + cardreference + terminalid + datetime + hash ] end end diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb index ca4cf11a316..b6ba7faf243 100644 --- a/lib/active_merchant/billing/gateways/worldpay.rb +++ b/lib/active_merchant/billing/gateways/worldpay.rb @@ -1,3 +1,5 @@ +require 'nokogiri' + module ActiveMerchant #:nodoc: module Billing #:nodoc: class WorldpayGateway < Gateway @@ -6,22 +8,24 @@ class WorldpayGateway < Gateway self.default_currency = 'GBP' self.money_format = :cents - self.supported_countries = %w(HK GB AU AD AR BE BR CA CH CN CO CR CY CZ DE DK ES FI FR GI GR HU IE IN IT JP LI LU MC MT MY MX NL NO NZ PA PE PL PT SE SG SI SM TR UM VA) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :elo] - self.currencies_without_fractions = %w(HUF IDR ISK JPY KRW) - self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND) + 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 BM BN BO BR BS BT BW + BY BZ CA CC CF CH CK CL CM CN CO CR CV CX CY CZ DE DJ DK DO DZ EC EE EG EH ES ET FI FJ FK + FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GT GU GW GY HK HM HN HR HT HU ID IE IL + IM IN IO IS IT JE JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LC LI LK LS LT LU LV MA MC MD + ME MG MH MK ML MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ + OM PA PE PF PH PK PL PN PR PT PW PY QA RE RO RS RU RW SA SB SC SE SG SI SK SL SM SN ST SV + SZ TC TD TF TG TH TJ TK TM TO TR TT TV TW TZ UA UG US UY UZ VA VC VE VI VN VU WF WS YE YT + ZA ZM) + self.supported_cardtypes = %i[visa master american_express discover jcb maestro elo naranja cabal unionpay] + self.currencies_without_fractions = %w(HUF IDR JPY KRW BEF XOF XAF XPF GRD GNF ITL LUF MGA MGF PYG PTE RWF ESP TRL VND KMF) + self.currencies_with_three_decimal_places = %w(BHD KWD OMR TND LYD JOD IQD) self.homepage_url = 'http://www.worldpay.com/' self.display_name = 'Worldpay Global' - 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' + NETWORK_TOKEN_TYPE = { + apple_pay: 'APPLEPAY', + google_pay: 'GOOGLEPAY', + network_token: 'NETWORKTOKEN' } AVS_CODE_MAP = { @@ -34,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 = {}) @@ -52,27 +56,30 @@ def initialize(options = {}) def purchase(money, payment_method, options = {}) MultiResponse.run do |r| r.process { authorize(money, payment_method, options) } - r.process { capture(money, r.authorization, options.merge(:authorization_validated => true)) } + r.process { capture(money, r.authorization, options.merge(authorization_validated: true)) } unless options[:skip_capture] end end def authorize(money, payment_method, options = {}) requires!(options, :order_id) - authorize_request(money, payment_method, options) + payment_details = payment_details(payment_method) + authorize_request(money, payment_method, payment_details.merge(options)) end def capture(money, authorization, options = {}) + authorization = order_id_from_authorization(authorization.to_s) MultiResponse.run do |r| - r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated] + r.process { inquire_request(authorization, options, 'AUTHORISED', 'CAPTURED') } unless options[:authorization_validated] if r.params authorization_currency = r.params['amount_currency_code'] - options = options.merge(:currency => authorization_currency) if authorization_currency.present? + options = options.merge(currency: authorization_currency) if authorization_currency.present? end r.process { capture_request(money, authorization, options) } end end def void(authorization, options = {}) + authorization = order_id_from_authorization(authorization.to_s) MultiResponse.run do |r| r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated] r.process { cancel_request(authorization, options) } @@ -80,15 +87,20 @@ def void(authorization, options = {}) end def refund(money, authorization, options = {}) + authorization = order_id_from_authorization(authorization.to_s) + success_criteria = %w(CAPTURED SETTLED SETTLED_BY_MERCHANT SENT_FOR_REFUND) + success_criteria.push('AUTHORIZED') if options[:cancel_or_refund] response = MultiResponse.run do |r| - r.process { inquire_request(authorization, options, 'CAPTURED', 'SETTLED', 'SETTLED_BY_MERCHANT') } + r.process { inquire_request(authorization, options, *success_criteria) } unless options[:authorization_validated] r.process { refund_request(money, authorization, options) } end - return response if response.success? - return response unless options[:force_full_refund_if_unsettled] - - void(authorization, options) if response.params['last_event'] == 'AUTHORISED' + if !response.success? && options[:force_full_refund_if_unsettled] && + response.params['last_event'] == 'AUTHORISED' + void(authorization, options) + else + response + end end # Credits only function on a Merchant ID/login/profile flagged for Payouts @@ -96,35 +108,57 @@ def refund(money, authorization, options = {}) # and other transactions should be performed on a normal eCom-flagged # merchant ID. def credit(money, payment_method, options = {}) - credit_request(money, payment_method, options.merge(:credit => true)) + payment_details = payment_details(payment_method) + if options[:fast_fund_credit] + fast_fund_credit_request(money, payment_method, payment_details.merge(credit: true, **options)) + else + credit_request(money, payment_method, payment_details.merge(credit: true, **options)) + end end - def verify(credit_card, options={}) + def verify(payment_method, options = {}) + amount = (eligible_for_0_auth?(payment_method, options) ? 0 : 100) MultiResponse.run(:use_first_response) do |r| - r.process { authorize(100, credit_card, options) } - r.process(:ignore_result) { void(r.authorization, options.merge(:authorization_validated => true)) } + r.process { authorize(amount, payment_method, options) } + r.process(:ignore_result) { void(r.authorization, options.merge(authorization_validated: true)) } end end + def store(credit_card, options = {}) + requires!(options, :customer) + store_request(credit_card, options) + end + + def inquire(authorization, options = {}) + order_id = order_id_from_authorization(authorization.to_s) || options[:order_id] + commit('direct_inquiry', build_order_inquiry_request(order_id, options), :ok, options) + end + def supports_scrubbing true end + def supports_network_tokenization? + true + end + def scrub(transcript) transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r(()\d+()), '\1[FILTERED]\2'). - gsub(%r(()[^<]+()), '\1[FILTERED]\2') + gsub(%r(()[^<]+()), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2'). + gsub(%r(()[^<]+()), '\1[FILTERED]\2') end private def authorize_request(money, payment_method, options) - commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', options) + commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', 'CAPTURED', options) end def capture_request(money, authorization, options) - commit('capture', build_capture_request(money, authorization, options), :ok, options) + commit('capture', build_capture_request(money, authorization, options), 'CAPTURED', :ok, options) end def cancel_request(authorization, options) @@ -136,18 +170,26 @@ def inquire_request(authorization, options, *success_criteria) end def refund_request(money, authorization, options) - commit('refund', build_refund_request(money, authorization, options), :ok, options) + commit('refund', build_refund_request(money, authorization, options), :ok, 'SENT_FOR_REFUND', options) end def credit_request(money, payment_method, options) commit('credit', build_authorization_request(money, payment_method, options), :ok, 'SENT_FOR_REFUND', options) end + def fast_fund_credit_request(money, payment_method, options) + commit('fast_credit', build_fast_fund_credit_request(money, payment_method, options), :ok, 'PUSH_APPROVED', options) + end + + def store_request(credit_card, options) + commit('store', build_store_request(credit_card, options), options) + end + def build_request - xml = Builder::XmlMarkup.new :indent => 2 - xml.instruct! :xml, :encoding => 'UTF-8' + xml = Builder::XmlMarkup.new indent: 2 + xml.instruct! :xml, encoding: 'UTF-8' xml.declare! :DOCTYPE, :paymentService, :PUBLIC, '-//WorldPay//DTD WorldPay PaymentService v1//EN', 'http://dtd.worldpay.com/paymentService_v1.dtd' - xml.tag! 'paymentService', 'version' => '1.4', 'merchantCode' => @options[:login] do + xml.paymentService 'version' => '1.4', 'merchantCode' => @options[:login] do yield xml end xml.target! @@ -155,8 +197,8 @@ def build_request def build_order_modify_request(authorization) build_request do |xml| - xml.tag! 'modify' do - xml.tag! 'orderModification', 'orderCode' => authorization do + xml.modify do + xml.orderModification 'orderCode' => authorization do yield xml end end @@ -165,64 +207,352 @@ def build_order_modify_request(authorization) def build_order_inquiry_request(authorization, options) build_request do |xml| - xml.tag! 'inquiry' do - xml.tag! 'orderInquiry', 'orderCode' => authorization + xml.inquiry do + xml.orderInquiry 'orderCode' => authorization end end end def build_authorization_request(money, payment_method, options) build_request do |xml| - xml.tag! 'submit' do - xml.tag! 'order', order_tag_attributes(options) do + xml.submit do + xml.order order_tag_attributes(options) do xml.description(options[:description].blank? ? 'Purchase' : options[:description]) add_amount(xml, money, options) - if options[:order_content] - xml.tag! 'orderContent' do - xml.cdata! options[:order_content] - end - end + add_order_content(xml, options) add_payment_method(xml, money, payment_method, options) - add_email(xml, options) - if options[:hcg_additional_data] - add_hcg_additional_data(xml, options) - end - if options[:instalments] - add_instalments_data(xml, options) - end + add_shopper(xml, options) + add_statement_narrative(xml, options) + add_risk_data(xml, options[:risk_data]) if options[:risk_data] + add_sub_merchant_data(xml, options[:sub_merchant_data]) if options[:sub_merchant_data] + add_hcg_additional_data(xml, options) if options[:hcg_additional_data] + add_instalments_data(xml, options) if options[:instalments] + add_additional_data(xml, money, options) if options[:level_2_data] || options[:level_3_data] + add_moto_flag(xml, options) if options.dig(:metadata, :manual_entry) + add_additional_3ds_data(xml, options) if options[:execute_threed] && options[:three_ds_version] && options[:three_ds_version] =~ /^2/ + add_3ds_exemption(xml, options) if options[:exemption_type] + end + end + end + end + + def add_additional_data(xml, amount, options) + level_two_data = options[:level_2_data] || {} + level_three_data = options[:level_3_data] || {} + level_two_and_three_data = level_two_data.merge(level_three_data).symbolize_keys + + xml.branchSpecificExtension do + xml.purchase do + add_level_two_and_three_data(xml, amount, level_two_and_three_data) + end + end + end + + 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', + discount_amount: 'discountAmount', + shipping_amount: 'shippingAmount', + duty_amount: 'dutyAmount' + }.each do |key, tag| + 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) + 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 + end + + def add_items_into_level_three_data(xml, item) + 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 end end end + def add_optional_data_level_two_and_three(xml, data) + xml.shipFromPostalCode data[:ship_from_postal_code] if data.include?(:ship_from_postal_code) + 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) + end + def order_tag_attributes(options) - { 'orderCode' => options[:order_id], 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v } + { 'orderCode' => clean_order_id(options[:order_id]), 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v.present? } + end + + def clean_order_id(order_id) + order_id.to_s.gsub(/(\s|\||<|>|'|")/, '')[0..64] + end + + def add_order_content(xml, options) + return unless options[:order_content] + + xml.orderContent do + xml.cdata! options[:order_content] + end end def build_capture_request(money, authorization, options) build_order_modify_request(authorization) do |xml| - xml.tag! 'capture' do + xml.capture do time = Time.now - xml.tag! 'date', 'dayOfMonth' => time.day, 'month' => time.month, 'year'=> time.year + xml.date 'dayOfMonth' => time.day, 'month' => time.month, 'year' => time.year add_amount(xml, money, options) end end end def build_void_request(authorization, options) - build_order_modify_request(authorization) do |xml| - xml.tag! 'cancel' + if options[:cancel_or_refund] + build_order_modify_request(authorization, &:cancelOrRefund) + else + build_order_modify_request(authorization, &:cancel) end end def build_refund_request(money, authorization, options) build_order_modify_request(authorization) do |xml| - xml.tag! 'refund' do - add_amount(xml, money, options.merge(:debit_credit_indicator => 'credit')) + if options[:cancel_or_refund] + # Worldpay docs claim amount must be passed. This causes an error. + xml.cancelOrRefund # { add_amount(xml, money, options.merge(debit_credit_indicator: 'credit')) } + else + xml.refund do + add_amount(xml, money, options.merge(debit_credit_indicator: 'credit')) + end + end + end + end + + def build_store_request(credit_card, options) + build_request do |xml| + xml.submit do + xml.paymentTokenCreate do + add_authenticated_shopper_id(xml, options) + xml.createToken + xml.paymentInstrument do + xml.cardDetails do + add_card(xml, credit_card, options) + end + end + add_transaction_identifier(xml, options) if network_transaction_id(options) + end + end + end + end + + def network_transaction_id(options) + options[:stored_credential_transaction_id] || options.dig(:stored_credential, :network_transaction_id) + end + + def add_transaction_identifier(xml, options) + xml.storedCredentials 'usage' => 'FIRST' do + xml.schemeTransactionIdentifier network_transaction_id(options) + end + end + + def build_fast_fund_credit_request(money, payment_method, options) + build_request do |xml| + xml.submit do + xml.order order_tag_attributes(options) do + xml.description(options[:description].blank? ? 'Fast Fund Credit' : options[:description]) + add_amount(xml, money, options) + add_order_content(xml, options) + add_payment_details_for_ff_credit(xml, payment_method, options) + add_shopper_id(xml, options) + end + end + end + end + + def add_payment_details_for_ff_credit(xml, payment_method, options) + xml.paymentDetails do + xml.tag! 'FF_DISBURSE-SSL' do + if payment_method.is_a?(CreditCard) + add_card_for_ff_credit(xml, payment_method, options) + else + add_token_for_ff_credit(xml, payment_method, options) + end + end + end + end + + def add_card_for_ff_credit(xml, payment_method, options) + xml.recipient do + xml.paymentInstrument do + xml.cardDetails do + add_card(xml, payment_method, options) + end end end end + def add_token_for_ff_credit(xml, payment_method, options) + return unless payment_method.is_a?(String) + + token_details = token_details_from_authorization(payment_method) + + xml.tag! 'recipient', 'tokenScope' => token_details[:token_scope] do + xml.paymentTokenID token_details[:token_id] + add_authenticated_shopper_id(xml, token_details) + end + end + + def add_additional_3ds_data(xml, options) + additional_data = { 'dfReferenceId' => options[:df_reference_id] } + additional_data['challengeWindowSize'] = options[:browser_size] if options[:browser_size] + + xml.additional3DSData additional_data + end + + def add_3ds_exemption(xml, options) + xml.exemption 'type' => options[:exemption_type], 'placement' => options[:exemption_placement] || 'AUTHORISATION' + end + + def add_risk_data(xml, risk_data) + xml.riskData do + add_authentication_risk_data(xml, risk_data[:authentication_risk_data]) + add_shopper_account_risk_data(xml, risk_data[:shopper_account_risk_data]) + add_transaction_risk_data(xml, risk_data[:transaction_risk_data]) + end + end + + def add_authentication_risk_data(xml, authentication_risk_data) + return unless authentication_risk_data + + timestamp = authentication_risk_data.fetch(:authentication_date, {}) + + xml.authenticationRiskData('authenticationMethod' => authentication_risk_data[:authentication_method]) do + xml.authenticationTimestamp do + xml.date( + 'dayOfMonth' => timestamp[:day_of_month], + 'month' => timestamp[:month], + 'year' => timestamp[:year], + 'hour' => timestamp[:hour], + 'minute' => timestamp[:minute], + 'second' => timestamp[:second] + ) + end + end + end + + def add_sub_merchant_data(xml, options) + xml.subMerchantData do + xml.pfId options[:pf_id] if options[:pf_id] + xml.subName options[:sub_name] if options[:sub_name] + xml.subId options[:sub_id] if options[:sub_id] + xml.subStreet options[:sub_street] if options[:sub_street] + xml.subCity options[:sub_city] if options[:sub_city] + xml.subState options[:sub_state] if options[:sub_state] + xml.subCountryCode options[:sub_country_code] if options[:sub_country_code] + xml.subPostalCode options[:sub_postal_code] if options[:sub_postal_code] + xml.subTaxId options[:sub_tax_id] if options[:sub_tax_id] + end + end + + def add_shopper_account_risk_data(xml, shopper_account_risk_data) + return unless shopper_account_risk_data + + data = { + 'transactionsAttemptedLastDay' => shopper_account_risk_data[:transactions_attempted_last_day], + 'transactionsAttemptedLastYear' => shopper_account_risk_data[:transactions_attempted_last_year], + 'purchasesCompletedLastSixMonths' => shopper_account_risk_data[:purchases_completed_last_six_months], + 'addCardAttemptsLastDay' => shopper_account_risk_data[:add_card_attempts_last_day], + 'previousSuspiciousActivity' => shopper_account_risk_data[:previous_suspicious_activity], + 'shippingNameMatchesAccountName' => shopper_account_risk_data[:shipping_name_matches_account_name], + 'shopperAccountAgeIndicator' => shopper_account_risk_data[:shopper_account_age_indicator], + 'shopperAccountChangeIndicator' => shopper_account_risk_data[:shopper_account_change_indicator], + 'shopperAccountPasswordChangeIndicator' => shopper_account_risk_data[:shopper_account_password_change_indicator], + 'shopperAccountShippingAddressUsageIndicator' => shopper_account_risk_data[:shopper_account_shipping_address_usage_indicator], + 'shopperAccountPaymentAccountIndicator' => shopper_account_risk_data[:shopper_account_payment_account_indicator] + }.reject { |_k, v| v.nil? } + + xml.shopperAccountRiskData(data) do + add_date_element(xml, 'shopperAccountCreationDate', shopper_account_risk_data[:shopper_account_creation_date]) + add_date_element(xml, 'shopperAccountModificationDate', shopper_account_risk_data[:shopper_account_modification_date]) + add_date_element(xml, 'shopperAccountPasswordChangeDate', shopper_account_risk_data[:shopper_account_password_change_date]) + add_date_element(xml, 'shopperAccountShippingAddressFirstUseDate', shopper_account_risk_data[:shopper_account_shipping_address_first_use_date]) + add_date_element(xml, 'shopperAccountPaymentAccountFirstUseDate', shopper_account_risk_data[:shopper_account_payment_account_first_use_date]) + end + end + + def add_transaction_risk_data(xml, transaction_risk_data) + return unless transaction_risk_data + + data = { + 'shippingMethod' => transaction_risk_data[:shipping_method], + 'deliveryTimeframe' => transaction_risk_data[:delivery_timeframe], + 'deliveryEmailAddress' => transaction_risk_data[:delivery_email_address], + 'reorderingPreviousPurchases' => transaction_risk_data[:reordering_previous_purchases], + 'preOrderPurchase' => transaction_risk_data[:pre_order_purchase], + 'giftCardCount' => transaction_risk_data[:gift_card_count] + }.reject { |_k, v| v.nil? } + + xml.transactionRiskData(data) do + xml.transactionRiskDataGiftCardAmount do + amount_hash = { + 'value' => transaction_risk_data.dig(:transaction_risk_data_gift_card_amount, :value), + 'currencyCode' => transaction_risk_data.dig(:transaction_risk_data_gift_card_amount, :currency), + 'exponent' => transaction_risk_data.dig(:transaction_risk_data_gift_card_amount, :exponent) + } + debit_credit_indicator = transaction_risk_data.dig(:transaction_risk_data_gift_card_amount, :debit_credit_indicator) + amount_hash['debitCreditIndicator'] = debit_credit_indicator if debit_credit_indicator + xml.amount(amount_hash) + end + add_date_element(xml, 'transactionRiskDataPreOrderDate', transaction_risk_data[:transaction_risk_data_pre_order_date]) + end + end + + def add_date_element(xml, name, date) + xml.tag! name do + xml.date('dayOfMonth' => date[:day_of_month], 'month' => date[:month], 'year' => date[:year]) + end + end + def add_amount(xml, money, options) currency = options[:currency] || currency(money) @@ -232,59 +562,120 @@ def add_amount(xml, money, options) 'exponent' => currency_exponent(currency) } - if options[:debit_credit_indicator] - amount_hash['debitCreditIndicator'] = options[:debit_credit_indicator] - end + amount_hash['debitCreditIndicator'] = options[:debit_credit_indicator] if options[:debit_credit_indicator] - xml.tag! 'amount', amount_hash + xml.amount amount_hash end def add_payment_method(xml, amount, payment_method, options) - if payment_method.is_a?(String) - if options[:merchant_code] - xml.tag! 'payAsOrder', 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do - add_amount(xml, amount, options) + case options[:payment_type] + 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) + else + add_card_or_token(xml, payment_method, options) + end + end + + def add_amount_for_pay_as_order(xml, amount, payment_method, options) + if options[:merchant_code] + xml.payAsOrder 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do + add_amount(xml, amount, options) + end + else + xml.payAsOrder 'orderCode' => payment_method do + add_amount(xml, amount, options) + end + end + end + + def add_network_tokenization_card(xml, payment_method) + token_type = NETWORK_TOKEN_TYPE.fetch(payment_method.source, 'NETWORKTOKEN') + + xml.paymentDetails do + xml.tag! 'EMVCO_TOKEN-SSL', 'type' => token_type do + xml.tokenNumber payment_method.number + xml.expiryDate do + xml.date( + 'month' => format(payment_method.month, :two_digits), + 'year' => format(payment_method.year, :four_digits_year) + ) 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 + end + end + end + + def add_card_or_token(xml, payment_method, options) + xml.paymentDetails credit_fund_transfer_attribute(options) do + if options[:payment_type] == :token + add_token_details(xml, options) else - xml.tag! 'payAsOrder', 'orderCode' => payment_method do - add_amount(xml, amount, options) - end + add_card_details(xml, payment_method, options) end - else - xml.tag! 'paymentDetails', credit_fund_transfer_attribute(options) do - xml.tag! CARD_CODES[card_brand(payment_method)] do - xml.tag! 'cardNumber', payment_method.number - xml.tag! 'expiryDate' do - xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits) - end + add_stored_credential_options(xml, options) + add_shopper_id(xml, options) + add_three_d_secure(xml, options) + end + end - xml.tag! 'cardHolderName', options[:execute_threed] ? '3D' : payment_method.name - xml.tag! 'cvc', payment_method.verification_value + def add_token_details(xml, options) + xml.tag! 'TOKEN-SSL', 'tokenScope' => options[:token_scope] do + xml.paymentTokenID options[:token_id] + end + end - add_address(xml, (options[:billing_address] || options[:address])) - end - add_stored_credential_options(xml, options) - if options[:ip] && options[:session_id] - xml.tag! 'session', 'shopperIPAddress' => options[:ip], 'id' => options[:session_id] - else - xml.tag! 'session', 'shopperIPAddress' => options[:ip] if options[:ip] - xml.tag! 'session', 'id' => options[:session_id] if options[:session_id] - end + def add_card_details(xml, payment_method, options) + xml.tag! 'CARD-SSL' do + add_card(xml, payment_method, options) + end + end - if three_d_secure = options[:three_d_secure] - xml.tag! 'info3DSecure' do - xml.tag! 'threeDSVersion', three_d_secure[:version] - xid_tag = three_d_secure[:version] =~ /^2/ ? 'dsTransactionId' : 'xid' - xml.tag! xid_tag, three_d_secure[:xid] - xml.tag! 'cavv', three_d_secure[:cavv] - xml.tag! 'eci', three_d_secure[:eci] - end - end + def add_shopper_id(xml, options) + if options[:ip] && options[:session_id] + xml.session 'shopperIPAddress' => options[:ip], 'id' => options[:session_id] + else + xml.session 'shopperIPAddress' => options[:ip] if options[:ip] + xml.session 'id' => options[:session_id] if options[:session_id] + end + end + + def add_three_d_secure(xml, options) + return unless three_d_secure = options[:three_d_secure] + + xml.info3DSecure do + xml.threeDSVersion three_d_secure[:version] + if three_d_secure[:version] && three_d_secure[:ds_transaction_id] + xml.dsTransactionId three_d_secure[:ds_transaction_id] + else + xml.xid three_d_secure[:xid] end + xml.cavv three_d_secure[:cavv] + xml.eci three_d_secure[:eci] end end - def add_stored_credential_options(xml, options={}) + def add_card(xml, payment_method, options) + xml.cardNumber payment_method.number + xml.expiryDate do + xml.date( + 'month' => format(payment_method.month, :two_digits), + 'year' => format(payment_method.year, :four_digits_year) + ) + end + 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) + end + + def add_stored_credential_options(xml, options = {}) if options[:stored_credential] add_stored_credential_using_normalized_fields(xml, options) else @@ -294,7 +685,7 @@ def add_stored_credential_options(xml, options={}) def add_stored_credential_using_normalized_fields(xml, options) if options[:stored_credential][:initial_transaction] - xml.tag! 'storedCredentials', 'usage' => 'FIRST' + xml.storedCredentials 'usage' => 'FIRST' else reason = case options[:stored_credential][:reason_type] when 'installment' then 'INSTALMENT' @@ -302,8 +693,8 @@ def add_stored_credential_using_normalized_fields(xml, options) when 'unscheduled' then 'UNSCHEDULED' end - xml.tag! 'storedCredentials', 'usage' => 'USED', 'merchantInitiatedReason' => reason do - xml.tag! 'schemeTransactionIdentifier', options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + xml.storedCredentials 'usage' => 'USED', 'merchantInitiatedReason' => reason do + xml.schemeTransactionIdentifier options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] end end end @@ -312,62 +703,76 @@ def add_stored_credential_using_gateway_specific_fields(xml, options) return unless options[:stored_credential_usage] if options[:stored_credential_initiated_reason] - xml.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage], 'merchantInitiatedReason' => options[:stored_credential_initiated_reason] do - xml.tag! 'schemeTransactionIdentifier', options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id] + 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.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage] + xml.storedCredentials 'usage' => options[:stored_credential_usage] end end - def add_email(xml, options) - return unless options[:execute_threed] || options[:email] - xml.tag! 'shopper' do - xml.tag! 'shopperEmailAddress', options[:email] if options[:email] - xml.tag! 'browser' do - xml.tag! 'acceptHeader', options[:accept_header] - xml.tag! 'userAgentHeader', options[:user_agent] + def add_shopper(xml, options) + return unless options[:execute_threed] || options[:email] || options[:customer] + + xml.shopper do + xml.shopperEmailAddress options[:email] if options[:email] + add_authenticated_shopper_id(xml, options) + xml.browser do + xml.acceptHeader options[:accept_header] + xml.userAgentHeader options[:user_agent] end end end - def add_address(xml, address) + def add_statement_narrative(xml, options) + xml.statementNarrative truncate(options[:statement_narrative], 50) if options[:statement_narrative] + end + + def add_authenticated_shopper_id(xml, options) + xml.authenticatedShopperID options[:customer] if options[:customer] + end + + def add_address(xml, address, options) return unless address address = address_with_defaults(address) - xml.tag! 'cardAddress' do - xml.tag! 'address' do + xml.cardAddress do + xml.address do if m = /^\s*([^\s]+)\s+(.+)$/.match(address[:name]) - xml.tag! 'firstName', m[1] - xml.tag! 'lastName', m[2] + xml.firstName m[1] + xml.lastName m[2] end - xml.tag! 'address1', address[:address1] - xml.tag! 'address2', address[:address2] if address[:address2] - xml.tag! 'postalCode', address[:zip] - xml.tag! 'city', address[:city] - xml.tag! 'state', address[:state] - xml.tag! 'countryCode', address[:country] - xml.tag! 'telephoneNumber', address[:phone] if address[:phone] + xml.address1 address[:address1] + xml.address2 address[:address2] if address[:address2] + xml.postalCode address[:zip] + xml.city address[:city] + xml.state address[:state] unless address[:country] != 'US' && options[:execute_threed] + xml.countryCode address[:country] + xml.telephoneNumber address[:phone] if address[:phone] end end end def add_hcg_additional_data(xml, options) - xml.tag! 'hcgAdditionalData' do + xml.hcgAdditionalData do options[:hcg_additional_data].each do |k, v| - xml.tag! 'param', {name: k.to_s}, v + xml.param({ name: k.to_s }, v) end end end def add_instalments_data(xml, options) - xml.tag! 'thirdPartyData' do - xml.tag! 'instalments', options[:instalments] - xml.tag! 'cpf', options[:cpf] if options[:cpf] + xml.thirdPartyData do + xml.instalments options[:instalments] + xml.cpf options[:cpf] if options[:cpf] end end + def add_moto_flag(xml, options) + xml.dynamicInteractionType 'type' => 'MOTO' + end + def address_with_defaults(address) address ||= {} address.delete_if { |_, v| v.blank? } @@ -376,64 +781,92 @@ def address_with_defaults(address) def default_address { - address1: 'N/A', zip: '0000', + country: 'US', city: 'N/A', - state: 'N/A', - country: 'US' + address1: 'N/A' } end def parse(action, xml) - parse_element({:action => action}, REXML::Document.new(xml)) + xml = xml.strip.gsub(/\&/, '&') + doc = Nokogiri::XML(xml, &:strict) + doc.remove_namespaces! + resp_params = { action: action } + + parse_elements(doc.root, resp_params) + extract_issuer_response(doc.root, resp_params) + + resp_params + end + + def extract_issuer_response(doc, response) + return unless issuer_response = doc.at_xpath('//paymentService//reply//orderStatus//payment//IssuerResponseCode') + + response[:issuer_response_code] = issuer_response['code'] + response[:issuer_response_description] = issuer_response['description'] end - def parse_element(raw, node) + def parse_elements(node, response) + node_name = node.name.underscore node.attributes.each do |k, v| - raw["#{node.name.underscore}_#{k.underscore}".to_sym] = v + response["#{node_name}_#{k.underscore}".to_sym] = v.value end - if node.has_elements? - raw[node.name.underscore.to_sym] = true unless node.name.blank? - node.elements.each { |e| parse_element(raw, e) } + if node.elements.empty? + response[node_name.to_sym] = node.text unless node.text.blank? else - raw[node.name.underscore.to_sym] = node.text unless node.text.nil? + response[node_name.to_sym] = true unless node.name.blank? + node.elements.each do |childnode| + parse_elements(childnode, response) + end end - raw end def headers(options) + idempotency_key = options[:idempotency_key] + headers = { 'Content-Type' => 'text/xml', 'Authorization' => encoded_credentials } - if options[:cookie] - headers['Cookie'] = options[:cookie] if options[:cookie] - end + + # 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 = defined?(@cookie) ? @cookie : nil + cookie = options[:cookie] || cookie + headers['Cookie'] = cookie if cookie + + headers['Idempotency-Key'] = idempotency_key if idempotency_key headers end def commit(action, request, *success_criteria, options) xml = ssl_post(url, request, headers(options)) raw = parse(action, xml) + if options[:execute_threed] - raw[:cookie] = @cookie + raw[:cookie] = @cookie if defined?(@cookie) raw[:session_id] = options[:session_id] + raw[:is3DSOrder] = true end - success, message = success_and_message_from(raw, success_criteria) + success = success_from(action, raw, success_criteria) + message = message_from(success, raw, success_criteria, action) Response.new( success, message, raw, - :authorization => authorization_from(raw), - :error_code => error_code_from(success, raw), - :test => test?, - :avs_result => AVSResult.new(code: AVS_CODE_MAP[raw[:avs_result_code_description]]), - :cvv_result => CVVResult.new(CVC_CODE_MAP[raw[:cvc_result_code_description]]) + authorization: authorization_from(action, raw, options), + error_code: error_code_from(success, raw), + test: test?, + avs_result: AVSResult.new(code: AVS_CODE_MAP[raw[:avs_result_code_description]]), + cvv_result: CVVResult.new(CVC_CODE_MAP[raw[:cvc_result_code_description]]) ) + rescue Nokogiri::SyntaxError + unparsable_response(xml) rescue ActiveMerchant::ResponseError => e if e.response.code.to_s == '401' - return Response.new(false, 'Invalid credentials', {}, :test => test?) + return Response.new(false, 'Invalid credentials', {}, test: test?) else raise e end @@ -443,53 +876,136 @@ def url test? ? self.test_url : self.live_url end + def unparsable_response(raw_response) + message = 'Unparsable response received from Worldpay. Please contact Worldpay if you continue to receive this message.' + message += " (The raw response returned by the API was: #{raw_response.inspect})" + return Response.new(false, message) + end + # Override the regular handle response so we can access the headers # Set-Cookie value is needed for 3DS transactions def handle_response(response) case response.code.to_i when 200...300 - @cookie = response['Set-Cookie'] + cookie = response.header['Set-Cookie']&.match('^[^;]*') + @cookie = cookie[0] if cookie response.body else raise ResponseError.new(response) end end + def success_from(action, raw, success_criteria) + success_criteria_success?(raw, success_criteria) || action_success?(action, raw) + end + + def message_from(success, raw, success_criteria, action) + return 'SUCCESS' if success + + raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria, action) || raw[:issuer_response_description] + end + # success_criteria can be: # - a string or an array of strings (if one of many responses) # - An array of strings if one of many responses could be considered a # success. - def success_and_message_from(raw, success_criteria) - success = (success_criteria.include?(raw[:last_event]) || raw[:ok].present?) - if success - message = 'SUCCESS' + def success_criteria_success?(raw, success_criteria) + return if raw[:error] + + raw[:ok].present? || (success_criteria.include?(raw[:last_event]) if raw[:last_event]) + end + + def action_success?(action, raw) + case action + when 'store' + raw[:token].present? + when 'direct_inquiry' + raw[:last_event].present? else - message = (raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria)) + false end - - [ success, message ] end def error_code_from(success, raw) - unless success == 'SUCCESS' - raw[:iso8583_return_code_code] || raw[:error_code] || nil - end + raw[:iso8583_return_code_code] || raw[:error_code] || nil unless success == 'SUCCESS' end - def required_status_message(raw, success_criteria) - if(!success_criteria.include?(raw[:last_event])) - "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(" or ")} is required." + def required_status_message(raw, success_criteria, action) + return if success_criteria.include?(raw[:last_event]) + return unless %w[cancel refund inquiry credit fast_credit].include?(action) + + "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(' or ')} is required." + end + + def authorization_from(action, raw, options) + order_id = order_id_from(raw) + + case action + when 'store' + authorization_from_token_details( + order_id: order_id, + token_id: raw[:payment_token_id], + token_scope: 'shopper', + customer: options[:customer] + ) + else + order_id end end - def authorization_from(raw) - pair = raw.detect { |k, v| k.to_s =~ /_order_code$/ } + def order_id_from(raw) + pair = raw.detect { |k, _v| k.to_s =~ /_order_code$/ } (pair ? pair.last : nil) end + def authorization_from_token_details(options = {}) + [options[:order_id], options[:token_id], options[:token_scope], options[:customer]].join('|') + end + + def order_id_from_authorization(authorization) + token_details_from_authorization(authorization)[:order_id] + end + + def token_details_from_authorization(authorization) + order_id, token_id, token_scope, customer = authorization.split('|') + + token_details = {} + token_details[:order_id] = order_id if order_id.present? + token_details[:token_id] = token_id if token_id.present? + token_details[:token_scope] = token_scope if token_scope.present? + token_details[:customer] = customer if customer.present? + + token_details + end + + def payment_details(payment_method) + case payment_method + when String + token_type_and_details(payment_method) + else + type = network_token?(payment_method) ? :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 + + token_details + end + def credit_fund_transfer_attribute(options) return unless options[:credit] - {'action' => 'REFUND'} + + { 'action' => 'REFUND' } end def encoded_credentials @@ -500,8 +1016,17 @@ def encoded_credentials def currency_exponent(currency) return 0 if non_fractional_currency?(currency) return 3 if three_decimal_currency?(currency) + return 2 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 + + def card_holder_name(payment_method, options) + test? && options[:execute_threed] && !options[:three_ds_version]&.start_with?('2') ? '3D' : payment_method.name + 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 d3a03ffbeec..4ec743470d8 100644 --- a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +++ b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb @@ -1,27 +1,27 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class WorldpayOnlinePaymentsGateway < Gateway - self.live_url = 'https://api.worldpay.com/v1/' + self.live_url = 'https://api.worldpay.com/v1/' self.default_currency = 'GBP' self.money_format = :cents self.supported_countries = %w(HK US GB BE CH CZ DE DK ES FI FR GR HU IE IT LU MT NL NO PL PT SE SG TR) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro] + self.supported_cardtypes = %i[visa master american_express discover jcb maestro] self.homepage_url = 'http://online.worldpay.com' self.display_name = 'Worldpay Online Payments' - def initialize(options={}) + def initialize(options = {}) requires!(options, :client_key, :service_key) @client_key = options[:client_key] @service_key = options[:service_key] super end - def authorize(money, credit_card, options={}) - response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) + def authorize(money, credit_card, options = {}) + response = create_token(true, credit_card.first_name + ' ' + credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) if response.success? options[:authorizeOnly] = true post = create_post_for_auth_or_purchase(response.authorization, money, options) @@ -30,24 +30,25 @@ def authorize(money, credit_card, options={}) response end - def capture(money, authorization, options={}) + def capture(money, authorization, options = {}) if authorization - commit(:post, "orders/#{CGI.escape(authorization)}/capture", {'captureAmount'=>money}, options, 'capture') + 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 + test: test?, + authorization: false, + avs_result: {}, + cvv_result: {}, + error_code: false ) end end - def purchase(money, credit_card, options={}) - response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) + def purchase(money, credit_card, options = {}) + response = create_token(true, credit_card.first_name + ' ' + credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) if response.success? post = create_post_for_auth_or_purchase(response.authorization, money, options) response = commit(:post, 'orders', post, options, 'purchase') @@ -55,20 +56,18 @@ def purchase(money, credit_card, options={}) response end - def refund(money, orderCode, options={}) - obj = money ? {'refundAmount' => money} : {} + def refund(money, orderCode, options = {}) + obj = money ? { 'refundAmount' => money } : {} commit(:post, "orders/#{CGI.escape(orderCode)}/refund", obj, options, 'refund') end - def void(orderCode, options={}) + def void(orderCode, options = {}) response = commit(:delete, "orders/#{CGI.escape(orderCode)}", nil, options, 'void') - if !response.success? && (response.params && response.params['customCode'] != 'ORDER_NOT_FOUND') - response = refund(nil, orderCode) - end + response = refund(nil, orderCode) if !response.success? && (response.params && response.params['customCode'] != 'ORDER_NOT_FOUND') response end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) authorize(0, credit_card, options) end @@ -76,18 +75,18 @@ def verify(credit_card, options={}) def create_token(reusable, name, exp_month, exp_year, number, cvc) obj = { - 'reusable'=> reusable, - 'paymentMethod'=> { - 'type'=> 'Card', - 'name'=> name, - 'expiryMonth'=> exp_month, - 'expiryYear'=> exp_year, - 'cardNumber'=> number, - 'cvc'=> cvc + 'reusable' => reusable, + 'paymentMethod' => { + 'type' => 'Card', + 'name' => name, + 'expiryMonth' => exp_month, + 'expiryYear' => exp_year, + 'cardNumber' => number, + 'cvc' => cvc }, - 'clientKey'=> @client_key + 'clientKey' => @client_key } - token_response = commit(:post, 'tokens', obj, {'Authorization' => @service_key}, 'token') + token_response = commit(:post, 'tokens', obj, { 'Authorization' => @service_key }, 'token') token_response end @@ -97,16 +96,16 @@ def create_post_for_auth_or_purchase(token, money, options) 'orderDescription' => options[:description] || 'Worldpay Order', 'amount' => money, 'currencyCode' => options[:currency] || default_currency, - 'name' => options[:billing_address]&&options[:billing_address][:name] ? options[:billing_address][:name] : '', + 'name' => options[:billing_address] && options[:billing_address][:name] ? options[:billing_address][:name] : '', 'billingAddress' => { - 'address1'=>options[:billing_address]&&options[:billing_address][:address1] ? options[:billing_address][:address1] : '', - 'address2'=>options[:billing_address]&&options[:billing_address][:address2] ? options[:billing_address][:address2] : '', - 'address3'=>'', - 'postalCode'=>options[:billing_address]&&options[:billing_address][:zip] ? options[:billing_address][:zip] : '', - 'city'=>options[:billing_address]&&options[:billing_address][:city] ? options[:billing_address][:city] : '', - 'state'=>options[:billing_address]&&options[:billing_address][:state] ? options[:billing_address][:state] : '', - 'countryCode'=>options[:billing_address]&&options[:billing_address][:country] ? options[:billing_address][:country] : '' - }, + 'address1' => options[:billing_address] && options[:billing_address][:address1] ? options[:billing_address][:address1] : '', + 'address2' => options[:billing_address] && options[:billing_address][:address2] ? options[:billing_address][:address2] : '', + 'address3' => '', + 'postalCode' => options[:billing_address] && options[:billing_address][:zip] ? options[:billing_address][:zip] : '', + 'city' => options[:billing_address] && options[:billing_address][:city] ? options[:billing_address][:city] : '', + 'state' => options[:billing_address] && options[:billing_address][:state] ? options[:billing_address][:state] : '', + 'countryCode' => options[:billing_address] && options[:billing_address][:country] ? options[:billing_address][:country] : '' + }, 'customerOrderCode' => options[:order_id], 'orderType' => 'ECOM', 'authorizeOnly' => options[:authorizeOnly] ? true : false @@ -123,15 +122,13 @@ def headers(options = {}) 'Content-Type' => 'application/json', 'User-Agent' => "Worldpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", 'X-Worldpay-Client-User-Agent' => user_agent, - 'X-Worldpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json + 'X-Worldpay-Client-User-Metadata' => { ip: options[:ip] }.to_json } - if options['Authorization'] - headers['Authorization'] = options['Authorization'] - end + headers['Authorization'] = options['Authorization'] if options['Authorization'] headers end - def commit(method, url, parameters=nil, options = {}, type = false) + def commit(method, url, parameters = nil, options = {}, type = false) raw_response = response = nil success = false begin @@ -151,7 +148,7 @@ def commit(method, url, parameters=nil, options = {}, type = false) success = true elsif type == 'purchase' && response['paymentStatus'] == 'SUCCESS' success = true - elsif type == 'capture' || type=='refund' || type=='void' + elsif type == 'capture' || type == 'refund' || type == 'void' success = true end end @@ -175,14 +172,15 @@ 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'] + test: test?, + authorization: authorization, + avs_result: {}, + cvv_result: {}, + error_code: success ? nil : response['customCode'] ) end @@ -209,7 +207,6 @@ def json_error(raw_response) def handle_response(response) response.body end - end end end diff --git a/lib/active_merchant/billing/gateways/worldpay_us.rb b/lib/active_merchant/billing/gateways/worldpay_us.rb index 303b5b1767e..d0b7844d214 100644 --- a/lib/active_merchant/billing/gateways/worldpay_us.rb +++ b/lib/active_merchant/billing/gateways/worldpay_us.rb @@ -15,14 +15,14 @@ class WorldpayUsGateway < Gateway self.supported_countries = ['US'] self.default_currency = 'USD' self.money_format = :dollars - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb] + self.supported_cardtypes = %i[visa master american_express discover jcb] - def initialize(options={}) + def initialize(options = {}) requires!(options, :acctid, :subid, :merchantpin) super end - def purchase(money, payment_method, options={}) + def purchase(money, payment_method, options = {}) post = {} add_invoice(post, money, options) add_payment_method(post, payment_method) @@ -31,7 +31,7 @@ def purchase(money, payment_method, options={}) commit('purchase', options, post) end - def authorize(money, payment, options={}) + def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_credit_card(post, payment) @@ -40,7 +40,7 @@ def authorize(money, payment, options={}) commit('authorize', options, post) end - def capture(amount, authorization, options={}) + def capture(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -49,7 +49,7 @@ def capture(amount, authorization, options={}) commit('capture', options, post) end - def refund(amount, authorization, options={}) + def refund(amount, authorization, options = {}) post = {} add_invoice(post, amount, options) add_reference(post, authorization) @@ -58,14 +58,14 @@ def refund(amount, authorization, options={}) commit('refund', options, post) end - def void(authorization, options={}) + def void(authorization, options = {}) post = {} add_reference(post, authorization) commit('void', options, post) end - def verify(credit_card, options={}) + def verify(credit_card, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } @@ -91,7 +91,7 @@ def url(options) end def add_customer_data(post, options) - if(billing_address = (options[:billing_address] || options[:address])) + if (billing_address = (options[:billing_address] || options[:address])) post[:ci_companyname] = billing_address[:company] post[:ci_billaddr1] = billing_address[:address1] post[:ci_billaddr2] = billing_address[:address2] @@ -105,13 +105,13 @@ def add_customer_data(post, options) post[:ci_ipaddress] = billing_address[:ip] end - if(shipping_address = options[:shipping_address]) + if (shipping_address = options[:shipping_address]) post[:ci_shipaddr1] = shipping_address[:address1] post[:ci_shipaddr2] = shipping_address[:address2] post[:ci_shipcity] = shipping_address[:city] post[:ci_shipstate] = shipping_address[:state] - post[:ci_shipzip] = shipping_address[:zip] - post[:ci_shipcountry] = shipping_address[:country] + post[:ci_shipzip] = shipping_address[:zip] + post[:ci_shipcountry] = shipping_address[:country] end end @@ -139,7 +139,7 @@ def add_credit_card(post, payment_method) ACCOUNT_TYPES = { 'checking' => '1', - 'savings' => '2', + 'savings' => '2' } def add_check(post, payment_method) @@ -178,7 +178,7 @@ def parse(xml) 'refund' => 'ns_credit', 'authorize' => 'ns_quicksale_cc', 'capture' => 'ns_quicksale_cc', - 'void' => 'ns_void', + 'void' => 'ns_void' } def commit(action, options, post) @@ -196,8 +196,8 @@ def commit(action, options, post) succeeded, message_from(succeeded, raw), raw, - :authorization => authorization_from(raw), - :test => test? + authorization: authorization_from(raw), + test: test? ) end diff --git a/lib/active_merchant/billing/gateways/xpay.rb b/lib/active_merchant/billing/gateways/xpay.rb new file mode 100644 index 00000000000..6aaefc99154 --- /dev/null +++ b/lib/active_merchant/billing/gateways/xpay.rb @@ -0,0 +1,135 @@ +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://stg-ta.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 = { + purchase: 'orders/2steps/payment', + authorize: 'orders/2steps/init', + capture: 'operations/{%s}/captures', + verify: 'orders/card_verification', + void: 'operations/{%s}/cancels', + refund: 'operations/{%s}/refunds' + } + + def initialize(options = {}) + requires!(options, :api_key) + @api_key = options[:api_key] + super + end + + def purchase(amount, payment_method, options = {}) + post = {} + commit('purchase', post, options) + end + + def authorize(amount, payment_method, options = {}) + post = {} + add_auth_purchase(post, amount, payment_method, options) + commit('authorize', post, options) + end + + def capture(amount, authorization, options = {}) + post = {} + commit('capture', post, options) + end + + def void(authorization, options = {}) + post = {} + commit('void', post, options) + end + + def refund(amount, authorization, options = {}) + post = {} + commit('refund', post, options) + end + + def verify(credit_card, options = {}) + post = {} + commit('verify', post, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) end + + private + + def add_invoice(post, money, options) end + + def add_payment_method(post, payment_method) end + + def add_reference(post, authorization) end + + def add_auth_purchase(post, money, payment, options) end + + def commit(action, params, options) + url = build_request_url(action) + response = ssl_post(url, params.to_json, request_headers(options)) + + unless response + Response.new( + success_from(response), + message_from(success_from(response), response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['some_avs_result_key']), + cvv_result: CVVResult.new(response['some_cvv_result_key']), + test: test?, + error_code: error_code_from(response) + ) + end + rescue ResponseError => e + response = e.response.body + JSON.parse(response) + end + + def request_headers(options) + headers = { + 'Content-Type' => 'application/json', + 'X-Api-Key' => @api_key, + 'Correlation-Id' => options.dig(:order, :order_id) || SecureRandom.uuid + } + headers + end + + def build_request_url(action, id = nil) + base_url = test? ? test_url : live_url + endpoint = ENDPOINTS_MAPPING[action.to_sym] % id + base_url + endpoint + end + + def success_from(response) + response == 'SUCCESS' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response.dig('errors') unless response + end + end + + def authorization_from(response) + response.dig('latest_payment_attempt', 'payment_intent_id') unless response + end + + def error_code_from(response) + response['provider_original_response_code'] || response['code'] unless success_from(response) + 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 0f358948ab8..e4108977f80 100644 --- a/lib/active_merchant/billing/network_tokenization_credit_card.rb +++ b/lib/active_merchant/billing/network_tokenization_credit_card.rb @@ -14,10 +14,10 @@ class NetworkTokenizationCreditCard < CreditCard self.require_verification_value = false self.require_name = false - attr_accessor :payment_cryptogram, :eci, :transaction_id + attr_accessor :payment_cryptogram, :eci, :transaction_id, :metadata attr_writer :source - SOURCES = %i(apple_pay android_pay google_pay) + SOURCES = %i(apple_pay android_pay google_pay network_token) def source if defined?(@source) && SOURCES.include?(@source) diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb index 491bb0cab5b..fb1c502d7ee 100644 --- a/lib/active_merchant/billing/response.rb +++ b/lib/active_merchant/billing/response.rb @@ -4,12 +4,16 @@ class Error < ActiveMerchantError #:nodoc: end class Response - attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization + attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization, :network_transaction_id def success? @success end + def failure? + !success? + end + def test? @test end @@ -25,18 +29,19 @@ def initialize(success, message, params = {}, options = {}) @fraud_review = options[:fraud_review] @error_code = options[:error_code] @emv_authorization = options[:emv_authorization] + @network_transaction_id = options[:network_transaction_id] @avs_result = if options[:avs_result].kind_of?(AVSResult) options[:avs_result].to_hash else AVSResult.new(options[:avs_result]).to_hash - end + end @cvv_result = if options[:cvv_result].kind_of?(CVVResult) options[:cvv_result].to_hash else CVVResult.new(options[:cvv_result]).to_hash - end + end end end @@ -53,14 +58,14 @@ def initialize(use_first_response = false) @primary_response = nil end - def process(ignore_result=false) + def process(ignore_result = false) return unless success? response = yield self << response unless ignore_result - if(@use_first_response && response.success?) + if @use_first_response && response.success? @primary_response ||= response else @primary_response = response @@ -80,7 +85,21 @@ def success? (primary_response ? primary_response.success? : true) end - %w(params message test authorization avs_result cvv_result error_code emv_authorization test? fraud_review?).each do |m| + def avs_result + return @primary_response.try(:avs_result) if @use_first_response + + result = responses.reverse.find { |r| r.avs_result['code'].present? } + result.try(:avs_result) || responses.last.try(:avs_result) + end + + def cvv_result + return @primary_response.try(:cvv_result) if @use_first_response + + result = responses.reverse.find { |r| r.cvv_result['code'].present? } + result.try(:cvv_result) || responses.last.try(:cvv_result) + end + + %w(params message test authorization error_code emv_authorization test? fraud_review?).each do |m| class_eval %( def #{m} (@responses.empty? ? nil : primary_response.#{m}) diff --git a/lib/active_merchant/billing/three_d_secure_eci_mapper.rb b/lib/active_merchant/billing/three_d_secure_eci_mapper.rb new file mode 100644 index 00000000000..b5e1776d642 --- /dev/null +++ b/lib/active_merchant/billing/three_d_secure_eci_mapper.rb @@ -0,0 +1,27 @@ +module ActiveMerchant + module Billing + module ThreeDSecureEciMapper + NON_THREE_D_SECURE_TRANSACTION = :non_three_d_secure_transaction + ATTEMPTED_AUTHENTICATION_TRANSACTION = :attempted_authentication_transaction + FULLY_AUTHENTICATED_TRANSACTION = :fully_authenticated_transaction + + ECI_00_01_02_MAP = { '00' => NON_THREE_D_SECURE_TRANSACTION, '01' => ATTEMPTED_AUTHENTICATION_TRANSACTION, '02' => FULLY_AUTHENTICATED_TRANSACTION }.freeze + ECI_05_06_07_MAP = { '05' => FULLY_AUTHENTICATED_TRANSACTION, '06' => ATTEMPTED_AUTHENTICATION_TRANSACTION, '07' => NON_THREE_D_SECURE_TRANSACTION }.freeze + BRAND_TO_ECI_MAP = { + american_express: ECI_05_06_07_MAP, + dankort: ECI_05_06_07_MAP, + diners_club: ECI_05_06_07_MAP, + discover: ECI_05_06_07_MAP, + elo: ECI_05_06_07_MAP, + jcb: ECI_05_06_07_MAP, + maestro: ECI_00_01_02_MAP, + master: ECI_00_01_02_MAP, + visa: ECI_05_06_07_MAP + }.freeze + + def self.map(brand, eci) + BRAND_TO_ECI_MAP.dig(brand, eci) + end + end + end +end diff --git a/lib/active_merchant/connection.rb b/lib/active_merchant/connection.rb index e6731ed8566..626881a136e 100644 --- a/lib/active_merchant/connection.rb +++ b/lib/active_merchant/connection.rb @@ -62,6 +62,7 @@ def initialize(endpoint) def wiredump_device=(device) raise ArgumentError, "can't wiredump to frozen #{device.class}" if device&.frozen? + @wiredump_device = device end @@ -71,20 +72,19 @@ def request(method, body, headers = {}) headers = headers.dup headers['connection'] ||= 'close' - retry_exceptions(:max_retries => max_retries, :logger => logger, :tag => tag) do - begin - info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag + retry_exceptions(max_retries: max_retries, logger: logger, tag: tag) do + info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag - result = nil + result = nil - realtime = Benchmark.realtime do - http.start unless http.started? - @ssl_connection = http.ssl_connection - info "connection_ssl_version=#{ssl_connection[:version]} connection_ssl_cipher=#{ssl_connection[:cipher]}", tag + realtime = Benchmark.realtime do + http.start unless http.started? + @ssl_connection = http.ssl_connection + info "connection_ssl_version=#{ssl_connection[:version]} connection_ssl_cipher=#{ssl_connection[:cipher]}", tag - result = case method + 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 @@ -110,12 +110,11 @@ def request(method, body, headers = {}) else raise ArgumentError, "Unsupported request method #{method.to_s.upcase}" end - end - - info '--> %d %s (%d %.4fs)' % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag - debug result.body - result end + + info '--> %d %s (%d %.4fs)' % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag + debug result.body + result end ensure info 'connection_request_total_time=%.4fs' % [Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start], tag diff --git a/lib/active_merchant/country.rb b/lib/active_merchant/country.rb index e6db5cb3e28..6fee9d6a874 100644 --- a/lib/active_merchant/country.rb +++ b/lib/active_merchant/country.rb @@ -39,7 +39,7 @@ class Country def initialize(options = {}) @name = options.delete(:name) - @codes = options.collect { |k, v| CountryCode.new(v) } + @codes = options.collect { |_k, v| CountryCode.new(v) } end def code(format) @@ -183,6 +183,8 @@ def to_s { alpha2: 'KI', name: 'Kiribati', alpha3: 'KIR', numeric: '296' }, { alpha2: 'KP', name: 'Korea, Democratic People\'s Republic of', alpha3: 'PRK', numeric: '408' }, { alpha2: 'KR', name: 'Korea, Republic of', alpha3: 'KOR', numeric: '410' }, + { alpha2: 'XK', name: 'Kosovo', alpha3: 'XKX', numeric: '900' }, + { alpha2: 'QZ', name: 'Kosovo', alpha3: 'XKX', numeric: '900' }, { alpha2: 'KW', name: 'Kuwait', alpha3: 'KWT', numeric: '414' }, { alpha2: 'KG', name: 'Kyrgyzstan', alpha3: 'KGZ', numeric: '417' }, { alpha2: 'LA', name: 'Lao People\'s Democratic Republic', alpha3: 'LAO', numeric: '418' }, @@ -329,6 +331,7 @@ def self.find(name) country = COUNTRIES.detect { |c| c[:name].casecmp(name).zero? } end raise InvalidCountryCodeError, "No country could be found for the country #{name}" if country.nil? + Country.new(country.dup) end end diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb index af4bcb8b1be..5f68f0e59b5 100644 --- a/lib/active_merchant/errors.rb +++ b/lib/active_merchant/errors.rb @@ -23,10 +23,25 @@ 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 + + if response.respond_to?(:message) + return response.message if response.message.start_with?('Failed') + end + + "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/net_http_ssl_connection.rb b/lib/active_merchant/net_http_ssl_connection.rb index c0ae5ce1080..17babbf0510 100644 --- a/lib/active_merchant/net_http_ssl_connection.rb +++ b/lib/active_merchant/net_http_ssl_connection.rb @@ -4,6 +4,7 @@ module NetHttpSslConnection refine Net::HTTP do def ssl_connection return {} unless use_ssl? && @socket.present? + { version: @socket.io.ssl_version, cipher: @socket.io.cipher[0] } end end diff --git a/lib/active_merchant/network_connection_retries.rb b/lib/active_merchant/network_connection_retries.rb index 09e1b146f30..267524b27cd 100644 --- a/lib/active_merchant/network_connection_retries.rb +++ b/lib/active_merchant/network_connection_retries.rb @@ -17,26 +17,24 @@ def self.included(base) base.send(:attr_accessor, :retry_safe) end - def retry_exceptions(options={}) + def retry_exceptions(options = {}) connection_errors = DEFAULT_CONNECTION_ERRORS.merge(options[:connection_exceptions] || {}) retry_network_exceptions(options) do - begin - yield - rescue Errno::ECONNREFUSED => e - raise ActiveMerchant::RetriableConnectionError.new('The remote server refused the connection', e) - rescue OpenSSL::X509::CertificateError => e - NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag]) - raise ActiveMerchant::ClientCertificateError, 'The remote server did not accept the provided SSL certificate' - rescue Zlib::BufError - raise ActiveMerchant::InvalidResponseError, 'The remote server replied with an invalid response' - rescue *connection_errors.keys => e - raise ActiveMerchant::ConnectionError.new(derived_error_message(connection_errors, e.class), e) - end + yield + rescue Errno::ECONNREFUSED => e + raise ActiveMerchant::RetriableConnectionError.new('The remote server refused the connection', e) + rescue OpenSSL::X509::CertificateError => e + NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag]) + raise ActiveMerchant::ClientCertificateError, 'The remote server did not accept the provided SSL certificate' + rescue Zlib::BufError + raise ActiveMerchant::InvalidResponseError, 'The remote server replied with an invalid response' + rescue *connection_errors.keys => e + raise ActiveMerchant::ConnectionError.new(derived_error_message(connection_errors, e.class), e) end end - def self.log(logger, level, message, tag=nil) + def self.log(logger, level, message, tag = nil) tag ||= self.class.to_s message = "[#{tag}] #{message}" logger&.send(level, message) @@ -52,17 +50,17 @@ def retry_network_exceptions(options = {}) begin request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = yield - log_with_retry_details(options[:logger], initial_retries-retries + 1, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, 'success', options[:tag]) + log_with_retry_details(options[:logger], initial_retries - retries + 1, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, 'success', options[:tag]) result rescue ActiveMerchant::RetriableConnectionError => e retries -= 1 - log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) + log_with_retry_details(options[:logger], initial_retries - retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) retry unless retries.zero? raise ActiveMerchant::ConnectionError.new(e.message, e) rescue ActiveMerchant::ConnectionError, ActiveMerchant::InvalidResponseError => e retries -= 1 - log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) + log_with_retry_details(options[:logger], initial_retries - retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) retry if (options[:retry_safe] || retry_safe) && !retries.zero? raise end diff --git a/lib/active_merchant/post_data.rb b/lib/active_merchant/post_data.rb index c95b85244d2..2ad6a15fdb1 100644 --- a/lib/active_merchant/post_data.rb +++ b/lib/active_merchant/post_data.rb @@ -2,11 +2,12 @@ module ActiveMerchant class PostData < Hash - class_attribute :required_fields, :instance_writer => false + class_attribute :required_fields, instance_writer: false self.required_fields = [] def []=(key, value) return if value.blank? && !required?(key) + super end @@ -14,7 +15,7 @@ def to_post_data collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end - alias_method :to_s, :to_post_data + alias to_s to_post_data private diff --git a/lib/active_merchant/posts_data.rb b/lib/active_merchant/posts_data.rb index 15446c09fd8..ded8a8f3a70 100644 --- a/lib/active_merchant/posts_data.rb +++ b/lib/active_merchant/posts_data.rb @@ -1,5 +1,5 @@ module ActiveMerchant #:nodoc: - module PostsData #:nodoc: + module PostsData #:nodoc: def self.included(base) base.class_attribute :ssl_strict base.ssl_strict = true @@ -32,7 +32,7 @@ def self.included(base) base.class_attribute :proxy_port end - def ssl_get(endpoint, headers={}) + def ssl_get(endpoint, headers = {}) ssl_request(:get, endpoint, nil, headers) end diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index e9a183351bf..68017e31781 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = '1.91.0' + VERSION = '1.135.0' end diff --git a/lib/certs/cacert.pem b/lib/certs/cacert.pem index 72bbb947fe5..cb5a702074c 100644 --- a/lib/certs/cacert.pem +++ b/lib/certs/cacert.pem @@ -1,130 +1,23 @@ ## -## ca-bundle.crt -- Bundle of CA Root Certificates +## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Apr 22 08:29:31 2014 +## Certificate data from Mozilla as of: Tue Apr 13 03:12:04 2021 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: -## http://mxr.mozilla.org/mozilla-release/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## +## Conversion done with mk-ca-bundle.pl version 1.28. +## SHA256: f377673fa3c22ba2188a4cea041c7b8c99a4817ffde6821e98325ce89324e5aa +## -GTE CyberTrust Global Root -========================== ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg -Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG -A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz -MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL -Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 -IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u -sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql -HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID -AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW -M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF -NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -Thawte Server CA -================ ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs -dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE -AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j -b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV -BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u -c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG -A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 -ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl -/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 -1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR -MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J -GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ -GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -Thawte Premium Server CA -======================== ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs -dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE -AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl -ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT -AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU -VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 -aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ -cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 -aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh -Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ -qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm -SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf -8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t -UCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - -Equifax Secure CA -================= ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE -ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 -MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT -B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR -fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW -8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG -A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE -CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG -A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS -spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB -Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 -zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB -BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 -70+sB3c4 ------END CERTIFICATE----- - -Verisign Class 3 Public Primary Certification Authority -======================================================= ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx -FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow -XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz -IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 -f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol -hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA -TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah -WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf -Tqj/ZA1k ------END CERTIFICATE----- - -Verisign Class 3 Public Primary Certification Authority - G2 -============================================================ ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT -MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy -eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz -dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT -MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy -eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz -dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO -FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 -lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB -MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT -1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD -Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 ------END CERTIFICATE----- - GlobalSign Root CA ================== -----BEGIN CERTIFICATE----- @@ -168,138 +61,6 @@ BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -ValiCert Class 1 VA -=================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy -MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi -GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm -DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG -lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX -icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP -Orf1LXLI ------END CERTIFICATE----- - -ValiCert Class 2 VA -=================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw -MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC -CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf -ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ -SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV -UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 -W9ViH0Pd ------END CERTIFICATE----- - -RSA Root Certificate 1 -====================== ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp -b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh -bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw -MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 -d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg -UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 -LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td -3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H -BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs -3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF -V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r -on+jjBXu ------END CERTIFICATE----- - -Verisign Class 3 Public Primary Certification Authority - G3 -============================================================ ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV -UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv -cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy -dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv -cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 -EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc -cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw -EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj -055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA -ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f -j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 -xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa -t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -Verisign Class 4 Public Primary Certification Authority - G3 -============================================================ ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV -UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv -cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy -dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv -cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS -tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM -8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW -Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX -Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA -j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt -mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd -RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG -UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- - -Entrust.net Secure Server CA -============================ ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV -BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg -cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl -ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG -A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi -eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p -dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ -aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 -gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw -ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw -CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l -dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw -NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow -HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA -BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN -Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 -n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- @@ -345,136 +106,6 @@ Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -Equifax Secure Global eBusiness CA -================================== ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp -bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx -HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds -b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV -PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN -qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn -hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j -BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs -MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN -I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY -NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -Equifax Secure eBusiness CA 1 -============================= ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB -LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE -ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz -IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ -1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a -IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk -MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW -Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF -AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 -lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ -KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - -AddTrust Low-Value Services Root -================================ ------BEGIN CERTIFICATE----- -MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU -cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw -CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO -ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 -54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr -oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 -Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui -GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w -HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD -AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT -RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw -HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt -ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph -iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY -eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr -mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj -ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= ------END CERTIFICATE----- - -AddTrust External Root -====================== ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD -VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw -NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU -cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg -Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 -+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw -Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo -aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy -2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 -7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL -VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk -VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl -j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 -e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u -G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -AddTrust Public Services Root -============================= ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU -cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ -BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l -dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu -nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i -d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG -Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw -HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G -A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G -A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 -JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL -+YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao -GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 -Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H -EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= ------END CERTIFICATE----- - -AddTrust Qualified Certificates Root -==================================== ------BEGIN CERTIFICATE----- -MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU -cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx -CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ -IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx -64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 -KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o -L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR -wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU -MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE -BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y -azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG -GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X -dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze -RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB -iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= ------END CERTIFICATE----- - Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- @@ -501,283 +132,6 @@ W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -Entrust G2 Root Certificate Authority -===================================== ------BEGIN CERTIFICATE----- -MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 -cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs -IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz -dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy -NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu -dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt -dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 -aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T -RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN -cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW -wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 -U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 -jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN -BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ -jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ -Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v -1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R -nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH -VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== ------END CERTIFICATE----- - -Entrust L1M Chain Root Certificate -================================== ------BEGIN CERTIFICATE----- -MIIE/zCCA+egAwIBAgIEUdNARDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 -Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW -KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDkyMjE3MTQ1N1oXDTI0MDkyMzAx -MzE1M1owgb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgw -JgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQL -EzAoYykgMjAwOSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9u -bHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoS2ctueDGvi -mekwAad26jK4lUEaydphTlhyz/72gnm/c2EGCqUn2LNf00VOHHLWTjLycooP94MZ -0GqAgABFHrDH55q/ElcnHKNoLwqHvWprDl5l8xx31dSFjXAhtLMy54ui1YY5ArG4 -0kfO5MlJxDun3vtUfVe+8OhuwnmyOgtV4lCYFjITXC94VsHClLPyWuQnmp8k18bs -0JslguPMwsRFxYyXegZrKhGfqQpuSDtv29QRGUL3jwe/9VNfnD70FyzmaaxOMkxi -d+q36OW7NLwZi66cUee3frVTsTMi5W3PcDwa+uKbZ7aD9I2lr2JMTeBYrGQ0EgP4 -to2UYySkcQIDAQABo4IBDzCCAQswDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI -MAYBAf8CAQEwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz -cC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1 -c3QubmV0L3Jvb3RjYTEuY3JsMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYGCCsGAQUF -BwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NQUzAdBgNVHQ4EFgQUanImetAe -733nO2lR1GyNn5ASZqswHwYDVR0jBBgwFoAUaJDkZ6SmU4DHhmak8fdLQ/uEvW0w -DQYJKoZIhvcNAQELBQADggEBAGkzg/woem99751V68U+ep11s8zDODbZNKIoaBjq -HmnTvefQd9q4AINOSs9v0fHBIj905PeYSZ6btp7h25h3LVY0sag82f3Azce/BQPU -AsXx5cbaCKUTx2IjEdFhMB1ghEXveajGJpOkt800uGnFE/aRs8lFc3a2kvZ2Clvh -A0e36SlMkTIjN0qcNdh4/R0f5IOJJICtt/nP5F2l1HHEhVtwH9s/HAHrGkUmMRTM -Zb9n3srMM2XlQZHXN75BGpad5oqXnafOrE6aPb0BoGrZTyIAi0TVaWJ7LuvMuueS -fWlnPfy4fN5Bh9Bp6roKGHoalUOzeXEodm2h+1dK7E3IDhA= ------END CERTIFICATE----- - -RSA Security 2048 v3 -==================== ------BEGIN CERTIFICATE----- -MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK -ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy -MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb -BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 -Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb -WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH -KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP -+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ -MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E -FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY -v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj -0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj -VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 -nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA -pKnXwiJPZ9d37CAFYd4= ------END CERTIFICATE----- - -GeoTrust Global CA -================== ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw -MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j -LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo -BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet -8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc -T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU -vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk -DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q -zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 -d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 -mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p -XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm -Mw== ------END CERTIFICATE----- - -GeoTrust Global CA 2 -==================== ------BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN -R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw -MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j -LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ -NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k -LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA -Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b -HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH -K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 -srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh -ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL -OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC -x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF -H4z1Ir+rzoPz4iIprn2DQKi6bA== ------END CERTIFICATE----- - -GeoTrust Universal CA -===================== ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN -R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 -MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu -Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t -JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e -RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs -7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d -8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V -qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga -Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB -Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu -KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 -ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 -XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB -hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 -qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL -oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK -xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF -KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 -DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK -xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU -p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI -P/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -GeoTrust Universal CA 2 -======================= ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN -R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 -MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg -SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 -DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 -j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q -JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a -QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 -WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP -20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn -ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC -SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG -8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 -+/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E -BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ -4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ -mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq -A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg -Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP -pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d -FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp -gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm -X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -America Online Root Certification Authority 1 -============================================= ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG -A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg -T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG -v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z -DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh -sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP -8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T -AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z -o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf -GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF -VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft -3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g -Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -America Online Root Certification Authority 2 -============================================= ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT -QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG -A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg -T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en -fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 -f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO -qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN -RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 -gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn -6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid -FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 -Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj -B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op -aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY -T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p -+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg -JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy -zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO -ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh -1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf -GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff -Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP -cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= ------END CERTIFICATE----- - -Visa eCommerce Root -=================== ------BEGIN CERTIFICATE----- -MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG -EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug -QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 -WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm -VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv -bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL -F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b -RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 -TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI -/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs -GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG -MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc -CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW -YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz -zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu -YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt -398znM/jra6O1I7mT1GvFpLgXPYHDw== ------END CERTIFICATE----- - -Certum Root CA -============== ------BEGIN CERTIFICATE----- -MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK -ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla -Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u -by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x -wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL -kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ -89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K -Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P -NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ -GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg -GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ -0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS -qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== ------END CERTIFICATE----- - Comodo AAA Services root ======================== -----BEGIN CERTIFICATE----- @@ -802,56 +156,6 @@ Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z 12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -Comodo Secure Services root -=========================== ------BEGIN CERTIFICATE----- -MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS -R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg -TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw -MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu -Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi -BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP -9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc -rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC -oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V -p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E -FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w -gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj -YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm -aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm -4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj -Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL -DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw -pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H -RR3B7Hzs/Sk= ------END CERTIFICATE----- - -Comodo Trusted Services root -============================ ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS -R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg -TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw -MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h -bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw -IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 -3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y -/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 -juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS -ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud -DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp -ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl -cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw -uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA -BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l -R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O -9y5Xt5hwXsjEeLBi ------END CERTIFICATE----- - QuoVadis Root CA ================ -----BEGIN CERTIFICATE----- @@ -991,250 +295,6 @@ EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH llpwrN9M -----END CERTIFICATE----- -Staat der Nederlanden Root CA -============================= ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE -ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g -Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w -HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh -bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt -vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P -jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca -C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth -vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 -22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV -HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v -dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN -BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR -EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw -MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y -nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR -iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== ------END CERTIFICATE----- - -TDC Internet Root CA -==================== ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE -ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx -NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu -ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j -xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL -znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc -5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 -otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI -AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM -VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM -MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC -AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe -UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G -CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m -gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb -O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU -Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - -UTN DATACorp SGC Root CA -======================== ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE -BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl -IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ -BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa -MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w -HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy -dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys -raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo -wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA -9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv -33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud -DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 -BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD -LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 -DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 -I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx -EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP -DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- - -UTN USERFirst Hardware Root CA -============================== ------BEGIN CERTIFICATE----- -MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE -BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl -IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd -BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx -OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 -eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz -ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI -wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd -tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 -i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf -Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw -gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF -lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF -UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF -BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM -//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW -XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 -lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn -iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 -nfhmqA== ------END CERTIFICATE----- - -Camerfirma Chambers of Commerce Root -==================================== ------BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe -QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i -ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx -NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp -cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn -MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC -AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU -xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH -NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW -DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV -d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud -EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v -cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P -AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh -bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD -VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz -aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi -fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD -L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN -UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n -ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 -erfutGWaIZDgqtCYvDi1czyL+Nw= ------END CERTIFICATE----- - -Camerfirma Global Chambersign Root -================================== ------BEGIN CERTIFICATE----- -MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe -QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i -ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx -NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt -YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg -MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw -ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J -1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O -by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl -6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c -8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ -BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j -aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B -Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj -aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y -ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh -bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA -PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y -gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ -PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 -IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes -t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== ------END CERTIFICATE----- - -NetLock Notary (Class A) Root -============================= ------BEGIN CERTIFICATE----- -MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI -EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 -dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j -ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX -DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH -EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD -VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz -cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM -D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ -z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC -/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 -tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 -4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG -A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC -Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv -bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu -IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn -LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 -ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz -IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh -IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu -b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh -bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg -Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp -bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 -ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP -ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB -CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr -KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM -8CgHrTwXZoi1/baI ------END CERTIFICATE----- - -NetLock Business (Class B) Root -=============================== ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT -CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV -BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg -VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD -VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv -bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg -VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB -iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S -o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr -1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV -HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ -RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh -dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 -ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv -c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg -YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz -Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA -bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl -IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 -YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj -cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM -43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR -stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -NetLock Express (Class C) Root -============================== ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT -CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV -BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD -KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ -BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 -dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j -ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB -jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z -W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 -euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw -DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN -RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn -YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB -IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i -aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 -ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y -emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k -IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ -UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg -YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 -xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW -gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- @@ -1307,109 +367,6 @@ KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -StartCom Certification Authority -================================ ------BEGIN CERTIFICATE----- -MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN -U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu -ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 -NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk -LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg -U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y -o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ -Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d -eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt -2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z -6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ -osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ -untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc -UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT -37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE -FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 -Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj -YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH -AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw -Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg -U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 -LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl -cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh -cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT -dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC -AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh -3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm -vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk -fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 -fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ -EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq -yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl -1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ -lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro -g14= ------END CERTIFICATE----- - -Taiwan GRCA -=========== ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG -EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X -DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv -dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN -w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 -BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O -1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO -htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov -J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 -Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t -B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB -O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 -lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV -HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 -09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj -Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 -Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU -D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz -DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk -Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk -7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ -CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy -+fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS ------END CERTIFICATE----- - -Swisscom Root CA 1 -================== ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG -EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy -dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 -MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln -aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC -IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM -MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF -NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe -AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC -b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn -7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN -cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp -WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 -haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY -MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw -HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j -BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 -MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn -jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ -MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H -VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl -vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl -OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 -1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq -nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy -x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW -NY6E0F/6MBr1mmz0DlP5OlvRHA== ------END CERTIFICATE----- - DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- @@ -1454,31 +411,6 @@ UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- -DigiCert Global Root G2 -======================= ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI -2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx -1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ -q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz -tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ -vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV -5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY -1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 -NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG -Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 -8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe -pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl -MrY= ------END CERTIFICATE----- - DigiCert High Assurance EV Root CA ================================== -----BEGIN CERTIFICATE----- @@ -1501,28 +433,6 @@ mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- -Certplus Class 2 Primary CA -=========================== ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE -BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN -OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy -dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR -5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ -Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO -YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e -e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME -CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ -YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t -L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD -P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R -TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ -7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW -//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - DST Root CA X3 ============== -----BEGIN CERTIFICATE----- @@ -1543,78 +453,6 @@ RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- -DST ACES CA X6 -============== ------BEGIN CERTIFICATE----- -MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG -EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT -MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha -MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE -CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI -DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa -pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow -GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy -MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu -Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy -dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU -CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 -5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t -Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq -nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs -vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 -oKfN5XozNmr6mis= ------END CERTIFICATE----- - -TURKTRUST Certificate Services Provider Root 1 -============================================== ------BEGIN CERTIFICATE----- -MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF -bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP -MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 -acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx -MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg -U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB -TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC -aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX -yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i -Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ -8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 -W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME -BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 -sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE -q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy -B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY -nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H ------END CERTIFICATE----- - -TURKTRUST Certificate Services Provider Root 2 -============================================== ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF -bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP -MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg -QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN -MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr -dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G -A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls -acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe -LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI -x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g -QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr -5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB -AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt -Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 -Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ -hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P -9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 -UrbnBEI= ------END CERTIFICATE----- - SwissSign Gold CA - G2 ====================== -----BEGIN CERTIFICATE----- @@ -1677,78 +515,6 @@ DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -GeoTrust Primary Certification Authority -======================================== ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx -CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ -cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN -b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 -nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge -RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt -tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD -AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI -hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K -Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN -NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa -Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG -1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -thawte Primary Root CA -====================== ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE -BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 -aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv -cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 -MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg -SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv -KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT -FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs -oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ -1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc -q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K -aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p -afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF -AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE -uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 -jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH -z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== ------END CERTIFICATE----- - -VeriSign Class 3 Public Primary Certification Authority - G5 -============================================================ ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE -BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO -ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk -IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln -biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh -dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz -j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD -Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ -Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r -fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ -BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv -Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG -SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ -X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE -KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC -Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE -ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - SecureTrust CA ============== -----BEGIN CERTIFICATE----- @@ -1840,33 +606,6 @@ wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey -----END CERTIFICATE----- -WellsSecure Public Root Certificate Authority -============================================= ------BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM -F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw -NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN -MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl -bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD -VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 -iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 -i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 -bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB -K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB -AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu -cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm -lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB -i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww -GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg -Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI -K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 -bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj -qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es -E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ -tylv2G0xffX8oRAHh84vWdw+WNs= ------END CERTIFICATE----- - COMODO ECC Certification Authority ================================== -----BEGIN CERTIFICATE----- @@ -1884,114 +623,6 @@ FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -IGC/A -===== ------BEGIN CERTIFICATE----- -MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD -VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE -Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy -MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI -EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT -STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 -TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW -So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy -HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd -frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ -tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB -egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC -iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK -q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q -MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg -Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI -lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF -0mBWWg== ------END CERTIFICATE----- - -Security Communication EV RootCA1 -================================= ------BEGIN CERTIFICATE----- -MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc -U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh -dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE -BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl -Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO -/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX -WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z -ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 -bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK -9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG -SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm -iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG -Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW -mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW -T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 ------END CERTIFICATE----- - -OISTE WISeKey Global Root GA CA -=============================== ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE -BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG -A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH -bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD -VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw -IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 -IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 -Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg -Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD -d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ -/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R -LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ -KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm -MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 -+vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY -okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= ------END CERTIFICATE----- - -Microsec e-Szigno Root CA -========================= ------BEGIN CERTIFICATE----- -MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE -BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL -EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 -MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz -dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT -GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG -d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N -oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc -QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ -PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb -MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG -IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD -VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 -LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A -dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn -AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA -4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg -AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA -egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 -Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO -PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv -c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h -cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw -IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT -WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV -MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER -MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp -Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal -HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT -nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE -aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a -86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK -yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB -S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= ------END CERTIFICATE----- - Certigna ======== -----BEGIN CERTIFICATE----- @@ -2014,161 +645,6 @@ PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. -====================================== ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT -AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg -LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w -HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ -U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh -IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN -yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU -2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 -4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP -2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm -8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf -HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa -Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK -5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b -czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g -ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF -BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug -cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf -AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX -EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v -/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 -MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 -3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk -eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f -/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h -RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU -Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== ------END CERTIFICATE----- - -TC TrustCenter Class 2 CA II -============================ ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC -REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy -IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw -MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 -c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE -AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw -IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 -xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ -Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u -SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB -7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 -Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU -cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i -SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G -dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ -KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj -TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP -JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk -vQ== ------END CERTIFICATE----- - -TC TrustCenter Class 3 CA II -============================ ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC -REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy -IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw -MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 -c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE -AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W -yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo -6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ -uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk -2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB -7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 -Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU -cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i -SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE -O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 -yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 -IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal -092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc -5A== ------END CERTIFICATE----- - -TC TrustCenter Universal CA I -============================= ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC -REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy -IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN -MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg -VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw -JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC -qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv -xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw -ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O -gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j -BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG -1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy -vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 -ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a -7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - -Deutsche Telekom Root CA 2 -========================== ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT -RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG -A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 -MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G -A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS -b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 -bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI -KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY -AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK -Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV -jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV -HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr -E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy -zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 -rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G -dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - -ComSign Secured CA -================== ------BEGIN CERTIFICATE----- -MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE -AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w -NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD -QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs -49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH -7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB -kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 -9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw -AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t -U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA -j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC -AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a -BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp -FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP -51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz -OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== ------END CERTIFICATE----- - Cybertrust Global Root ====================== -----BEGIN CERTIFICATE----- @@ -2222,106 +698,6 @@ sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- -T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 -============================================================================================================================= ------BEGIN CERTIFICATE----- -MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH -DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q -aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry -b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV -BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg -S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 -MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl -IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF -n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl -IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft -dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl -cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO -Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 -xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR -6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL -hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd -BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 -N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT -y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh -LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M -dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= ------END CERTIFICATE----- - -Buypass Class 2 CA 1 -==================== ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU -QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 -MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh -c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M -cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 -0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 -0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R -uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P -AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV -1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt -7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 -fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w -wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho ------END CERTIFICATE----- - -Buypass Class 3 CA 1 -==================== ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU -QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 -MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh -c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx -ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 -n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia -AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c -1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P -AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 -pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA -EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 -htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj -el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 ------END CERTIFICATE----- - -EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 -========================================================================== ------BEGIN CERTIFICATE----- -MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF -bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg -QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe -Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p -ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt -IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by -X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b -gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr -eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ -TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy -Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn -uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI -qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm -ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 -Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB -/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW -Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t -FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm -zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k -XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT -bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU -RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK -1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt -2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ -Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 -AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT ------END CERTIFICATE----- - certSIGN ROOT CA ================ -----BEGIN CERTIFICATE----- @@ -2342,181 +718,8 @@ vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -CNNIC ROOT -========== ------BEGIN CERTIFICATE----- -MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE -ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw -OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD -o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz -VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT -VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or -czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK -y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC -wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S -lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 -Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM -O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 -BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 -G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m -mxE= ------END CERTIFICATE----- - -ApplicationCA - Japanese Government -=================================== ------BEGIN CERTIFICATE----- -MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT -SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw -MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl -cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 -fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN -wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE -jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu -nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU -WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV -BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD -vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs -o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g -/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD -io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW -dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL -rosot4LKGAfmt1t06SAZf7IbiVQ= ------END CERTIFICATE----- - -GeoTrust Primary Certification Authority - G3 -============================================= ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 -IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy -eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz -NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo -YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT -LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j -K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE -c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C -IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu -dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr -2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 -cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE -Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s -t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -thawte Primary Root CA - G2 -=========================== ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC -VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu -IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg -Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV -MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG -b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt -IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS -LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 -8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU -mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN -G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K -rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -thawte Primary Root CA - G3 -=========================== ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE -BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 -aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv -cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w -ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD -VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG -A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At -P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC -+BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY -7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW -vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ -KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK -A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC -8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm -er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -GeoTrust Primary Certification Authority - G2 -============================================= ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu -Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 -OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl -b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG -BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc -KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ -EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m -ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 -npaqBA+K ------END CERTIFICATE----- - -VeriSign Universal Root Certification Authority -=============================================== ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE -BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO -ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk -IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u -IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV -UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv -cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj -1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP -MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 -9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I -AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR -tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G -CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O -a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 -Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx -Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx -P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P -wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 -mJO37M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -VeriSign Class 3 Public Primary Certification Authority - G4 -============================================================ ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC -VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 -b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz -ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU -cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo -b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 -Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz -rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw -HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u -Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD -A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx -AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - NetLock Arany (Class Gold) Főtanúsítvány -============================================ +======================================== -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 @@ -2539,90 +742,6 @@ NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -Staat der Nederlanden Root CA - G2 -================================== ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE -CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g -Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC -TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l -ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ -5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn -vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj -CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil -e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR -OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI -CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 -48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi -trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 -qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB -AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC -ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA -A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz -+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj -f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN -kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk -CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF -URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb -CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h -oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV -IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm -66+KAQ== ------END CERTIFICATE----- - -CA Disig -======== ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK -QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw -MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz -bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm -GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD -Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo -hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt -ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w -gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P -AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz -aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff -ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa -BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t -WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 -mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ -CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K -ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA -4Z7CRneC9VkGjCFMhwnN5ag= ------END CERTIFICATE----- - -Juur-SK -======= ------BEGIN CERTIFICATE----- -MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA -c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw -DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG -SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy -aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf -TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC -+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw -UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa -Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF -MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD -HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh -AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA -cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr -AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw -cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE -FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G -A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo -ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL -abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 -IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh -Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 -yyqcjg== ------END CERTIFICATE----- - Hongkong Post Root CA 1 ======================= -----BEGIN CERTIFICATE----- @@ -2664,53 +783,6 @@ y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= -----END CERTIFICATE----- -ACEDICOM Root -============= ------BEGIN CERTIFICATE----- -MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD -T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 -MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG -A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk -WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD -YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew -MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb -m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk -HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT -xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 -3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 -2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq -TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz -4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU -9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv -bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg -aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP -eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk -zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 -ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI -KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq -nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE -I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp -MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o -tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== ------END CERTIFICATE----- - -Verisign Class 3 Public Primary Certification Authority -======================================================= ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx -FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow -XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz -IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA -A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 -f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol -hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky -CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX -bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ -D/xwzoiQ ------END CERTIFICATE----- - Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- @@ -2735,28 +807,6 @@ yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- -E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi -=================================================== ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG -EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz -ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 -MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 -cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u -aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY -8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y -jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI -JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk -9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD -AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG -SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d -F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq -D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 -Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX ------END CERTIFICATE----- - GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- @@ -3093,95 +1143,6 @@ Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- -Certinomis - Autorité Racine -============================= ------BEGIN CERTIFICATE----- -MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK -Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg -LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG -A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw -JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa -wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly -Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw -2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N -jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q -c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC -lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb -xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g -530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna -4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ -KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x -WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva -R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 -nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B -CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv -JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE -qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b -WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE -wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ -vgt2Fl43N+bYdJeimUV5 ------END CERTIFICATE----- - -Root CA Generalitat Valenciana -============================== ------BEGIN CERTIFICATE----- -MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE -ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 -IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 -WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE -CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 -F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B -ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ -D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte -JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB -AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n -dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB -ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl -AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA -YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy -AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA -aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt -AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA -YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu -AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA -OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 -dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV -BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G -A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S -b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh -TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz -Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 -NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH -iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt -+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= ------END CERTIFICATE----- - -A-Trust-nQual-03 -================ ------BEGIN CERTIFICATE----- -MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE -Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy -a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R -dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw -RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 -ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 -c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA -zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n -yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE -SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 -iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V -cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV -eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 -ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr -sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd -JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS -mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 -ahq97BvIxYSazQ== ------END CERTIFICATE----- - TWCA Root Certification Authority ================================= -----BEGIN CERTIFICATE----- @@ -3330,75 +1291,6 @@ l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl iB6XzCGcKQENZetX2fNXlrtIzYE= -----END CERTIFICATE----- -StartCom Certification Authority -================================ ------BEGIN CERTIFICATE----- -MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN -U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu -ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 -NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk -LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg -U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y -o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ -Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d -eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt -2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z -6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ -osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ -untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc -UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT -37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ -Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 -dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu -c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv -bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 -aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t -L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG -cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 -fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm -N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN -Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T -tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX -e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA -2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs -HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE -JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib -D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= ------END CERTIFICATE----- - -StartCom Certification Authority G2 -=================================== ------BEGIN CERTIFICATE----- -MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN -U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE -ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O -o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG -4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi -Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul -Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs -O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H -vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L -nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS -FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa -z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ -KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K -2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk -J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ -JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG -/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc -nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld -blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc -l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm -7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm -obp573PYtlNXLfbQ4ddI ------END CERTIFICATE----- - Buypass Class 2 Root CA ======================= -----BEGIN CERTIFICATE----- @@ -3481,55 +1373,6 @@ P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- -EE Certification Centre Root CA -=============================== ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG -EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy -dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw -MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB -UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy -ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM -TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 -rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw -93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN -P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T -AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ -MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF -BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj -xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM -lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU -3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM -dcGWxZ0= ------END CERTIFICATE----- - -TURKTRUST Certificate Services Provider Root 2007 -================================================= ------BEGIN CERTIFICATE----- -MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF -bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP -MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg -QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X -DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl -a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN -BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp -bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N -YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv -KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya -KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT -rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC -AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s -Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I -aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO -Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb -BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK -poRq0Tl9 ------END CERTIFICATE----- - D-TRUST Root Class 3 CA 2 2009 ============================== -----BEGIN CERTIFICATE----- @@ -3579,171 +1422,6 @@ NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv w9y4AyHqnxbxLFS1 -----END CERTIFICATE----- -PSCProcert -========== ------BEGIN CERTIFICATE----- -MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk -ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ -MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz -dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl -cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw -IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw -MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w -DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD -ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp -Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw -DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC -wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA -3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh -RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO -EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 -0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH -0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU -td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw -Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp -r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ -AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz -Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId -xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp -ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH -EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h -Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k -ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG -9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG -MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG -LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 -ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy -YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v -Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o -dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq -T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN -g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q -uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 -n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn -FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo -5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq -3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 -poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y -eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km ------END CERTIFICATE----- - -China Internet Network Information Center EV Certificates Root -============================================================== ------BEGIN CERTIFICATE----- -MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV -BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D -aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg -Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG -A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM -PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl -cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y -jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV -98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H -klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 -KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC -7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD -glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 -0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM -7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws -ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 -5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= ------END CERTIFICATE----- - -Swisscom Root CA 2 -================== ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG -EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy -dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 -MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln -aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC -IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM -LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo -ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ -wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH -Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a -SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS -NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab -mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY -Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 -qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw -HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O -BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu -MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO -v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ -82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz -o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs -a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx -OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW -mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o -+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC -rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX -5OfNeOI5wSsSnqaeG8XmDtkx2Q== ------END CERTIFICATE----- - -Swisscom Root EV CA 2 -===================== ------BEGIN CERTIFICATE----- -MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE -BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl -cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN -MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT -HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg -Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz -o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy -Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti -GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li -qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH -Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG -alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa -m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox -bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi -xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ -BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED -MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB -bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL -j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU -wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 -XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH -59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ -23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq -J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA -HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi -uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW -l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= ------END CERTIFICATE----- - -CA Disig Root R1 -================ ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw -EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp -ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx -EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp -c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy -3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 -u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 -m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk -CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa -YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 -vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL -LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX -ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is -XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ -04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR -xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B -LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM -CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb -VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 -YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS -ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix -lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N -UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ -a7+h89n07eLw4+1knj0vllJPgFOL ------END CERTIFICATE----- - CA Disig Root R2 ================ -----BEGIN CERTIFICATE----- @@ -3950,39 +1628,1587 @@ TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + COMODO RSA Certification Authority ================================== -----BEGIN CERTIFICATE----- -MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR -6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X -pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC -9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV -/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf -Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z -+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w -qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah -SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC -u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf -Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq -crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E -FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl -wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM -4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV -2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna -FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ -CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK -boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke -jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL -S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb -QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl -0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB -NVOFBkpdn627G190 +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl +OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV +MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF +JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +TrustCor RootCert CA-1 +====================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx +MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu +YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe +VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy +dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq +jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4 +pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0 +JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h +gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw +/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j +BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5 +mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C +qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P +3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +TrustCor RootCert CA-2 +====================== +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w +DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT +eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0 +eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy +MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h +bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0 +IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb +ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk +RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1 +oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb +XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1 +/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q +jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP +eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg +rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU +2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h +Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp +kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv +2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3 +S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw +PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv +DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU +RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE +xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX +RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ +-----END CERTIFICATE----- + +TrustCor ECA-1 +============== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw +N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5 +MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y +IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR +MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23 +xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc +p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+ +fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj +YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL +f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF +AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u +/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs +J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC +jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG +EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv +b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG +A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx +9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r +aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW +r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM +LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly +4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr +06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om +3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu +JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM +BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 +d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv +fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm +ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b +gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq +4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr +tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo +pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0 +sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql +CFF1pkgl +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG +EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv +b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG +A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk +k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo +7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI +m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm +dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu +ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz +cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl +aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy +5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM +BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT +vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ ++YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw +c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da +WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r +n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu +Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ +7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs +gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld +o/DUhgkC +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU +Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej +QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP +0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0 +glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa +KaqW04MjyaR7YbPMAuhd +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa +6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj +QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV +2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI +N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x +zPKwTdb+mciUqXWi4w== +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= -----END CERTIFICATE----- diff --git a/lib/support/gateway_support.rb b/lib/support/gateway_support.rb index b3bb28e54cf..c1e358db323 100644 --- a/lib/support/gateway_support.rb +++ b/lib/support/gateway_support.rb @@ -3,7 +3,7 @@ require 'active_merchant' class GatewaySupport #:nodoc: - ACTIONS = [:purchase, :authorize, :capture, :void, :credit, :recurring] + ACTIONS = %i[purchase authorize capture void credit recurring] include ActiveMerchant::Billing diff --git a/lib/support/ssl_verify.rb b/lib/support/ssl_verify.rb index 5570e7fde47..eb5a9c61157 100644 --- a/lib/support/ssl_verify.rb +++ b/lib/support/ssl_verify.rb @@ -2,7 +2,6 @@ require 'support/gateway_support' class SSLVerify - def initialize @gateways = GatewaySupport.new.gateways end @@ -18,9 +17,7 @@ def test_gateways next end - if !g.ssl_strict - disabled << g - end + disabled << g if !g.ssl_strict uri = URI.parse(g.live_url) result, message = ssl_verify_peer?(uri) @@ -30,10 +27,10 @@ def test_gateways success << g when :fail print 'F' - failed << {:gateway => g, :message => message} + failed << { gateway: g, message: message } when :error print 'E' - errored << {:gateway => g, :message => message} + errored << { gateway: g, message: message } end end @@ -83,10 +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 ed7c716c9c0..7a6def3a5d5 100644 --- a/lib/support/ssl_version.rb +++ b/lib/support/ssl_version.rb @@ -29,10 +29,10 @@ def test_gateways(min_version = :TLS1_1) success << g when :fail print 'F' - failed << {:gateway => g, :message => message} + failed << { gateway: g, message: message } when :error print 'E' - errored << {:gateway => g, :message => message} + errored << { gateway: g, message: message } end end @@ -75,13 +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/comm_stub.rb b/test/comm_stub.rb index 9293ef77955..23aa23f8aa6 100644 --- a/test/comm_stub.rb +++ b/test/comm_stub.rb @@ -8,17 +8,29 @@ def initialize(gateway, method_to_stub, action) @check = nil end - def check_request(&block) - @check = block - self + def check_request(skip_response: false, &block) + if skip_response + @complete = true + overwrite_gateway_method do |*args| + block&.call(*args) || '' + end + @action.call + else + @check = block + self + end + end + + def overwrite_gateway_method(&block) + singleton_class = (class << @gateway; self; end) + singleton_class.send(:undef_method, @method_to_stub) + singleton_class.send(:define_method, @method_to_stub, &block) end def respond_with(*responses) @complete = true check = @check - singleton_class = (class << @gateway; self; end) - singleton_class.send(:undef_method, @method_to_stub) - singleton_class.send(:define_method, @method_to_stub) do |*args| + overwrite_gateway_method do |*args| check&.call(*args) (responses.size == 1 ? responses.last : responses.shift) end @@ -40,7 +52,7 @@ def last_comm_stub @last_comm_stub ||= Stub::Complete.new end - def stub_comms(gateway=@gateway, method_to_stub=:ssl_post, &action) + def stub_comms(gateway = @gateway, method_to_stub = :ssl_post, &action) assert last_comm_stub.complete?, "Tried to stub communications when there's a stub already in progress." @last_comm_stub = Stub.new(gateway, method_to_stub, action) end diff --git a/test/fixtures.yml b/test/fixtures.yml index e58150b1857..7f3606fbc68 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -16,6 +16,14 @@ adyen: password: '' merchant_account: '' +airwallex: + client_id: SOMECREDENTIAL + client_api_key: ANOTHERCREDENTIAL + +alelo: + client_id: xxxxxxx + client_secret: xxxxxxx + allied_wallet: site_id: site_id merchant_id: merchant_id @@ -108,6 +116,13 @@ braintree_blue: private_key: Z merchant_account_id: A +braintree_blue_with_ach_enabled: + merchant_id: X + public_key: Y + private_key: Z + merchant_account_id: A + venmo_profile_id: B + braintree_blue_with_processing_rules: merchant_id: X public_key: Y @@ -163,7 +178,8 @@ cecabank: merchant_id: MERCHANTID acquirer_bin: ACQUIRERBIN terminal_id: TERMINALID - key: KEY + signature_key: KEY + is_rest_json: true cenpos: merchant_id: SOMECREDENTIAL @@ -179,7 +195,13 @@ checkout: password: Password1! checkout_v2: - secret_key: secret_key + secret_key: SECRET_KEY_FOR_BASIC_TRANSACTIONS + 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 @@ -203,6 +225,11 @@ clearhaus_secure: InviQqJd1KTGRDmWIGrE5YACVmW2JSszD9t5VKxkAA== -----END RSA PRIVATE KEY----- +commerce_hub: + api_key: API KEY + api_secret: API SECRET + merchant_id: MERCHANT ID + terminal_id: TERMINAL ID # Contact Support at it_support@commercegate.com for credentials and offer/site commercegate: @@ -220,6 +247,9 @@ creditcall: terminal_id: '99961426' transaction_key: '9drdRU9wJ65SNRw3' +# NOTE: the IP address you run the remote tests from will need to be +# whitelisted by Credorax; contact support@credorax.com as necessary to request +# your IP address be added to the whitelist for your test account. credorax: merchant_id: 'merchant_id' cipher_key: 'cipher_key' @@ -243,6 +273,16 @@ cyber_source: login: X password: Y +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 @@ -253,6 +293,27 @@ data_cash: login: X password: Y +# Working credentials, no need to replace +decidir_authorize: + api_key: 5a15fbc227224edabdb6f2e8219e8b28 + preauth_mode: true + +decidir_plus: + public_key: SOMECREDENTIAL + private_key: SOMECREDENTIAL + +decidir_plus_preauth: + public_key: SOMECREDENTIAL + private_key: SOMECREDENTIAL + +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 @@ -275,10 +336,17 @@ efsnet: password: Y # Provided for url update test + elavon: - login: "000127" - user: ssltest - password: "IERAOBEE5V0D6Q3Q6R51TG89XAIVGEQ3LGLKMKCKCVQBGGGAU7FN627GPA54P5HR" + login: "009005" + user: "devportal" + password: "BDDZY5KOUDCNPV4L3821K7PETO4Z7TPYOJB06TYBI1CW771IDHXBVBP51HZ6ZANJ" + +elavon_multi_currency: + login: "009006" + user: "devportal" + password: "XWJS3QTFCH40HW0QGHJKXAYADCTDH0TXXAKXAEZCGCCJ29CFNPCZT4KA9D5KQMDA" + multi_currency: true element: account_id: "1013963" @@ -377,8 +445,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" @@ -393,6 +466,9 @@ hdfc: hps: secret_api_key: "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ" +hps_echeck: + secret_api_key: + iats_payments: agent_code: TEST88 password: TEST88 @@ -406,6 +482,14 @@ instapay: login: TEST0 password: +# Working credentials, no need to replace +ipg: + store_id: "YOUR STORE ID" + 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 @@ -425,6 +509,12 @@ iveri: cert_id: CB69E68D-C7E7-46B9-9B7A-025DCABAD6EF app_id: d10a603d-4ade-405b-93f1-826dfc0181e8 +ixopay: + username: USERNAME + api_key: API_KEY + password: PASSWORD + secret: SHARED_SECRET + jetpay: login: TESTTERMINAL @@ -517,32 +607,40 @@ migs: advanced_login: activemerchant advanced_password: test12345 +# Working credentials, no need to replace +mit: + commerce_id: '147' + user: IVCA33721 + api_key: IGECPJ0QOJJCEHUI + key_session: CB0DC4887DD1D5CEA205E66EE934E430 + test: true + modern_payments: login: login password: password +moka: + dealer_code: DEALER_CODE + username: USERNAME + password: PASSWORD + # Working credentials, no need to replace monei: - sender_id: 8a829417488d34c401489a5cd1350977 - channel_id: 8a829417488d34c401489a5de5dd097b - login: 8a829417488d34c401489a5cd1360979 - pwd: GyNSEAp2 + api_key: pk_test_3cb2d54b7ee145fa92d683c01816ad15 # Working credentials, no need to replace moneris: login: store3 password: yesguy -moneris_us: - login: monusqa002 - password: qatoken - money_movers: login: demo password: password mundipagg: api_key: api_key + gateway_affiliation_id: gateway_affiliation_id + # left for backward-compatibility gateway_id: gateway_id # Working credentials, no need to replace @@ -590,6 +688,9 @@ nmi: login: demo password: password +nmi_secure: + security_key: '6457Thfj624V5r7WUwc5v6a68Zsd6YEm' + ogone: login: LOGIN user: USER @@ -608,8 +709,7 @@ openpay: merchant_id: 'mzdtln0bmtms6o3kck8f' opp: - user_id: '8a8294174b7ecb28014b9699220015cc' - password: 'sy6KJsT8' + access_token: 'OGE4Mjk0MTc0YjdlY2IyODAxNGI5Njk5MjIwMDE1Y2N8c3k2S0pzVDg=' entity_id: '8a8294174d0a8edd014d242337942575' optimal_payment: @@ -622,6 +722,16 @@ orbital_gateway: password: PASSWORD merchant_id: MERCHANTID +orbital_tandem_gateway: + login: LOGIN + 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' @@ -632,6 +742,10 @@ pago_facil: merchant_id: 62ad6f592ecf2faa87ef2437ed85a4d175e73c58 service_id: 3 +# Working credentials, no need to replace +pay_arc: + api_key: APIKEY + # Working credentials, no need to replace pay_conex: account_id: '220614968961' @@ -664,10 +778,20 @@ pay_secure: login: LOGIN password: PASSWORD +pay_trace: + username: USERNAME + password: PASSWORD + integrator_id: INTEGRATOR_ID + paybox_direct: - login: 199988863 + login: 1999888 password: 1999888I - rang: 85 + rang: 222 + credit_card_ok_3ds: 4012001037141112 + credit_card_nok_3ds: 4012001037141113 + credit_card_ok_3ds_not_enrolled: 4012001038443335 + credit_card_ok: 1111222233334444 + credit_card_nok: 1111222233334445 # Working credentials, no need to replace payeezy: @@ -699,6 +823,10 @@ paymentez: application_code: APPCODE app_key: APPKEY +paymentez_ecuador: + application_code: APPCODE + app_key: APPKEY + paymill: private_key: a9580be4a7b9d0151a3da88c6c935ce0 public_key: 57313835619696ac361dc591bc973626 @@ -746,6 +874,11 @@ paypal_signature: password: HBC6A84QLRWC923A signature: AFcWxV21C7fd0v3bYYYRCpSSRl31AC-11AKBL8FFO9tjImL311y8a0hx +paysafe: + login: SOMECREDENTIAL + secret_key: ANOTHERCREDENTIAL + account_id: CREDENTIAL + payscout: username: demo password: password @@ -770,13 +903,30 @@ payway: password: pem: +# Working credentials, no need to replace +payway_dot_com: + login: "sprerestwsdev" + password: "sprerestwsdev1!" + company_id: "3" + source_id: "67" + pin: api_key: "I_mo9BUUUXIwXF-avcs3LA" +plexo: + client_id: YOUR_CLIENT_ID + api_key: YOUR_API_KEY + merchant_id: YOUR_MERCHANT_ID + plugnpay: login: LOGIN password: PASSWORD +priority: + api_key: SANDBOX_KEY + secret: SECRET + merchant_id: MERCHANT_ID + # Working credentials, no need to replace pro_pay: cert_str: "5ab9cddef2e4911b77e0c4ffb70f03" @@ -855,8 +1005,17 @@ quantum: password: Y # You will need to create a developer sandbox at https://developer.intuit.com/ and -# successfully generate an OAuth 1.0a access token and token secret. +# successfully generate an OAuth 2.0 access token and refresh_token. Access token +# expires every 60 minutes quickbooks: + client_id: + client_secret: + refresh_token: + access_token: + +# You will need to create a developer sandbox at https://developer.intuit.com/ and +# successfully generate an OAuth 1.0a access token and token secret. +quickbooks_oauth_1: consumer_key: consumer_secret: access_token: @@ -880,6 +1039,11 @@ quickpay_with_api_key: apikey: "fB46983ZwR5dzy46A3r6ngDx7P37N5YTu1F4S9W2JKCs9v4t5L9m2Q8Mlbjpa2I1" version: 7 +# To get this credential, log into: https://quickstream.support.qvalent.com/->click Administration->Facility Settings-> +# Authentication and Credentials->update "Technology" to SOAP and save->create cert and download .pfx file-> +# use the following command to get the Bag Attributes: +# $ openssl pkcs12 -in filename.pfx -out cert.pem -nodes +# open cert.pem file in a text editor (e.g. TextEdit) and you'll see the Bag Attributes to add below under "pem:" field qvalent: username: "QRSL" password: "QRSLTEST" @@ -978,11 +1142,19 @@ qvalent: RFjxWKtn9pXbM1PLmUXCkKQnSJSeD1K0NjV+g8KFChTEgmhnLogyF/7YYw/amfc= -----END CERTIFICATE----- +rapyd: + access_key: SOMECREDENTIAL + secret_key: ANOTHERCREDENTIAL + raven_pac_net: user: ernest secret: "all good men die young" prn: 987743 +reach: + merchant_id: 'xxxxxxx' + secret: 'xxxxxxx' + realex: login: X password: Y @@ -1031,6 +1203,12 @@ realex_visa: year: '2020' verification_value: '123' +realex_visa_3ds_enrolled: + number: '4012001037141112' + month: '9' + year: '2021' + verification_value: '123' + realex_visa_coms_error: number: '4009830000001985' month: '6' @@ -1115,6 +1293,18 @@ secure_pay_tech: securion_pay: secret_key: pr_test_qZN4VVIKCySfCeXCBoHO9DBe +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' + client_secret: 'fq2riPpiDJaAwS4_UMAXZy1_nU1jNGz0F6gAFWOJFNmm_TfC8EFiHwMmGKAEDkwY' + # Replace with your serial numbers for the skipjack test environment skipjack: login: X @@ -1136,13 +1326,17 @@ stripe: # Working credentials, no need to replace stripe_destination: - stripe_user_id: "acct_17FRNfIPBJTitsen" + stripe_user_id: "acct_1K5HlrPT5iqVqrJn" # Externally verified bank account for testing stripe_verified_bank_account: customer_id: "cus_7s22nNueP2Hjj6" bank_account_id: "ba_17cHxeAWOtgoysogv3NM8CJ1" +sum_up: + access_token: SOMECREDENTIAL + pay_to_email: SOMECREDENTIAL + # Working credentials, no need to replace swipe_checkout: login: 2077103073D8B5 @@ -1158,10 +1352,15 @@ tns: userid: TESTSPREEDLY01 password: 3f34fe50334fbe6cbe04c283411a5860 +# This account seem to have expired tns_ap: userid: TESTUNISOLMAA01 password: b7f8119fda3bd27c17656badb52c95bb +tns_pay_mode: + userid: USERID + password: password + trans_first: login: 45567 password: TNYYKYMFZ59HSN7Q @@ -1232,6 +1431,13 @@ visanet_peru: merchant_id: "543025501" ruc: '20341198217' +vpos: + public_key: SOMECREDENTIAL + private_key: ANOTHERCREDENTIAL + encryption_key: "-----BEGIN PUBLIC KEY-----\n ..." + commerce: 123 + commerce_branch: 45 + webpay: login: "test_secret_eHn4TTgsGguBcW764a2KA8Yd" @@ -1253,6 +1459,10 @@ wirecard_checkout_page: shop_id: '' paymenttype: IDL +wompi: + test_public_key: SOMECREDENTIAL + test_private_key: ANOTHERCREDENTIAL + # Working credentials, no need to replace world_net: terminal_id: '6001' @@ -1274,3 +1484,6 @@ worldpay_us: acctid: MPNAB subid: SPREE merchantpin: "1234567890" + +x_pay: + api_key: 2d708950-50a1-434e-9a93-5d3ae2f1dd9f diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb index 10629ddbbb1..1c49c85eee9 100644 --- a/test/remote/gateways/remote_adyen_test.rb +++ b/test/remote/gateways/remote_adyen_test.rb @@ -6,25 +6,89 @@ def setup @amount = 100 - @credit_card = credit_card('4111111111111111', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'visa' + @bank_account = check(account_number: '123456789', routing_number: '121000358') + + @declined_bank_account = check(account_number: '123456789', routing_number: '121000348') + + @general_bank_account = check(name: 'A. Klaassen', account_number: '123456789', routing_number: 'NL13TEST0123456789') + + @credit_card = credit_card( + '4111111111111111', + month: 3, + year: 2030, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'visa' + ) + + @avs_credit_card = credit_card( + '4400000000000008', + month: 3, + year: 2030, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'visa' + ) + + @elo_credit_card = credit_card( + '5066 9911 1111 1118', + month: 3, + year: 2030, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'elo' ) - @elo_credit_card = credit_card('5066 9911 1111 1118', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @three_ds_enrolled_card = credit_card( + '4917610000000000', + month: 3, + year: 2030, + verification_value: '737', + brand: :visa + ) + + @cabal_credit_card = credit_card( + '6035 2277 1642 7021', + month: 3, + year: 2030, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'cabal' + ) + + @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' + ) + + @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' ) - @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) + @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' + ) @declined_card = credit_card('4000300011112220') @@ -38,31 +102,88 @@ def setup brand: 'mastercard' ) - @apple_pay_card = network_tokenization_credit_card('4111111111111111', - :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', - :month => '08', - :year => '2018', - :source => :apple_pay, - :verification_value => nil + @apple_pay_card = network_tokenization_credit_card( + '4761209980011439', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '11', + year: '2022', + source: :apple_pay, + verification_value: 569 ) - @google_pay_card = network_tokenization_credit_card('4111111111111111', - :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', - :month => '08', - :year => '2018', - :source => :google_pay, - :verification_value => nil + @google_pay_card = network_tokenization_credit_card( + '4761209980011439', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '11', + year: '2022', + source: :google_pay, + verification_value: nil ) + @nt_credit_card = network_tokenization_credit_card( + '4895370015293175', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '07', + source: :network_token, + 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', - shopper_email: 'john.smith@test.com', - shopper_ip: '77.110.174.153', + email: 'john.smith@test.com', + ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: @us_address, + order_id: '123', + stored_credential: { reason_type: 'unscheduled' } + } + + @normalized_3ds_2_options = { + reference: '345123', + email: 'john.smith@test.com', + ip: '77.110.174.153', shopper_reference: 'John Smith', billing_address: address(), order_id: '123', - recurring_processing_model: 'CardOnFile' + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + notification_url: 'https://example.com/notification', + browser_info: { + accept_header: 'unknown', + depth: 48, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } } + + @long_order_id = 'asdfjkl;asdfjkl;asdfj;aiwyutinvpoaieryutnmv;203987528752098375j3q-p489756ijmfpvbijpq348nmdf;vbjp3845' end def test_successful_authorize @@ -71,8 +192,32 @@ def test_successful_authorize assert_equal 'Authorised', response.message end + def test_successful_authorize_with_bank_account + response = @gateway.authorize(@amount, @bank_account, @options) + assert_success response + assert_equal 'Authorised', response.message + end + + def test_successful_authorize_avs + # Account configuration may need to be done: https://docs.adyen.com/developers/api-reference/payments-api#paymentresultadditionaldata + options = @options.update({ + billing_address: { + address1: 'Infinite Loop', + address2: 1, + country: 'US', + city: 'Cupertino', + state: 'CA', + zip: '95014' + } + }) + response = @gateway.authorize(@amount, @avs_credit_card, options) + assert_success response + assert_equal 'Authorised', response.message + assert_equal 'D', response.avs_result['code'] + end + def test_successful_authorize_with_idempotency_key - options = @options.merge(idempotency_key: 'test123') + options = @options.merge(idempotency_key: SecureRandom.hex) response = @gateway.authorize(@amount, @credit_card, options) assert_success response assert_equal 'Authorised', response.message @@ -93,6 +238,33 @@ def test_successful_authorize_with_3ds refute response.params['paRequest'].blank? end + def test_successful_authorize_with_execute_threed_false + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: false, sca_exemption: 'lowValue')) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'Authorised' + end + + def test_successful_authorize_with_3ds_with_idempotency_key + options = @options.merge(idempotency_key: SecureRandom.hex, execute_threed: true) + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'RedirectShopper' + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + + assert response2 = @gateway.authorize(@amount, @three_ds_enrolled_card, options) + assert_success response2 + refute response2.authorization.blank? + assert_equal response2.params['resultCode'], 'RedirectShopper' + refute response2.params['issuerUrl'].blank? + refute response2.params['md'].blank? + refute response2.params['paRequest'].blank? + assert_equal response.authorization, response2.authorization + end + def test_successful_authorize_with_3ds_dynamic assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(threed_dynamic: true)) assert response.test? @@ -103,15 +275,80 @@ def test_successful_authorize_with_3ds_dynamic refute response.params['paRequest'].blank? end + def test_successful_authorize_with_3ds2_browser_client_data + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_authorize_with_network_token + response = @gateway.authorize(@amount, @nt_credit_card, @options) + assert_success response + assert_equal 'Authorised', response.message + end + + def test_successful_purchase_with_3ds2_exemption_requested_and_execute_threed_false + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options.merge(execute_threed: false, sca_exemption: 'lowValue')) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'Authorised' + end + + # According to Adyen documentation, if execute_threed is set to true and an exemption provided + # the gateway will apply and request for the specified exemption in the authentication request, + # after the device fingerprint is submitted to the issuer. + def test_successful_purchase_with_3ds2_exemption_requested_and_execute_threed_true + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options.merge(execute_threed: true, sca_exemption: 'lowValue')) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_authorize_with_3ds2_app_based_request + three_ds_app_based_options = { + reference: '345123', + email: 'john.smith@test.com', + ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'app' + } + } + + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, three_ds_app_based_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank? + end + # with rule set in merchant account to skip 3DS for cards of this brand def test_successful_authorize_with_3ds_dynamic_rule_broken - mastercard_threed = credit_card('5212345678901234', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'mastercard' + mastercard_threed = credit_card( + '5212345678901234', + month: 3, + year: 2030, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'mastercard' ) assert response = @gateway.authorize(@amount, mastercard_threed, @options.merge(threed_dynamic: true)) assert response.test? @@ -119,11 +356,79 @@ def test_successful_authorize_with_3ds_dynamic_rule_broken assert_equal response.params['resultCode'], 'Authorised' end + # Fail in situations where neither execute_threed nor dynamic_threed is + # present, but the account is set to dynamic 3ds and it is triggered. This + # test assumes a Dynamic 3DS rule set for the Adyen test account to always + # perform 3ds auth for an amount of 8484 + def test_purchase_fails_on_unexpected_3ds_initiation + response = @gateway.purchase(8484, @three_ds_enrolled_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 + end + + def test_successful_purchase_with_auth_data_via_threeds1_standalone + eci = '05' + cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=' + cavv_algorithm = '1' + xid = 'ODUzNTYzOTcwODU5NzY3Qw==' + enrolled = 'Y' + authentication_response_status = 'Y' + options = @options.merge( + three_d_secure: { + eci: eci, + cavv: cavv, + cavv_algorithm: cavv_algorithm, + xid: xid, + enrolled: enrolled, + authentication_response_status: authentication_response_status + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + assert_equal 'Authorised', auth.message + # The assertion below requires the "3D Secure Result" data activated for the test account + assert_equal 'true', auth.params['additionalData']['liabilityShift'] + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_auth_data_via_threeds2_standalone + version = '2.1.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + directory_response_status = 'C' + authentication_response_status = 'Y' + + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + assert_equal 'Authorised', auth.message + # The assertion below requires the "3D Secure Result" data activated for the test account + assert_equal 'true', auth.params['additionalData']['liabilityShift'] + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + end + def test_successful_authorize_with_no_address options = { reference: '345123', - shopper_email: 'john.smith@test.com', - shopper_ip: '77.110.174.153', + email: 'john.smith@test.com', + ip: '77.110.174.153', shopper_reference: 'John Smith', order_id: '123', recurring_processing_model: 'CardOnFile' @@ -133,18 +438,50 @@ def test_successful_authorize_with_no_address assert_equal 'Authorised', response.message end + def test_successful_authorize_with_credit_card_no_name + credit_card_no_name = ActiveMerchant::Billing::CreditCard.new({ + number: '4111111111111111', + month: 3, + year: 2030, + verification_value: '737', + brand: 'visa' + }) + + response = @gateway.authorize(@amount, credit_card_no_name, @options) + assert_success response + assert_equal 'Authorised', response.message + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response assert_equal 'Refused', response.message end + def test_failed_authorize_with_bank_account + response = @gateway.authorize(@amount, @declined_bank_account, @options) + assert_failure response + assert_equal 'Bank Account or Bank Location Id not valid or missing', 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 'BankDetails missing', response.message + end + def test_successful_purchase response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal '[capture-received]', response.message end + def test_successful_purchase_with_bank_account + response = @gateway.purchase(@amount, @bank_account, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + def test_successful_purchase_no_cvv credit_card = @credit_card credit_card.verification_value = nil @@ -154,7 +491,13 @@ def test_successful_purchase_no_cvv end def test_successful_purchase_with_more_options - options = @options.merge!(fraudOffset: '1', installments: 2, shopper_statement: 'statement note', device_fingerprint: 'm7Cmrf++0cW4P6XfF7m/rA') + options = @options.merge!( + fraudOffset: '1', + installments: 2, + shopper_statement: 'statement note', + device_fingerprint: 'm7Cmrf++0cW4P6XfF7m/rA', + capture_delay_hours: 4 + ) response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal '[capture-received]', response.message @@ -176,7 +519,7 @@ def test_successful_purchase_with_risk_data end def test_successful_purchase_with_idempotency_key - options = @options.merge(idempotency_key: 'testkey45678') + options = @options.merge(idempotency_key: SecureRandom.hex) response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal '[capture-received]', response.message @@ -194,7 +537,13 @@ def test_successful_purchase_with_apple_pay end def test_succesful_purchase_with_brand_override - response = @gateway.purchase(@amount, @improperly_branded_maestro, @options.merge({overwrite_brand: true, selected_brand: 'maestro'})) + response = @gateway.purchase(@amount, @improperly_branded_maestro, @options.merge({ overwrite_brand: true, selected_brand: 'maestro' })) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_succesful_purchase_with_brand_override_with_execute_threed_false + response = @gateway.purchase(@amount, @improperly_branded_maestro, @options.merge({ execute_threed: false, overwrite_brand: true, selected_brand: 'maestro' })) assert_success response assert_equal '[capture-received]', response.message end @@ -205,18 +554,60 @@ def test_successful_purchase_with_google_pay assert_equal '[capture-received]', response.message end + def test_successful_purchase_with_google_pay_and_truncate_order_id + response = @gateway.purchase(@amount, @google_pay_card, @options.merge(order_id: @long_order_id)) + assert_success response + assert_equal '[capture-received]', response.message + end + def test_successful_purchase_with_elo_card response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL')) assert_success response assert_equal '[capture-received]', response.message end + def test_successful_purchase_with_cabal_card + response = @gateway.purchase(@amount, @cabal_credit_card, @options.merge(currency: 'ARS')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_unionpay_card + response = @gateway.purchase(@amount, @unionpay_credit_card, @options.merge(currency: 'CNY')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_network_token + response = @gateway.purchase(@amount, @nt_credit_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response assert_equal 'Refused', response.message end + def test_failed_purchase_with_bank_account + response = @gateway.purchase(@amount, @declined_bank_account, @options) + assert_failure response + assert_equal 'Bank Account or Bank Location Id not valid or missing', response.message + end + + def test_failed_purchase_with_invalid_cabal_card + response = @gateway.purchase(@amount, @invalid_cabal_credit_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + end + + def test_failed_purchase_with_invalid_unionpay_card + response = @gateway.purchase(@amount, @invalid_unionpay_credit_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + end + def test_successful_authorize_and_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -235,11 +626,47 @@ def test_successful_authorize_and_capture_with_elo_card assert_equal '[capture-received]', capture.message end + def test_successful_authorize_and_capture_with_cabal_card + auth = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + end + + def test_successful_authorize_and_capture_with_unionpay_card + auth = @gateway.authorize(@amount, @unionpay_credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + end + + def test_error_code_render_from_response + options = { + order_id: '123', + email: 'shopper@sky.uk', + billing_address: { + address2: 'address2', + zip: '31331', + city: 'Wanaque', + state: 'NJ', + country: 'IE' + }, + delivery_date: 'invalid' + } + response = @gateway.authorize(@amount, @credit_card, options) + assert_failure response + assert_equal '702', response.error_code + end + def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -258,6 +685,25 @@ def test_successful_refund assert_equal '[refund-received]', refund.message end + def test_successful_refund_with_bank_account + purchase = @gateway.purchase(@amount, @bank_account, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal '[refund-received]', refund.message + end + + def test_successful_refund_with_auth_original_reference + auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + assert_equal 'Authorised', auth_response.message + + refund_resp = @gateway.refund(@amount, auth_response.authorization) + assert_success refund_resp + assert_equal '[refund-received]', refund_resp.message + end + def test_successful_refund_with_elo_card purchase = @gateway.purchase(@amount, @elo_credit_card, @options) assert_success purchase @@ -267,11 +713,37 @@ def test_successful_refund_with_elo_card assert_equal '[refund-received]', refund.message end + def test_successful_refund_with_cabal_card + purchase = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal '[refund-received]', refund.message + end + + def test_successful_refund_with_unionpay_card + purchase = @gateway.purchase(@amount, @unionpay_credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal '[refund-received]', refund.message + end + def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_partial_refund_with_bank_account + purchase = @gateway.purchase(@amount, @bank_account, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -281,43 +753,308 @@ def test_failed_refund assert_equal 'Original pspReference required for this operation', response.message end - def test_successful_void - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) - assert void = @gateway.void(auth.authorization) - assert_success void - assert_equal '[cancel-received]', void.message + assert_success response + assert_equal 'Received', response.message end - def test_successful_void_with_elo_card - auth = @gateway.authorize(@amount, @elo_credit_card, @options) - assert_success auth + def test_successful_credit_with_bank_account + @options[:currency] = 'EUR' + @options[:billing_address][:country] = 'NL' + response = @gateway.credit(1500, @general_bank_account, @options) - assert void = @gateway.void(auth.authorization) - assert_success void - assert_equal '[cancel-received]', void.message + assert_success response + assert_equal 'Received', response.message end - def test_failed_void - response = @gateway.void('') + def test_failed_credit + response = @gateway.credit(@amount, '') assert_failure response - assert_equal 'Original pspReference required for this operation', response.message + assert_equal "Required field 'reference' is not provided.", response.message end - def test_successful_store - assert response = @gateway.store(@credit_card, @options) + def test_successful_payout_with_credit_card + response = @gateway.credit(2500, @credit_card, @payout_options) assert_success response - assert !response.authorization.split('#')[2].nil? assert_equal 'Authorised', response.message end - def test_successful_store_with_elo_card - assert response = @gateway.store(@elo_credit_card, @options) + 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 !response.authorization.split('#')[2].nil? + 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 + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_with_bank_account + auth = @gateway.authorize(@amount, @bank_account, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_with_elo_card + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_with_cabal_card + auth = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_with_unionpay_card + auth = @gateway.authorize(@amount, @unionpay_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successul_void_of_pending_3ds_authorization + assert auth = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true)) + assert auth.test? + refute auth.authorization.blank? + assert_equal auth.params['resultCode'], 'RedirectShopper' + refute auth.params['issuerUrl'].blank? + refute auth.params['md'].blank? + refute auth.params['paRequest'].blank? + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_requires_unique_idempotency_key + idempotency_key = SecureRandom.hex + options = @options.merge(idempotency_key: idempotency_key) + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert void = @gateway.void(auth.authorization, idempotency_key: idempotency_key) + assert_failure void + + assert void = @gateway.void(auth.authorization, idempotency_key: "#{idempotency_key}-auto-void") + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + def test_successful_asynchronous_adjust + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert adjust = @gateway.adjust(200, authorize.authorization, @options) + assert_success adjust + assert_equal '[adjustAuthorisation-received]', adjust.message + end + + def test_successful_asynchronous_adjust_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert adjust = @gateway.adjust(200, authorize.authorization, @options) + assert_success adjust + assert_equal '[adjustAuthorisation-received]', adjust.message + + assert capture = @gateway.capture(200, authorize.authorization) + assert_success capture + end + + def test_failed_asynchronous_adjust + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert response = @gateway.adjust(200, '', @options) + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_successful_synchronous_adjust_using_adjust_data + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth', shopper_statement: 'statement note')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'], update_shopper_statement: 'new statement note', industry_usage: 'DelayedCharge') + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_success adjust + assert_equal 'Authorised', adjust.message + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_successful_synchronous_adjust_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData']) + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_success adjust + assert_equal 'Authorised', adjust.message + + assert capture = @gateway.capture(200, authorize.authorization) + assert_success capture + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_failed_synchronous_adjust_using_adjust_data + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'], + requested_test_acquirer_response_code: '2') + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_failure adjust + assert_equal 'Refused', adjust.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + end + + def test_successful_store_with_bank_account + assert response = @gateway.store(@bank_account, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + end + + def test_successful_unstore + assert response = @gateway.store(@credit_card, @options) + + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + + shopper_reference = response.params['additionalData']['recurring.shopperReference'] + recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference'] + + assert response = @gateway.unstore(shopper_reference: shopper_reference, + recurring_detail_reference: recurring_detail_reference) + + assert_success response + assert_equal '[detail-successfully-disabled]', response.message + end + + def test_successful_unstore_with_bank_account + assert response = @gateway.store(@bank_account, @options) + + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + + shopper_reference = response.params['additionalData']['recurring.shopperReference'] + recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference'] + + assert response = @gateway.unstore(shopper_reference: shopper_reference, + recurring_detail_reference: recurring_detail_reference) + + assert_success response + assert_equal '[detail-successfully-disabled]', response.message + end + + def test_failed_unstore + assert response = @gateway.store(@credit_card, @options) + + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + + shopper_reference = response.params['additionalData']['recurring.shopperReference'] + recurring_detail_reference = response.params['additionalData']['recurring.recurringDetailReference'] + + assert response = @gateway.unstore(shopper_reference: 'random_reference', + recurring_detail_reference: recurring_detail_reference) + + assert_failure response + assert_equal 'Contract not found', response.message + + assert response = @gateway.unstore(shopper_reference: shopper_reference, + recurring_detail_reference: 'random_reference') + + assert_failure response + assert_equal 'PaymentDetail not found', response.message + end + + def test_successful_tokenize_only_store + assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true })) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Success', response.message + end + + def test_successful_tokenize_only_store_with_ntid + assert response = @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' })) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Success', response.message + end + + def test_successful_store_with_elo_card + assert response = @gateway.store(@elo_credit_card, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + end + + def test_successful_store_with_cabal_card + assert response = @gateway.store(@cabal_credit_card, @options) + assert_success response + end + + def test_successful_store_with_unionpay_card + assert response = @gateway.store(@unionpay_credit_card, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? assert_equal 'Authorised', response.message end @@ -361,6 +1098,18 @@ def test_successful_verify assert_match 'Authorised', response.message end + def test_successful_verify_with_custom_amount + response = @gateway.verify(@credit_card, @options.merge({ verify_amount: '500' })) + assert_success response + assert_match 'Authorised', response.message + end + + def test_successful_verify_with_bank_account + response = @gateway.verify(@bank_account, @options) + assert_success response + assert_match 'Authorised', response.message + end + def test_failed_verify response = @gateway.verify(@declined_card, @options) assert_failure response @@ -368,7 +1117,7 @@ def test_failed_verify end def test_verify_with_idempotency_key - options = @options.merge(idempotency_key: 'test123') + options = @options.merge(idempotency_key: SecureRandom.hex) response = @gateway.authorize(0, @credit_card, options) assert_success response assert_equal 'Authorised', response.message @@ -400,6 +1149,17 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:password], transcript) end + def test_transcript_scrubbing_with_bank_account + 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) + assert_scrubbed(@gateway.options[:password], transcript) + end + def test_transcript_scrubbing_network_tokenization_card transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @apple_pay_card, @options) @@ -429,7 +1189,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 'Expiry Date Invalid: 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 @@ -471,18 +1231,23 @@ def test_missing_state_for_purchase assert_success response end - def test_invalid_country_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 end - def test_invalid_state_for_purchase + def test_nil_state_for_purchase + @options[:billing_address][:state] = nil + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_blank_state_for_purchase @options[:billing_address][:state] = '' response = @gateway.authorize(@amount, @credit_card, @options) - assert_failure response - assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_address], response.error_code + assert_success response end def test_missing_phone_for_purchase @@ -490,4 +1255,462 @@ def test_missing_phone_for_purchase response = @gateway.authorize(@amount, @credit_card, @options) assert_success response end + + def test_successful_auth_application_info + options = @options.merge!( + externalPlatform: { + name: 'Acme', + version: '1', + integrator: 'abc' + }, + merchantApplication: { + name: 'Acme Inc.', + version: '2' + } + ) + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + end + + def test_successful_authorize_phone + @options[:billing_address][:phone] = '1234567890' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_authorize_phone_number + @options[:billing_address].delete(:phone) + @options[:billing_address][:phone_number] = '0987654321' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_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 = stored_credential_options(:recurring, :cardholder, ntid: auth.network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_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 = stored_credential_options(:recurring, :cardholder, ntid: auth.network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_cit + initial_options = stored_credential_options(:cardholder, :unscheduled, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success auth + assert_equal 'CardOnFile', auth.params['additionalData']['recurringProcessingModel'] + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + + used_options = stored_credential_options(:unscheduled, :cardholder, ntid: auth.network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_mit + initial_options = stored_credential_options(:merchant, :unscheduled, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success auth + assert_equal 'UnscheduledCardOnFile', auth.params['additionalData']['recurringProcessingModel'] + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + + used_options = stored_credential_options(:unscheduled, :cardholder, ntid: auth.network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_auth_and_capture_with_network_txn_id + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_options) + assert auth.network_transaction_id + + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(network_transaction_id: auth.network_transaction_id)) + assert_success capture + end + + def test_auth_and_capture_with_network_txn_id_from_stored_cred_hash + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_options) + assert auth.network_transaction_id + + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(stored_credential: { network_transaction_id: auth.network_transaction_id })) + assert_success capture + end + + def test_auth_capture_refund_with_network_txn_id + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert auth = @gateway.authorize(@amount, @credit_card, initial_options) + assert auth.network_transaction_id + + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(network_transaction_id: auth.network_transaction_id)) + assert_success capture + + assert refund = @gateway.refund(@amount, auth.authorization, @options.merge(network_transaction_id: auth.network_transaction_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', + sub_merchant_name: 'Wildsea', + sub_merchant_street: '1234 Street St', + sub_merchant_city: 'Night City', + sub_merchant_state: 'East Block', + sub_merchant_postal_code: '112233', + sub_merchant_country: 'EUR', + sub_merchant_tax_id: '12345abcde67', + sub_merchant_mcc: '1234' + } + options = @options.update({ + installments: 2, + billing_address: { + address1: 'Infinite Loop', + address2: 1, + country: 'US', + city: 'Cupertino', + state: 'CA', + zip: '95014' + } + }) + assert response = @gateway.authorize(@amount, @avs_credit_card, options.merge(sub_merchant_data)) + assert response.test? + refute response.authorization.blank? + assert_success response + 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', + customer_reference: '101' + } + assert response = @gateway.authorize(@amount, @avs_credit_card, @options.merge(level_2_data: level_2_data)) + assert response.test? + refute response.authorization.blank? + assert_success response + end + + def test_successful_purchase_with_level_2_data + level_2_data = { + total_tax_amount: '160', + customer_reference: '101' + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: level_2_data)) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_authorize_with_level_3_data + level_3_data = { + total_tax_amount: '12800', + customer_reference: '101', + freight_amount: '300', + destination_state_province_code: 'NYC', + ship_from_postal_code: '1082GM', + order_date: '101216', + destination_postal_code: '1082GM', + destination_country_code: 'NLD', + duty_amount: '500', + items: [ + { + description: 'T16 Test products 1', + product_code: 'TEST120', + commodity_code: 'COMMCODE1', + quantity: '5', + unit_of_measure: 'm', + unit_price: '1000', + discount_amount: '60', + total_amount: '4940' + } + ] + } + assert response = @gateway.authorize(@amount, @avs_credit_card, @options.merge(level_3_data: level_3_data)) + assert response.test? + assert_success response + end + + def test_successful_purchase_with_level_3_data + level_3_data = { + total_tax_amount: '12800', + customer_reference: '101', + freight_amount: '300', + destination_state_province_code: 'NYC', + ship_from_postal_code: '1082GM', + order_date: '101216', + destination_postal_code: '1082GM', + destination_country_code: 'NLD', + duty_amount: '500', + items: [ + { + description: 'T16 Test products 1', + product_code: 'TEST120', + commodity_code: 'COMMCODE1', + quantity: '5', + unit_of_measure: 'm', + unit_price: '1000', + discount_amount: '60', + total_amount: '4940' + } + ] + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(level_3_data: level_3_data)) + assert_success response + 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 + + assert cancel = @gateway.void(auth.authorization) + assert_success cancel + assert_equal '[cancel-received]', cancel.message + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + @options[:cancel_or_refund] = true + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal '[cancelOrRefund-received]', void.message + assert_void_references_original_authorization(void, auth) + end + + def test_successful_cancel_or_refund_passing_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @options[:cancel_or_refund] = true + assert void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal '[cancelOrRefund-received]', void.message + assert_void_references_original_authorization(void, purchase.responses.first) + end + + def test_successful_cancel_or_refund_passing_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + @options[:cancel_or_refund] = true + assert void = @gateway.void(capture.authorization, @options) + assert_success void + assert_equal '[cancelOrRefund-received]', void.message + assert_void_references_original_authorization(void, auth) + end + + def test_successful_authorize_with_alternate_kosovo_code + @options[:billing_address][:country] = 'XK' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Billing address problem (Country XK invalid)', response.message + + @options[:billing_address][:country] = 'QZ' + response = @gateway.authorize(@amount, @credit_card, @options) + 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 + + private + + def stored_credential_options(*args, ntid: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, network_transaction_id: ntid)) + end + + def assert_void_references_original_authorization(void, auth) + assert_equal void.authorization.split('#').first, auth.params['pspReference'] + end end diff --git a/test/remote/gateways/remote_airwallex_test.rb b/test/remote/gateways/remote_airwallex_test.rb new file mode 100644 index 00000000000..24aa9fe3b61 --- /dev/null +++ b/test/remote/gateways/remote_airwallex_test.rb @@ -0,0 +1,260 @@ +require 'test_helper' + +class RemoteAirwallexTest < Test::Unit::TestCase + def setup + @gateway = AirwallexGateway.new(fixtures(:airwallex)) + + # https://www.airwallex.com/docs/online-payments__test-card-numbers + @amount = 100 + @declined_amount = 8014 + @credit_card = credit_card('4012 0003 0000 1003') + @declined_card = credit_card('2223 0000 1018 1375') + @options = { return_url: 'https://example.com', description: 'a test transaction' } + @stored_credential_cit_options = { initial_transaction: true, initiator: 'cardholder', reason_type: 'recurring', network_transaction_id: nil } + @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 + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_purchase_with_shipping_address + response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: address)) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_purchase_with_address + response = @gateway.purchase(@amount, @credit_card, @options.merge(address)) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_purchase_with_specified_ids + request_id = SecureRandom.uuid + merchant_order_id = SecureRandom.uuid + response = @gateway.purchase(@amount, @credit_card, @options.merge(request_id: request_id, merchant_order_id: merchant_order_id)) + assert_success response + assert_match(request_id, response.params.dig('request_id')) + assert_match(merchant_order_id, response.params.dig('merchant_order_id')) + end + + def test_successful_purchase_with_skip_3ds + response = @gateway.purchase(@amount, @credit_card, @options.merge({ skip_3ds: 'true' })) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@declined_amount, @declined_card, @options) + assert_failure response + assert_equal 'The card issuer declined this transaction. Please refer to the original response code.', response.message + assert_equal '14', response.error_code + end + + def test_purchase_with_reused_id_raises_error + assert_raise ArgumentError do + @gateway.purchase(@amount, @credit_card, @options.merge(request_id: '1234')) + end + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'CAPTURE_REQUESTED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@declined_amount, @declined_card, @options) + assert_failure response + assert_equal 'The card issuer declined this transaction. Please refer to the original response code.', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@declined_amount, '12345', @options) + assert_failure response + assert_match(/The requested endpoint does not exist/, response.message) + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'RECEIVED', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@declined_amount, '12345', @options) + assert_failure response + assert_match(/The PaymentIntent with ID 12345 cannot be found./, response.message) + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'CANCELLED', void.message + end + + def test_failed_void + response = @gateway.void('12345', @options) + assert_failure response + assert_match(/The requested endpoint does not exist/, response.message) + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{AUTHORIZED}, response.message + end + + def test_failed_verify + response = @gateway.verify(credit_card('1111111111111111'), @options) + assert_failure response + assert_match %r{Invalid card number}, response.message + end + + def test_successful_cit_with_recurring_stored_credential + auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_cit_options)) + assert_success auth + end + + def test_successful_mit_with_recurring_stored_credential + auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_cit_options)) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_mit_options)) + assert_success purchase + end + + def test_successful_mit_with_unscheduled_stored_credential + @stored_credential_cit_options[:reason_type] = 'unscheduled' + @stored_credential_mit_options[:reason_type] = 'unscheduled' + + auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_cit_options)) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_mit_options)) + assert_success purchase + end + + def test_successful_mit_with_installment_stored_credential + @stored_credential_cit_options[:reason_type] = 'installment' + @stored_credential_mit_options[:reason_type] = 'installment' + + auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_cit_options)) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: @stored_credential_mit_options)) + assert_success purchase + end + + def test_successful_network_transaction_id_override_with_mastercard + mastercard = credit_card('2223 0000 1018 1375', { brand: 'master' }) + + auth = @gateway.authorize(@amount, mastercard, @options.merge(stored_credential: @stored_credential_cit_options)) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = @gateway.purchase(@amount, mastercard, @options.merge(stored_credential: @stored_credential_mit_options)) + assert_success purchase + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:client_api_key], transcript) + end + + def test_successful_authorize_with_3ds_v1_options + @options[:three_d_secure] = { + version: '1', + cavv: 'VGhpcyBpcyBhIHRlc3QgYmFzZTY=', + eci: '02', + xid: 'b2h3aDZrd3BJWXVCWEFMbzJqSGQ=' + } + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match 'AUTHORIZED', response.message + end + + def test_successful_authorize_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.2.0', + cavv: 'MTIzNDU2Nzg5MDA5ODc2NTQzMjE=', + ds_transaction_id: 'f25084f0-5b16-4c0a-ae5d-b24808a95e4b', + eci: '02', + three_ds_server_trans_id: 'df8b9557-e41b-4e17-87e9-2328694a2ea0' + } + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match 'AUTHORIZED', response.message + end + + def test_successful_purchase_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.0', + cavv: 'MTIzNDU2Nzg5MDA5ODc2NTQzMjE=', + ds_transaction_id: 'f25084f0-5b16-4c0a-ae5d-b24808a95e4b', + eci: '02', + three_ds_server_trans_id: 'df8b9557-e41b-4e17-87e9-2328694a2ea0' + } + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match 'AUTHORIZED', response.message + end + + private + + def add_cit_network_transaction_id_to_stored_credential(auth) + @stored_credential_mit_options[:network_transaction_id] = auth.params['latest_payment_attempt']['provider_transaction_id'] + end +end diff --git a/test/remote/gateways/remote_alelo_test.rb b/test/remote/gateways/remote_alelo_test.rb new file mode 100644 index 00000000000..8a4fef24e7b --- /dev/null +++ b/test/remote/gateways/remote_alelo_test.rb @@ -0,0 +1,204 @@ +require 'test_helper' +require 'singleton' + +class RemoteAleloTest < Test::Unit::TestCase + def setup + @gateway = AleloGateway.new(fixtures(:alelo)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000300011112220') + @options = { + order_id: '1', + establishment_code: '000002007690360', + sub_merchant_mcc: '5499', + player_identification: '1', + description: 'Store Purchase', + external_trace_number: '123456' + } + end + + def test_access_token_success + resp = @gateway.send :fetch_access_token + + assert_kind_of Response, resp + refute_nil resp.message + end + + def test_failure_access_token_with_invalid_keys + error = assert_raises(ActiveMerchant::OAuthResponseError) do + gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' }) + gateway.send :fetch_access_token + end + + assert_match(/401/, error.message) + end + + def test_successful_remote_encryption_key_with_provided_access_token + access_token = @gateway.send :fetch_access_token + resp = @gateway.send(:remote_encryption_key, access_token.message) + + assert_kind_of Response, resp + refute_nil resp.message + end + + def test_ensure_credentials_with_no_provided_access_token_key_are_generated + credentials = @gateway.send :ensure_credentials, {} + + refute_nil credentials[:key] + refute_nil credentials[:access_token] + assert_kind_of Response, credentials[:multiresp] + assert_equal 2, credentials[:multiresp].responses.size + end + + def test_sucessful_encryption_key_requested_when_access_token_provided + access_token = @gateway.send :fetch_access_token + @gateway.options[:access_token] = access_token.message + credentials = @gateway.send :ensure_credentials + + refute_nil credentials[:key] + refute_nil credentials[:access_token] + assert_equal access_token.message, credentials[:access_token] + assert_kind_of Response, credentials[:multiresp] + assert_equal 1, credentials[:multiresp].responses.size + end + + def test_successful_fallback_with_expired_access_token + @gateway.options[:access_token] = 'abc123' + credentials = @gateway.send :ensure_credentials + + refute_nil credentials[:key] + refute_nil credentials[:access_token] + refute_equal 'abc123', credentials[:access_token] + assert_kind_of Response, credentials[:multiresp] + assert_equal 2, credentials[:multiresp].responses.size + end + + def test_successful_purchase + set_credentials! + @gateway.options[:encryption_uuid] = '53141521-afc8-4a08-af0c-f0382aef43c1' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_match %r{confirmada}i, response.message + end + + def test_successful_purchase_with_no_predefined_credentials + @gateway.options[:encryption_uuid] = '53141521-afc8-4a08-af0c-f0382aef43c1' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_match %r{confirmada}i, response.message + refute_nil response.params['access_token'] + refute_nil response.params['encryption_key'] + end + + def test_unsuccessful_purchase_with_merchant_discredited + set_credentials! + @gateway.options[:encryption_uuid] = '7c82f46e-64f7-4745-9c60-335a689b8e90' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_match %r(contato), response.message + end + + def test_unsuccessful_purchase_with_insuffieicent_funds + set_credentials! + @gateway.options[:encryption_uuid] = 'a36aa740-d505-4d47-8aa6-6c31c7526a68' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_match %r(insuficiente), response.message + end + + def test_unsuccessful_purchase_with_invalid_fields + set_credentials! + @gateway.options[:encryption_uuid] = 'd7aff4a6-1ea1-4e74-b81a-934589385958' + response = @gateway.purchase(@amount, @declined_card, @options) + + assert_failure response + assert_match %r{Erro}, response.message + end + + def test_unsuccessful_purchase_with_blocked_card + set_credentials! + @gateway.options[:encryption_uuid] = 'd2a0350d-e872-47bf-a543-2d36c2ad693e' + response = @gateway.purchase(@amount, @declined_card, @options) + + assert_failure response + assert_match %r(Bloqueado), response.message + end + + def test_successful_purchase_with_geolocalitation + set_credentials! + options = { + geo_longitude: '10.451526', + geo_latitude: '51.165691', + uuid: '53141521-afc8-4a08-af0c-f0382aef43c1' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + assert_match %r(Confirmada), response.message + end + + def test_invalid_login + gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre') + + error = assert_raises(ActiveMerchant::OAuthResponseError) do + gateway.purchase(@amount, @credit_card, @options) + end + + assert_match(/401/, error.message) + end + + def test_transcript_scrubbing + set_credentials! + transcript = capture_transcript(@gateway) do + @gateway.options[:encryption_uuid] = '53141521-afc8-4a08-af0c-f0382aef43c1' + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:client_id], transcript) + assert_scrubbed(@gateway.options[:client_secret], transcript) + end + + def test_successful_refund + set_credentials! + response = @gateway.refund(@amount, '990a39dd-3df2-46a5-89ac-012cca00ef0b#def456', {}) + + assert_success response + assert_match %r{Estornada}, response.message + end + + def test_failure_refund_with_invalid_uuid + set_credentials! + response = @gateway.refund(@amount, '7f723387-d449-4c6c-aca3-9a583689dc34', {}) + + assert_failure response + end + + private + + def set_credentials! + if AleloCredentials.instance.access_token.nil? + credentials = @gateway.send :ensure_credentials, {} + AleloCredentials.instance.access_token = credentials[:access_token] + AleloCredentials.instance.key = credentials[:key] + AleloCredentials.instance.uuid = credentials[:uuid] + end + + @gateway.options[:access_token] = AleloCredentials.instance.access_token + @gateway.options[:encryption_key] = AleloCredentials.instance.key + @gateway.options[:encryption_uuid] = AleloCredentials.instance.uuid + end +end + +# A simple singleton so an access token and key can +# be shared among several tests +class AleloCredentials + include Singleton + + attr_accessor :access_token, :key, :uuid +end diff --git a/test/remote/gateways/remote_alelo_test_certification.rb b/test/remote/gateways/remote_alelo_test_certification.rb new file mode 100644 index 00000000000..f6a2cb0d771 --- /dev/null +++ b/test/remote/gateways/remote_alelo_test_certification.rb @@ -0,0 +1,136 @@ +require 'test_helper' +require 'singleton' + +class RemoteAleloTestCertification < Test::Unit::TestCase + def setup + @gateway = AleloGateway.new(fixtures(:alelo_certification)) + @amount = 1000 + @cc_alimentacion = credit_card('5098870005467012', { + month: 8, + year: 2027, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: 747, + brand: 'mc' + }) + @cc_snack = credit_card('5067580024660011', { + month: 8, + year: 2027, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: 576, + brand: 'mc' + }) + @options = { + order_id: SecureRandom.uuid, + establishment_code: '1040471819', + sub_merchant_mcc: '5499', + player_identification: '7', + description: 'Store Purchase', + external_trace_number: '123456' + } + end + + def test_failure_purchase_with_wrong_cvv_ct05 + set_credentials! + @cc_snack.verification_value = 999 + response = @gateway.purchase(@amount, @cc_snack, @options) + + assert_failure response + assert_match %r{incorreto}i, response.message + end + + def test_failure_with_incomplete_required_options_ct06 + set_credentials! + @options.delete(:establishment_code) + response = @gateway.purchase(@amount, @cc_alimentacion, @options) + + assert_failure response + assert_match %r{Erro ao validar dados}i, response.message + end + + def test_failure_refund_with_non_existent_uuid_ct07 + set_credentials! + response = @gateway.refund(@amount, SecureRandom.uuid, {}) + + assert_failure response + assert_match %r{RequestId informado, não encontrado!}, response.message + end + + def test_successful_purchase_with_alimentazao_cc_ct08 + response = @gateway.purchase(@amount, @cc_alimentacion, @options) + + assert_success response + assert_match %r{confirmada}i, response.message + end + + # Testing High value transaction disables the test credit card + # + # def test_successful_purchase_with_alimentazao_cc_ct08_B_high_value + # response = @gateway.purchase(10_000_000, @cc_alimentacion, @options) + # assert_failure response + # end + + def test_successful_refund_ct09 + set_credentials! + purchase = @gateway.purchase(@amount, @cc_alimentacion, @options) + response = @gateway.refund(@amount, purchase.authorization, {}) + + assert_success response + assert_match %r{Transação Estornada com sucesso}, response.message + end + + def test_failure_with_non_exitent_establishment_code_ct10 + @options[:establishment_code] = '0987654321' + @options[:sub_merchant_mcc] = '5411' + + response = @gateway.purchase(@amount, @cc_alimentacion, @options) + + assert_failure response + assert_match %r{no adquirente}i, response.message + end + + def test_failure_when_cc_expired_ct11 + @cc_alimentacion.year = 2020 + set_credentials! + + response = @gateway.purchase(@amount, @cc_alimentacion, @options) + + assert_failure response + end + + def test_failure_refund_with_purchase_already_refunded_ct12 + set_credentials! + purchase = @gateway.purchase(@amount, @cc_alimentacion, @options) + assert_success purchase + + response = @gateway.refund(@amount, purchase.authorization, {}) + assert_success response + + response = @gateway.refund(@amount, purchase.authorization, {}) + assert_failure response + end + + private + + def set_credentials! + if AleloCredentials.instance.access_token.nil? + credentials = @gateway.send :ensure_credentials, {} + AleloCredentials.instance.access_token = credentials[:access_token] + AleloCredentials.instance.key = credentials[:key] + AleloCredentials.instance.uuid = credentials[:uuid] + end + + @gateway.options[:access_token] = AleloCredentials.instance.access_token + @gateway.options[:encryption_key] = AleloCredentials.instance.key + @gateway.options[:encryption_uuid] = AleloCredentials.instance.uuid + end +end + +# A simple singleton so an access token and key can +# be shared among several tests +class AleloCredentials + include Singleton + + attr_accessor :access_token, :key, :uuid +end diff --git a/test/remote/gateways/remote_allied_wallet_test.rb b/test/remote/gateways/remote_allied_wallet_test.rb index 8ab761e508b..7511b5f9644 100644 --- a/test/remote/gateways/remote_allied_wallet_test.rb +++ b/test/remote/gateways/remote_allied_wallet_test.rb @@ -9,7 +9,7 @@ def setup @declined_card = credit_card('4242424242424242', verification_value: '555') @options = { - billing_address: address, + billing_address: address } end @@ -32,11 +32,12 @@ def test_failed_purchase_no_address end def test_successful_purchase_with_more_options - response = @gateway.purchase(@amount, @credit_card, @options.merge( + options = @options.merge( order_id: generate_unique_id, ip: '127.0.0.1', email: 'jim_smith@example.com' - )) + ) + response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'Succeeded', response.message end diff --git a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb index cf430b4a975..836af912cb3 100644 --- a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb +++ b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb @@ -47,14 +47,14 @@ def test_successful_apple_pay_authorization_and_void end def test_failed_apple_pay_authorization - response = @gateway.authorize(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options) + response = @gateway.authorize(@amount, apple_pay_payment_token(payment_data: { data: 'garbage' }), @options) assert_failure response assert_equal 'There was an error processing the payment data', response.message assert_equal 'processing_error', response.error_code end def test_failed_apple_pay_purchase - response = @gateway.purchase(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options) + response = @gateway.purchase(@amount, apple_pay_payment_token(payment_data: { data: 'garbage' }), @options) assert_failure response assert_equal 'There was an error processing the payment data', response.message assert_equal 'processing_error', response.error_code @@ -82,11 +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] ) end - end diff --git a/test/remote/gateways/remote_authorize_net_arb_test.rb b/test/remote/gateways/remote_authorize_net_arb_test.rb index 5a04f9de419..77ba6ae7c8f 100644 --- a/test/remote/gateways/remote_authorize_net_arb_test.rb +++ b/test/remote/gateways/remote_authorize_net_arb_test.rb @@ -8,17 +8,17 @@ def setup @check = check @options = { - :amount => 100, - :subscription_name => 'Test Subscription 1', - :credit_card => @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :interval => { - :length => 1, - :unit => :months + amount: 100, + subscription_name: 'Test Subscription 1', + credit_card: @credit_card, + billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'), + interval: { + length: 1, + unit: :months }, - :duration => { - :start_date => Date.today, - :occurrences => 1 + duration: { + start_date: Date.today, + occurrences: 1 } } end @@ -30,7 +30,7 @@ def test_successful_recurring subscription_id = response.authorization - assert response = @gateway.update_recurring(:subscription_id => subscription_id, :amount => @amount * 2) + assert response = @gateway.update_recurring(subscription_id: subscription_id, amount: @amount * 2) assert_success response assert response = @gateway.status_recurring(subscription_id) @@ -50,8 +50,8 @@ def test_recurring_should_fail_expired_credit_card def test_bad_login gateway = AuthorizeNetArbGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.recurring(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_authorize_net_cim_test.rb b/test/remote/gateways/remote_authorize_net_cim_test.rb index 124b81c7753..9fe51696f8c 100644 --- a/test/remote/gateways/remote_authorize_net_cim_test.rb +++ b/test/remote/gateways/remote_authorize_net_cim_test.rb @@ -9,39 +9,39 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') @payment = { - :credit_card => @credit_card + credit_card: @credit_card } @profile = { - :merchant_customer_id => 'Up to 20 chars', # Optional - :description => 'Up to 255 Characters', # Optional - :email => 'Up to 255 Characters', # Optional - :payment_profiles => { # Optional - :customer_type => 'individual', # Optional - :bill_to => address, - :payment => @payment + merchant_customer_id: 'Up to 20 chars', # Optional + description: 'Up to 255 Characters', # Optional + email: 'Up to 255 Characters', # Optional + payment_profiles: { # Optional + customer_type: 'individual', # Optional + bill_to: address, + payment: @payment }, - :ship_to_list => { - :first_name => 'John', - :last_name => 'Doe', - :company => 'Widgets, Inc', - :address1 => '1234 Fake Street', - :city => 'Anytown', - :state => 'MD', - :zip => '12345', - :country => 'USA', - :phone_number => '(123)123-1234', # Optional - Up to 25 digits (no letters) - :fax_number => '(123)123-1234' # Optional - Up to 25 digits (no letters) + ship_to_list: { + first_name: 'John', + last_name: 'Doe', + company: 'Widgets, Inc', + address1: '1234 Fake Street', + city: 'Anytown', + state: 'MD', + zip: '12345', + country: 'USA', + phone_number: '(123)123-1234', # Optional - Up to 25 digits (no letters) + fax_number: '(123)123-1234' # Optional - Up to 25 digits (no letters) } } @options = { - :ref_id => '1234', # Optional - :profile => @profile + ref_id: '1234', # Optional + profile: @profile } end def teardown if @customer_profile_id - assert response = @gateway.delete_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.delete_customer_profile(customer_profile_id: @customer_profile_id) assert_success response @customer_profile_id = nil end @@ -54,7 +54,7 @@ def test_successful_profile_create_get_update_and_delete assert_success response assert response.test? - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert response.test? assert_success response assert_equal @customer_profile_id, response.authorization @@ -68,16 +68,35 @@ def test_successful_profile_create_get_update_and_delete assert_equal @profile[:ship_to_list][:phone_number], response.params['profile']['ship_to_list']['phone_number'] assert_equal @profile[:ship_to_list][:company], response.params['profile']['ship_to_list']['company'] - assert response = @gateway.update_customer_profile(:profile => {:customer_profile_id => @customer_profile_id, :email => 'new email address'}) + assert response = @gateway.update_customer_profile(profile: { customer_profile_id: @customer_profile_id, email: 'new email address' }) assert response.test? assert_success response assert_nil response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) - assert_nil response.params['profile']['merchant_customer_id'] - assert_nil response.params['profile']['description'] + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_equal 'new email address', response.params['profile']['email'] end + def test_get_customer_profile_with_unmasked_exp_date_and_issuer_info + assert response = @gateway.create_customer_profile(@options) + @customer_profile_id = response.authorization + + assert_success response + assert response.test? + + assert response = @gateway.get_customer_profile( + customer_profile_id: @customer_profile_id, + unmask_expiration_date: true, + include_issuer_info: true + ) + assert response.test? + assert_success response + assert_equal @customer_profile_id, response.authorization + assert_equal 'Successful.', response.message + assert_equal "XXXX#{@credit_card.last_digits}", response.params['profile']['payment_profiles']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}" + assert_equal formatted_expiration_date(@credit_card), response.params['profile']['payment_profiles']['payment']['credit_card']['expiration_date'] + assert_equal @credit_card.first_digits, response.params['profile']['payment_profiles']['payment']['credit_card']['issuer_number'] + end + # NOTE - prior_auth_capture should be used to complete an auth_only request # (not capture_only as that will leak the authorization), so don't use this # test as a template. @@ -85,15 +104,15 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :amount => @amount + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_only, + amount: @amount } ) @@ -111,12 +130,12 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu # Capture the previously authorized funds assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :capture_only, - :amount => @amount, - :approval_code => approval_code + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :capture_only, + amount: @amount, + approval_code: approval_code } ) @@ -133,22 +152,22 @@ def test_successful_create_customer_profile_transaction_auth_capture_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => '1234', - :description => 'Test Order Description', - :purchase_order_number => '4321' + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: '1234', + description: 'Test Order Description', + purchase_order_number: '4321' }, - :recurring_billing => true, - :card_code => '900', # authorize.net says this is a matching CVV - :amount => @amount + recurring_billing: true, + card_code: '900', # authorize.net says this is a matching CVV + amount: @amount } ) @@ -169,17 +188,17 @@ def test_successful_create_customer_payment_profile_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['payment_profiles'] assert response = @gateway.create_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => payment_profile + customer_profile_id: @customer_profile_id, + payment_profile: payment_profile ) assert response.test? assert_success response - assert_nil response.authorization + assert_equal @customer_profile_id, response.authorization assert customer_payment_profile_id = response.params['customer_payment_profile_id'] assert customer_payment_profile_id =~ /\d+/, "The customerPaymentProfileId should be numeric. It was #{customer_payment_profile_id}" end @@ -189,36 +208,36 @@ def test_successful_create_customer_payment_profile_request_with_bank_account assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['payment_profiles'] assert response = @gateway.create_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_type => 'individual', # Optional - :bill_to => @address, - :payment => { - :bank_account => { - :account_type => :checking, - :name_on_account => 'John Doe', - :echeck_type => :ccd, - :bank_name => 'Bank of America', - :routing_number => '123456789', - :account_number => '12345' + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_type: 'individual', # Optional + bill_to: @address, + payment: { + bank_account: { + account_type: :checking, + name_on_account: 'John Doe', + echeck_type: :ccd, + bank_name: 'Bank of America', + routing_number: '123456789', + account_number: '12345' } }, - :drivers_license => { - :state => 'MD', - :number => '12345', - :date_of_birth => '1981-3-31' + drivers_license: { + state: 'MD', + number: '12345', + date_of_birth: '1981-3-31' }, - :tax_id => '123456789' + tax_id: '123456789' } ) assert response.test? assert_success response - assert_nil response.authorization + assert_equal @customer_profile_id, response.authorization assert customer_payment_profile_id = response.params['customer_payment_profile_id'] assert customer_payment_profile_id =~ /\d+/, "The customerPaymentProfileId should be numeric. It was #{customer_payment_profile_id}" end @@ -228,68 +247,68 @@ def test_successful_create_customer_shipping_address_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['ship_to_list'] assert response = @gateway.create_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :address => shipping_address + customer_profile_id: @customer_profile_id, + address: shipping_address ) assert response.test? assert_success response - assert_nil response.authorization + assert_equal @customer_profile_id, response.authorization assert customer_address_id = response.params['customer_address_id'] assert customer_address_id =~ /\d+/, "The customerAddressId should be numeric. It was #{customer_address_id}" end def test_successful_get_customer_profile_with_multiple_payment_profiles second_payment_profile = { - :customer_type => 'individual', - :bill_to => @address, - :payment => { - :credit_card => credit_card('1234123412341234') + customer_type: 'individual', + bill_to: @address, + payment: { + credit_card: credit_card('4111111111111111') } } assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert response = @gateway.create_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => second_payment_profile + customer_profile_id: @customer_profile_id, + payment_profile: second_payment_profile ) assert response.test? assert_success response - assert_nil response.authorization + assert_equal @customer_profile_id, response.authorization assert customer_payment_profile_id = response.params['customer_payment_profile_id'] assert customer_payment_profile_id =~ /\d+/, "The customerPaymentProfileId should be numeric. It was #{customer_payment_profile_id}" - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_equal 2, response.params['profile']['payment_profiles'].size - assert_equal 'XXXX4242', response.params['profile']['payment_profiles'][0]['payment']['credit_card']['card_number'] - assert_equal 'XXXX1234', response.params['profile']['payment_profiles'][1]['payment']['credit_card']['card_number'] + assert(response.params['profile']['payment_profiles'].one? { |payment| payment['payment']['credit_card']['card_number'] == 'XXXX4242' }) + assert(response.params['profile']['payment_profiles'].one? { |payment| payment['payment']['credit_card']['card_number'] == 'XXXX1111' }) end def test_successful_delete_customer_payment_profile_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.delete_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) assert response.test? assert_success response assert_nil response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['payment_profiles'] end @@ -297,19 +316,19 @@ def test_successful_delete_customer_shipping_address_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] assert response = @gateway.delete_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :customer_address_id => customer_address_id + customer_profile_id: @customer_profile_id, + customer_address_id: customer_address_id ) assert response.test? assert_success response assert_nil response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['ship_to_list'] end @@ -317,12 +336,12 @@ def test_successful_get_customer_payment_profile_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) assert response.test? @@ -338,34 +357,37 @@ def test_successful_get_customer_payment_profile_unmasked_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id, - :unmask_expiration_date => true + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id, + unmask_expiration_date: true, + include_issuer_info: true ) assert response.test? assert_success response assert_nil response.authorization + assert response.params['payment_profile']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number' assert_equal "XXXX#{@credit_card.last_digits}", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}" assert_equal @profile[:payment_profiles][:customer_type], response.params['payment_profile']['customer_type'] assert_equal formatted_expiration_date(@credit_card), response.params['payment_profile']['payment']['credit_card']['expiration_date'] + assert_equal @credit_card.first_digits, response.params['payment_profile']['payment']['credit_card']['issuer_number'] end def test_successful_get_customer_shipping_address_request assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] assert response = @gateway.get_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :customer_address_id => customer_address_id + customer_profile_id: @customer_profile_id, + customer_address_id: customer_address_id ) assert response.test? @@ -381,13 +403,13 @@ def test_successful_update_customer_payment_profile_request @customer_profile_id = response.authorization # Get the customerPaymentProfileId - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] # Get the customerPaymentProfile assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) # The value before updating @@ -395,11 +417,11 @@ def test_successful_update_customer_payment_profile_request # Update the payment profile assert response = @gateway.update_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_payment_profile_id => customer_payment_profile_id, - :payment => { - :credit_card => credit_card('1234123412341234') + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_payment_profile_id: customer_payment_profile_id, + payment: { + credit_card: credit_card('1234123412341234') } } ) @@ -409,8 +431,8 @@ def test_successful_update_customer_payment_profile_request # Get the updated payment profile assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) # Show that the payment profile was updated @@ -419,25 +441,25 @@ def test_successful_update_customer_payment_profile_request assert_nil response.params['payment_profile']['customer_type'] new_billing_address = response.params['payment_profile']['bill_to'] - new_billing_address.update(:first_name => 'Frank', :last_name => 'Brown') - masked_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => response.params['payment_profile']['payment']['credit_card']['card_number']) + new_billing_address.update(first_name: 'Frank', last_name: 'Brown') + masked_credit_card = ActiveMerchant::Billing::CreditCard.new(number: response.params['payment_profile']['payment']['credit_card']['card_number']) # Update only the billing address with a masked card and expiration date assert @gateway.update_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_payment_profile_id => customer_payment_profile_id, - :bill_to => new_billing_address, - :payment => { - :credit_card => masked_credit_card + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_payment_profile_id: customer_payment_profile_id, + bill_to: new_billing_address, + payment: { + credit_card: masked_credit_card } } ) # Get the updated payment profile assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) # Show that the billing address on the payment profile was updated @@ -450,40 +472,40 @@ def test_successful_update_customer_payment_profile_request_with_credit_card_las @customer_profile_id = response.authorization # Get the customerPaymentProfileId - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] # Get the customerPaymentProfile assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) # Card number last 4 digits is 4242 assert_equal 'XXXX4242', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in 4242' new_billing_address = response.params['payment_profile']['bill_to'] - new_billing_address.update(:first_name => 'Frank', :last_name => 'Brown') + new_billing_address.update(first_name: 'Frank', last_name: 'Brown') # Initialize credit card with only last 4 digits as the number - last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits + last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(number: '4242') # Credit card with only last four digits # Update only the billing address with a card with the last 4 digits and expiration date assert @gateway.update_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_payment_profile_id => customer_payment_profile_id, - :bill_to => new_billing_address, - :payment => { - :credit_card => last_four_credit_card + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_payment_profile_id: customer_payment_profile_id, + bill_to: new_billing_address, + payment: { + credit_card: last_four_credit_card } } ) # Get the updated payment profile assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => customer_payment_profile_id + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: customer_payment_profile_id ) # Show that the billing address on the payment profile was updated @@ -496,13 +518,13 @@ def test_successful_update_customer_shipping_address_request @customer_profile_id = response.authorization # Get the customerAddressId - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] # Get the customerShippingAddress assert response = @gateway.get_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :customer_address_id => customer_address_id + customer_profile_id: @customer_profile_id, + customer_address_id: customer_address_id ) assert address = response.params['address'] @@ -511,14 +533,14 @@ def test_successful_update_customer_shipping_address_request # Update the address and remove the phone_number new_address = address.symbolize_keys.merge!( - :address => '5678 Fake Street' + address: '5678 Fake Street' ) new_address.delete(:phone_number) # Update the shipping address assert response = @gateway.update_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :address => new_address + customer_profile_id: @customer_profile_id, + address: new_address ) assert response.test? assert_success response @@ -526,8 +548,8 @@ def test_successful_update_customer_shipping_address_request # Get the updated shipping address assert response = @gateway.get_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :customer_address_id => customer_address_id + customer_profile_id: @customer_profile_id, + customer_address_id: customer_address_id ) # Show that the shipping address was updated @@ -540,15 +562,15 @@ def test_successful_validate_customer_payment_profile_request_live assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert @customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] assert response = @gateway.validate_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :customer_address_id => @customer_address_id, - :validation_mode => :live + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + customer_address_id: @customer_address_id, + validation_mode: :live ) assert response.test? @@ -562,15 +584,15 @@ def test_validate_customer_payment_profile_request_live_requires_billing_address assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert @customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] assert response = @gateway.validate_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :customer_address_id => @customer_address_id, - :validation_mode => :live + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + customer_address_id: @customer_address_id, + validation_mode: :live ) assert response.test? @@ -583,15 +605,15 @@ def test_validate_customer_payment_profile_request_old_does_not_require_billing_ assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert @customer_address_id = response.params['profile']['ship_to_list']['customer_address_id'] assert response = @gateway.validate_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :customer_address_id => @customer_address_id, - :validation_mode => :old + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + customer_address_id: @customer_address_id, + validation_mode: :old ) assert response.test? @@ -603,24 +625,24 @@ def test_should_create_duplicate_customer_profile_transactions_with_duplicate_wi assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] key = (Time.now.to_f * 1000000).to_i.to_s customer_profile_transaction = { - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => key.to_s, - :description => "Test Order Description #{key}", - :purchase_order_number => key.to_s + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: key.to_s, + description: "Test Order Description #{key}", + purchase_order_number: key.to_s }, - :amount => @amount + amount: @amount }, - :extra_options => { 'x_duplicate_window' => 1 } + extra_options: { 'x_duplicate_window' => 1 } } assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction) @@ -639,22 +661,22 @@ def test_should_not_create_duplicate_customer_profile_transactions_without_dupli assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] key = (Time.now.to_f * 1000000).to_i.to_s customer_profile_transaction = { - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => key.to_s, - :description => "Test Order Description #{key}", - :purchase_order_number => key.to_s + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: key.to_s, + description: "Test Order Description #{key}", + purchase_order_number: key.to_s }, - :amount => @amount + amount: @amount } } @@ -674,9 +696,9 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_void_r response = get_and_validate_auth_capture_response assert response = @gateway.create_customer_profile_transaction_for_void( - :transaction => { - :type => :void, - :trans_id => response.params['direct_response']['transaction_id'] + transaction: { + type: :void, + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -689,12 +711,12 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund response = get_and_validate_auth_capture_response assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :trans_id => response.params['direct_response']['transaction_id'] + transaction: { + type: :refund, + amount: 1, + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -710,13 +732,13 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund response = get_and_validate_auth_capture_response assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :trans_id => response.params['direct_response']['transaction_id'], - :order => {} + transaction: { + type: :refund, + amount: 1, + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + trans_id: response.params['direct_response']['transaction_id'], + order: {} } ) assert_instance_of Response, response @@ -732,11 +754,11 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund response = get_and_validate_auth_capture_response assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, - :credit_card_number_masked => 'XXXX4242', - :trans_id => response.params['direct_response']['transaction_id'] + transaction: { + type: :refund, + amount: 1, + credit_card_number_masked: 'XXXX4242', + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -752,10 +774,10 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_prior_aut response = get_and_validate_auth_only_response assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :prior_auth_capture, - :trans_id => response.params['direct_response']['transaction_id'], - :amount => response.params['direct_response']['amount'] + transaction: { + type: :prior_auth_capture, + trans_id: response.params['direct_response']['transaction_id'], + amount: response.params['direct_response']['amount'] } ) assert_instance_of Response, response @@ -770,36 +792,36 @@ def get_and_validate_customer_payment_profile_request_with_bank_account_response assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_nil response.params['profile']['payment_profiles'] assert response = @gateway.create_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_type => 'individual', # Optional - :bill_to => @address, - :payment => { - :bank_account => { - :account_type => :checking, - :name_on_account => 'John Doe', - :echeck_type => :ccd, - :bank_name => 'Bank of America', - :routing_number => '123456789', - :account_number => '12345678' + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_type: 'individual', # Optional + bill_to: @address, + payment: { + bank_account: { + account_type: :checking, + name_on_account: 'John Doe', + echeck_type: :ccd, + bank_name: 'Bank of America', + routing_number: '123456789', + account_number: '12345678' } }, - :drivers_license => { - :state => 'MD', - :number => '12345', - :date_of_birth => '1981-3-31' + drivers_license: { + state: 'MD', + number: '12345', + date_of_birth: '1981-3-31' }, - :tax_id => '123456789' + tax_id: '123456789' } ) assert response.test? assert_success response - assert_nil response.authorization + assert_equal @customer_profile_id, response.authorization assert @customer_payment_profile_id = response.params['customer_payment_profile_id'] assert @customer_payment_profile_id =~ /\d+/, "The customerPaymentProfileId should be numeric. It was #{@customer_payment_profile_id}" return response @@ -809,22 +831,22 @@ def get_and_validate_auth_capture_response assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] key = (Time.now.to_f * 1000000).to_i.to_s assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => key.to_s, - :description => "Test Order Description #{key}", - :purchase_order_number => key.to_s + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: key.to_s, + description: "Test Order Description #{key}", + purchase_order_number: key.to_s }, - :amount => @amount + amount: @amount } ) @@ -847,19 +869,19 @@ def get_and_validate_auth_only_response key = (Time.now.to_f * 1000000).to_i.to_s - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :order => { - :invoice_number => key.to_s, - :description => "Test Order Description #{key}", - :purchase_order_number => key.to_s + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_only, + order: { + invoice_number: key.to_s, + description: "Test Order Description #{key}", + purchase_order_number: key.to_s }, - :amount => @amount + amount: @amount } ) @@ -872,5 +894,4 @@ def get_and_validate_auth_only_response return response end - end diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb index 2b239ad18bb..3670f8e9254 100644 --- a/test/remote/gateways/remote_authorize_net_test.rb +++ b/test/remote/gateways/remote_authorize_net_test.rb @@ -9,6 +9,17 @@ def setup @check = check @declined_card = credit_card('400030001111222') + @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 = { order_id: '1', email: 'anet@example.com', @@ -19,19 +30,19 @@ def setup @level_2_options = { tax: { - amount: '100', - name: 'tax name', - description: 'tax description' - }, + amount: '100', + name: 'tax name', + description: 'tax description' + }, duty: { - amount: '200', - name: 'duty name', - description: 'duty description' - }, + amount: '200', + name: 'duty name', + description: 'duty description' + }, shipping: { amount: '300', name: 'shipping name', - description: 'shipping description', + description: 'shipping description' }, tax_exempt: 'false', po_number: '123' @@ -56,6 +67,36 @@ def test_successful_purchase assert response.authorization end + def test_successful_purchase_with_google_pay + @payment_token.source = :google_pay + response = @gateway.purchase(@amount, @payment_token, @options.merge(turn_on_nt_flow: true)) + + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_apple_pay + @payment_token.source = :apple_pay + response = @gateway.purchase(@amount, @payment_token, @options.merge(turn_on_nt_flow: true)) + + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_apple_pay_without_turn_on_nt_flow_field + @payment_token.source = :apple_pay + response = @gateway.purchase(@amount, @payment_token, @options) + + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + def test_successful_purchase_with_minimal_options response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email: 'anet@example.com', billing_address: address) assert_success response @@ -164,7 +205,7 @@ def test_successful_purchase_with_utf_character assert_match %r{This transaction has been approved}, response.message end - def test_successful_echeck_purchase + def test_successful_echeck_purchase_with_checking_account_type response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.test? @@ -172,6 +213,15 @@ def test_successful_echeck_purchase assert response.authorization end + def test_successful_echeck_purchase_with_savings_account_type + savings_account = check(account_type: 'savings') + response = @gateway.purchase(@amount, savings_account, @options) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + def test_card_present_purchase_with_no_data no_data_credit_card = ActiveMerchant::Billing::CreditCard.new response = @gateway.purchase(@amount, no_data_credit_card, @options) @@ -202,8 +252,92 @@ def test_successful_purchase_with_disable_partial_authorize assert_success purchase end + def test_successful_auth_and_capture_with_recurring_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'cardholder', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: auth.params['network_trans_id'] + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_auth_and_capture_with_unscheduled_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: auth.params['network_trans_id'] + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_auth_and_capture_with_installment_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'installment', + initiator: 'cardholder', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: auth.params['network_trans_id'] + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + def test_successful_authorize_with_email_and_ip - options = @options.merge({email: 'hello@example.com', ip: '127.0.0.1'}) + options = @options.merge({ email: 'hello@example.com', ip: '127.0.0.1' }) auth = @gateway.authorize(@amount, @credit_card, options) assert_success auth @@ -220,7 +354,7 @@ def test_failed_authorize end def test_card_present_authorize_and_capture_with_track_data_only - track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') + track_credit_card = ActiveMerchant::Billing::CreditCard.new(track_data: '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') assert authorization = @gateway.authorize(@amount, track_credit_card, @options) assert_success authorization @@ -245,7 +379,7 @@ def test_failed_echeck_authorization end def test_card_present_purchase_with_track_data_only - track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') + track_credit_card = ActiveMerchant::Billing::CreditCard.new(track_data: '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') response = @gateway.purchase(@amount, track_credit_card, @options) assert response.test? assert_equal 'This transaction has been approved', response.message @@ -290,11 +424,76 @@ 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 assert_equal 'This transaction has been approved', response.message - assert_success response.responses.last, 'The void should succeed' + assert_equal response.responses.count, 2 + end + + def test_successful_verify_with_no_address + @options[:billing_address] = nil + response = @gateway.verify(@credit_card, @options) + + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal response.responses.count, 2 + end + + def test_successful_verify_with_verify_amount_and_billing_address + @options[:verify_amount] = 1 + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal response.responses.count, 2 + end + + def test_successful_verify_after_store_with_custom_verify_amount + @options[:verify_amount] = 1 + assert store = @gateway.store(@credit_card, @options) + assert_success store + response = @gateway.verify(store.authorization, @options) + assert_success response + assert_equal response.responses.count, 2 + end + + def test_successful_verify_with_apple_pay + 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 + end + + def test_successful_verify_with_check + response = @gateway.verify(@check, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_successful_verify_with_nil_custom_verify_amount + @options[:verify_amount] = nil + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_verify_tpt_with_custom_verify_amount_and_no_address + @options[:verify_amount] = 100 + assert store = @gateway.store(@credit_card, @options) + assert_success store + @options[:billing_address] = nil + response = @gateway.verify(store.authorization, @options) + assert_success response end def test_failed_verify @@ -318,7 +517,7 @@ def test_successful_store_new_payment_profile assert store.authorization new_card = credit_card('4424222222222222') - customer_profile_id, _, _ = store.authorization.split('#') + customer_profile_id, = store.authorization.split('#') assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id) assert_success response @@ -332,7 +531,7 @@ def test_failed_store_new_payment_profile assert store.authorization new_card = credit_card('141241') - customer_profile_id, _, _ = store.authorization.split('#') + customer_profile_id, = store.authorization.split('#') assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id) assert_failure response @@ -384,7 +583,7 @@ def test_successful_purchase_using_stored_card_new_payment_profile assert store.authorization new_card = credit_card('4007000000027') - customer_profile_id, _, _ = store.authorization.split('#') + customer_profile_id, = store.authorization.split('#') assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id, email: 'anet@example.com', billing_address: address) assert_success response @@ -554,8 +753,8 @@ def test_failed_void_using_stored_card def test_bad_login gateway = AuthorizeNetGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) response = gateway.purchase(@amount, @credit_card) @@ -569,6 +768,7 @@ def test_bad_login card_code cardholder_authentication_code full_response_code + network_trans_id response_code response_reason_code response_reason_text @@ -583,7 +783,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - capture = @gateway.capture(@amount-1, auth.authorization) + capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -638,12 +838,24 @@ def test_successful_echeck_refund purchase = @gateway.purchase(@amount, @check, @options) assert_success purchase - @options.update(transaction_id: purchase.params['transaction_id'], test_request: true) + @options.update(transaction_id: purchase.params['transaction_id'], test_request: true) refund = @gateway.credit(@amount, @check, @options) assert_failure refund assert_match %r{The transaction cannot be found}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' end + def test_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 @@ -669,7 +881,8 @@ 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 ) @@ -682,7 +895,8 @@ 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 ) @@ -698,8 +912,9 @@ 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=', + credit_card = network_tokenization_credit_card( + '5424000000000015', + payment_cryptogram: 'EjRWeJASNFZ4kBI0VniQEjRWeJA=', verification_value: nil ) @@ -710,10 +925,11 @@ def test_successful_credit_with_network_tokenization end def test_network_tokenization_transcript_scrubbing - credit_card = network_tokenization_credit_card('4111111111111111', - :brand => 'visa', - :eci => '05', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + credit_card = network_tokenization_credit_card( + '4111111111111111', + brand: 'visa', + eci: '05', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' ) transcript = capture_transcript(@gateway) do @@ -737,11 +953,19 @@ def test_purchase_scrubbing assert_scrubbed(@gateway.options[:password], transcript) end + def test_account_number_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + end + def test_verify_credentials assert @gateway.verify_credentials gateway = AuthorizeNetGateway.new(login: 'unknown_login', password: 'not_right') assert !gateway.verify_credentials end - end diff --git a/test/remote/gateways/remote_axcessms_test.rb b/test/remote/gateways/remote_axcessms_test.rb index 54b1c82213a..225064740f9 100644 --- a/test/remote/gateways/remote_axcessms_test.rb +++ b/test/remote/gateways/remote_axcessms_test.rb @@ -30,7 +30,7 @@ def test_successful_authorize_and_capture assert_success auth, 'Authorize failed' assert_match %r{Successful Processing - Request successfully processed}, auth.message - assert capture = @gateway.capture(@amount, auth.authorization, {mode: @mode}) + assert capture = @gateway.capture(@amount, auth.authorization, { mode: @mode }) assert_success capture, 'Capture failed' assert_match %r{Successful Processing - Request successfully processed}, capture.message end @@ -40,7 +40,7 @@ def test_successful_authorize_and_partial_capture assert_success auth, 'Authorize failed' assert_match %r{Successful Processing - Request successfully processed}, auth.message - assert capture = @gateway.capture(@amount-30, auth.authorization, {mode: @mode}) + assert capture = @gateway.capture(@amount - 30, auth.authorization, { mode: @mode }) assert_success capture, 'Capture failed' assert_match %r{Successful Processing - Request successfully processed}, capture.message end @@ -50,7 +50,7 @@ def test_successful_authorize_and_void assert_success auth, 'Authorize failed' assert_match %r{Successful Processing - Request successfully processed}, auth.message - assert void = @gateway.void(auth.authorization, {mode: @mode}) + assert void = @gateway.void(auth.authorization, { mode: @mode }) assert_success void, 'Void failed' assert_match %r{Successful Processing - Request successfully processed}, void.message end @@ -82,7 +82,7 @@ def test_successful_purchase_and_refund assert_success purchase, 'Purchase failed' assert_match %r{Successful Processing - Request successfully processed}, purchase.message - assert refund = @gateway.refund(@amount, purchase.authorization, {mode: @mode}) + assert refund = @gateway.refund(@amount, purchase.authorization, { mode: @mode }) assert_success refund, 'Refund failed' assert_match %r{Successful Processing - Request successfully processed}, refund.message end @@ -92,7 +92,7 @@ def test_successful_purchase_and_partial_refund assert_success purchase, 'Purchase failed' assert_match %r{Successful Processing - Request successfully processed}, purchase.message - assert refund = @gateway.refund(@amount-50, purchase.authorization, {mode: @mode}) + assert refund = @gateway.refund(@amount - 50, purchase.authorization, { mode: @mode }) assert_success refund, 'Refund failed' assert_match %r{Successful Processing - Request successfully processed}, refund.message end @@ -115,7 +115,7 @@ def test_failed_bigger_capture_then_authorised auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth, 'Authorize failed' - assert capture = @gateway.capture(@amount+30, auth.authorization, {mode: @mode}) + assert capture = @gateway.capture(@amount + 30, auth.authorization, { mode: @mode }) assert_failure capture, 'Capture failed' assert_match %r{PA value exceeded}, capture.message end @@ -127,13 +127,13 @@ def test_failed_authorize end def test_failed_refund - assert refund = @gateway.refund(@amount, 'invalid authorization', {mode: @mode}) + assert refund = @gateway.refund(@amount, 'invalid authorization', { mode: @mode }) assert_failure refund assert_match %r{Configuration Validation - Invalid payment data}, refund.message end def test_failed_void - void = @gateway.void('invalid authorization', {mode: @mode}) + void = @gateway.void('invalid authorization', { mode: @mode }) assert_failure void assert_match %r{Reference Error - reversal}, void.message end diff --git a/test/remote/gateways/remote_balanced_test.rb b/test/remote/gateways/remote_balanced_test.rb index d5930116419..9613e557f06 100644 --- a/test/remote/gateways/remote_balanced_test.rb +++ b/test/remote/gateways/remote_balanced_test.rb @@ -144,7 +144,7 @@ def test_refund_partial_purchase def test_store new_email_address = '%d@example.org' % Time.now store = @gateway.store(@credit_card, { - email: new_email_address + email: new_email_address }) assert_instance_of String, store.authorization end diff --git a/test/remote/gateways/remote_bambora_apac_test.rb b/test/remote/gateways/remote_bambora_apac_test.rb index 896f0d17865..43f92c7be3b 100644 --- a/test/remote/gateways/remote_bambora_apac_test.rb +++ b/test/remote/gateways/remote_bambora_apac_test.rb @@ -9,7 +9,7 @@ def setup @options = { order_id: '1', billing_address: address, - description: 'Store Purchase', + description: 'Store Purchase' } end @@ -69,14 +69,14 @@ def test_failed_refund def test_successful_void response = @gateway.purchase(200, @credit_card, @options) assert_success response - response = @gateway.void(200, response.authorization) + response = @gateway.void(response.authorization, amount: 200) assert_success response end def test_failed_void response = @gateway.purchase(200, @credit_card, @options) assert_success response - response = @gateway.void(200, 123) + response = @gateway.void(123, amount: 200) assert_failure response assert_equal 'Cannot find matching transaction to VOID', response.message end diff --git a/test/remote/gateways/remote_bank_frick_test.rb b/test/remote/gateways/remote_bank_frick_test.rb index d1bb6447d4e..e817b7c0aff 100644 --- a/test/remote/gateways/remote_bank_frick_test.rb +++ b/test/remote/gateways/remote_bank_frick_test.rb @@ -24,7 +24,7 @@ def test_successful_purchase end def test_successful_purchase_with_minimal_options - assert response = @gateway.purchase(@amount, @credit_card, {address: address}) + assert response = @gateway.purchase(@amount, @credit_card, { address: address }) assert_success response assert response.test? assert_match %r{Transaction succeeded}, response.message @@ -63,7 +63,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -84,7 +84,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end diff --git a/test/remote/gateways/remote_banwire_test.rb b/test/remote/gateways/remote_banwire_test.rb index 0af462e9a7b..37004591279 100644 --- a/test/remote/gateways/remote_banwire_test.rb +++ b/test/remote/gateways/remote_banwire_test.rb @@ -7,12 +7,12 @@ def setup @gateway = BanwireGateway.new(fixtures(:banwire)) @amount = 100 - @credit_card = credit_card('5204164299999999', :verification_value => '999', :brand => 'mastercard') - @visa_credit_card = credit_card('4485814063899108', :verification_value => '434') + @credit_card = credit_card('5204164299999999', verification_value: '999', brand: 'mastercard') + @visa_credit_card = credit_card('4485814063899108', verification_value: '434') @declined_card = credit_card('4000300011112220') @options = { - billing_address: address, + billing_address: address } end @@ -45,9 +45,9 @@ def test_unsuccessful_purchase def test_invalid_login gateway = BanwireGateway.new( - :login => 'fakeuser', - :currency => 'MXN' - ) + login: 'fakeuser', + currency: 'MXN' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'ID de cuenta invalido', response.message @@ -62,5 +62,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.number, clean_transcript) assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end - end diff --git a/test/remote/gateways/remote_barclaycard_smartpay_test.rb b/test/remote/gateways/remote_barclaycard_smartpay_test.rb index 1f0444d6a08..1839dfe8ea9 100644 --- a/test/remote/gateways/remote_barclaycard_smartpay_test.rb +++ b/test/remote/gateways/remote_barclaycard_smartpay_test.rb @@ -7,83 +7,86 @@ def setup @amount = 100 @error_amount = 1_000_000_000_000_000_000_000 - @credit_card = credit_card('4111111111111111', :month => 10, :year => 2020, :verification_value => 737) - @declined_card = credit_card('4000300011112220', :month => 3, :year => 2030, :verification_value => 737) + @credit_card = credit_card('4111111111111111', month: 10, year: 2020, verification_value: 737) + @declined_card = credit_card('4000300011112220', month: 3, year: 2030, verification_value: 737) @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) + @three_ds_2_enrolled_card = credit_card('4917610000000000', month: 10, year: 2020, verification_value: '737', brand: :visa) @options = { order_id: '1', - billing_address: { - name: 'Jim Smith', - address1: '100 Street', - company: 'Widgets Inc', - city: 'Ottawa', - state: 'ON', - zip: 'K1C2N6', - country: 'CA', - phone: '(555)555-5555', - fax: '(555)555-6666'}, + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, email: 'long@bob.com', customer: 'Longbob Longsen', description: 'Store Purchase' } @options_with_alternate_address = { - order_id: '1', - billing_address: { - name: 'PU JOI SO', - address1: '新北市店溪路3579號139樓', - company: 'Widgets Inc', - city: '新北市', - zip: '231509', - country: 'TW', - phone: '(555)555-5555', - fax: '(555)555-6666' - }, - email: 'pujoi@so.com', - customer: 'PU JOI SO', - description: 'Store Purchase' + order_id: '1', + billing_address: { + name: 'PU JOI SO', + address1: '新北市店溪路3579號139樓', + company: 'Widgets Inc', + city: '新北市', + zip: '231509', + country: 'TW', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + email: 'pujoi@so.com', + customer: 'PU JOI SO', + description: 'Store Purchase' } @options_with_house_number_and_street = { - order_id: '1', - house_number: '100', - street: 'Top Level Drive', - billing_address: { - name: 'Jim Smith', - address1: '100 Top Level Dr', - company: 'Widgets Inc', - city: 'Ottawa', - state: 'ON', - zip: 'K1C2N6', - country: 'CA', - phone: '(555)555-5555', - fax: '(555)555-6666' - }, - email: 'long@deb.com', - customer: 'Longdeb Longsen', - description: 'Store Purchase' + order_id: '1', + house_number: '100', + street: 'Top Level Drive', + billing_address: { + name: 'Jim Smith', + address1: '100 Top Level Dr', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + email: 'long@deb.com', + customer: 'Longdeb Longsen', + description: 'Store Purchase' } @options_with_no_address = { - order_id: '1', - email: 'long@bob.com', - customer: 'Longbob Longsen', - description: 'Store Purchase' + order_id: '1', + email: 'long@bob.com', + customer: 'Longbob Longsen', + description: 'Store Purchase' } @options_with_credit_fields = { order_id: '1', - billing_address: { - name: 'Jim Smith', - address1: '100 Street', - company: 'Widgets Inc', - city: 'Ottawa', - state: 'ON', - zip: 'K1C2N6', - country: 'CA', - phone: '(555)555-5555', - fax: '(555)555-6666'}, + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, email: 'long@bob.com', customer: 'Longbob Longsen', description: 'Store Purchase', @@ -97,21 +100,47 @@ def setup } } - @avs_credit_card = credit_card('4400000000000008', - :month => 8, - :year => 2018, - :verification_value => 737) + @avs_credit_card = credit_card( + '4400000000000008', + month: 8, + year: 2018, + verification_value: 737 + ) @avs_address = @options.clone @avs_address.update(billing_address: { - name: 'Jim Smith', - street: 'Test AVS result', - houseNumberOrName: '2', - city: 'Cupertino', - state: 'CA', - zip: '95014', - country: 'US' - }) + name: 'Jim Smith', + street: 'Test AVS result', + houseNumberOrName: '2', + city: 'Cupertino', + state: 'CA', + zip: '95014', + country: 'US' + }) + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + }, + notification_url: 'https://example.com/notification' + } + } end def teardown @@ -131,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 @@ -166,6 +201,17 @@ def test_successful_purchase_with_device_fingerprint assert_equal '[capture-received]', response.message end + def test_successful_purchase_with_shopper_statement + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge(shopper_statement: 'One-year premium subscription') + ) + + assert_success response + assert_equal '[capture-received]', response.message + end + def test_successful_authorize_with_3ds assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true)) assert_equal 'RedirectShopper', response.message @@ -176,6 +222,63 @@ def test_successful_authorize_with_3ds refute response.params['paRequest'].blank? end + def test_successful_authorize_with_3ds2_browser_client_data + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, @normalized_3ds_2_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_purchase_with_3ds2_exemption_requested_and_execute_threed_false + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, @normalized_3ds_2_options.merge(execute_threed: false, sca_exemption: 'lowValue')) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'Authorised' + end + + # According to Adyen documentation, if execute_threed is set to true and an exemption provided + # the gateway will apply and request for the specified exemption in the authentication request, + # after the device fingerprint is submitted to the issuer. + def test_successful_purchase_with_3ds2_exemption_requested_and_execute_threed_true + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, @normalized_3ds_2_options.merge(execute_threed: true, sca_exemption: 'lowValue')) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_authorize_with_3ds2_app_based_request + three_ds_app_based_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'app' + } + } + + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, three_ds_app_based_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank? + end + def test_successful_authorize_and_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -247,7 +350,7 @@ def test_failed_credit_insufficient_validation end def test_successful_third_party_payout - response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({third_party_payout: true})) + response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({ third_party_payout: true })) assert_success response end @@ -295,7 +398,7 @@ def test_successful_store end def test_failed_store - response = @gateway.store(credit_card('4111111111111111', :month => '', :year => '', :verification_value => ''), @options) + response = @gateway.store(credit_card('4111111111111111', month: '', year: '', verification_value: ''), @options) assert_failure response assert_equal '129: Expiry Date Invalid', response.message end @@ -314,17 +417,17 @@ def test_avs_no_with_house_number end def test_nonfractional_currency - response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'JPY')) + response = @gateway.authorize(1234, @credit_card, @options.merge(currency: 'JPY')) assert_success response - response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'JPY')) + response = @gateway.purchase(1234, @credit_card, @options.merge(currency: 'JPY')) assert_success response end def test_three_decimal_currency - response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR')) + response = @gateway.authorize(1234, @credit_card, @options.merge(currency: 'OMR')) assert_success response - response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'OMR')) + response = @gateway.purchase(1234, @credit_card, @options.merge(currency: 'OMR')) assert_success response end diff --git a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb index dd06e16d724..69a37d7bf1c 100644 --- a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb +++ b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb @@ -6,15 +6,15 @@ class RemoteBarclaysEpdqExtraPlusTest < Test::Unit::TestCase def setup @gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus)) @amount = 100 - @credit_card = credit_card('4000100011112224', :verification_value => '987') - @mastercard = credit_card('5399999999999999', :brand => 'mastercard') + @credit_card = credit_card('4000100011112224', verification_value: '987') + @mastercard = credit_card('5399999999999999', brand: 'mastercard') @declined_card = credit_card('1111111111111111') - @credit_card_d3d = credit_card('4000000000000002', :verification_value => '111') + @credit_card_d3d = credit_card('4000000000000002', verification_value: '111') @options = { - :order_id => generate_unique_id[0...30], - :billing_address => address, - :description => 'Store Purchase', - :currency => fixtures(:barclays_epdq_extra_plus)[:currency] || 'GBP' + order_id: generate_unique_id[0...30], + billing_address: address, + description: 'Store Purchase', + currency: fixtures(:barclays_epdq_extra_plus)[:currency] || 'GBP' } end @@ -37,13 +37,13 @@ def test_successful_purchase_with_minimal_info end def test_successful_purchase_with_utf8_encoding_1 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'Rémy', :last_name => 'Fröåïør'), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', first_name: 'Rémy', last_name: 'Fröåïør'), @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message end def test_successful_purchase_with_utf8_encoding_2 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'ワタシ', :last_name => 'ёжзийклмнопрсуфхцч'), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', first_name: 'ワタシ', last_name: 'ёжзийклмнопрсуфхцч'), @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message end @@ -51,7 +51,7 @@ def test_successful_purchase_with_utf8_encoding_2 # NOTE: You have to set the "Hash algorithm" to "SHA-1" in the "Technical information"->"Global security parameters" # section of your account admin before running this test def test_successful_purchase_with_signature_encryptor_to_sha1 - gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:signature_encryptor => 'sha1')) + gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(signature_encryptor: 'sha1')) assert response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message @@ -60,7 +60,7 @@ def test_successful_purchase_with_signature_encryptor_to_sha1 # NOTE: You have to set the "Hash algorithm" to "SHA-256" in the "Technical information"->"Global security parameters" # section of your account admin before running this test def test_successful_purchase_with_signature_encryptor_to_sha256 - gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:signature_encryptor => 'sha256')) + gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(signature_encryptor: 'sha256')) assert response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message @@ -69,12 +69,21 @@ def test_successful_purchase_with_signature_encryptor_to_sha256 # NOTE: You have to set the "Hash algorithm" to "SHA-512" in the "Technical information"->"Global security parameters" # section of your account admin before running this test def test_successful_purchase_with_signature_encryptor_to_sha512 - gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:signature_encryptor => 'sha512')) + gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(signature_encryptor: 'sha512')) assert response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message end + def test_successful_purchase_with_custom_eci + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(eci: 1)) + assert_success response + assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message + assert_equal '1', response.params['ECI'] + assert_equal @options[:currency], response.params['currency'] + assert_equal @options[:order_id], response.order_id + end + 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) @@ -92,7 +101,7 @@ def test_successful_purchase_without_explicit_order_id def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'No brand', response.message + assert_equal 'No brand or invalid card number', response.message end def test_successful_authorize_with_mastercard @@ -113,7 +122,7 @@ def test_authorize_and_capture def test_unsuccessful_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert_equal 'No card no, no exp date, no brand', response.message + assert_equal 'No card no, no exp date, no brand or invalid card number', response.message end def test_successful_void @@ -125,76 +134,75 @@ def test_successful_void assert_success void end -=begin Enable if/when fully enabled account is available to test - 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_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_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_success response - end - - def test_successful_store - assert response = @gateway.store(@credit_card, :billing_id => 'test_alias') - assert_success response - assert purchase = @gateway.purchase(@amount, 'test_alias') - assert_success purchase - end - - def test_successful_store_generated_alias - assert response = @gateway.store(@credit_card) - assert_success response - assert purchase = @gateway.purchase(@amount, response.billing_id) - assert_success purchase - end - - def test_successful_store - assert response = @gateway.store(@credit_card, :billing_id => 'test_alias') - assert_success response - assert purchase = @gateway.purchase(@amount, 'test_alias') - assert_success purchase - end - - def test_successful_unreferenced_credit - assert credit = @gateway.credit(@amount, @credit_card, @options) - assert_success credit - assert credit.authorization - assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, credit.message - end - - # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" - # section of your account admin before running this test - def test_successful_purchase_with_custom_currency_at_the_gateway_level - gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:currency => 'USD')) - assert response = gateway.purchase(@amount, @credit_card) - assert_success response - assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message - assert_equal "USD", response.params["currency"] - end - - # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" - # section of your account admin before running this test - def test_successful_purchase_with_custom_currency - gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:currency => 'EUR')) - assert response = gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) - assert_success response - assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message - assert_equal "USD", response.params["currency"] - end - - # NOTE: You have to contact Barclays to make sure your test account allow 3D Secure transactions before running this test - def test_successful_purchase_with_3d_secure - assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(:d3d => true)) - assert_success response - assert_equal '46', response.params["STATUS"] - assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message - assert response.params["HTML_ANSWER"] - end -=end + # Enable if/when fully enabled account is available to test + # 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_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_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_success response + # end + # + # def test_successful_store + # assert response = @gateway.store(@credit_card, :billing_id => 'test_alias') + # assert_success response + # assert purchase = @gateway.purchase(@amount, 'test_alias') + # assert_success purchase + # end + # + # def test_successful_store_generated_alias + # assert response = @gateway.store(@credit_card) + # assert_success response + # assert purchase = @gateway.purchase(@amount, response.billing_id) + # assert_success purchase + # end + # + # def test_successful_store + # assert response = @gateway.store(@credit_card, :billing_id => 'test_alias') + # assert_success response + # assert purchase = @gateway.purchase(@amount, 'test_alias') + # assert_success purchase + # end + # + # def test_successful_unreferenced_credit + # assert credit = @gateway.credit(@amount, @credit_card, @options) + # assert_success credit + # assert credit.authorization + # assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, credit.message + # end + # + # # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" + # # section of your account admin before running this test + # def test_successful_purchase_with_custom_currency_at_the_gateway_level + # gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:currency => 'USD')) + # assert response = gateway.purchase(@amount, @credit_card) + # assert_success response + # assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message + # assert_equal "USD", response.params["currency"] + # end + # + # # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" + # # section of your account admin before running this test + # def test_successful_purchase_with_custom_currency + # gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus).merge(:currency => 'EUR')) + # assert response = gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) + # assert_success response + # assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message + # assert_equal "USD", response.params["currency"] + # end + # + # # NOTE: You have to contact Barclays to make sure your test account allow 3D Secure transactions before running this test + # def test_successful_purchase_with_3d_secure + # assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(:d3d => true)) + # assert_success response + # assert_equal '46', response.params["STATUS"] + # assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message + # assert response.params["HTML_ANSWER"] + # end def test_successful_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) @@ -208,7 +216,7 @@ def test_successful_refund def test_unsuccessful_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount+1, purchase.authorization, @options) # too much refund requested + assert refund = @gateway.refund(@amount + 1, purchase.authorization, @options) # too much refund requested assert_failure refund assert refund.authorization assert_equal 'Overflow in refunds requests', refund.message @@ -216,11 +224,11 @@ def test_unsuccessful_refund def test_invalid_login gateway = BarclaysEpdqExtraPlusGateway.new( - :login => '', - :user => '', - :password => '', - :signature_encryptor => 'none' - ) + login: '', + user: '', + password: '', + signature_encryptor: 'none' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Some of the data entered is incorrect. please retry.', response.message diff --git a/test/remote/gateways/remote_be2bill_test.rb b/test/remote/gateways/remote_be2bill_test.rb index 6e48bdcba5e..0c0b1da2761 100644 --- a/test/remote/gateways/remote_be2bill_test.rb +++ b/test/remote/gateways/remote_be2bill_test.rb @@ -9,13 +9,13 @@ def setup @declined_card = credit_card('5555557376384001') @options = { - :order_id => '1', - :description => 'Store Purchase', - :client_id => '1', - :referrer => 'google.com', - :user_agent => 'Firefox 25', - :ip => '127.0.0.1', - :email => 'customer@yopmail.com' + order_id: '1', + description: 'Store Purchase', + client_id: '1', + referrer: 'google.com', + user_agent: 'Firefox 25', + ip: '127.0.0.1', + email: 'customer@yopmail.com' } end @@ -49,8 +49,8 @@ def test_failed_capture def test_invalid_login gateway = Be2billGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_beanstream_interac_test.rb b/test/remote/gateways/remote_beanstream_interac_test.rb index 4f4d1b9b79e..4b716d8be38 100644 --- a/test/remote/gateways/remote_beanstream_interac_test.rb +++ b/test/remote/gateways/remote_beanstream_interac_test.rb @@ -1,30 +1,29 @@ require 'test_helper' class RemoteBeanstreamInteracTest < Test::Unit::TestCase - def setup @gateway = BeanstreamInteracGateway.new(fixtures(:beanstream_interac)) @amount = 100 @options = { - :order_id => generate_unique_id, - :billing_address => { - :name => 'xiaobo zzz', - :phone => '555-555-5555', - :address1 => '1234 Levesque St.', - :address2 => 'Apt B', - :city => 'Montreal', - :state => 'QC', - :country => 'CA', - :zip => 'H2C1X8' + order_id: generate_unique_id, + billing_address: { + name: 'xiaobo zzz', + phone: '555-555-5555', + address1: '1234 Levesque St.', + address2: 'Apt B', + city: 'Montreal', + state: 'QC', + country: 'CA', + zip: 'H2C1X8' }, - :email => 'xiaobozzz@example.com', - :subtotal => 800, - :shipping => 100, - :tax1 => 100, - :tax2 => 100, - :custom => 'reference one' + email: 'xiaobozzz@example.com', + subtotal: 800, + shipping: 100, + tax1: 100, + tax2: 100, + custom: 'reference one' } end @@ -42,10 +41,10 @@ def test_failed_confirmation def test_invalid_login gateway = BeanstreamInteracGateway.new( - :merchant_id => '', - :login => '', - :password => '' - ) + merchant_id: '', + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @options) assert_failure response assert_equal 'Invalid merchant id (merchant_id = 0)', response.message diff --git a/test/remote/gateways/remote_beanstream_test.rb b/test/remote/gateways/remote_beanstream_test.rb index eed3b72531f..cc950418571 100644 --- a/test/remote/gateways/remote_beanstream_test.rb +++ b/test/remote/gateways/remote_beanstream_test.rb @@ -6,7 +6,6 @@ # only work the first time you run them since the profile, if created again, becomes a duplicate. There is a setting in order settings which, when unchecked will allow the tests to be run any number # of times without needing the manual deletion step between test runs. The setting is: Do not allow profile to be created with card data duplicated from an existing profile. class RemoteBeanstreamTest < Test::Unit::TestCase - def setup @gateway = BeanstreamGateway.new(fixtures(:beanstream)) @@ -18,50 +17,51 @@ def setup @mastercard = credit_card('5100000010001004') @declined_mastercard = credit_card('5100000020002000') - @amex = credit_card('371100001000131', {:verification_value => 1234}) - @declined_amex = credit_card('342400001000180', {:verification_value => 1234}) + @amex = credit_card('371100001000131', { verification_value: 1234 }) + @declined_amex = credit_card('342400001000180', { verification_value: 1234 }) # Canadian EFT - @check = check( - :institution_number => '001', - :transit_number => '26729' - ) + @check = check( + institution_number: '001', + transit_number: '26729' + ) @amount = 1500 @options = { - :order_id => generate_unique_id, - :billing_address => { - :name => 'xiaobo zzz', - :phone => '555-555-5555', - :address1 => '4444 Levesque St.', - :address2 => 'Apt B', - :city => 'Montreal', - :state => 'Quebec', - :country => 'CA', - :zip => 'H2C1X8' + order_id: generate_unique_id, + billing_address: { + name: 'xiaobo zzz', + phone: '555-555-5555', + address1: '4444 Levesque St.', + address2: 'Apt B', + city: 'Montreal', + state: 'Quebec', + country: 'CA', + zip: 'H2C1X8' }, - :shipping_address => { - :name => 'shippy', - :phone => '888-888-8888', - :address1 => '777 Foster Street', - :address2 => 'Ste #100', - :city => 'Durham', - :state => 'North Carolina', - :country => 'US', - :zip => '27701' + shipping_address: { + name: 'shippy', + phone: '888-888-8888', + address1: '777 Foster Street', + address2: 'Ste #100', + city: 'Durham', + state: 'North Carolina', + country: 'US', + zip: '27701' }, - :email => 'xiaobozzz@example.com', - :subtotal => 800, - :shipping => 100, - :tax1 => 100, - :tax2 => 100, - :custom => 'reference one' + email: 'xiaobozzz@example.com', + subtotal: 800, + shipping: 100, + tax1: 100, + tax2: 100, + custom: 'reference one' } @recurring_options = @options.merge( - :interval => { :unit => :months, :length => 1 }, - :occurences => 5) + interval: { unit: :months, length: 1 }, + occurences: 5 + ) end def test_successful_visa_purchase @@ -146,9 +146,9 @@ def test_successful_purchase_with_state_in_iso_format def test_successful_purchase_with_only_email options = { - :order_id => generate_unique_id, - :email => 'xiaobozzz@example.com', - :shipping_email => 'ship@mail.com' + order_id: generate_unique_id, + email: 'xiaobozzz@example.com', + shipping_email: 'ship@mail.com' } assert response = @gateway.purchase(@amount, @visa, options) @@ -187,6 +187,16 @@ def test_failed_purchase_due_to_missing_country_with_state assert_match %r{Invalid shipping country id}, response.message end + def test_authorize_and_void + assert auth = @gateway.authorize(@amount, @visa, @options) + assert_success auth + assert_equal 'Approved', auth.message + assert_false auth.authorization.blank? + + assert void = @gateway.void(auth.authorization) + assert_success void + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @visa, @options) assert_success auth @@ -283,7 +293,7 @@ def test_successful_update_recurring assert response.test? assert_false response.authorization.blank? - assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(:account_id => response.params['rbAccountId'])) + assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(account_id: response.params['rbAccountId'])) assert_success response end @@ -293,16 +303,16 @@ def test_successful_cancel_recurring assert response.test? assert_false response.authorization.blank? - assert response = @gateway.cancel_recurring(:account_id => response.params['rbAccountId']) + assert response = @gateway.cancel_recurring(account_id: response.params['rbAccountId']) assert_success response end def test_invalid_login gateway = BeanstreamGateway.new( - :merchant_id => '', - :login => '', - :password => '' - ) + merchant_id: '', + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @visa, @options) assert_failure response assert_equal 'merchantid=Invalid merchant id (merchant_id = )', response.message @@ -360,7 +370,7 @@ def test_delete_from_vault_with_unstore_method def test_successful_add_to_vault_and_use test_add_to_vault_with_custom_vault_id_with_store_method - assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) + assert second_response = @gateway.purchase(@amount * 2, @options[:vault_id], @options) assert_equal 'Approved', second_response.message assert second_response.success? end @@ -370,13 +380,13 @@ def test_unsuccessful_visa_with_vault assert response = @gateway.update(@options[:vault_id], @declined_visa) assert_success response - assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) + assert second_response = @gateway.purchase(@amount * 2, @options[:vault_id], @options) assert_equal 'DECLINE', second_response.message end def test_unsuccessful_closed_profile_charge test_delete_from_vault - assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) + assert second_response = @gateway.purchase(@amount * 2, @options[:vault_id], @options) assert_failure second_response assert_match %r{Invalid customer code\.}, second_response.message end @@ -393,6 +403,35 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:api_key], clean_transcript) end + def test_successful_authorize_with_3ds_v1_options + @options[:three_d_secure] = { + version: '1.0', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + xid: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'true', + authentication_response_status: 'Y' + } + assert response = @gateway.purchase(@amount, @visa, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_authorize_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.2.0', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'Y', + authentication_response_status: 'Y' + } + + assert response = @gateway.purchase(@amount, @visa, @options) + assert_success response + assert_equal 'Approved', response.message + end + private def generate_single_use_token(credit_card) @@ -407,7 +446,7 @@ def generate_single_use_token(credit_card) 'number' => credit_card.number, 'expiry_month' => '01', 'expiry_year' => (Time.now.year + 1) % 100, - 'cvd' => credit_card.verification_value, + 'cvd' => credit_card.verification_value }.to_json response = http.request(request) diff --git a/test/remote/gateways/remote_blue_pay_test.rb b/test/remote/gateways/remote_blue_pay_test.rb index 4c0fc7b5477..af412f99ccf 100644 --- a/test/remote/gateways/remote_blue_pay_test.rb +++ b/test/remote/gateways/remote_blue_pay_test.rb @@ -8,19 +8,19 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase', - :ip => '192.168.0.1' + order_id: generate_unique_id, + billing_address: address, + description: 'Store purchase', + ip: '192.168.0.1' } @recurring_options = { - :rebill_amount => 100, - :rebill_start_date => Date.today, - :rebill_expression => '1 DAY', - :rebill_cycles => '4', - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :duplicate_override => 1 + rebill_amount: 100, + rebill_start_date: Date.today, + rebill_expression: '1 DAY', + rebill_cycles: '4', + billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'), + duplicate_override: 1 } end @@ -34,10 +34,19 @@ def test_successful_purchase # The included test account credentials do not support ACH processor. def test_successful_purchase_with_check - assert response = @gateway.purchase(@amount, check, @options.merge(:email=>'foo@example.com')) + assert response = @gateway.purchase(@amount, check, @options.merge(email: 'foo@example.com')) assert_success response assert response.test? - assert_equal 'App ACH Sale', response.message + assert_equal 'ACH Accepted', response.message + assert response.authorization + end + + def test_successful_purchase_with_stored_credential + options = @options.merge(stored_credential: { initiator: 'cardholder', reason_type: 'recurring' }) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message assert response.authorization end @@ -50,7 +59,7 @@ def test_expired_credit_card end def test_forced_test_mode_purchase - gateway = BluePayGateway.new(fixtures(:blue_pay).update(:test => true)) + gateway = BluePayGateway.new(fixtures(:blue_pay).update(test: true)) assert response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.test? @@ -81,7 +90,7 @@ def test_that_we_understand_and_parse_all_keys_in_rebilling_response assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) assert_success response rebill_id = response.params['rebid'] - assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) + assert response = @gateway.update_recurring(rebill_id: rebill_id, rebill_amount: @amount * 2) assert_success response response_keys = response.params.keys.map(&:to_sym) @@ -111,8 +120,8 @@ def test_authorization_and_void def test_bad_login gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card) @@ -123,8 +132,8 @@ def test_bad_login def test_using_test_request gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card) assert_equal Response, response.class @@ -140,7 +149,7 @@ def test_successful_recurring rebill_id = response.params['rebid'] - assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) + assert response = @gateway.update_recurring(rebill_id: rebill_id, rebill_amount: @amount * 2) assert_success response assert response = @gateway.status_recurring(rebill_id) @@ -171,6 +180,24 @@ def test_successful_purchase_with_solution_id ActiveMerchant::Billing::BluePayGateway.application_id = nil end + def test_successful_refund_with_check + assert response = @gateway.purchase(@amount, check, @options.merge(email: 'foo@example.com')) + assert_success response + assert response.test? + assert_equal 'ACH Accepted', response.message + assert response.authorization + + assert refund = @gateway.refund(@amount, response.authorization, @options.merge(doc_type: 'PPD')) + assert_success refund + assert_equal 'ACH VOIDED', refund.message + end + + def test_successful_credit_with_check + assert credit = @gateway.credit(@amount, check, @options.merge(doc_type: 'PPD')) + assert_success credit + assert_equal 'ACH Accepted', credit.message + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) @@ -180,4 +207,13 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.number, clean_transcript) assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end + + def test_account_number_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(check.account_number, clean_transcript) + end end diff --git a/test/remote/gateways/remote_blue_snap_test.rb b/test/remote/gateways/remote_blue_snap_test.rb index 39511e0940a..a984beeeb1c 100644 --- a/test/remote/gateways/remote_blue_snap_test.rb +++ b/test/remote/gateways/remote_blue_snap_test.rb @@ -6,12 +6,53 @@ 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) + + # BlueSnap may require support contact to activate fraud checking on sandbox accounts. + # Specific merchant-configurable thresholds can be set as follows: + # Order Total Amount Decline Threshold = 3728 + # Payment Country Decline List = Brazil + @fraudulent_amount = 3729 + @fraudulent_card = credit_card('4007702835532454') + @options = { billing_address: address } + @options_3ds2 = @options.merge( + three_d_secure: { + eci: '05', + cavv: 'AAABAWFlmQAAAABjRWWZEEFgFz+A', + xid: 'MGpHWm5ZWVpKclo0aUk0VmltVDA=', + ds_transaction_id: 'jhg34-sdgds87-sdg87-sdfg7', + version: '2.2.0' + } + ) + @refund_options = { + reason: 'Refund for order #1992', + cancel_subscription: 'false', + tax_amount: 0.05, + transaction_meta_data: [ + { + meta_key: 'refundedItems', + meta_value: '1552,8832', + meta_description: 'Refunded Items', + meta_is_visible: 'false' + }, + { + meta_key: 'Number2', + meta_value: 'KTD', + meta_description: 'Metadata 2', + meta_is_visible: 'true' + } + ] + } @check = check - @invalid_check = check(:routing_number => '123456', :account_number => '123456789') + @invalid_check = check(routing_number: '123456', account_number: '123456789') @valid_check_options = { billing_address: { address1: '123 Street', @@ -30,6 +71,38 @@ def test_successful_purchase assert_equal 'Success', response.message end + def test_successful_fractionless_currency_purchase + options = @options.merge(currency: 'JPY') + response = @gateway.purchase(12300, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_three_decimal_currency_purchase + options = @options.merge(currency: 'BHD') + response = @gateway.purchase(1234, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_cabal_card + options = @options.merge({ + email: 'joe@example.com' + }) + response = @gateway.purchase(@amount, @cabal_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_naranja_card + options = @options.merge({ + email: 'joe@example.com' + }) + response = @gateway.purchase(@amount, @naranja_card, options) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_purchase_sans_options response = @gateway.purchase(@amount, @credit_card) assert_success response @@ -49,6 +122,165 @@ def test_successful_purchase_with_more_options response = @gateway.purchase(@amount, @credit_card, more_options) assert_success response assert_equal 'Success', response.message + + # description SHOULD BE set as a meta-data field + assert_not_empty response.params['transaction-meta-data'] + meta = response.params['transaction-meta-data'] + + assert_equal 1, meta.length + assert_equal 'description', meta[0]['meta-key'] + assert_equal 'Product Description', meta[0]['meta-value'] + assert_equal 'Description', meta[0]['meta-description'] + end + + def test_successful_purchase_with_meta_data + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + transaction_meta_data: [ + { + meta_key: 'stateTaxAmount', + meta_value: '20.00', + meta_description: 'State Tax Amount' + }, + { + meta_key: 'cityTaxAmount', + meta_value: '10.00', + meta_description: 'City Tax Amount' + }, + { + meta_key: 'description', + meta_value: 'Product ABC', + meta_description: 'Product Description' + } + ], + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = @gateway.purchase(@amount, @credit_card, more_options) + assert_success response + assert_equal 'Success', response.message + + # description SHOULD BE set as a meta-data field + assert_not_empty response.params['transaction-meta-data'] + meta = response.params['transaction-meta-data'] + + assert_equal 3, meta.length + + meta.each { |m| + assert_true m['meta-key'].length > 0 + assert_true m['meta-value'].length > 0 + assert_true m['meta-description'].length > 0 + + case m['meta-key'] + when 'description' + assert_equal 'Product ABC', m['meta-value'] + assert_equal 'Product Description', m['meta-description'] + when 'cityTaxAmount' + assert_equal '10.00', m['meta-value'] + assert_equal 'City Tax Amount', m['meta-description'] + when 'stateTaxAmount' + assert_equal '20.00', m['meta-value'] + assert_equal 'State Tax Amount', m['meta-description'] + end + } + end + + def test_successful_purchase_with_metadata_empty + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = @gateway.purchase(@amount, @credit_card, more_options) + assert_success response + assert_equal 'Success', response.message + + assert_nil response.params['transaction-meta-data'] + end + + def test_successful_purchase_with_card_holder_info + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ', + billing_address: { + address1: '123 Street', + address2: 'Apt 1', + city: 'Happy City', + state: 'CA', + zip: '94901' + }, + phone_number: '555 888 0000' + }) + + response = @gateway.purchase(@amount, @credit_card, more_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_shipping_contact_info + more_options = @options.merge({ + shipping_address: { + address1: '123 Main St', + address2: 'Apt B', + city: 'Springfield', + state: 'NC', + country: 'US', + zip: '27701' + } + }) + + response = @gateway.purchase(@amount, @credit_card, more_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_3ds2_auth + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_for_stored_credentials_with_cit + cit_stored_credentials = { + initiator: 'cardholder' + } + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2.merge({ stored_credential: cit_stored_credentials })) + assert_success response + assert_equal 'Success', response.message + + cit_stored_credentials = { + initiator: 'cardholder', + network_transaction_id: response.params['original-network-transaction-id'] + } + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2.merge({ stored_credential: cit_stored_credentials })) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_for_stored_credentials_with_mit + mit_stored_credentials = { + initiator: 'merchant' + } + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2.merge({ stored_credential: mit_stored_credentials })) + assert_success response + assert_equal 'Success', response.message + + mit_stored_credentials = { + initiator: 'merchant', + network_transaction_id: response.params['original-network-transaction-id'] + } + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2.merge({ stored_credential: mit_stored_credentials })) + assert_success response + assert_equal 'Success', response.message end def test_successful_purchase_with_currency @@ -111,12 +343,50 @@ def test_successful_purchase_with_level3_data assert_equal '9', response.params['line-item-total'] end + def test_successful_purchase_with_unused_state_code + unrecognized_state_code_options = { + billing_address: { + city: 'Dresden', + state: 'Sachsen', + country: 'DE', + zip: '01069' + } + } + + response = @gateway.purchase(@amount, @credit_card, unrecognized_state_code_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_transaction_fraud_info + fraud_info_options = @options.merge({ + ip: '123.12.134.1', + transaction_fraud_info: { + fraud_session_id: 'fbcc094208f54c0e974d56875c73af7a' + } + }) + + response = @gateway.purchase(@amount, @credit_card, fraud_info_options) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_echeck_purchase response = @gateway.purchase(@amount, @check, @options.merge(@valid_check_options)) assert_success response assert_equal 'Success', response.message end + def test_fraudulent_purchase + # Reflects specific settings on Bluesnap sandbox account. + response = @gateway.purchase(@fraudulent_amount, @fraudulent_card, @options) + assert_failure response + assert_match(/fraud-reference-id/, response.message) + assert_match(/fraud-event/, response.message) + assert_match(/blacklistPaymentCountryDecline/, response.message) + assert_match(/orderTotalDecline/, response.message) + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -124,6 +394,13 @@ def test_failed_purchase assert_equal '14002', response.error_code end + def test_failed_purchase_with_invalid_cabal_card + response = @gateway.purchase(@amount, @invalid_cabal_card, @options) + assert_failure response + assert_match(/'Card Number' should be a valid Credit Card/, response.message) + assert_equal '10001', response.error_code + end + def test_cvv_result response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -146,7 +423,7 @@ def test_failed_echeck_purchase end def test_failed_unauthorized_echeck_purchase - response = @gateway.purchase(@amount, @check, @options.merge({authorized_by_shopper: false})) + response = @gateway.purchase(@amount, @check, @options.merge({ authorized_by_shopper: false })) assert_failure response assert_match(/The payment was not authorized by shopper/, response.message) assert_equal '16004', response.error_code @@ -161,6 +438,31 @@ def test_successful_authorize_and_capture assert_equal 'Success', capture.message end + def test_successful_authorize_and_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_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 + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response @@ -171,7 +473,7 @@ def test_partial_capture_succeeds_even_though_amount_is_ignored_by_gateway auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -185,7 +487,18 @@ def test_successful_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert refund = @gateway.refund(@amount, purchase.authorization, @refund_options) + assert_success refund + assert_equal 'Success', refund.message + assert_not_nil refund.authorization + end + + def test_successful_refund_with_merchant_id + order_id = generate_unique_id + purchase = @gateway.purchase(@amount, @credit_card, @options.merge({ order_id: order_id })) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @refund_options.merge({ merchant_transaction_id: order_id })) assert_success refund assert_equal 'Success', refund.message end @@ -194,14 +507,13 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @refund_options) assert_success refund end def test_failed_refund response = @gateway.refund(@amount, '') assert_failure response - assert_match(/cannot be completed due to missing transaction ID/, response.message) end def test_successful_void @@ -281,7 +593,7 @@ def test_successful_purchase_using_stored_echeck assert_success store_response assert_match(/check/, store_response.authorization) - response = @gateway.purchase(@amount, store_response.authorization, @options.merge({authorized_by_shopper: true})) + response = @gateway.purchase(@amount, store_response.authorization, @options.merge({ authorized_by_shopper: true })) assert_success response assert_equal 'Success', response.message end @@ -332,4 +644,9 @@ def test_transcript_scrubbing_with_echeck assert_scrubbed(@gateway.options[:api_password], transcript) end + def test_successful_purchase_with_idempotency_key + response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: 'test123')) + assert_success response + assert_equal 'Success', response.message + end end diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb index 32f7b1b6fc7..1108632f95f 100644 --- a/test/remote/gateways/remote_borgun_test.rb +++ b/test/remote/gateways/remote_borgun_test.rb @@ -8,7 +8,8 @@ def setup @gateway = BorgunGateway.new(fixtures(:borgun)) @amount = 100 - @credit_card = credit_card('5587402000012011', year: 2018, month: 9, verification_value: 415) + @credit_card = credit_card('5587402000012011', year: 2027, month: 9, verification_value: 415) + @frictionless_3ds_card = credit_card('5455330200000016', verification_value: 415, month: 9, year: 2027) @declined_card = credit_card('4155520000000002') @options = { @@ -28,6 +29,21 @@ def test_successful_purchase assert_equal 'Succeeded', response.message end + def test_successful_preauth_3ds + 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({ redirect_url: 'http://localhost/index.html', apply_3d_secure: '1' })) + assert_success response + assert_equal 'Succeeded', response.message + assert_nil response.params['redirecttoacsform'] + assert_equal response.params['threedsfrictionless'], 'A' + end + def test_successful_purchase_usd response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) assert_success response @@ -54,6 +70,27 @@ def test_successful_authorize_and_capture assert_success capture end + def test_successful_authorize_airline_data + passenger_itinerary_data = { + 'MessageNumber' => '1111111', + 'TrDate' => '20120222', + 'TrTime' => '151515', + 'PassengerName' => 'Jane Doe', + 'ServiceClassCode_1' => '100', + 'FlightNumber_1' => '111111', + 'TravelDate_1' => '20120222', + 'DepartureAirport_1' => 'KEF', + 'CarrierCode_1' => 'CC', + 'TravelAgencyCode' => 'A7654321', + 'TravelAgencyName' => 'Spreedly Inc', + 'TicketNumber' => '900.123.222' + } + + options = @options.merge(passenger_itinerary_data: passenger_itinerary_data) + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + end + def test_successful_authorize_and_capture_usd auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD')) assert_success auth @@ -71,7 +108,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -100,7 +137,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -150,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_bpoint_test.rb b/test/remote/gateways/remote_bpoint_test.rb index 32f0124e787..e176bc4c648 100644 --- a/test/remote/gateways/remote_bpoint_test.rb +++ b/test/remote/gateways/remote_bpoint_test.rb @@ -40,6 +40,12 @@ def test_successful_purchase assert_equal 'Approved', response.message end + def test_successful_purchase_with_more_options + response = @gateway.purchase(@amount, @credit_card, @options.merge({ crn1: 'ref' })) + assert_success response + assert_equal 'Approved', response.message + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -63,7 +69,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -84,7 +90,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -97,12 +103,12 @@ def test_successful_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert void = @gateway.void(@amount, auth.authorization) + assert void = @gateway.void(auth.authorization, amount: @amount) assert_success void end def test_failed_void - response = @gateway.void(@amount, '') + response = @gateway.void('', amount: @amount) assert_failure response end diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index dc32dd9611e..695315f7308 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -2,7 +2,8 @@ class RemoteBraintreeBlueTest < Test::Unit::TestCase def setup - @gateway = BraintreeGateway.new(fixtures(:braintree_blue)) + fixture_key = method_name.match?(/bank_account/i) ? :braintree_blue_with_ach_enabled : :braintree_blue + @gateway = BraintreeGateway.new(fixtures(fixture_key)) @braintree_backend = @gateway.instance_eval { @braintree_gateway } @amount = 100 @@ -11,10 +12,31 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :order_id => '1', - :billing_address => address(:country_name => 'Canada'), - :description => 'Store Purchase' + order_id: '1', + billing_address: address(country_name: 'Canada'), + description: 'Store Purchase' } + + ach_mandate = 'By clicking "Checkout", I authorize Braintree, a service of PayPal, ' \ + 'on behalf of My Company (i) to verify my bank account information ' \ + 'using bank information and consumer reports and (ii) to debit my bank account.' + + @check_required_options = { + billing_address: { + address1: '1670', + address2: '1670 NW 82ND AVE', + city: 'Miami', + state: 'FL', + zip: '32191' + }, + 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 @@ -38,19 +60,26 @@ 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 = { - :billing_address => { - :name => 'John Smith', - :phone => '123-456-7890', - :company => nil, - :address1 => nil, - :address2 => '', - :city => nil, - :state => nil, - :zip => nil, - :country => '' + billing_address: { + name: 'John Smith', + phone: '123-456-7890', + company: nil, + address1: nil, + address2: '', + city: nil, + state: nil, + zip: nil, + country: '' } } assert response = @gateway.authorize(@amount, credit_card, options) @@ -67,15 +96,22 @@ def test_masked_card_number assert_equal('510510', response.params['braintree_transaction']['credit_card_details']['bin']) end + def test_successful_setup_purchase + assert response = @gateway.setup_purchase + 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 response = @gateway.authorize(@amount, @credit_card, order_id: '123') assert_success response assert_equal '1000 Approved', response.message assert_equal '123', response.params['braintree_transaction']['order_id'] end def test_successful_purchase_with_hold_in_escrow - @options.merge({:merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id], :hold_in_escrow => true}) + @options.merge({ merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id], hold_in_escrow: true }) assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal '1000 Approved', response.message @@ -116,49 +152,49 @@ def test_successful_purchase_using_card_token credit_card_token = response.params['credit_card_token'] assert_match %r{^\w+$}, credit_card_token - assert response = @gateway.purchase(@amount, credit_card_token, :payment_method_token => true) + assert response = @gateway.purchase(@amount, credit_card_token, payment_method_token: true) assert_success response assert_equal '1000 Approved', response.message assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] end def test_successful_purchase_with_level_2_data - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:tax_amount => '20', :purchase_order_number => '6789')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(tax_amount: '20', purchase_order_number: '6789')) assert_success response assert_equal '1000 Approved', response.message end def test_successful_purchase_with_level_2_and_3_data options = { - :tax_amount => '20', - :purchase_order_number => '6789', - :shipping_amount => '300', - :discount_amount => '150', - :ships_from_postal_code => '90210', - :line_items => [ + tax_amount: '20', + purchase_order_number: '6789', + shipping_amount: '300', + discount_amount: '150', + ships_from_postal_code: '90210', + line_items: [ { - :name => 'Product Name', - :kind => 'debit', - :quantity => '10.0000', - :unit_amount => '9.5000', - :unit_of_measure => 'unit', - :total_amount => '95.00', - :tax_amount => '5.00', - :discount_amount => '0.00', - :product_code => '54321', - :commodity_code => '98765' + name: 'Product Name', + kind: 'debit', + quantity: '10.0000', + unit_amount: '9.5000', + unit_of_measure: 'unit', + total_amount: '95.00', + tax_amount: '5.00', + discount_amount: '0.00', + product_code: '54321', + commodity_code: '98765' }, { - :name => 'Other Product Name', - :kind => 'debit', - :quantity => '1.0000', - :unit_amount => '2.5000', - :unit_of_measure => 'unit', - :total_amount => '90.00', - :tax_amount => '2.00', - :discount_amount => '1.00', - :product_code => '54322', - :commodity_code => '98766' + name: 'Other Product Name', + kind: 'debit', + quantity: '1.0000', + unit_amount: '2.5000', + unit_of_measure: 'unit', + total_amount: '90.00', + tax_amount: '2.00', + discount_amount: '1.00', + product_code: '54322', + commodity_code: '98766' } ] } @@ -167,6 +203,47 @@ def test_successful_purchase_with_level_2_and_3_data assert_equal '1000 Approved', response.message end + def test_successful_purchase_sending_risk_data + options = @options.merge( + risk_data: { + customer_browser: 'User-Agent Header', + customer_ip: '127.0.0.1' + } + ) + assert response = @gateway.purchase(@amount, @credit_card, options) + 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. + def test_successful_purchase_with_venmo_profile_id + options = @options.merge(venmo_profile_id: fixtures(:braintree_blue)[:venmo_profile_id], payment_method_nonce: 'fake-venmo-account-nonce') + assert response = @gateway.purchase(@amount, 'fake-venmo-account-nonce', options) + assert_success response + end + + def test_successful_partial_capture + options = @options.merge(venmo_profile_id: fixtures(:braintree_blue)[:venmo_profile_id], payment_method_nonce: 'fake-venmo-account-nonce') + assert auth = @gateway.authorize(@amount, 'fake-venmo-account-nonce', options) + assert_success auth + assert_equal '1000 Approved', auth.message + assert auth.authorization + assert capture_one = @gateway.capture(50, auth.authorization, { partial_capture: true }) + assert_success capture_one + assert capture_two = @gateway.capture(50, auth.authorization, { partial_capture: true }) + assert_success capture_two + end + def test_successful_verify assert response = @gateway.verify(@credit_card, @options) assert_success response @@ -179,9 +256,26 @@ def test_failed_verify assert_match %r{number is not an accepted test number}, response.message end + def test_successful_credit_card_verification + card = credit_card('4111111111111111') + assert response = @gateway.verify(card, @options.merge({ allow_card_verification: true })) + assert_success response + assert_match 'OK', response.message + assert_equal 'M', response.cvv_result['code'] + assert_equal 'P', response.avs_result['code'] + end + + def test_failed_credit_card_verification + credit_card = credit_card('378282246310005', verification_value: '544') + + assert response = @gateway.verify(credit_card, @options.merge({ allow_card_verification: true })) + assert_failure response + assert_match 'CVV must be 4 digits for American Express and 3 digits for other card types. (81707)', response.message + end + def test_successful_verify_with_device_data # Requires Advanced Fraud Tools to be enabled - assert response = @gateway.verify(@credit_card, @options.merge({device_data: 'device data for verify'})) + assert response = @gateway.verify(@credit_card, @options.merge({ device_data: 'device data for verify' })) assert_success response assert_equal '1000 Approved', response.message @@ -190,26 +284,26 @@ def test_successful_verify_with_device_data assert transaction['risk_data']['id'] assert_equal 'Approve', transaction['risk_data']['decision'] assert_equal false, transaction['risk_data']['device_data_captured'] - assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] + assert_equal 'fraud_protection', transaction['risk_data']['fraud_service_provider'] end def test_successful_validate_on_store - card = credit_card('4111111111111111', :verification_value => '101') - assert response = @gateway.store(card, :verify_card => true) + card = credit_card('4111111111111111', verification_value: '101') + assert response = @gateway.store(card, verify_card: true) assert_success response assert_equal 'OK', response.message end def test_failed_validate_on_store - card = credit_card('4000111111111115', :verification_value => '200') - assert response = @gateway.store(card, :verify_card => true) + card = credit_card('4000111111111115', verification_value: '200') + assert response = @gateway.store(card, verify_card: true) assert_failure response assert_equal 'Processor declined: Do Not Honor (2000)', response.message end def test_successful_store_with_no_validate - card = credit_card('4000111111111115', :verification_value => '200') - assert response = @gateway.store(card, :verify_card => false) + card = credit_card('4000111111111115', verification_value: '200') + assert response = @gateway.store(card, verify_card: false) assert_success response assert_equal 'OK', response.message end @@ -222,46 +316,46 @@ def test_successful_store_with_invalid_card def test_successful_store_with_billing_address billing_address = { - :address1 => '1 E Main St', - :address2 => 'Suite 403', - :city => 'Chicago', - :state => 'Illinois', - :zip => '60622', - :country_name => 'United States of America' + address1: '1 E Main St', + address2: 'Suite 403', + city: 'Chicago', + state: 'Illinois', + zip: '60622', + country_name: 'United States of America' } credit_card = credit_card('5105105105105100') - assert response = @gateway.store(credit_card, :billing_address => billing_address) + assert response = @gateway.store(credit_card, billing_address: billing_address) assert_success response assert_equal 'OK', response.message vault_id = response.params['customer_vault_id'] purchase_response = @gateway.purchase(@amount, vault_id) response_billing_details = { - 'country_name'=>'United States of America', - 'region'=>'Illinois', - 'company'=>nil, - 'postal_code'=>'60622', - 'extended_address'=>'Suite 403', - 'street_address'=>'1 E Main St', - 'locality'=>'Chicago' + 'country_name' => 'United States of America', + 'region' => 'Illinois', + 'company' => nil, + 'postal_code' => '60622', + 'extended_address' => 'Suite 403', + 'street_address' => '1 E Main St', + 'locality' => 'Chicago' } assert_equal purchase_response.params['braintree_transaction']['billing_details'], response_billing_details end def test_successful_store_with_nil_billing_address_options billing_address = { - :name => 'John Smith', - :phone => '123-456-7890', - :company => nil, - :address1 => nil, - :address2 => nil, - :city => nil, - :state => nil, - :zip => nil, - :country_name => nil + name: 'John Smith', + phone: '123-456-7890', + company: nil, + address1: nil, + address2: nil, + city: nil, + state: nil, + zip: nil, + country_name: nil } credit_card = credit_card('5105105105105100') - assert response = @gateway.store(credit_card, :billing_address => billing_address) + assert response = @gateway.store(credit_card, billing_address: billing_address) assert_success response assert_equal 'OK', response.message @@ -292,11 +386,12 @@ def test_successful_store_with_new_customer_id def test_successful_store_with_existing_customer_id credit_card = credit_card('5105105105105100') customer_id = generate_unique_id - assert response = @gateway.store(credit_card, customer: customer_id) + assert response = @gateway.store(credit_card, @options.merge(customer: customer_id)) assert_success response assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size - assert response = @gateway.store(credit_card, customer: customer_id) + credit_card = credit_card('4111111111111111') + assert response = @gateway.store(credit_card, @options.merge(customer: customer_id)) assert_success response assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size assert_equal customer_id, response.params['customer_vault_id'] @@ -308,17 +403,17 @@ def test_successful_store_with_existing_customer_id_and_nil_billing_address_opti credit_card = credit_card('5105105105105100') customer_id = generate_unique_id options = { - :customer => customer_id, - :billing_address => { - :name => 'John Smith', - :phone => '123-456-7890', - :company => nil, - :address1 => nil, - :address2 => nil, - :city => nil, - :state => nil, - :zip => nil, - :country_name => nil + customer: customer_id, + billing_address: { + name: 'John Smith', + phone: '123-456-7890', + company: nil, + address1: nil, + address2: nil, + city: nil, + state: nil, + zip: nil, + country_name: nil } } assert response = @gateway.store(credit_card, options) @@ -376,28 +471,26 @@ def test_avs end def test_cvv_match - assert response = @gateway.purchase(@amount, credit_card('5105105105105100', :verification_value => '400')) + assert response = @gateway.purchase(@amount, credit_card('5105105105105100', verification_value: '400')) assert_success response - assert_equal({'code' => 'M', 'message' => ''}, response.cvv_result) + assert_equal({ 'code' => 'M', 'message' => '' }, response.cvv_result) end def test_cvv_no_match - assert response = @gateway.purchase(@amount, credit_card('5105105105105100', :verification_value => '200')) + assert response = @gateway.purchase(@amount, credit_card('5105105105105100', verification_value: '200')) assert_success response - assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result) + assert_equal({ 'code' => 'N', 'message' => '' }, response.cvv_result) end def test_successful_purchase_with_email - assert response = @gateway.purchase(@amount, @credit_card, - :email => 'customer@example.com' - ) + 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'] end def test_successful_purchase_with_phone - assert response = @gateway.purchase(@amount, @credit_card, :phone => '123-345-5678') + assert response = @gateway.purchase(@amount, @credit_card, phone: '123-345-5678') assert_success response transaction = response.params['braintree_transaction'] assert_equal '123-345-5678', transaction['customer_details']['phone'] @@ -410,6 +503,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 @@ -417,6 +519,20 @@ def test_successful_purchase_with_skip_advanced_fraud_checking_option assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] end + def test_successful_purchase_with_skip_avs + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_avs: true)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'B', response.avs_result['code'] + end + + def test_successful_purchase_with_skip_cvv + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_cvv: true)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'B', response.cvv_result['code'] + end + def test_successful_purchase_with_device_data # Requires Advanced Fraud Tools to be enabled assert response = @gateway.purchase(@amount, @credit_card, @options.merge(device_data: 'device data for purchase')) @@ -428,12 +544,12 @@ def test_successful_purchase_with_device_data assert transaction['risk_data']['id'] assert_equal 'Approve', transaction['risk_data']['decision'] assert_equal false, transaction['risk_data']['device_data_captured'] - assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] + assert_equal 'fraud_protection', transaction['risk_data']['fraud_service_provider'] end def test_purchase_with_store_using_random_customer_id assert response = @gateway.purchase( - @amount, credit_card('5105105105105100'), @options.merge(:store => true) + @amount, credit_card('5105105105105100'), @options.merge(store: true) ) assert_success response assert_equal '1000 Approved', response.message @@ -445,7 +561,7 @@ def test_purchase_with_store_using_random_customer_id def test_purchase_with_store_using_specified_customer_id customer_id = rand(1_000_000_000).to_s assert response = @gateway.purchase( - @amount, credit_card('5105105105105100'), @options.merge(:store => customer_id) + @amount, credit_card('5105105105105100'), @options.merge(store: customer_id) ) assert_success response assert_equal '1000 Approved', response.message @@ -459,19 +575,16 @@ def test_purchase_with_transaction_source assert_success response customer_vault_id = response.params['customer_vault_id'] - assert response = @gateway.purchase(@amount, customer_vault_id, @options.merge(:transaction_source => 'unscheduled')) + assert response = @gateway.purchase(@amount, customer_vault_id, @options.merge(transaction_source: 'unscheduled')) assert_success response assert_equal '1000 Approved', response.message end def test_purchase_using_specified_payment_method_token assert response = @gateway.store( - credit_card('4111111111111111', - :first_name => 'Old First', :last_name => 'Old Last', - :month => 9, :year => 2012 - ), - :email => 'old@example.com', - :phone => '321-654-0987' + credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012), + email: 'old@example.com', + phone: '321-654-0987' ) payment_method_token = response.params['braintree_customer']['credit_cards'][0]['token'] assert response = @gateway.purchase( @@ -484,26 +597,28 @@ def test_purchase_using_specified_payment_method_token def test_successful_purchase_with_addresses billing_address = { - :address1 => '1 E Main St', - :address2 => 'Suite 101', - :company => 'Widgets Co', - :city => 'Chicago', - :state => 'IL', - :zip => '60622', - :country_name => 'United States of America' + address1: '1 E Main St', + address2: 'Suite 101', + company: 'Widgets Co', + city: 'Chicago', + state: 'IL', + zip: '60622', + country_name: 'United States of America' } shipping_address = { - :address1 => '1 W Main St', - :address2 => 'Suite 102', - :company => 'Widgets Company', - :city => 'Bartlett', - :state => 'Illinois', - :zip => '60103', - :country_name => 'Mexico' + address1: '1 W Main St', + address2: 'Suite 102', + company: 'Widgets Company', + city: 'Bartlett', + state: 'Illinois', + zip: '60103', + country_name: 'Mexico' } - assert response = @gateway.purchase(@amount, @credit_card, - :billing_address => billing_address, - :shipping_address => shipping_address + assert response = @gateway.purchase( + @amount, + @credit_card, + billing_address: billing_address, + shipping_address: shipping_address ) assert_success response transaction = response.params['braintree_transaction'] @@ -523,11 +638,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 = { eci: '05', cavv: 'cavv', xid: 'xid' } - assert 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) assert_success response end @@ -542,7 +664,13 @@ def test_unsuccessful_purchase_validation_error assert response = @gateway.purchase(@amount, credit_card('51051051051051000')) assert_failure response assert_match %r{Credit card number is invalid\. \(81715\)}, response.message - assert_equal({'processor_response_code'=>'91577'}, response.params['braintree_transaction']) + assert_equal('91577', response.params['braintree_transaction']['processor_response_code']) + end + + def test_unsuccessful_purchase_with_additional_processor_response + 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']) end def test_authorize_and_capture @@ -555,28 +683,11 @@ def test_authorize_and_capture end def test_authorize_and_capture_with_apple_pay_card - credit_card = network_tokenization_credit_card('4111111111111111', - :brand => 'visa', - :eci => '05', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' - ) - - assert auth = @gateway.authorize(@amount, credit_card, @options) - assert_success auth - assert_equal '1000 Approved', auth.message - assert auth.authorization - assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture - end - - def test_authorize_and_capture_with_android_pay_card - credit_card = network_tokenization_credit_card('4111111111111111', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', - :month => '01', - :year => '2024', - :source => :android_pay, - :transaction_id => '123456789', - :eci => '05' + credit_card = network_tokenization_credit_card( + '4111111111111111', + brand: 'visa', + eci: '05', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' ) assert auth = @gateway.authorize(@amount, credit_card, @options) @@ -588,13 +699,14 @@ 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', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', - :month => '01', - :year => '2024', - :source => :google_pay, - :transaction_id => '123456789', - :eci => '05' + credit_card = network_tokenization_credit_card( + '4111111111111111', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '01', + year: '2024', + source: :google_pay, + transaction_id: '123456789', + eci: '05' ) assert auth = @gateway.authorize(@amount, credit_card, @options) @@ -646,7 +758,7 @@ def test_failed_void assert failed_void = @gateway.void(auth.authorization) assert_failure failed_void assert_match('Transaction can only be voided if status is authorized', failed_void.message) - assert_equal({'processor_response_code'=>'91504'}, failed_void.params['braintree_transaction']) + assert_equal('91504', failed_void.params['braintree_transaction']['processor_response_code']) end def test_failed_capture_with_invalid_transaction_id @@ -656,7 +768,7 @@ def test_failed_capture_with_invalid_transaction_id end def test_invalid_login - gateway = BraintreeBlueGateway.new(:merchant_id => 'invalid', :public_key => 'invalid', :private_key => 'invalid') + gateway = BraintreeBlueGateway.new(merchant_id: 'invalid', public_key: 'invalid', private_key: 'invalid') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Braintree::AuthenticationError', response.message @@ -706,12 +818,9 @@ 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 - ), - :email => 'old@example.com', - :phone => '321-654-0987' + credit_card('4111111111111111', first_name: 'Old First', last_name: 'Old Last', month: 9, year: 2012), + email: 'old@example.com', + phone: '321-654-0987' ) assert_success response assert_equal 'OK', response.message @@ -728,12 +837,9 @@ 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 - ), - :email => 'new@example.com', - :phone => '987-765-5432' + credit_card('5105105105105100', first_name: 'New First', last_name: 'New Last', month: 10, year: 2014), + email: 'new@example.com', + phone: '987-765-5432' ) assert_success response assert_equal 'new@example.com', response.params['braintree_customer']['email'] @@ -747,7 +853,7 @@ def test_successful_update end def test_failed_customer_update - assert response = @gateway.store(credit_card('4111111111111111'), :email => 'email@example.com', :phone => '321-654-0987') + assert response = @gateway.store(credit_card('4111111111111111'), email: 'email@example.com', phone: '321-654-0987') assert_success response assert_equal 'OK', response.message assert customer_vault_id = response.params['customer_vault_id'] @@ -791,7 +897,7 @@ def test_failed_credit_card_update_on_verify assert response = @gateway.update( customer_vault_id, credit_card('4000111111111115'), - {:verify_card => true} + { verify_card: true } ) assert_failure response assert_equal 'Processor declined: Do Not Honor (2000)', response.message @@ -818,14 +924,33 @@ def test_failed_credit end def test_successful_credit_with_merchant_account_id - assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert response = @gateway.credit(@amount, @credit_card, merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml AND get credits enabled in your Sandbox account for this to pass.' + assert_equal '1002 Processed', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_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 response = @gateway.authorize(@amount, @credit_card, merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id]) assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.' assert_equal '1000 Approved', response.message assert_equal 'authorized', response.params['braintree_transaction']['status'] @@ -836,9 +961,38 @@ def test_authorize_with_descriptor assert_success auth end + def test_authorize_with_travel_data + 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, + 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 + def test_successful_validate_on_store_with_verification_merchant_account - card = credit_card('4111111111111111', :verification_value => '101') - assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + card = credit_card('4111111111111111', verification_value: '101') + assert response = @gateway.store(card, verify_card: true, verification_merchant_account_id: fixtures(:braintree_blue)[:merchant_account_id]) assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.' assert_equal 'OK', response.message end @@ -860,13 +1014,279 @@ def test_verify_credentials assert !gateway.verify_credentials end + def test_successful_merchant_purchase_initial + creds_options = stored_credential_options(:merchant, :recurring, :initial) + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + end + + def test_successful_subsequent_merchant_unscheduled_transaction + creds_options = stored_credential_options(:merchant, :unscheduled, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_subsequent_merchant_recurring_transaction + creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_initial + creds_options = stored_credential_options(:cardholder, :recurring, :initial) + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_recurring + creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_unscheduled + creds_options = stored_credential_options(:cardholder, :unscheduled, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_initial_setup + creds_options = { initiator: 'merchant', reason_type: 'recurring_first', initial_transaction: true } + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + assert_equal true, response.params['braintree_transaction']['recurring'] + end + + def test_successful_cardholder_purchase_initial_moto + creds_options = { initiator: 'merchant', reason_type: 'moto', initial_transaction: true } + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_store_bank_account_with_a_new_customer + bank_account = check({ account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(@check_required_options)) + + assert_success response + assert response.params['bank_account_token'] + assert response.params['verified'] + + customer = @braintree_backend.customer.find(response.params['customer_vault_id']) + bank_accounts = customer.us_bank_accounts + created_bank_account = bank_accounts.first + + assert_equal 1, bank_accounts.size + assert created_bank_account.verified + assert_equal bank_account.routing_number, created_bank_account.routing_number + assert_equal bank_account.account_number[-4..-1], created_bank_account.last_4 + assert_equal 'checking', created_bank_account.account_type + assert_equal 'Jim', customer.first_name + assert_equal 'Smith', customer.last_name + end + + def test_successful_store_bank_account_with_existing_customer + customer_id = generate_unique_id + bank_account = check({ account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(customer: customer_id).merge(@check_required_options)) + + assert response + assert_success response + + bank_account = check({ account_number: '1000000001', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(customer: customer_id).merge(@check_required_options)) + + assert response + assert_success response + + customer = @braintree_backend.customer.find(customer_id) + bank_accounts = customer.us_bank_accounts + + assert_equal 2, bank_accounts.size + assert bank_accounts.first.verified + assert bank_accounts.last.verified + end + + def test_successful_store_bank_account_with_customer_id_not_in_merchant_account + customer_id = generate_unique_id + bank_account = check({ account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(customer: customer_id).merge(@check_required_options)) + + assert response + assert_success response + assert response.params['bank_account_token'] + assert response.params['verified'] + assert_equal response.params['customer_vault_id'], customer_id + + customer = @braintree_backend.customer.find(customer_id) + bank_accounts = customer.us_bank_accounts + created_bank_account = bank_accounts.first + + assert created_bank_account.verified + assert_equal 1, bank_accounts.size + assert_equal bank_account.routing_number, created_bank_account.routing_number + assert_equal bank_account.account_number[-4..-1], created_bank_account.last_4 + assert_equal customer_id, customer.id + assert_equal 'checking', created_bank_account.account_type + assert_equal 'Jim', customer.first_name + assert_equal 'Smith', customer.last_name + end + + def test_successful_store_business_savings_bank_account + customer_id = generate_unique_id + bank_account = check({ account_type: 'savings', account_holder_type: 'business', account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(customer: customer_id).merge(@check_required_options)) + + assert response + assert_success response + + customer = @braintree_backend.customer.find(customer_id) + bank_accounts = customer.us_bank_accounts + created_bank_account = bank_accounts.first + assert created_bank_account.verified + assert_equal 1, bank_accounts.size + assert_equal 'savings', bank_account.account_type + assert_equal 'business', (created_bank_account.instance_eval { @ownership_type }) + end + + def test_unsuccessful_store_an_unverified_bank_account + customer_id = generate_unique_id + bank_account = check({ account_number: '1000000004', routing_number: '011000015' }) + options = @options.merge(customer: customer_id).merge(@check_required_options) + response = @gateway.store(bank_account, options) + + assert response + assert_failure response + assert_equal 'verification_status: [processor_declined], processor_response: [2046-Declined]', response.message + + customer = @braintree_backend.customer.find(customer_id) + bank_accounts = customer.us_bank_accounts + created_bank_account = bank_accounts.first + + refute created_bank_account.verified + assert_equal 1, bank_accounts.size + end + + def test_sucessful_purchase_using_a_bank_account_token + bank_account = check({ account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(@check_required_options)) + + assert response + assert_success response + payment_method_token = response.params['bank_account_token'] + sleep 2 + + assert response = @gateway.purchase(@amount, payment_method_token, @options.merge(payment_method_token: true)) + assert_success response + assert_equal '4002 Settlement Pending', response.message + end + + def test_successful_purchase_with_the_same_bank_account_several_times + bank_account = check({ account_number: '1000000000', routing_number: '011000015' }) + response = @gateway.store(bank_account, @options.merge(@check_required_options)) + + assert response + assert_success response + + payment_method_token = response.params['bank_account_token'] + sleep 2 + + # Purchase # 1 + assert response = @gateway.purchase(@amount, payment_method_token, @options.merge(payment_method_token: true)) + assert_success response + assert_equal '4002 Settlement Pending', response.message + + # Purchase # 2 + assert response = @gateway.purchase(120, payment_method_token, @options.merge(payment_method_token: true)) + assert_success response + 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_with_with_prepaid_debit_issuing_bank + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal '1000 Approved', response.message + 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_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)) + + assert response + assert_failure response + + payment_method_token = response.params['bank_account_token'] + assert response = @gateway.purchase(@amount, payment_method_token, @options.merge(payment_method_token: true)) + + assert_failure response + assert_equal 'US bank account payment method must be verified prior to transaction. (915172)', response.message + end + + def test_unsuccessful_store_with_incomplete_bank_account + bank_account = check({ account_type: 'blah', + account_holder_type: 'blah', + account_number: nil, + routing_number: nil, + name: nil }) + + response = @gateway.store(bank_account, @options.merge(@check_required_options)) + + assert response + assert_failure response + assert_equal 'cannot be empty', response.message[:account_number].first + assert_equal 'cannot be empty', response.message[:routing_number].first + assert_equal 'cannot be empty', response.message[:name].first + assert_equal 'must be checking or savings', response.message[:account_type].first + assert_equal 'must be personal or business', response.message[:account_holder_type].first + end + private + def stored_credential_options(*args, id: nil) + stored_credential(*args, id: id) + end + def assert_avs(address1, zip, expected_avs_code) - response = @gateway.purchase(@amount, @credit_card, billing_address: {address1: address1, zip: zip}) + response = @gateway.purchase(@amount, @credit_card, billing_address: { address1: address1, zip: zip }) assert_success response assert_equal expected_avs_code, response.avs_result['code'] end - end diff --git a/test/remote/gateways/remote_braintree_orange_test.rb b/test/remote/gateways/remote_braintree_orange_test.rb index 0816eb1821b..6f69e221f56 100644 --- a/test/remote/gateways/remote_braintree_orange_test.rb +++ b/test/remote/gateways/remote_braintree_orange_test.rb @@ -8,9 +8,8 @@ def setup @credit_card = credit_card('4111111111111111') @check = check() @declined_amount = rand(99) - @options = { :order_id => generate_unique_id, - :billing_address => address - } + @options = { order_id: generate_unique_id, + billing_address: address } end def test_successful_purchase @@ -21,12 +20,12 @@ def test_successful_purchase def test_successful_purchase_with_echeck check = ActiveMerchant::Billing::Check.new( - :name => 'Fredd Bloggs', - :routing_number => '111000025', # Valid ABA # - Bank of America, TX - :account_number => '999999999999', - :account_holder_type => 'personal', - :account_type => 'checking' - ) + name: 'Fredd Bloggs', + routing_number: '111000025', # Valid ABA # - Bank of America, TX + account_number: '999999999999', + account_holder_type: 'personal', + account_type: 'checking' + ) assert response = @gateway.purchase(@amount, check, @options) assert_equal 'This transaction has been approved', response.message assert_success response @@ -61,7 +60,7 @@ def test_successful_add_to_vault_and_use assert_success response assert_not_nil customer_id = response.params['customer_vault_id'] - assert second_response = @gateway.purchase(@amount*2, customer_id, @options) + assert second_response = @gateway.purchase(@amount * 2, customer_id, @options) assert_equal 'This transaction has been approved', second_response.message assert second_response.success? end @@ -90,7 +89,7 @@ def test_add_to_vault_with_store_and_check def test_update_vault test_add_to_vault_with_custom_vault_id - @credit_card = credit_card('4111111111111111', :month => 10) + @credit_card = credit_card('4111111111111111', month: 10) assert response = @gateway.update(@options[:store], @credit_card) assert_success response assert_equal 'Customer Update Successful', response.message @@ -163,9 +162,9 @@ def test_failed_verify def test_invalid_login gateway = BraintreeOrangeGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Invalid Username', response.message assert_failure response diff --git a/test/remote/gateways/remote_braintree_token_nonce_test.rb b/test/remote/gateways/remote_braintree_token_nonce_test.rb new file mode 100644 index 00000000000..cbc8dbc3c24 --- /dev/null +++ b/test/remote/gateways/remote_braintree_token_nonce_test.rb @@ -0,0 +1,92 @@ +require 'test_helper' + +class RemoteBraintreeTokenNonceTest < Test::Unit::TestCase + def setup + @gateway = BraintreeGateway.new(fixtures(:braintree_blue)) + @braintree_backend = @gateway.instance_eval { @braintree_gateway } + + ach_mandate = 'By clicking ["Checkout"], I authorize Braintree, a service of PayPal, ' \ + 'on behalf of My Company (i) to verify my bank account information ' \ + 'using bank information and consumer reports and (ii) to debit my bank account.' + + @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 + } + end + + def test_client_token_generation + generator = TokenNonce.new(@braintree_backend) + token = generator.client_token + assert_not_nil token + end + + def test_successfully_create_token_nonce_for_bank_account + generator = TokenNonce.new(@braintree_backend, @options) + bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' }) + tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account) + + assert_not_nil tokenized_bank_account + assert_match %r(^tokenusbankacct_), tokenized_bank_account + assert_nil err_messages + end + + def test_unsucesfull_create_token_with_invalid_state + @options[:billing_address][:state] = nil + generator = TokenNonce.new(@braintree_backend, @options) + bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' }) + tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account) + + assert_nil tokenized_bank_account + 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 + @options[:billing_address][:zip] = nil + generator = TokenNonce.new(@braintree_backend, @options) + bank_account = check({ account_number: '4012000033330125', routing_number: '011000015' }) + tokenized_bank_account, err_messages = generator.create_token_nonce_for_payment_method(bank_account) + + assert_nil tokenized_bank_account + 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 + config_base = { + merchant_id: 'test', + public_key: 'test', + private_key: 'test', + environment: :sandbox + } + + configuration = Braintree::Configuration.new(config_base) + braintree_backend = Braintree::Gateway.new(configuration) + generator = TokenNonce.new(braintree_backend) + + assert_equal 'https://payments.sandbox.braintree-api.com/graphql', generator.url + + configuration = Braintree::Configuration.new(config_base.update(environment: :production)) + braintree_backend = Braintree::Gateway.new(configuration) + generator = TokenNonce.new(braintree_backend) + + 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_bridge_pay_test.rb b/test/remote/gateways/remote_bridge_pay_test.rb index 88f91a8212c..1b1e175b788 100644 --- a/test/remote/gateways/remote_bridge_pay_test.rb +++ b/test/remote/gateways/remote_bridge_pay_test.rb @@ -9,10 +9,10 @@ def setup @declined_card = credit_card('4000300011100000') @check = check( - :name => 'John Doe', - :routing_number => '490000018', - :account_number => '1234567890', - :number => '1001' + name: 'John Doe', + routing_number: '490000018', + account_number: '1234567890', + number: '1001' ) @options = { @@ -57,7 +57,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -78,7 +78,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -142,4 +142,13 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end + + def test_account_number_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(150, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + end end diff --git a/test/remote/gateways/remote_cams_test.rb b/test/remote/gateways/remote_cams_test.rb index e193a39ce7e..b1e6a61ed75 100644 --- a/test/remote/gateways/remote_cams_test.rb +++ b/test/remote/gateways/remote_cams_test.rb @@ -73,7 +73,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -94,7 +94,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end diff --git a/test/remote/gateways/remote_card_connect_test.rb b/test/remote/gateways/remote_card_connect_test.rb index 61f587cd168..68301145fd6 100644 --- a/test/remote/gateways/remote_card_connect_test.rb +++ b/test/remote/gateways/remote_card_connect_test.rb @@ -34,28 +34,28 @@ def test_successful_purchase_with_more_options ship_from_date: '20877', items: [ { - line_no: '1', + lineno: '1', material: 'MATERIAL-1', description: 'DESCRIPTION-1', upc: 'UPC-1', quantity: '1000', uom: 'CS', - unit_cost: '900', - net_amnt: '150', - tax_amnt: '117', - disc_amnt: '0' + unitcost: '900', + netamnt: '150', + taxamnt: '117', + discamnt: '0' }, { - line_no: '2', + lineno: '2', material: 'MATERIAL-2', description: 'DESCRIPTION-2', upc: 'UPC-1', quantity: '2000', uom: 'CS', - unit_cost: '450', - net_amnt: '300', - tax_amnt: '117', - disc_amnt: '0' + unitcost: '450', + netamnt: '300', + taxamnt: '117', + discamnt: '0' } ] } @@ -65,11 +65,82 @@ def test_successful_purchase_with_more_options assert_equal 'Approval Queued for Capture', response.message end - def test_successful_purchase_3DS + def test_successful_purchase_with_more_options_but_no_PO + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + tax_amount: '50', + freight_amount: '29', + duty_amount: '67', + order_date: '20170507', + ship_from_date: '20877', + items: [ + { + lineno: '1', + material: 'MATERIAL-1', + description: 'DESCRIPTION-1', + upc: 'UPC-1', + quantity: '1000', + uom: 'CS', + unitcost: '900', + netamnt: '150', + taxamnt: '117', + discamnt: '0' + }, + { + lineno: '2', + material: 'MATERIAL-2', + description: 'DESCRIPTION-2', + upc: 'UPC-1', + quantity: '2000', + uom: 'CS', + unitcost: '450', + netamnt: '300', + taxamnt: '117', + discamnt: '0' + } + ] + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approval', response.message + end + + def test_successful_purchase_with_user_fields + # `response` does not contain userfields, but the transaction may be checked after + # running the test suite via an authorized call to the inquireByOrderid endpoint: + # /cardconnect/rest/inquireByOrderid// + options = { + order_id: '138510', + ip: '127.0.0.1', + email: 'joe@example.com', + po_number: '5FSD4', + tax_amount: '50', + freight_amount: '29', + duty_amount: '67', + order_date: '20170507', + ship_from_date: '20877', + user_fields: [ + { 'udf0': 'value0' }, + { 'udf1': 'value1' }, + { 'udf2': 'value2' } + ] + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approval Queued for Capture', response.message + end + + def test_successful_purchase_three_ds three_ds_options = @options.merge( - secure_flag: 'se3453', - secure_value: '233frdf', - secure_xid: '334ef34' + three_d_secure: { + eci: 'se3453', + cavv: 'AJkBByEyYgAAAASwgmEodQAAAAA=', + ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==' + } ) response = @gateway.purchase(@amount, @credit_card, three_ds_options) assert_success response @@ -83,6 +154,31 @@ def test_successful_purchase_with_profile assert_success purchase_response end + def test_successful_purchase_using_stored_credential_framework + stored_credential_options = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant' + } + response = @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + assert_success response + assert_equal response.params['cof'], 'M' + + stored_credential_options = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant' + } + response = @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + assert_success response + assert_equal response.params['cof'], 'M' + end + + def test_successful_purchase_with_telephonic_ecomind + response = @gateway.purchase(@amount, @credit_card, @options.merge({ ecomind: 'T' })) + assert_success response + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -130,22 +226,27 @@ def test_failed_echeck_purchase assert_equal 'Invalid card', response.message end - def test_successful_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase - - assert refund = @gateway.refund(@amount, purchase.authorization) - assert_success refund - assert_equal 'Approval', refund.message - end - - def test_partial_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase - - assert refund = @gateway.refund(@amount - 1, purchase.authorization) - assert_success refund - end + # A transaction cannot be refunded before settlement so these tests will + # fail with the following response, to properly test refunds create a purchase + # save the reference and test the next day, check: + # https://cardconnect.com/launchpointe/running-a-business/payment-processing-101#how_long_it_takes + # + # def test_successful_refund + # purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + # + # assert refund = @gateway.refund(@amount, purchase.authorization) + # assert_success refund + # assert_equal 'Approval', refund.message + # end + # + # def test_partial_refund + # purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + # + # assert refund = @gateway.refund(@amount - 1, purchase.authorization) + # assert_success refund + # end def test_failed_refund response = @gateway.refund(@amount, @invalid_txn) diff --git a/test/remote/gateways/remote_card_save_test.rb b/test/remote/gateways/remote_card_save_test.rb index 06ee56d1053..a3c34c19f3c 100644 --- a/test/remote/gateways/remote_card_save_test.rb +++ b/test/remote/gateways/remote_card_save_test.rb @@ -1,20 +1,19 @@ require 'test_helper' class RemoteCardSaveTest < Test::Unit::TestCase - def setup @gateway = CardSaveGateway.new(fixtures(:card_save)) @amount = 100 - @credit_card = credit_card('4976000000003436', :verification_value => '452') - @declined_card = credit_card('4221690000004963', :verification_value => '125') - @addresses = {'4976000000003436' => { :name => 'John Watson', :address1 => '32 Edward Street', :city => 'Camborne,', :state => 'Cornwall', :country => 'GB', :zip => 'TR14 8PA' }, - '4221690000004963' => { :name => 'Ian Lee', :address1 => '274 Lymington Avenue', :city => 'London', :state => 'London', :country => 'GB', :zip => 'N22 6JN' }} + @credit_card = credit_card('4976000000003436', verification_value: '452') + @declined_card = credit_card('4221690000004963', verification_value: '125') + @addresses = { '4976000000003436' => { name: 'John Watson', address1: '32 Edward Street', city: 'Camborne,', state: 'Cornwall', country: 'GB', zip: 'TR14 8PA' }, + '4221690000004963' => { name: 'Ian Lee', address1: '274 Lymington Avenue', city: 'London', state: 'London', country: 'GB', zip: 'N22 6JN' } } @options = { - :order_id => '1', - :billing_address => @addresses[@credit_card.number], - :description => 'Store Purchase' + order_id: '1', + billing_address: @addresses[@credit_card.number], + description: 'Store Purchase' } end @@ -32,7 +31,7 @@ def test_unsuccessful_purchase end def test_authorize_and_capture - amount = @amount+10 + amount = @amount + 10 assert auth = @gateway.authorize(amount, @credit_card, @options) assert_success auth assert auth.message =~ /AuthCode: ([0-9]+)/ @@ -49,8 +48,8 @@ def test_failed_capture def test_invalid_login gateway = CardSaveGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_card_stream_test.rb b/test/remote/gateways/remote_card_stream_test.rb index fd6d3e8e3ef..48fe71952ec 100644 --- a/test/remote/gateways/remote_card_stream_test.rb +++ b/test/remote/gateways/remote_card_stream_test.rb @@ -6,118 +6,136 @@ def setup @gateway = CardStreamGateway.new(fixtures(:card_stream)) - @amex = credit_card('374245455400001', - :month => '12', - :year => '2014', - :verification_value => '4887', - :brand => :american_express + @amex = credit_card( + '374245455400001', + month: '12', + year: Time.now.year + 1, + verification_value: '4887', + brand: :american_express ) - @mastercard = credit_card('5301250070000191', - :month => '12', - :year => '2014', - :verification_value => '419', - :brand => :master + @mastercard = credit_card( + '5301250070000191', + month: '12', + year: Time.now.year + 1, + verification_value: '419', + brand: :master ) - @visacreditcard = credit_card('4929421234600821', - :month => '12', - :year => '2014', - :verification_value => '356', - :brand => :visa + @visacreditcard = credit_card( + '4929421234600821', + month: '12', + year: Time.now.year + 1, + verification_value: '356', + brand: :visa ) - @visadebitcard = credit_card('4539791001730106', - :month => '12', - :year => '2014', - :verification_value => '289', - :brand => :visa + @visadebitcard = credit_card( + '4539791001730106', + month: '12', + year: Time.now.year + 1, + verification_value: '289', + brand: :visa ) - @declined_card = credit_card('4000300011112220', - :month => '9', - :year => '2014' + @declined_card = credit_card( + '4000300011112220', + month: '9', + year: Time.now.year + 1 ) @amex_options = { - :billing_address => { - :address1 => 'The Hunts Way', - :city => '', - :state => 'Leicester', - :zip => 'SO18 1GW', - :country => 'GB' + billing_address: { + address1: 'The Hunts Way', + city: '', + state: 'Leicester', + zip: 'SO18 1GW', + country: 'GB' }, - :order_id => generate_unique_id, - :description => 'AM test purchase', - :ip => '1.1.1.1' + order_id: generate_unique_id, + description: 'AM test purchase', + ip: '1.1.1.1' } @visacredit_options = { - :billing_address => { - :address1 => 'Flat 6, Primrose Rise', - :address2 => '347 Lavender Road', - :city => '', - :state => 'Northampton', - :zip => 'NN17 8YG', - :country => 'GB' + billing_address: { + address1: 'Flat 6, Primrose Rise', + address2: '347 Lavender Road', + city: '', + state: 'Northampton', + zip: 'NN17 8YG', + country: 'GB' }, - :order_id => generate_unique_id, - :description => 'AM test purchase', - :ip => '1.1.1.1' + order_id: generate_unique_id, + description: 'AM test purchase', + ip: '1.1.1.1' } @visacredit_descriptor_options = { - :billing_address => { - :address1 => 'Flat 6, Primrose Rise', - :address2 => '347 Lavender Road', - :city => '', - :state => 'Northampton', - :zip => 'NN17 8YG', - :country => 'GB' + billing_address: { + address1: 'Flat 6, Primrose Rise', + address2: '347 Lavender Road', + city: '', + state: 'Northampton', + zip: 'NN17 8YG', + country: 'GB' }, - :merchant_name => 'merchant', - :dynamic_descriptor => 'product', - :ip => '1.1.1.1', + order_id: generate_unique_id, + merchant_name: 'merchant', + dynamic_descriptor: 'product', + ip: '1.1.1.1' } @visacredit_reference_options = { - :order_id => generate_unique_id, - :description => 'AM test purchase', - :ip => '1.1.1.1' + order_id: generate_unique_id, + description: 'AM test purchase', + ip: '1.1.1.1' } @visadebit_options = { - :billing_address => { - :address1 => 'Unit 5, Pickwick Walk', - :address2 => '120 Uxbridge Road', - :city => 'Hatch End', - :state => 'Middlesex', - :zip => 'HA6 7HJ', - :country => 'GB' + billing_address: { + address1: 'Unit 5, Pickwick Walk', + address2: '120 Uxbridge Road', + city: 'Hatch End', + state: 'Middlesex', + zip: 'HA6 7HJ', + country: 'GB' }, - :order_id => generate_unique_id, - :description => 'AM test purchase', - :ip => '1.1.1.1' + order_id: generate_unique_id, + description: 'AM test purchase', + ip: '1.1.1.1' } @mastercard_options = { - :billing_address => { - :address1 => '25 The Larches', - :city => 'Narborough', - :state => 'Leicester', - :zip => 'LE10 2RT', - :country => 'GB' + billing_address: { + address1: '25 The Larches', + city: 'Narborough', + state: 'Leicester', + zip: 'LE10 2RT', + country: 'GB' }, - :order_id => generate_unique_id, - :description => 'AM test purchase', - :ip => '1.1.1.1' + order_id: generate_unique_id, + description: 'AM test purchase', + ip: '1.1.1.1' } - @three_ds_enrolled_card = credit_card('4012001037141112', - :month => '12', - :year => '2020', - :brand => :visa + @three_ds_enrolled_card = credit_card( + '4012001037141112', + month: '12', + year: '2020', + brand: :visa ) + + @visacredit_three_ds_options = { + threeds_required: true, + three_d_secure: { + enrolled: 'true', + authentication_response_status: 'Y', + eci: '05', + cavv: 'Y2FyZGluYWxjb21tZXJjZWF1dGg', + xid: '00000000000004717472' + } + } end def test_successful_visacreditcard_authorization_and_capture @@ -166,7 +184,7 @@ def test_failed_visacreditcard_purchase_and_refund assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visacredit_options) assert_failure responseRefund - assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_equal 'Cannot REFUND this SALE transaction', responseRefund.message assert responseRefund.test? end @@ -223,7 +241,7 @@ def test_failed_visadebitcard_purchase_and_refund assert !responsePurchase.authorization.blank? assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visadebit_options) - assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_equal 'Cannot REFUND this SALE transaction', responseRefund.message assert_failure responseRefund assert responseRefund.test? end @@ -261,7 +279,7 @@ def test_failed_amex_purchase_and_refund assert !responsePurchase.authorization.blank? assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @amex_options) - assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_equal 'Cannot REFUND this SALE transaction', responseRefund.message assert_failure responseRefund assert responseRefund.test? end @@ -299,7 +317,7 @@ def test_failed_mastercard_purchase_and_refund assert !responsePurchase.authorization.blank? assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @mastercard_options) - assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_equal 'Cannot REFUND this SALE transaction', responseRefund.message assert_failure responseRefund assert responseRefund.test? end @@ -313,7 +331,7 @@ def test_successful_visacreditcard_purchase end def test_successful_visacreditcard_purchase_via_reference - assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge({:type => '9'})) + assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge({ type: '9' })) assert_equal 'APPROVED', response.message assert_success response assert response.test? @@ -343,6 +361,13 @@ def test_failed_purchase_non_existent_currency assert_match %r{MISSING_CURRENCYCODE}, response.message end + def test_successful_purchase_and_amount_for_non_decimal_currency + assert response = @gateway.purchase(14200, @visacreditcard, @visacredit_options.merge(currency: 'JPY')) + assert_success response + assert_equal '392', response.params['currencyCode'] + assert_equal '142', response.params['amount'] + end + def test_successful_visadebitcard_purchase assert response = @gateway.purchase(142, @visadebitcard, @visadebit_options) assert_equal 'APPROVED', response.message @@ -376,8 +401,8 @@ def test_successful_amex_purchase def test_invalid_login gateway = CardStreamGateway.new( - :login => '', - :shared_secret => '' + login: '', + shared_secret: '' ) assert response = gateway.purchase(142, @mastercard, @mastercard_options) assert_match %r{MISSING_MERCHANTID}, response.message @@ -393,7 +418,7 @@ def test_successful_verify def test_failed_verify response = @gateway.verify(@declined_card, @mastercard_options) assert_failure response - assert_match %r{INVALID_CARDNUMBER}, response.message + assert_match %r{Disallowed cardnumber}, response.message end def test_successful_3dsecure_purchase @@ -418,6 +443,38 @@ def test_successful_3dsecure_auth assert !response.params['threeDSPaReq'].blank? end + def test_3dsecure2_auth_authenticated_card + assert response = @gateway.authorize(1202, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + assert_equal 'APPROVED', response.message + assert_equal '0', response.params['responseCode'] + assert_equal 'Success', response.params['threeDSResponseMessage'] + assert response.success? + assert response.test? + assert !response.authorization.blank? + end + + def test_3dsecure2_auth_not_authenticated_card + @visacredit_three_ds_options[:three_d_secure][:authentication_response_status] = 'N' + assert response = @gateway.authorize(1202, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + assert_equal '3DS DECLINED', response.message + assert_equal '65803', response.params['responseCode'] + assert_equal 'not authenticated', response.params['threeDSCheck'] + refute response.success? + assert response.test? + refute response.authorization.blank? + end + + def test_3dsecure2_auth_not_enrolled_card + @visacredit_three_ds_options[:three_d_secure][:enrolled] = 'false' + assert response = @gateway.authorize(1202, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + assert_equal '3DS DECLINED', response.message + assert_equal '65803', response.params['responseCode'] + assert_equal 'not checked', response.params['threeDSCheck'] + refute response.success? + assert response.test? + refute response.authorization.blank? + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @visacreditcard, @visacredit_options) diff --git a/test/remote/gateways/remote_cardknox_test.rb b/test/remote/gateways/remote_cardknox_test.rb index 211e1e9afcb..ca7b0aef440 100644 --- a/test/remote/gateways/remote_cardknox_test.rb +++ b/test/remote/gateways/remote_cardknox_test.rb @@ -33,7 +33,7 @@ def setup zip: '46112', country: 'US', phone: '(555)555-5555', - fax: '(555)555-6666', + fax: '(555)555-6666' } } @@ -124,7 +124,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -138,7 +138,7 @@ def test_credit_card_purchase_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -146,7 +146,7 @@ def test_failed_credit_card_authorize_partial_refund auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert refund = @gateway.refund(@amount-1, auth.authorization) + assert refund = @gateway.refund(@amount - 1, auth.authorization) assert_failure refund assert_equal 'Refund not allowed on non-captured auth.', refund.message end @@ -155,7 +155,7 @@ def test_failed_partial_check_refund # the gate way does not support this transa purchase = @gateway.purchase(@amount, @check, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_failure refund assert_equal "Transaction is in a state that cannot be refunded\nParameter name: originalReferenceNumber", refund.message # "Only allowed to refund transactions that have settled. This is the best we can do for now testing wise." end @@ -167,7 +167,7 @@ def test_credit_card_capture_partial_refund assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture - assert refund = @gateway.refund(@amount-1, capture.authorization) + assert refund = @gateway.refund(@amount - 1, capture.authorization) assert_success refund end @@ -190,7 +190,7 @@ def test_successful_credit_card_capture_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture assert void = @gateway.void(capture.authorization, @options) @@ -214,7 +214,7 @@ def test_successful_credit_card_refund_void assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture - assert refund = @gateway.refund(@amount-1, capture.authorization) + assert refund = @gateway.refund(@amount - 1, capture.authorization) assert_success refund assert void = @gateway.void(refund.authorization, @options) diff --git a/test/remote/gateways/remote_cardprocess_test.rb b/test/remote/gateways/remote_cardprocess_test.rb index 951c07e0ab3..c1763f9bd0e 100644 --- a/test/remote/gateways/remote_cardprocess_test.rb +++ b/test/remote/gateways/remote_cardprocess_test.rb @@ -50,7 +50,7 @@ def test_successful_authorize_and_capture end def test_failed_authorize - @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '800.100.151'}) + @gateway.instance_variable_set(:@test_options, { 'customParameters[forceResultCode]' => '800.100.151' }) response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 'transaction declined (invalid card)', response.message @@ -60,7 +60,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -89,7 +89,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -121,7 +121,7 @@ def test_successful_verify end def test_failed_verify - @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '600.200.100'}) + @gateway.instance_variable_set(:@test_options, { 'customParameters[forceResultCode]' => '600.200.100' }) response = @gateway.verify(@credit_card, @options) assert_failure response assert_match %r{invalid Payment Method}, response.message diff --git a/test/remote/gateways/remote_cashnet_test.rb b/test/remote/gateways/remote_cashnet_test.rb index 87aea788007..2b12a7116a3 100644 --- a/test/remote/gateways/remote_cashnet_test.rb +++ b/test/remote/gateways/remote_cashnet_test.rb @@ -7,7 +7,7 @@ def setup @credit_card = credit_card( '5454545454545454', month: 12, - year: 2015 + year: Time.new.year + 1 ) @options = { order_id: generate_unique_id, @@ -15,6 +15,22 @@ def setup } end + def test_successful_purchase + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.test? + assert_equal 'Success', purchase.message + end + + def test_successful_purchase_with_multiple_items + options = @options.merge({ item_codes: { item_code: 'FEE', item_code2: 'LOBSTER', item_code3: 'CODES', amount: 5679, amount2: 1234, amount3: 4321 } }) + + assert purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + assert purchase.test? + assert_equal 'Success', purchase.message + end + def test_successful_purchase_and_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -43,6 +59,15 @@ def test_failed_purchase assert_equal '5', response.params['result'] end + def test_failed_purchase_with_multiple_items + options = @options.merge({ item_codes: { item_code2: 'NONE', amount2: 4321 } }) + + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_match %r{Invalid item code, no code specified}, response.message + assert_equal '4', response.params['result'] + end + def test_failed_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase 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..3abd1878553 --- /dev/null +++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb @@ -0,0 +1,170 @@ +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: '20231231', + 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 '106900640', response.message + assert_match '1-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 '106900640', response.message + assert_match '807', response.error_code + end + + def test_successful_purchase + assert response = @gateway.purchase(@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_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match '106900640', response.message + assert_match '1-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 '106900640', 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 '106900640', 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_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 '106900640', 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 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 108fb3ea106..217ed8cc501 100644 --- a/test/remote/gateways/remote_cecabank_test.rb +++ b/test/remote/gateways/remote_cecabank_test.rb @@ -5,12 +5,12 @@ def setup @gateway = CecabankGateway.new(fixtures(:cecabank)) @amount = 100 - @credit_card = credit_card('5540500001000004', {:month => 12, :year => Time.now.year, :verification_value => 989}) - @declined_card = credit_card('5540500001000004', {:month => 11, :year => Time.now.year + 1, :verification_value => 001}) + @credit_card = credit_card('5540500001000004', { month: 12, year: Time.now.year, verification_value: 989 }) + @declined_card = credit_card('5540500001000004', { month: 11, year: Time.now.year + 1, verification_value: 001 }) @options = { - :order_id => generate_unique_id, - :description => 'Active Merchant Test Purchase' + order_id: generate_unique_id, + description: 'Active Merchant Test Purchase' } end @@ -23,7 +23,7 @@ def test_successful_purchase def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'ERROR', response.message + assert_match 'ERROR', response.message end def test_successful_refund @@ -36,20 +36,19 @@ def test_successful_refund end def test_unsuccessful_refund - assert response = @gateway.refund(@amount, 'wrongreference', @options) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert response = @gateway.refund(@amount, purchase.authorization, @options.merge(currency: 'USD')) assert_failure response - assert_equal 'ERROR', response.message + assert_match 'Error', response.message end def test_invalid_login - gateway = CecabankGateway.new( - :merchant_id => '', - :acquirer_bin => '', - :terminal_id => '', - :key => '' - ) + gateway = CecabankGateway.new(fixtures(:cecabank).merge(signature_key: 'invalid')) + assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'ERROR', response.message + assert_match 'ERROR', response.message end end diff --git a/test/remote/gateways/remote_cenpos_test.rb b/test/remote/gateways/remote_cenpos_test.rb index a3474658bbc..0edde763895 100644 --- a/test/remote/gateways/remote_cenpos_test.rb +++ b/test/remote/gateways/remote_cenpos_test.rb @@ -5,13 +5,19 @@ def setup @gateway = CenposGateway.new(fixtures(:cenpos)) @amount = SecureRandom.random_number(10000) - @credit_card = credit_card('4111111111111111', month: 02, year: 18, verification_value: 999) + @declined_amount = 100 + @credit_card = credit_card('4003440008007566', month: 12, year: 2025, verification_value: 999) + @declined_card = credit_card('4000300011112220') @invalid_card = credit_card('9999999999999999') @options = { order_id: SecureRandom.random_number(1000000), - billing_address: address + billing_address: { + name: 'Jim Smith', + address1: 'D8320', + zip: 'D5284' + } } end @@ -61,21 +67,21 @@ def test_successful_purchase_with_currency end def test_failed_purchase - response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@declined_amount, @declined_card, @options) assert_failure response assert_equal 'Decline transaction', response.message assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end def test_failed_purchase_cvv_result - response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@declined_amount, @declined_card, @options) %w(code message).each do |key| assert_equal nil, response.cvv_result[key] end end def test_failed_purchase_avs_result - response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@declined_amount, @declined_card, @options) %w(code message).each do |key| assert_equal nil, response.avs_result[key] end @@ -93,7 +99,7 @@ def test_successful_authorize_and_capture end def test_failed_authorize - response = @gateway.authorize(@amount, @declined_card, @options) + response = @gateway.authorize(@declined_amount, @declined_card, @options) assert_failure response assert_equal 'Decline transaction', response.message assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code @@ -107,7 +113,7 @@ def test_failed_capture @gateway.capture(@amount, response.authorization) capture = @gateway.capture(@amount, response.authorization) assert_failure capture - assert_equal 'Duplicated force transaction.', capture.message + assert_match(/Duplicated.*transaction/, capture.message) end def test_successful_void @@ -166,11 +172,13 @@ def test_failed_credit assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code end - def test_successful_verify - response = @gateway.verify(@credit_card, @options) - assert_success response - assert_match %r{Succeeded}, response.message - end + # This test appears to fail due to the amount of 100 being set in verify + # That amount is automatically triggering a decline message in tests + # def test_successful_verify + # response = @gateway.verify(@credit_card, @options) + # assert_success response + # assert_match %r{Succeeded}, response.message + # end def test_failed_verify response = @gateway.verify(@declined_card, @options) diff --git a/test/remote/gateways/remote_checkout_test.rb b/test/remote/gateways/remote_checkout_test.rb index 206442a70fa..4977d1889f4 100644 --- a/test/remote/gateways/remote_checkout_test.rb +++ b/test/remote/gateways/remote_checkout_test.rb @@ -9,7 +9,7 @@ def setup year: '2017', verification_value: '956' ) - @declined_card = credit_card( + @declined_card = credit_card( '4543474002249996', month: '06', year: '2018', @@ -27,13 +27,14 @@ def test_successful_purchase end def test_successful_purchase_with_extra_options - response = @gateway.purchase(100, @credit_card, @options.merge( + options = @options.merge( currency: 'EUR', email: 'bob@example.com', order_id: generate_unique_id, customer: generate_unique_id, ip: '127.0.0.1' - )) + ) + response = @gateway.purchase(100, @credit_card, options) assert_success response assert_equal 'Successful', response.message end @@ -60,7 +61,7 @@ def test_successful_authorize_and_capture auth = @gateway.authorize(100, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(100, auth.authorization, {currency: 'CAD'}) + assert capture = @gateway.capture(100, auth.authorization, { currency: 'CAD' }) assert_success capture assert_equal 'Successful', capture.message end @@ -101,7 +102,7 @@ def test_successful_refund assert response = @gateway.purchase(100, @credit_card, @options) assert_success response - assert refund = @gateway.refund(100, response.authorization, {currency: 'CAD'}) + assert refund = @gateway.refund(100, response.authorization, { currency: 'CAD' }) assert_success refund assert_equal 'Successful', refund.message end @@ -110,7 +111,7 @@ def test_failed_refund assert response = @gateway.purchase(100, @credit_card, @options) assert_success response - assert refund = @gateway.refund(100, '||||', {currency: 'CAD'}) + assert refund = @gateway.refund(100, '||||', { currency: 'CAD' }) assert_failure refund end diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb index 893347aa7f8..13149453ae4 100644 --- a/test/remote/gateways/remote_checkout_v2_test.rb +++ b/test/remote/gateways/remote_checkout_v2_test.rb @@ -2,24 +2,178 @@ class RemoteCheckoutV2Test < Test::Unit::TestCase def setup - @gateway = CheckoutV2Gateway.new(fixtures(:checkout_v2)) + 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: '2018') + @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('4000300011112220') + @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', + payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + 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: 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: Time.now.year + 1, + source: :google_pay, + verification_value: nil + ) + + @google_pay_master_cryptogram_3ds_network_token = network_tokenization_credit_card( + '5436031030606378', + payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + 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: 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' + email: 'longbob.longsen@example.com', + processing_channel_id: 'pc_lxgl7aqahkzubkundd2l546hdm' } @additional_options = @options.merge( card_on_file: true, transaction_indicator: 2, - previous_charge_id: 'charge_12312' + previous_charge_id: 'pay_123', + processing_channel_id: 'pc_123' ) + @additional_options_3ds = @options.merge( + execute_threed: true, + three_d_secure: { + version: '1.0.2', + eci: '06', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=', + authentication_response_status: 'Y' + } + ) + @additional_options_3ds2 = @options.merge( + execute_threed: true, + attempt_n3d: true, + challenge_indicator: 'no_preference', + exemption: 'trusted_listing', + three_d_secure: { + version: '2.0.0', + eci: '06', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + ds_transaction_id: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=', + 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_access_token + 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 @@ -33,18 +187,201 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:secret_key], transcript) end + def test_transcript_scrubbing_via_oauth + declined_card = credit_card('4000300011112220', verification_value: '309') + transcript = capture_transcript(@gateway_oauth) do + @gateway_oauth.purchase(@amount, @credit_card, @options) + end + transcript = @gateway_oauth.scrub(transcript) + assert_scrubbed(declined_card.number, transcript) + assert_scrubbed(declined_card.verification_value, transcript) + assert_scrubbed(@gateway_oauth.options[:client_id], transcript) + assert_scrubbed(@gateway_oauth.options[:client_secret], transcript) + end + + def test_network_transaction_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(100, @apple_pay_network_token, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@apple_pay_network_token.payment_cryptogram, transcript) + assert_scrubbed(@apple_pay_network_token.number, transcript) + 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 assert_equal 'Succeeded', response.message end + def test_successful_purchase_via_oauth + response = @gateway_oauth.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_vts_network_token + response = @gateway.purchase(100, @vts_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['source']['payment_account_reference'] + end + + def test_successful_purchase_with_vts_network_token_via_oauth + response = @gateway_oauth.purchase(100, @vts_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['source']['payment_account_reference'] + end + + def test_successful_purchase_with_mdes_network_token + response = @gateway.purchase(100, @mdes_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['source']['payment_account_reference'] + end + + def test_successful_purchase_with_google_pay_visa_cryptogram_3ds_network_token + response = @gateway.purchase(100, @google_pay_visa_cryptogram_3ds_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_google_pay_visa_cryptogram_3ds_network_token_via_oauth + response = @gateway_oauth.purchase(100, @google_pay_visa_cryptogram_3ds_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_google_pay_master_cryptogram_3ds_network_token + response = @gateway.purchase(100, @google_pay_master_cryptogram_3ds_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_google_pay_pan_only_network_token + response = @gateway.purchase(100, @google_pay_pan_only_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_apple_pay_network_token + response = @gateway.purchase(100, @apple_pay_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_apple_pay_network_token_via_oauth + response = @gateway_oauth.purchase(100, @apple_pay_network_token, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + # # currently, checkout does not provide any valid test card numbers for testing mada cards + # def test_successful_purchase_with_mada_card + # response = @gateway.purchase(@amount, @mada_card, @options) + # assert_success response + # assert_equal 'Succeeded', response.message + # end + def test_successful_purchase_with_additional_options response = @gateway.purchase(@amount, @credit_card, @additional_options) assert_success response assert_equal 'Succeeded', response.message end + def test_successful_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Succeeded', initial_response.message + assert_not_nil initial_response.params['id'] + network_transaction_id = initial_response.params['id'] + + stored_options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'installment', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, stored_options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_stored_credentials_via_oauth + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway_oauth.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Succeeded', initial_response.message + assert_not_nil initial_response.params['id'] + network_transaction_id = initial_response.params['id'] + + stored_options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'installment', + network_transaction_id: network_transaction_id + } + ) + response = @gateway_oauth.purchase(@amount, @credit_card, stored_options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id + stored_options = @options.merge( + stored_credential: { + reason_type: 'installment' + }, + merchant_initiated_transaction_id: 'pay_7emayabnrtjkhkrbohn4m2zyoa321' + ) + response = @gateway.purchase(@amount, @credit_card, stored_options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_moto_flag + response = @gateway.authorize(@amount, @credit_card, @options.merge(transaction_indicator: 3)) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_moto_flag_via_oauth + response = @gateway_oauth.authorize(@amount, @credit_card, @options.merge(transaction_indicator: 3)) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_manual_entry_flag + response = @gateway.authorize(@amount, @credit_card, @options.merge(metadata: { manual_entry: true })) + + assert_success response + assert_equal 'Succeeded', response.message + end + def test_successful_purchase_includes_avs_result response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -53,6 +390,14 @@ def test_successful_purchase_includes_avs_result assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message'] end + def test_successful_purchase_includes_avs_result_via_oauth + response = @gateway_oauth.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'G', response.avs_result['code'] + assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message'] + end + def test_successful_authorize_includes_avs_result response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -68,6 +413,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 @@ -75,6 +426,41 @@ def test_successful_authorize_includes_cvv_result assert_equal 'Y', response.cvv_result['code'] end + def test_successful_authorize_includes_cvv_result_via_oauth + response = @gateway_oauth.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_authorize_with_estimated_type + response = @gateway.authorize(@amount, @credit_card, @options.merge({ authorization_type: 'Estimated' })) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_with_incremental_authoriation + response = @gateway_oauth.authorize(@amount, @credit_card, @options.merge({ authorization_type: 'Estimated' })) + assert_success response + assert_equal 'Succeeded', response.message + + response = @gateway_oauth.authorize(@amount, @credit_card, @options.merge({ incremental_authorization: response.authorization })) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_with_estimated_type_via_oauth + response = @gateway_oauth.authorize(@amount, @credit_card, @options.merge({ authorization_type: 'Estimated' })) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_with_processing_channel_id + response = @gateway.authorize(@amount, @credit_card, @options.merge({ processing_channel_id: 'pc_ovo75iz4hdyudnx6tu74mum3fq' })) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_successful_purchase_with_descriptors options = @options.merge(descriptor_name: 'shop', descriptor_city: 'london') response = @gateway.purchase(@amount, @credit_card, options) @@ -82,14 +468,59 @@ def test_successful_purchase_with_descriptors assert_equal 'Succeeded', response.message end + def test_successful_purchase_with_descriptors_via_oauth + options = @options.merge(descriptor_name: 'shop', descriptor_city: 'london') + response = @gateway_oauth.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_metadata + options = @options.merge( + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + ) + response = @gateway.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: { + coupon_code: 'NY2018', + partner_id: '123989' + } + ) + response = @gateway_oauth.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_successful_purchase_with_minimal_options response = @gateway.purchase(@amount, @credit_card, billing_address: address) assert_success response assert_equal 'Succeeded', response.message end + def test_successful_purchase_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 @@ -101,21 +532,33 @@ def test_successful_purchase_with_ip end def test_failed_purchase - response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(100, @credit_card_dnh, @options) assert_failure response assert_equal 'Invalid Card Number', response.message end + def test_failed_purchase_via_oauth + response = @gateway_oauth.purchase(100, @declined_card, @options) + assert_failure response + assert_equal 'request_invalid: card_number_invalid', response.message + end + def test_avs_failed_purchase - response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(address1: 'Test_A')) + response = @gateway.purchase(@amount, @declined_card, billing_address: address.update(address1: 'Test_A')) assert_failure response - assert_equal '40111 - Street Match Only', response.message + assert_equal 'request_invalid: card_number_invalid', response.message end def test_avs_failed_authorize - response = @gateway.authorize(@amount, @credit_card, billing_address: address.update(address1: 'Test_A')) + response = @gateway.authorize(@amount, @declined_card, billing_address: address.update(address1: 'Test_A')) assert_failure response - assert_equal '40111 - Street Match Only', response.message + 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 @@ -126,6 +569,30 @@ def test_successful_authorize_and_capture assert_success capture end + def test_successful_authorize_and_capture_via_oauth + auth = @gateway_oauth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway_oauth.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture((@amount / 2).to_i, auth.authorization, { capture_type: 'NonFinal' }) + assert_success capture + end + + def test_successful_authorize_and_partial_capture_via_oauth + auth = @gateway_oauth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway_oauth.capture((@amount / 2).to_i, auth.authorization, { capture_type: 'NonFinal' }) + assert_success capture + end + def test_successful_authorize_and_capture_with_additional_options auth = @gateway.authorize(@amount, @credit_card, @additional_options) assert_success auth @@ -134,16 +601,78 @@ def test_successful_authorize_and_capture_with_additional_options assert_success capture end + def test_successful_authorize_and_capture_with_3ds + auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds_via_oauth + auth = @gateway_oauth.authorize(@amount, @credit_card, @additional_options_3ds) + assert_success auth + + assert capture = @gateway_oauth.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds2 + auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds2) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds2_via_oauth + auth = @gateway_oauth.authorize(@amount, @credit_card, @additional_options_3ds2) + assert_success auth + + assert capture = @gateway_oauth.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_metadata + options = @options.merge( + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_direct_3ds_authorize + auth = @gateway.authorize(@amount, @threeds_card, @options.merge(execute_threed: true)) + + assert_equal 'Pending', auth.message + assert_equal 'Y', auth.params['3ds']['enrolled'] + assert auth.params['_links']['redirect'] + end + def test_failed_authorize - response = @gateway.authorize(@amount, @declined_card, @options) + response = @gateway.authorize(12314, @declined_card, @options) + assert_failure response + assert_equal 'request_invalid: card_number_invalid', response.message + end + + def test_failed_authorize_via_oauth + response = @gateway_oauth.authorize(12314, @declined_card, @options) assert_failure response + assert_equal 'request_invalid: card_number_invalid', 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 capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -152,10 +681,161 @@ def test_failed_capture assert_failure response end + def test_failed_capture_via_oauth + response = @gateway_oauth.capture(nil, '') + assert_failure response + end + + def test_successful_credit + @credit_card.first_name = 'John' + @credit_card.last_name = 'Doe' + response = @gateway_oauth.credit(@amount, @credit_card, @options.merge({ source_type: 'currency_account', source_id: 'ca_spwmped4qmqenai7hcghquqle4', account_holder_type: 'individual' })) + assert_success response + 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')) + 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_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 + sleep 1 + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_successful_refund_via_oauth + purchase = @gateway_oauth.purchase(@amount, @credit_card, @options) + assert_success purchase + + sleep 1 + + assert refund = @gateway_oauth.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_successful_refund_with_metadata + options = @options.merge( + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + ) + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + sleep 1 + assert refund = @gateway.refund(@amount, purchase.authorization) assert_success refund end @@ -164,7 +844,9 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + sleep 1 + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -173,6 +855,11 @@ def test_failed_refund assert_failure response end + def test_failed_refund_via_oauth + response = @gateway_oauth.refund(nil, '') + assert_failure response + end + def test_successful_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -181,13 +868,63 @@ 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 + + assert void = @gateway_oauth.void(auth.authorization) + assert_success void + end + + def test_successful_void_with_metadata + options = @options.merge( + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + def test_failed_void response = @gateway.void('') assert_failure response end + def test_failed_void_via_oauth + response = @gateway_oauth.void('') + assert_failure response + end + 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 end @@ -195,14 +932,19 @@ def test_successful_verify def test_failed_verify response = @gateway.verify(@declined_card, @options) assert_failure response - assert_match %r{Invalid Card Number}, response.message - assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + assert_match %r{request_invalid: card_number_invalid}, response.message end def test_expired_card_returns_error_code response = @gateway.purchase(@amount, @expired_card, @options) assert_failure response - assert_equal 'Validation error: Expired Card', response.message - assert_equal '70000: 70077', response.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_citrus_pay_test.rb b/test/remote/gateways/remote_citrus_pay_test.rb index cf7d131b942..cfb4711d02a 100644 --- a/test/remote/gateways/remote_citrus_pay_test.rb +++ b/test/remote/gateways/remote_citrus_pay_test.rb @@ -34,7 +34,7 @@ def test_successful_purchase_sans_options def test_successful_purchase_with_more_options more_options = @options.merge({ ip: '127.0.0.1', - email: 'joe@example.com', + email: 'joe@example.com' }) assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) @@ -104,9 +104,9 @@ def test_successful_verify def test_invalid_login gateway = CitrusPayGateway.new( - :userid => 'nosuch', - :password => 'thing' - ) + userid: 'nosuch', + password: 'thing' + ) response = gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message @@ -130,5 +130,4 @@ def test_verify_credentials gateway = CitrusPayGateway.new(userid: 'unknown', password: 'unknown') assert !gateway.verify_credentials end - end diff --git a/test/remote/gateways/remote_clearhaus_test.rb b/test/remote/gateways/remote_clearhaus_test.rb index 9a716294dcb..844b748aee4 100644 --- a/test/remote/gateways/remote_clearhaus_test.rb +++ b/test/remote/gateways/remote_clearhaus_test.rb @@ -74,10 +74,17 @@ def test_successful_purchase_with_text_on_statement assert_equal 'Approved', response.message end + def test_successful_purchase_and_amount_for_non_decimal_currency + response = @gateway.purchase(14200, @credit_card, @options.merge(currency: 'JPY')) + assert_success response + assert_equal 142, response.params['amount'] + assert_equal 'Approved', response.message + end + def test_successful_purchase_with_more_options options = { order_id: '1', - ip: '127.0.0.1', + ip: '127.0.0.1' } response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) @@ -113,7 +120,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -136,7 +143,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -184,7 +191,7 @@ def test_failed_verify end def test_successful_authorize_with_nonfractional_currency - assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'KRW')) + assert response = @gateway.authorize(100, @credit_card, @options.merge(currency: 'KRW')) assert_equal 1, response.params['amount'] assert_success response end diff --git a/test/remote/gateways/remote_commerce_hub_test.rb b/test/remote/gateways/remote_commerce_hub_test.rb new file mode 100644 index 00000000000..c49ad1ebdaa --- /dev/null +++ b/test/remote/gateways/remote_commerce_hub_test.rb @@ -0,0 +1,291 @@ +require 'test_helper' + +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: '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', + brand: 'visa', + eci: '05', + month: '02', + year: '2035', + source: :apple_pay, + 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=' + ) + @declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123') + @master_card = credit_card('5454545454545454', brand: 'master') + @options = {} + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_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 + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_stored_credential_framework + stored_credential_options = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant' + } + first_response = @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + assert_success first_response + + ntxid = first_response.params['transactionDetails']['retrievalReferenceNumber'] + stored_credential_options = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: ntxid + } + response = @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match 'Unable to assign card to brand: Invalid', response.message + assert_equal '104', response.error_code + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + 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 + + # capture = @gateway.capture(@amount, authorize.authorization) + # assert_success capture + # end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match 'Unable to assign card to brand: Invalid', response.message + end + + def test_successful_authorize_and_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + + response = @gateway.void(response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_void + response = @gateway.void('123', @options) + assert_failure response + assert_equal 'Referenced transaction is invalid or not found', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + 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 + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + + response = @gateway.refund(nil, response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_and_partial_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + + response = @gateway.refund(@amount - 1, response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_refund + 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_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'TOKENIZE', response.message + end + + def test_successful_store_with_purchase + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'TOKENIZE', response.message + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + end + + def test_successful_purchase_with_google_pay + response = @gateway.purchase(@amount, @google_pay, @options) + assert_success response + assert_equal 'Approved', response.message + assert_equal 'DecryptedWallet', response.params['source']['sourceType'] + end + + def test_successful_purchase_with_apple_pay + response = @gateway.purchase(@amount, @apple_pay, @options) + assert_success response + assert_equal 'Approved', response.message + assert_equal 'DecryptedWallet', response.params['source']['sourceType'] + end + + def test_failed_purchase_with_declined_apple_pay + response = @gateway.purchase(@amount, @declined_apple_pay, @options) + assert_failure response + assert_match 'Unable to assign card to brand: Invalid', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + assert_scrubbed(@gateway.options[:api_secret], transcript) + end + + def test_transcript_scrubbing_apple_pay + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @apple_pay, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@apple_pay.number, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + 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_commercegate_test.rb b/test/remote/gateways/remote_commercegate_test.rb index 3ca37b555c2..4f9c0be7cf8 100644 --- a/test/remote/gateways/remote_commercegate_test.rb +++ b/test/remote/gateways/remote_commercegate_test.rb @@ -11,7 +11,7 @@ def setup } @credit_card = credit_card(fixtures(:commercegate)[:card_number]) - @expired_credit_card = credit_card(fixtures(:commercegate)[:card_number], year: Time.now.year-1) + @expired_credit_card = credit_card(fixtures(:commercegate)[:card_number], year: Time.now.year - 1) end def test_successful_authorize diff --git a/test/remote/gateways/remote_conekta_test.rb b/test/remote/gateways/remote_conekta_test.rb index c466b8ad74c..4f04fd19aa2 100644 --- a/test/remote/gateways/remote_conekta_test.rb +++ b/test/remote/gateways/remote_conekta_test.rb @@ -9,8 +9,8 @@ def setup @credit_card = ActiveMerchant::Billing::CreditCard.new( number: '4242424242424242', verification_value: '183', - month: '01', - year: '2019', + month: '12', + year: Date.today.year + 2, first_name: 'Mario F.', last_name: 'Moreno Reyes' ) @@ -25,7 +25,7 @@ def setup ) @options = { - :device_fingerprint => '41l9l92hjco6cuekf0c7dq68v4', + device_fingerprint: '41l9l92hjco6cuekf0c7dq68v4', description: 'Blue clip', billing_address: { address1: 'Rio Missisipi #123', @@ -34,18 +34,18 @@ def setup country: 'Mexico', zip: '5555', name: 'Mario Reyes', - phone: '12345678', + phone: '12345678' }, carrier: 'Estafeta', email: 'bob@something.com', line_items: [{ - name: 'Box of Cohiba S1s', - description: 'Imported From Mex.', - unit_price: 20000, - quantity: 1, - sku: '7500244909', - type: 'food' - }] + name: 'Box of Cohiba S1s', + description: 'Imported From Mex.', + unit_price: 20000, + quantity: 1, + sku: '7500244909', + type: 'food' + }] } end @@ -56,11 +56,16 @@ def test_successful_purchase end def test_successful_purchase_with_installments - assert response = @gateway.purchase(@amount * 300, @credit_card, @options.merge({monthly_installments: 3})) + assert response = @gateway.purchase(@amount * 300, @credit_card, @options.merge({ monthly_installments: 3 })) assert_success response assert_equal nil, response.message end + def test_unsuccessful_purchase_with_not_supported_currency + assert response = @gateway.purchase(8000, @credit_card, @options.merge({ currency: 'COP' })) + assert_equal 'At this time we process only Mexican pesos or U.S. dollars.', response.params['message'] + end + def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -143,7 +148,7 @@ def test_successful_purchase_passing_more_details city: 'Wanaque', state: 'NJ', country: 'USA', - zip: '01085', + zip: '01085' }, line_items: [ { @@ -172,12 +177,6 @@ def test_successful_purchase_passing_more_details assert_equal 'Guerrero', response.params['details']['billing_address']['city'] end - def test_failed_purchase_with_no_details - assert response = @gateway.purchase(@amount, @credit_card, {}) - assert_failure response - assert_equal 'Falta el correo del comprador.', response.message - end - def test_invalid_key gateway = ConektaGateway.new(key: 'invalid_token') assert response = gateway.purchase(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_creditcall_test.rb b/test/remote/gateways/remote_creditcall_test.rb index 527d60979a6..d7ed5a7d2fa 100644 --- a/test/remote/gateways/remote_creditcall_test.rb +++ b/test/remote/gateways/remote_creditcall_test.rb @@ -89,7 +89,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -112,7 +112,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb index 52f611fa200..30b2dddab3f 100644 --- a/test/remote/gateways/remote_credorax_test.rb +++ b/test/remote/gateways/remote_credorax_test.rb @@ -5,14 +5,106 @@ def setup @gateway = CredoraxGateway.new(fixtures(:credorax)) @amount = 100 - @credit_card = credit_card('4176661000001015', verification_value: '281', month: '12', year: '2022') - @declined_card = credit_card('4176661000001111', verification_value: '681', month: '12', year: '2022') + @adviser_amount = 1000001 + @credit_card = credit_card('4176661000001015', verification_value: '281', month: '12') + @fully_auth_card = credit_card('5223450000000007', brand: 'mastercard', verification_value: '090', month: '12') + @declined_card = credit_card('4176661000001111', verification_value: '681', month: '12') + @three_ds_card = credit_card('4761739000060016', verification_value: '212', month: '12') @options = { order_id: '1', currency: 'EUR', billing_address: address, description: 'Store Purchase' } + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + shipping_address: address(), + order_id: '123', + execute_threed: true, + three_ds_version: '2', + three_ds_challenge_window_size: '01', + three_ds_reqchallengeind: '04', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + notification_url: 'www.example.com', + browser_info: { + accept_header: 'unknown', + depth: 24, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } + } + + @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 + ) + + @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: '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 + response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_google_pay + response = @gateway.purchase(@amount, @google_pay_card, @options) + assert_success response + assert_equal '1', response.params['H9'] + 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) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@apple_pay_card.number, transcript) + assert_scrubbed(@apple_pay_card.payment_cryptogram, transcript) end def test_invalid_login @@ -28,6 +120,20 @@ def test_successful_purchase assert_equal 'Succeeded', response.message end + def test_successful_purchase_and_amount_for_non_decimal_currency + response = @gateway.purchase(14200, @credit_card, @options.merge(currency: 'JPY')) + assert_success response + assert_equal '142', response.params['A4'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_and_amount_for_isk + response = @gateway.purchase(14200, @credit_card, @options.merge(currency: 'ISK')) + assert_success response + assert_equal '142', response.params['A4'] + assert_equal 'Succeeded', response.message + end + def test_successful_purchase_with_extra_options response = @gateway.purchase(@amount, @credit_card, @options.merge(transaction_type: '10')) assert_success response @@ -35,12 +141,145 @@ def test_successful_purchase_with_extra_options assert_equal 'Succeeded', response.message end + def test_successful_purchase_with_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501', + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_auth_data_via_3ds1_fields_passing_3ds_version + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501', + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX', + three_ds_version: '1.0.2' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_auth_data_via_normalized_3ds1_options + version = '1.0.2' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + xid = '00000000000000000501' + + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + xid: xid + }, + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_3ds2_fields + options = @options.merge(@normalized_3ds_2_options) + response = @gateway.purchase(@amount, @three_ds_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_3ds_adviser + threeds_options = @options.merge(@normalized_3ds_2_options) + options = threeds_options.merge(three_ds_initiate: '03', f23: '1') + response = @gateway.purchase(@adviser_amount, @three_ds_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '01', response.params['SMART_3DS_RESULT'] + end + + def test_successful_moto_purchase + response = @gateway.purchase(@amount, @three_ds_card, @options.merge(metadata: { manual_entry: true })) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal '3', response.params['A2'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_auth_data_via_normalized_3ds2_options + version = '2.2.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + }, + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response assert_equal 'Transaction not allowed for cardholder', response.message end + def test_failed_purchase_invalid_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'this is not a valid xid, it will be rejected' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_failure response + assert_equal '-9', response.params['Z2'] + assert_match 'Parameter i8 is invalid', response.message + end + + def test_failed_purchase_invalid_auth_data_via_normalized_3ds2_options + version = '2.0' + eci = '02' + cavv = 'BOGUS;:' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_failure response + assert_equal '-9', response.params['Z2'] + assert_match 'malformed', response.message + end + def test_successful_authorize_and_capture response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -52,6 +291,51 @@ def test_successful_authorize_and_capture assert_equal 'Succeeded', capture.message end + def test_successful_authorize_with_authorization_details + options_with_auth_details = @options.merge({ authorization_type: '2', multiple_capture_count: '5' }) + response = @gateway.authorize(@amount, @credit_card, options_with_auth_details) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_authorize_with_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501', + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX' + ) + + response = @gateway.authorize(@amount, @fully_auth_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_authorize_with_auth_data_via_normalized_3ds2_options + version = '2.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + }, + # Having processor-specification enabled in Credorax test account causes 3DS tests to fail without a r1 (processor) parameter. + processor: 'CREDORAX' + ) + + response = @gateway.authorize(@amount, @fully_auth_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response @@ -115,6 +399,22 @@ def test_successful_refund assert_equal 'Succeeded', refund.message end + def test_successful_refund_with_recipient_fields + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund_options = { + recipient_street_address: 'street', + recipient_city: 'chicago', + recipient_province_code: '312', + recipient_country_code: 'USA' + } + + refund = @gateway.refund(@amount, response.authorization, refund_options) + assert_success refund + assert_equal 'Succeeded', refund.message + end + def test_successful_refund_and_void response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -134,13 +434,52 @@ def test_failed_refund assert_equal 'Referred to transaction has not been found.', response.message end + def test_successful_referral_cft + options = @options.merge(@normalized_3ds_2_options) + response = @gateway.purchase(@amount, @three_ds_card, options) + assert_success response + assert_equal 'Succeeded', response.message + + cft_options = { referral_cft: true, email: 'john.smith@test.com' } + referral_cft = @gateway.refund(@amount, response.authorization, cft_options) + assert_success referral_cft + assert_equal 'Succeeded', referral_cft.message + # Confirm that the operation code was `referral_cft` + assert_equal '34', referral_cft.params['O'] + end + + def test_successful_referral_cft_with_first_and_last_name + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + cft_options = { referral_cft: true, email: 'john.smith@test.com', first_name: 'John', last_name: 'Smith' } + referral_cft = @gateway.refund(@amount, response.authorization, cft_options) + assert_success referral_cft + assert_equal 'Succeeded', referral_cft.message + # Confirm that the operation code was `referral_cft` + assert_equal '34', referral_cft.params['O'] + end + + def test_failed_referral_cft + options = @options.merge(@normalized_3ds_2_options) + response = @gateway.purchase(@amount, @three_ds_card, options) + assert_success response + assert_equal 'Succeeded', response.message + + cft_options = { referral_cft: true, email: 'john.smith@test.com' } + referral_cft = @gateway.refund(@amount, '123;123;123', cft_options) + assert_failure referral_cft + assert_equal 'Referred to transaction has not been found.', referral_cft.message + end + def test_successful_credit - response = @gateway.credit(@amount, @credit_card, @options) + response = @gateway.credit(@amount, @credit_card, @options.merge(first_name: 'Test', last_name: 'McTest')) assert_success response assert_equal 'Succeeded', response.message end - def test_failed_credit + def test_failed_credit_with_zero_amount response = @gateway.credit(0, @declined_card, @options) assert_failure response assert_equal 'Invalid amount', response.message @@ -158,6 +497,107 @@ def test_failed_verify assert_equal 'Transaction not allowed for cardholder', response.message end + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '9', purchase.params['A9'] + assert network_transaction_id = purchase.params['Z13'] + + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_failed_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '1', purchase.params['A9'] + assert network_transaction_id = purchase.params['Z13'] + + used_options = stored_credential_options(:merchant, :recurring, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_failure purchase + assert_match 'Parameter g6 is invalid', purchase.message + end + + def test_successful_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '1', purchase.params['A9'] + assert initial_network_transaction_id = purchase.params['Z50'] + + used_options = stored_credential_options(:merchant, :recurring, id: initial_network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_cit + initial_options = stored_credential_options(:cardholder, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '9', purchase.params['A9'] + assert network_transaction_id = purchase.params['Z13'] + + used_options = stored_credential_options(:cardholder, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_mit + initial_options = stored_credential_options(:merchant, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '8', purchase.params['A9'] + 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) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_cit + initial_options = stored_credential_options(:cardholder, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '9', purchase.params['A9'] + assert network_transaction_id = purchase.params['Z13'] + + used_options = stored_credential_options(:cardholder, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_mit + initial_options = stored_credential_options(:merchant, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal '8', purchase.params['A9'] + 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) + assert_success purchase + end + + def test_authorize_and_capture_with_stored_credential + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert authorization = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success authorization + assert_equal '9', authorization.params['A9'] + assert network_transaction_id = authorization.params['Z13'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + + used_options = stored_credential_options(:cardholder, :recurring, id: network_transaction_id) + assert authorization = @gateway.authorize(@amount, @credit_card, used_options) + assert_success authorization + assert @gateway.capture(@amount, authorization.authorization) + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) @@ -165,7 +605,26 @@ def test_transcript_scrubbing clean_transcript = @gateway.scrub(transcript) assert_scrubbed(@credit_card.number, clean_transcript) - assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_cvv_scrubbed(clean_transcript) + end + + def test_purchase_passes_processor + # returns a successful response when a valid processor parameter is sent + assert good_response = @gateway.purchase(@amount, @credit_card, @options.merge(processor: 'CREDORAX')) + assert_success good_response + assert_equal 'Succeeded', good_response.message + assert_equal 'CREDORAX', good_response.params['Z33'] + + # returns a failed response when an invalid processor parameter is sent + assert bad_response = @gateway.purchase(@amount, @credit_card, @options.merge(processor: 'invalid')) + assert_failure bad_response + end + + def test_purchase_passes_d2_field + response = @gateway.purchase(@amount, @credit_card, @options.merge(echo: 'Echo Parameter')) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'Echo Parameter', response.params['D2'] end # ######################################################################### @@ -478,4 +937,15 @@ def test_transcript_scrubbing # assert_success void # assert_equal "Succeeded", void.message # end + + private + + def assert_cvv_scrubbed(transcript) + assert_match(/b5=\[FILTERED\]/, transcript) + end + + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id)) + end end diff --git a/test/remote/gateways/remote_ct_payment_certification_test.rb b/test/remote/gateways/remote_ct_payment_certification_test.rb index 7a4ef3988f4..2bc5d80aa16 100644 --- a/test/remote/gateways/remote_ct_payment_certification_test.rb +++ b/test/remote/gateways/remote_ct_payment_certification_test.rb @@ -239,5 +239,4 @@ def print_result(test_number, response) puts "Test #{test_number} | transaction number: #{response.params['transactionNumber']}, invoice number #{response.params['invoiceNumber']}, timestamp: #{response.params['timeStamp']}, result: #{response.params['returnCode']}" puts response.inspect end - end diff --git a/test/remote/gateways/remote_ct_payment_test.rb b/test/remote/gateways/remote_ct_payment_test.rb index 4fd0ba8738a..164475a5db2 100644 --- a/test/remote/gateways/remote_ct_payment_test.rb +++ b/test/remote/gateways/remote_ct_payment_test.rb @@ -5,7 +5,7 @@ def setup @gateway = CtPaymentGateway.new(fixtures(:ct_payment)) @amount = 100 - @credit_card = credit_card('4501161107217214', month: '07', year: 2020) + @credit_card = credit_card('4501161107217214') @declined_card = credit_card('4502244713161718') @options = { billing_address: address, @@ -47,7 +47,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11])) assert_success capture end @@ -70,7 +70,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11])) assert_success refund end @@ -169,5 +169,4 @@ def test_transcript_scrubbing_store assert_scrubbed(Base64.strict_encode64(@credit_card.number), transcript) assert_scrubbed(@gateway.options[:api_key], transcript) end - end diff --git a/test/remote/gateways/remote_culqi_test.rb b/test/remote/gateways/remote_culqi_test.rb index d8241b2aef3..c4c1fb7131a 100644 --- a/test/remote/gateways/remote_culqi_test.rb +++ b/test/remote/gateways/remote_culqi_test.rb @@ -58,7 +58,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture assert_match %r{Transaction has been successfully captured}, capture.message end @@ -97,7 +97,7 @@ def test_partial_refund auth = @gateway.authorize(@amount, @credit_card, @options) capture = @gateway.capture(@amount, auth.authorization) - refund = @gateway.refund(@amount-1, capture.authorization) + refund = @gateway.refund(@amount - 1, capture.authorization) assert_success refund assert_match %r{reversed}, refund.message end 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..bca3e7499e7 --- /dev/null +++ b/test/remote/gateways/remote_cyber_source_rest_test.rb @@ -0,0 +1,515 @@ +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') + + @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_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) + options[:reason_code] = '4' + 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_recurring_cit + options = stored_credential_options(:cardholder, :recurring, :initial) + options[:reason_code] = '4' + assert auth = @gateway.authorize(@amount, @visa_card, options) + assert_success auth + used_store_credentials = stored_credential_options(:cardholder, :recurring, ntid: auth.network_transaction_id) + used_store_credentials[:reason_code] = '4' + 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) + options[:reason_code] = '4' + assert auth = @gateway.authorize(@amount, @visa_card, options) + assert_success auth + used_store_credentials = stored_credential_options(:merchant, :recurring, ntid: auth.network_transaction_id) + used_store_credentials[:reason_code] = '4' + assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_cit + options = stored_credential_options(:cardholder, :installment, :initial) + options[:reason_code] = '4' + assert auth = @gateway.authorize(@amount, @visa_card, options) + assert_success auth + used_store_credentials = stored_credential_options(:cardholder, :installment, ntid: auth.network_transaction_id) + used_store_credentials[:reason_code] = '4' + assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_mit + options = stored_credential_options(:merchant, :installment, :initial) + options[:reason_code] = '4' + assert auth = @gateway.authorize(@amount, @visa_card, options) + assert_success auth + used_store_credentials = stored_credential_options(:merchant, :installment, ntid: auth.network_transaction_id) + used_store_credentials[:reason_code] = '4' + assert purchase = @gateway.purchase(@amount, @visa_card, used_store_credentials) + assert_success purchase + end + + def test_failure_stored_credential_invalid_reason_code + options = stored_credential_options(:cardholder, :internet, :initial) + assert auth = @gateway.authorize(@amount, @master_card, options) + assert_equal(auth.params['status'], 'INVALID_REQUEST') + assert_equal(auth.params['message'], 'Declined - One or more fields in the request contains invalid data') + assert_equal(auth.params['details'].first['field'], 'processingInformation.authorizationOptions.initiator.merchantInitiatedTransaction.reason') + end + + def test_auth_and_purchase_with_network_txn_id + options = stored_credential_options(:merchant, :recurring, :initial) + options[:reason_code] = '4' + assert auth = @gateway.authorize(@amount, @visa_card, options) + 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_purchase_in_australian_dollars + @options[:currency] = 'AUD' + response = @gateway.purchase(@amount, @visa_card, @options) + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + assert_nil response.params['_links']['capture'] + assert_equal 'AUD', response.params['orderInformation']['amountDetails']['currency'] + end +end diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb index eca6db9a23e..7f235ad910a 100644 --- a/test/remote/gateways/remote_cyber_source_test.rb +++ b/test/remote/gateways/remote_cyber_source_test.rb @@ -1,68 +1,121 @@ require 'test_helper' class RemoteCyberSourceTest < Test::Unit::TestCase + # Reduce code duplication: use `assert_successful_response` when feasible! def setup Base.mode = :test - @gateway = CyberSourceGateway.new({nexus: 'NC'}.merge(fixtures(:cyber_source))) + @gateway = CyberSourceGateway.new({ nexus: 'NC' }.merge(fixtures(:cyber_source))) + @gateway_latam = CyberSourceGateway.new({}.merge(fixtures(:cyber_source_latam_pe))) - @credit_card = credit_card('4111111111111111', verification_value: '321') + @credit_card = credit_card('4111111111111111', verification_value: '987') @declined_card = credit_card('801111111111111') + @master_credit_card = credit_card( + '5555555555554444', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :master + ) @pinless_debit_card = credit_card('4002269999999999') - @three_ds_unenrolled_card = credit_card('4000000000000051', + @elo_credit_card = credit_card( + '5067310000000010', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :elo + ) + @three_ds_unenrolled_card = credit_card( + '4000000000000051', verification_value: '321', month: '12', year: (Time.now.year + 2).to_s, brand: :visa ) - @three_ds_enrolled_card = credit_card('4000000000000002', + @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', + @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', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :master + ) + @visa_network_token = network_tokenization_credit_card( + '4111111111111111', + brand: 'visa', + eci: '05', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + @amex_network_token = network_tokenization_credit_card( + '378282246310005', + brand: 'american_express', + eci: '05', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) @amount = 100 @options = { - :order_id => generate_unique_id, - :line_items => [ - { - :declared_value => 100, - :quantity => 2, - :code => 'default', - :description => 'Giant Walrus', - :sku => 'WA323232323232323' - }, + order_id: generate_unique_id, + line_items: [ { - :declared_value => 100, - :quantity => 2, - :description => 'Marble Snowcone', - :sku => 'FAKE1232132113123' + declared_value: 100, + quantity: 2, + code: 'default', + description: 'Giant Walrus', + sku: 'WA323232323232323', + tax_amount: 10, + national_tax: 5 } ], - :currency => 'USD', - :ignore_avs => 'true', - :ignore_cvv => 'true' + currency: 'USD', + ignore_avs: 'true', + ignore_cvv: 'true', + commerce_indicator: 'internet', + user_po: 'ABC123', + taxable: true, + sales_slip_number: '456', + airline_agent_code: '7Q', + tax_management_indicator: 1, + invoice_amount: '3', + original_amount: '4', + reference_data_code: 'ABC123', + invoice_number: '123', + mobile_remote_payment_type: 'A1', + vat_tax_rate: '1' + } + + @capture_options = { + gratuity_amount: '3.50' } @subscription_options = { - :order_id => generate_unique_id, - :credit_card => @credit_card, - :subscription => { - :frequency => 'weekly', - :start_date => Date.today.next_week, - :occurrences => 4, - :auto_renew => true, - :amount => 100 + order_id: generate_unique_id, + credit_card: @credit_card, + subscription: { + frequency: 'weekly', + start_date: Date.today.next_week, + occurrences: 4, + auto_renew: true, + amount: 100 } } + + @issuer_additional_data = 'PR25000000000011111111111112222222sk111111111111111111111111111' + + '1111111115555555222233101abcdefghijkl7777777777777777777777777promotionCde' end def test_transcript_scrubbing @@ -77,114 +130,460 @@ 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 def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_authorization_with_reconciliation_id + options = @options.merge(reconciliation_id: '1936831') + 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) + assert_successful_response(response) + assert !response.authorization.blank? + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_authorize_with_solution_id_and_stored_creds + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + assert !response.authorization.blank? + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_authorization_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) assert !response.authorization.blank? end + def test_successful_authorization_with_issuer_additional_data_and_partner_solution_id + @options[:issuer_additional_data] = @issuer_additional_data + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + assert void = @gateway.void(auth.authorization, @options) + assert_successful_response(void) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_authorize_with_merchant_descriptor_and_partner_solution_id + @options[:merchant_descriptor] = 'Spreedly' + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + assert void = @gateway.void(auth.authorization, @options) + assert_successful_response(void) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_authorize_with_issuer_additional_data_stored_creds_merchant_desc_and_partner_solution_id + @options[:issuer_additional_data] = @issuer_additional_data + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + @options[:merchant_descriptor] = 'Spreedly' + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_authorization_with_elo + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_authorization_with_installment_data + options = @options.merge( + installment_total_count: 2, + installment_total_amount: 0.50, + installment_plan_type: 1, + first_installment_date: '300101', + installment_annual_interest_rate: 1.09, + installment_grace_period_duration: 1 + ) + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_authorization_with_less_installment_data + options = @options.merge(installment_grace_period_duration: '1') + + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_authorization_with_merchant_tax_id + options = @options.merge(merchant_tax_id: '123') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_authorization_with_sales_slip_number + options = @options.merge(sales_slip_number: '456') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_authorization_with_airline_agent_code + options = @options.merge(airline_agent_code: '7Q') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_authorization_with_tax_mgmt_indicator + options = @options.merge(tax_management_indicator: '3') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_bank_account_purchase_with_sec_code + options = @options.merge(sec_code: 'WEB') + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.purchase(@amount, bank_account, options) + assert_successful_response(response) + end + def test_unsuccessful_authorization assert response = @gateway.authorize(@amount, @declined_card, @options) assert response.test? assert_equal 'Invalid account number', response.message - assert_equal false, response.success? + assert_equal false, response.success? + end + + def test_purchase_and_void + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + assert void = @gateway.void(purchase.authorization, @options) + assert_successful_response(void) + end + + # Note: This test will only pass with test account credentials which + # have asynchronous adjustments enabled. + def test_successful_asynchronous_adjust + assert authorize = @gateway_latam.authorize(@amount, @credit_card, @options) + assert_successful_response(authorize) + assert adjust = @gateway_latam.adjust(@amount * 2, authorize.authorization, @options) + assert_success adjust + assert capture = @gateway_latam.capture(@amount, authorize.authorization, @options.merge({ national_tax_indicator: 1 })) + assert_successful_response(capture) end def test_authorize_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth + assert_successful_response(auth) assert void = @gateway.void(auth.authorization, @options) - assert_equal 'Successful transaction', void.message - assert_success void - assert void.test? + assert_successful_response(void) end def test_capture_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert capture = @gateway.capture(@amount, auth.authorization, @options) - assert_success capture + assert_successful_response(auth) + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge({ national_tax_indicator: 1 })) + assert_successful_response(capture) + assert void = @gateway.void(capture.authorization, @options) + assert_successful_response(void) + end + + def test_capture_and_void_with_elo + assert auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_successful_response(auth) + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge({ national_tax_indicator: 1 })) + assert_successful_response(capture) assert void = @gateway.void(capture.authorization, @options) - assert_equal 'Successful transaction', void.message - assert_success void - assert void.test? + assert_successful_response(void) + end + + def test_void_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + assert void = @gateway.void(auth.authorization, @options) + assert_successful_response(void) + end + + def test_void_with_mdd_fields + (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + assert void = @gateway.void(auth.authorization, @options) + assert_successful_response(void) + end + + def test_successful_void_with_solution_id + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + assert void = @gateway.void(auth.authorization, @options) + assert_successful_response(void) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil end def test_successful_tax_calculation assert response = @gateway.calculate_tax(@credit_card, @options) - assert_equal 'Successful transaction', response.message assert response.params['totalTaxAmount'] assert_not_equal '0', response.params['totalTaxAmount'] - assert_success response + assert_successful_response(response) end def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + end + + def test_successful_purchase_with_bank_account + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.purchase(10000, bank_account, @options) + 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) + assert_successful_response(response) + end + + def test_unsuccessful_purchase_with_bank_account_card_declined + bank_account = check({ account_number: '4201', routing_number: '011000015' }) + assert response = @gateway.purchase(10000, bank_account, @options) + assert_failure response + assert_equal 'General decline by the processor', response.message + end + + def test_unsuccessful_purchase_with_bank_account_merchant_configuration + bank_account = check({ account_number: '4241', routing_number: '011000015' }) + assert response = @gateway.purchase(10000, bank_account, @options) + assert_failure response + assert_equal 'A problem exists with your CyberSource merchant configuration', response.message + end + + def test_successful_purchase_with_national_tax_indicator + assert purchase = @gateway.purchase(@amount, @credit_card, @options.merge(national_tax_indicator: 1)) + assert_successful_response(purchase) + end + + def test_successful_purchase_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_purchase_with_merchant_descriptor + @options[:merchant_descriptor] = 'Spreedly' + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_purchase_with_issuer_additional_data_and_partner_solution_id + @options[:issuer_additional_data] = @issuer_additional_data + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_purchase_with_merchant_descriptor_and_partner_solution_id + @options[:merchant_descriptor] = 'Spreedly' + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_purchase_with_issuer_additional_data_stored_creds_merchant_desc_and_partner_solution_id + @options[:issuer_additional_data] = @issuer_additional_data + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + @options[:merchant_descriptor] = 'Spreedly' + + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_purchase_with_reconciliation_id + options = @options.merge(reconciliation_id: '1936831') + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_authorize_with_customer_id + options = @options.merge(customer_id: '7500BB199B4270EFE05348D0AFCAD') + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_authorize_with_national_tax_indicator + assert authorize = @gateway.authorize(@amount, @credit_card, @options.merge(national_tax_indicator: 1)) + assert_successful_response(authorize) + end + + def test_successful_purchase_with_customer_id + options = @options.merge(customer_id: '7500BB199B4270EFE00588D0AFCAD') + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_successful_response(response) + end + + def test_successful_purchase_with_elo + assert response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_successful_response(response) end def test_successful_purchase_sans_options assert response = @gateway.purchase(@amount, @credit_card) assert_equal 'Successful transaction', response.message - assert_success response + assert_successful_response(response) end def test_successful_purchase_with_billing_address_override - @options[:billing_address] = address + billing_address = { + address1: '111 North Pole Lane', + city: 'Santaland', + state: '', + phone: nil + } + @options[:billing_address] = billing_address @options[:email] = 'override@example.com' assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response + assert_equal true, response.success? + assert_successful_response(response) end def test_successful_purchase_with_long_country_name @options[:billing_address] = address(country: 'united states', state: 'NC') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response + assert_successful_response(response) end def test_successful_purchase_without_decision_manager @options[:decision_manager_enabled] = 'false' assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) end def test_successful_purchase_with_decision_manager_profile @options[:decision_manager_enabled] = 'true' @options[:decision_manager_profile] = 'Regular' assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) end - def test_successful_pinless_debit_card_puchase - assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true)) - assert_equal 'Successful transaction', response.message - assert_success response + def test_successful_purchase_with_solution_id + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + assert !response.authorization.blank? + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_purchase_with_solution_id_and_stored_creds + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + assert !response.authorization.blank? + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_purchase_with_country_submitted_as_empty_string + @options[:billing_address] = { country: '' } + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) end def test_unsuccessful_purchase @@ -195,140 +594,342 @@ def test_unsuccessful_purchase def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert_equal 'Successful transaction', auth.message + assert_successful_response(auth) + + assert capture = @gateway.capture(@amount, auth.authorization, @capture_options) + assert_successful_response(capture) + end + + def test_authorize_and_capture_with_elo + assert auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_successful_response(auth) assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture + assert_successful_response(capture) + end + + def test_successful_capture_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + assert response = @gateway.capture(@amount, auth.authorization) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_capture_with_solution_id + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + assert response = @gateway.capture(@amount, auth.authorization, @options.merge({ national_tax_indicator: 1 })) + assert_successful_response(response) + assert !response.authorization.blank? + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil end def test_successful_authorization_and_failed_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert_equal 'Successful transaction', auth.message + assert_successful_response(auth) - assert capture = @gateway.capture(@amount + 10, auth.authorization, @options) + assert capture = @gateway.capture(@amount + 100000000, auth.authorization, @options.merge({ national_tax_indicator: 1 })) assert_failure capture - assert_equal 'The requested amount exceeds the originally authorized amount', capture.message + assert_equal 'One or more fields contains invalid data: (Amount limit)', capture.message end def test_failed_capture_bad_auth_info assert @gateway.authorize(@amount, @credit_card, @options) - assert capture = @gateway.capture(@amount, 'a;b;c', @options) + assert capture = @gateway.capture(@amount, 'a;b;c', @options.merge({ national_tax_indicator: 1 })) assert_failure capture end def test_invalid_login - gateway = CyberSourceGateway.new(:login => 'asdf', :password => 'qwer') + gateway = CyberSourceGateway.new(login: 'asdf', password: 'qwer') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal "wsse:FailedCheck: \nSecurity Data : UsernameToken authentication failed.\n", response.message end + # Unable to test refunds for Elo cards, as the test account is setup to have + # Elo transactions routed to Comercio Latino which has very specific rules on + # refunds (i.e. that you cannot do a "Stand-Alone" refund). This means we need + # to go through a Capture cycle at least a day before submitting a refund. def test_successful_refund assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + assert response = @gateway.refund(@amount, response.authorization) - assert_equal 'Successful transaction', response.message - assert_success response + assert_successful_response(response) end - def test_successful_validate_pinless_debit_card - assert response = @gateway.validate_pinless_debit_card(@pinless_debit_card, @options) - assert response.test? - assert_equal 'Y', response.params['status'] - assert_equal true, response.success? + def test_successful_refund_with_solution_id + ActiveMerchant::Billing::CyberSourceGateway.application_id = 'A1000000' + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(purchase) + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_successful_response(refund) + ensure + ActiveMerchant::Billing::CyberSourceGateway.application_id = nil + end + + def test_successful_refund_with_bank_account_follow_on + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.purchase(10000, bank_account, @options) + assert_successful_response(response) + + assert response = @gateway.refund(10000, response.authorization, @options) + assert_successful_response(response) 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, @visa_network_token, @options) + assert_successful_response(auth) + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_successful_response(capture) + end + + def test_network_tokenization_with_amex_cc_and_basic_cryptogram + assert auth = @gateway.authorize(@amount, @amex_network_token, @options) + assert_successful_response(auth) + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_successful_response(capture) + end + + 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', + brand: 'american_express', + eci: '05', + payment_cryptogram: long_cryptogram ) assert auth = @gateway.authorize(@amount, credit_card, @options) - assert_success auth - assert_equal 'Successful transaction', auth.message + assert_successful_response(auth) assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture + assert_successful_response(capture) + end + + def test_purchase_with_network_tokenization_with_amex_cc + 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_authorize_with_mdd_fields (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response + assert_successful_response(response) end def test_successful_purchase_with_mdd_fields (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response + assert_successful_response(response) + end + + def test_successful_capture_with_mdd_fields + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge({ national_tax_indicator: 1 })) + assert_successful_response(capture) + end + + # this test should probably be removed, the fields do not appear to be part of the + # most current XSD file, also they are not added to the request correctly as top level fields + def test_merchant_description + merchant_options = { + merchantInformation: { + merchantDescriptor: { + name: 'Test Name', + address1: '123 Main Dr', + locality: 'Durham' + } + } + } + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(merchant_options)) + assert_successful_response(response) + end + + def test_successful_capture_with_tax + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(auth) + + capture_options = @options.merge(local_tax_amount: '0.17', national_tax_amount: '0.05', national_tax_indicator: 1) + assert capture = @gateway.capture(@amount, auth.authorization, capture_options) + assert_successful_response(capture) end def test_successful_authorize_with_nonfractional_currency - assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'JPY')) + assert response = @gateway.authorize(100, @credit_card, @options.merge(currency: 'JPY')) assert_equal '1', response.params['amount'] - assert_success response + assert_successful_response(response) + end + + def test_successful_authorize_with_additional_purchase_totals_data + assert response = @gateway.authorize(100, @credit_card, @options.merge(discount_management_indicator: 'T', purchase_tax_amount: 7.89)) + assert_successful_response(response) end def test_successful_subscription_authorization assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) - assert response = @gateway.authorize(@amount, response.authorization, :order_id => generate_unique_id) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert response = @gateway.authorize(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_subscription_authorization_with_bank_account + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.store(bank_account, order_id: generate_unique_id) + assert_successful_response(response) + + assert response = @gateway.purchase(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) assert !response.authorization.blank? end def test_successful_subscription_purchase assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) - assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id) - assert_equal 'Successful transaction', response.message - assert_success response + assert response = @gateway.purchase(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) + end + + def test_successful_subscription_purchase_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.purchase(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) + end + + def test_successful_standalone_credit_to_card + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_standalone_credit_to_card_with_merchant_descriptor + @options[:merchant_descriptor] = 'Spreedly' + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_standalone_credit_to_card_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_standalone_credit_to_card_with_mdd_fields + (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_failed_standalone_credit_to_card + assert response = @gateway.credit(@amount, @declined_card, @options) + + assert_equal 'Invalid account number', response.message + assert_failure response assert response.test? end - def test_successful_subscription_credit + def test_successful_standalone_credit_to_subscription assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) - assert response = @gateway.credit(@amount, response.authorization, :order_id => generate_unique_id) + assert response = @gateway.credit(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) + end - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + def test_successful_standalone_credit_to_subscription_with_merchant_descriptor + @subscription_options[:merchant_descriptor] = 'Spreedly' + assert response = @gateway.store(@credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.credit(@amount, response.authorization, order_id: generate_unique_id) + assert_successful_response(response) + end + + def test_successful_credit_with_bank_account + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.credit(10000, bank_account, order_id: generate_unique_id) + + assert_successful_response(response) end def test_successful_create_subscription assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + assert_equal 'credit_card', response.authorization.split(';')[7] + end + + def test_successful_create_subscription_with_bank_account + bank_account = check({ account_number: '4100', routing_number: '011000015' }) + assert response = @gateway.store(bank_account, @subscription_options) + assert_successful_response(response) + assert_equal 'check', response.authorization.split(';')[7] + end + + def test_successful_create_subscription_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert_successful_response(response) end def test_successful_create_subscription_with_setup_fee - assert response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 100)) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert response = @gateway.store(@credit_card, @subscription_options.merge(setup_fee: 100)) + assert_successful_response(response) end def test_successful_create_subscription_with_monthly_options - response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 99.0, :subscription => {:amount => 49.0, :automatic_renew => false, frequency: 'monthly'})) + response = @gateway.store(@credit_card, @subscription_options.merge(setup_fee: 99.0, subscription: { amount: 49.0, automatic_renew: false, frequency: 'monthly' })) assert_equal 'Successful transaction', response.message response = @gateway.retrieve(response.authorization, order_id: @subscription_options[:order_id]) assert_equal '0.49', response.params['recurringAmount'] @@ -337,27 +938,23 @@ def test_successful_create_subscription_with_monthly_options def test_successful_update_subscription_creditcard assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) - assert response = @gateway.update(response.authorization, @credit_card, {:order_id => generate_unique_id, :setup_fee => 100}) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert response = @gateway.update(response.authorization, @credit_card, { order_id: generate_unique_id, setup_fee: 100 }) + assert_successful_response(response) end def test_successful_update_subscription_billing_address assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) - assert response = @gateway.update(response.authorization, nil, - {:order_id => generate_unique_id, :setup_fee => 100, billing_address: address, email: 'someguy1232@fakeemail.net'}) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert 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 def test_successful_delete_subscription @@ -365,7 +962,17 @@ def test_successful_delete_subscription assert response.success? assert response.test? - assert response = @gateway.unstore(response.authorization, :order_id => generate_unique_id) + assert response = @gateway.unstore(response.authorization, order_id: generate_unique_id) + assert response.success? + assert response.test? + end + + def test_successful_delete_subscription_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert response.success? + assert response.test? + + assert response = @gateway.unstore(response.authorization, order_id: generate_unique_id) assert response.success? assert response.test? end @@ -375,7 +982,7 @@ def test_successful_retrieve_subscription assert response.success? assert response.test? - assert response = @gateway.retrieve(response.authorization, :order_id => generate_unique_id) + assert response = @gateway.retrieve(response.authorization, order_id: generate_unique_id) assert response.success? assert response.test? end @@ -406,6 +1013,22 @@ def test_successful_3ds_requests_with_unenrolled_card assert response.success? end + # to create a valid pares, use the test credentials to request `test_3ds_enroll_request_via_purchase` with debug=true. + # Extract this XML and generate an accessToken. Using this access token to create a form, visit the stepUpURL provided + # and check the network exchange in the browser dev console for a CCA, which will contain a usable PaRes. Documentation for this feature + # can be found at https://docs.cybersource.com/content/dam/new-documentation/documentation/en/fraud-management/payer-auth/so/payer-auth-so.pdf + # Version => September 2017 + # Chapter 2 "Authenticating Enrolled Cards" page 27 + # something like: + # + # + #
+ # + # + # + #
+ # + # def test_successful_3ds_validate_purchase_request assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @options.merge(payer_auth_validate_service: true, pares: pares)) assert_equal '100', response.params['reasonCode'] @@ -432,12 +1055,234 @@ def test_failed_3ds_validate_authorize_request assert !response.success? end + def test_successful_authorize_via_normalized_3ds2_fields + options = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 1, + enrolled: 'Y', + authentication_response_status: 'Y' + }, + commerce_indicator: 'vbv' + ) + + response = @gateway.authorize(@amount, @three_ds_enrolled_card, options) + assert_successful_response(response) + end + + def test_successful_mastercard_authorize_via_normalized_3ds2_fields + options = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC' + }, + commerce_indicator: 'spa', + collection_indicator: 2 + ) + + response = @gateway.authorize(@amount, @three_ds_enrolled_mastercard, options) + assert_successful_response(response) + end + + def test_successful_purchase_via_normalized_3ds2_fields + options = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC' + } + ) + + response = @gateway.purchase(@amount, @three_ds_enrolled_card, options) + assert_successful_response(response) + end + + def test_successful_mastercard_purchase_via_normalized_3ds2_fields + options = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC' + }, + commerce_indicator: 'spa', + collection_indicator: 2 + ) + + response = @gateway.purchase(@amount, @three_ds_enrolled_mastercard, options) + assert_successful_response(response) + end + + def test_successful_first_cof_authorize + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_subsequent_unscheduled_cof_authorize + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'unscheduled', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_recurring_cof_authorize + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'recurring', + initial_transaction: false, + network_transaction_id: '' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_subsequent_recurring_cof_authorize + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'recurring', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_subsequent_installment_cof_authorize + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'installment', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_subsequent_unscheduled_cof_purchase + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'unscheduled', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + assert response = @gateway.purchase(@amount, @credit_card, @options) + 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: { + address1: 'Unspecified', + city: 'Unspecified', + state: 'NC', + zip: '1234567890', + country: 'US' + } + }) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'One or more fields contains invalid data: c:billTo/c:postalCode', response.message + end + def pares - <<-PARES -eNqdmFuTqkgSgN+N8D90zD46M4B3J+yOKO6goNyFN25yEUHkUsiv31K7T/ec6dg9u75YlWRlZVVmflWw1uNrGNJa6DfX8G0thVXlRuFLErz+tgm67sRlbJr3ky4G9LWn8N/e1nughtVD4dFawFAodT8OqbBx4NLdj/o8y3JqKlavSLsNr1VS5G/En/if4zX20UUTXf3Yzeu3teuXpCC/TeerMTFfY+/d9Tm8CvRbEB7dJqvX2LO7xj7H7Zt7q0JOd0nwpo3VacjVvMc4pZcXfcjFpMqLc6UHr2vsrrEO3Dp8G+P4Ap+PZy/E9C+c+AtfrrGHfH25mwPnokG2CRxfY18Fa7Q71zD3b2/LKXr0o7cOu0uRh0gDre1He419+nZx8zf87z+kepeu9cPbuk7OX31a3X0iFmvsIV9XtVs31Zu9xt5ba99t2zcAAAksNjsr4N5MVctyGIaN2H6E1vpQWYd+8obPkFPo/zEKZFFxTer4fHf174I1dncFe4Tzba0lUY4mu4Yv3TnLURDjur78hWEQwj/h5M/iGmHIYRzDVxhSCKok+tdvz1FhIOTH4n8aRrl5kSe+myW9W6PEkMI6LoKXH759Z0ZX75YITGWoP5CpP3ximv9xl+ATYoZsYt8b/bKyX5nlZ2evlftHFbvEfYKfDL2t1fAY3jMifDFU4fW3f/1KZdBJFFb1/+PKhxtfLXzYM92sCd8qN5U5lrrNDZOFzkiecUIszvyCVJjXj3FPzTX2w/f3hT2j+GW3noobXm8xXJ3KK2aZztNbVdsLWbbOASZgzSY45eYqFNiK5ReRNLKbzvZSIDJj+zqBzIEkIx1L9ZTabYeDJa/MV51fF9A0dxDvxzf5CiPmttuVVBLHxmZSNp53lnBcJzh+IS3YpejKebycHjQlvMggwkvHdZjhYBHf8M1R4ikKjHxMGxlCfuCv+IqmxjTRk9GMnO2ynnXsWMvZSYdlk+Vmvpz1pVns4v05ugRWIGZNMhxUGLzoqs+VDe14Jtzli63TT06WBvpJg2+2UVLie+5mgGDlEVjip+7EmZhCvRdndtQHmKm0vaUDejhYTRgglbR5qysx6I1gf+vTyWJ3ahaXNOWBUrXRYnwasbKlbi3XsJLNuA3g6+uXrHqPzCa8PSNxmKElubX7bGmNl4Z+LbuIEJT8SrnXIMnd7IUOz8XLI4DX3192xucDQGlI8NmnijOiqR/+/rJ9lRCvCqSv6a+7OCl+f6FeDW2N/TzPY2IqvNbJEdUVwqUkCLTVo32vtAhAgQSRQAFNgLRii5vCEeLWl4HCsKQCoJMyWwmcOEAYDBlLlGlKHa2DLRnJ5nCAhkoksypca9nxKfDvUhIUEmvIsX9WL96ZrZTxqvYs82aPjQi1bz7NaBIJHhYpCEXplJ2GA8ea4a7lXCRVgUxk06ai0DSoDecg4wIvE3ZC0ooOQhbinUQzNyn1OzkFM5kWXSS7PWVKNxx8SCV+2VE9EJ8+2TrITF1ScEjBh3WBgere5bJWUpb3ld9lPAMd+e6JNxGQJS4F9vuKdObLigRGbj2LyPyznEmqAZmnxS0DO9o+iCfXmsUeRZIKIXW8Djy0Tw8rks4yX62omWctI2Oc5d7ZvKGokEIKZDI6lfEp4VYQJ+9RAGBHAWUJ7s+HAyraoB4DSmYSEIl4LuOMDMYCIZJ71pj7U99OwbapLHXFMLI66s7eKosO9qmWU56LwmJCul2tccin+XTKE4tV7EatfZaSNCQFH9bYXMNCetuoK2kl0SN6An3f3xmIMwGIT8KlZZS5pV/wpTIz8FzIF9fhIK6EhVLuzEDAg4MI+sybxjVzA/TGuEmsEHDZbZFBtjKxdKfgilSRZDLRoGjQmpWlzUEZGeJ+7CK6jCNPPgQe2ZInYsxH5YEWZoId7i5G2RJNax3USyCJo1OXS/jNLKdCtZiMSaCR4jKPaXvXqjl/6Et+OMBDRoth7MfSnLa3o7ItpxyV8CZcmjrVbJtyWykIypti158qotvx1VkJTm48GzeYBAUaKIAsJhUcDkL9mUO8KjEgBUCiIEdZFKcBjhsxAkpL5cjGxN7nzMYgZElgguweT/ugZg5F0s5BfGT2cGCPWdzRQfCwpkzRoa8YasSpRuIhBMUdRVxBGyn1FouIkytA/p5XKp4iAEO2AMZRSKQkIPDhgLC0ZSKTIV5IsXXC55ue+a566chmgKyLBwZfHlr7igWzo4Dn4m63WjXm3kMV3G7GNc3KJz9Ur5pt1AxBnafhdFf03bi2pnQlT8pZhWNWN7Mu+6RtWe/I6AbUz1wcFd6puR7FdrSYDwcYP5lcIsJ0ZNh7zOxcqcSFOjoUhaui645OzZ5qHGeazOnrqlxJ1+2eSJtTNOo7bBrgyvIanQyHuh9xP/PqO4BROI0Alp6/AOzbLYAh/asAo/t78d0L1ZdQ/mVerrZ+yoQSCZ+wiqCpjNmbw2WNbXW0NyZqFNzU0Uh0dHgTEUqqABnwhAENTjfNUu9WLs751LE60N8xINGsmvkTJTLOqzag/g624UDS72hjelmXP9GmKz9kEmf/R7DR4Ak2ZEmdQv7pz4YmzU84fQHYHWZ+DjomBcrTYiVRuig6KJ1R5Z5dhD5kiRQeewAg3Jqc2SOv+8ASIgVnYOQsf9558pl8OIIWJ4KCQ4u+QWKmIqgK7g5MOZ+0XJ4jemPuucVRUPf5rma5LL6U7RxuXQ4ax+NodrIvC4k53wRDanhGdkGrnhJRq2/UajccHM67ebQItvRyk3PEnFrl1y5dFuT0PEFYMqbn0dG2dlx+js/7Yt7HZFuSVXvsV5OYiTYHec4EG7kxo+GgKfvamoPtDhry3CPLjaJN7okBAJeGPTl7z5+AgQolAQC3wBZtwRGA7U2ViJFJcmnxxgo+jjHdwGGkjs0G5UYccOYJ7XDmP7IgS+9QkEj8YY2OFIsk1WUi3MTJQTed7U3A2YUW3Vh3OND14irp4PiAhSYxHA2siFSZKN1jhOVFme2MOa7LKcst80SEKId+OjqM+9GBjoxIIZfNxsBWkyVmbmYUa4iJghm7gzu+8jeiAxMvJwhiR80zcl4FSr2Q01jx442ebHWlimZHrNQymRgOto7dtFMgbPTdxmG4ayKWQJ+Lp3K0OcQ1rU2jtLyw+XKXOqWoLo7ulVFHgTebYaLWXho+Sr1OPy7AcHCGCar/njbEqWk2ib1Z6iWb3cbm1eTZ6PVXIdCmCAJJ+AEBEYh0tx8xmanGGwngHKWVnCZ4E/qRkgaQ+OgfpYOS+5vi+XoroMHnreA/3XIQBP7LPefzlvPj1oBuOd3zlsOKrYegcC+p4YCPfRmFv5NSZiLpNpR1cLPusvQhw3/IUnIqKRWknr5yDBRNo2dkCVSPmdGNAUBGH8cXr2f29z15gBBCTrfuBb66/SokhoP/gglTIqUPSEjvkNC88QpHo0kEguNHRIaDj5igJAWIBjKgKTJRNmSkUNPwevRaVWGow9Vezev9QtlZJaWDcZpjs3SywiKsxD0p8RVKHQ6u49ExWZz6zY28KaVz4ntbnC0nGDi0G9GFeM2id5cJkwbRKezMS2ZrYcnsZzuDlqaRqx0XJS9F5h6VycYt8nF7TfnOCimzY5NpNyWLIBPzY4ZhNZdu8FKm+3pxwqZyqLHWzSsT5f2mQACop8+THcXu42wXhB5bmeepaHFBHFcOzM7lZZr4DPOPs/073eHgQ5sGD22dBAZE4SSx/vtijxSQsEuSy0gWSqEshkxiw9xVEJhqg78mbmrU3nxGzJe1fLxwDDO59rxHzgrpzPiHrvK8WlDJpo33y3MdhU7GZ81W6fFSHfnjYpbBcDjo4CLNjoAvSxRlLaU2W76plphc5At/tEhKra8VXiLN0FuM59Ddt5zgHZitL1vFyttHamkZ44sToxvD5ubwK/BtsWOfr03Yj1epz5esx7ekx8eu+/ePrx/B/g0UAjN8 + <<~PARES + eNrdWVnPqsjWvn8T/8NOn0u7m0FROHG/STGIiKBMMtwxDwIyg/z6U/ruqfvsTrq/m5N8JsSiWLVqzU8t2OlJE4asFvp9E77vpLBt3Tj8lAaff0knQEp22WzrUXB886EJAfrL++4C1LB9EbxG2zEUat1PQibsnZF0L8u5zPOSWR/bz5B6CJs2vZfv2O/o7/gO+XoLN2r8xC27953r17Qgv683FI5tdsiX210RNgL7HoSR2+fdDvm43SHf113656iFQk9p8O5aCX3mMMcrhZVvBUezkK3wKh8dFnzeIU+KXeB24TuOolt0gxOfsPW/UezfKLlDXvO76skOFPce8sZwFMr648wOmqcJS//xTq7RHfLtbhdO1b0MIQVc8G28Q74LV7nlO/rH35M3nN3p1vuuS4sfhaKeQmHbHfKa37Wd2/Xtu71Dvox2vjsM7wAAGpj7vFDAc5ippulw3D7ez0uo7ItkF/rpO0pAoeD/axXI43uTdknxFPWPEzvkKQry8uf7TkvjEm7WhJ+mIi+hF5Ouq/6NIOM4/j6ufr83MQIFRhGUQiBB0Kbxv375WBUGQhnd/9Eyxi3vZeq7eTq7HYwMKeySe/Dpm2w/Y6OrT04YonLMb5DVbz62Ln97zqArjIA8kZ8z/UGzv7PLn4VtWve3NnGx5wZ/YvS+U8MofEZE+MlQhc+//OvvpAabxmHb/V9E+SrGjxy+8ru6eR++O8vlPccN0gpO/UnVr3L7SOdTx6+T+PPXdR+UO+Sb7F8U+/DiD9b6ILxg+32EFIdS56aldnloNGKZmq0POF6Q4jbs4jr3qkNnlXVs0IrDhM35cTpktiPcEAXdHNzYdq1TsHg7iCKgm7GKK0TFEH7i+UHFSEzMuvzUAwrV/VJADWQvlIziN4FCZse7mxTsw3HcUEiCOdLrjLeKxZvWrBQHD720kEJvo03HuDbpRpgBitVgsyLliA6t85ashcNqGZnS2d2qocv7EW1MUZOtqvhsrhn1sHgzm2FDUzw6HqyYrUStVsw4bSlBJgQ6omV/W0lzWib5MEAbpNHgPI4N1Z9TNKGVodHi2XcEyiGhTHO+tyQNazFKU7O8J9mDpd+waKscLsoSDE3S+PUGf+CV2/bNzZXw6dwH4PPnH6Lqi2fE8PHhCYtAKdbt3I+R1ntZ6HeyCysE89nQfv2k6Z/PSXr/9dPpswTrz7359dP5M+M2QVq6OXMvYPGEkcncm+revBICPje+EXwCDOTByN8n2LC4f3qFQrND/rzlSwbo3C6NYIrB0ikJwl6eGYamlzEYBRrEgiJd6qyvLtb13E/4SKZ6dk+iDMh0fKuTW8pTI0oDpd0DliYlpR0ZxWYXb1dF4bnxeDVmLpcYiQeYwTGJ5Cv4/uHweW+bE+vhWOdYx8zRaNZbHUd4JQGfD17GxRK9fq1ZvDGTZBn4NQusYxUcbrFtEjeBkwfPolvX3Pc2bkxHFqR0LF9pIOk8Kid+oVZesW8VnOo88/qANPHiDXJ5BEX+2k/RQbgf0Yekc/BSRokF8KLd11z2mntIs0HIeu5KAi9KKjryo81CvaB2LK2ytnW8uSaReAzNOSY2QMtVDk7kfsZdJfqLxuMofdd4jBVD1iXNGIUPTuKT0/Sd01MrE8v9Qs6fGvolPfjFHnVNqpcUcmSV16oDCxzZMQnUWwkTq4PTU/PFG3SWRHPU3TXJiZnB8cMetg7yqw79Sgv/5TNuD8CZAQoJns+ZWIRjDize0PqEcSYjpQTq66ZmqUctUor5gZrNbbMXToWzR+llZ1un9GIBKjVuwDgDkUtkkJa9pfZdh+pzDVNxPqbxdqncpzPnELA4dOeM6OJ1vKo4c8PKrddelputYaXu1pxEZmYiQjCJVevvLZnMUwPWPPHBtPni7Sjss2a7rDxPR31ZQrZOLNOyqHJeVavbZgnqod5adYyb28afFSwWtprnCd2c8UIWs2WsHCfE4IzF26boHXPj4m0uisGdUqahOtH1MNd6bvaIDlxvWddpvrGE430rr1VKnzXRly7ggjIPzen7x/XoUTixeNMLheCjyBN6ZsOfqSG+b7ubdT0BBX10N6yiguNSIik9jMix7ZY2w/cSMgosUAB9Xwt0xTIMcMEIowBAz6qoDpQDQgNhBCw4P/13UEgaRCQH45qhIeVBedHlNG2Pe2AL4mjTtGIcYK7yDNPyizegGHt6hPFHo88IDNhYMWla21BrV8QYa454dwCHCkNOhnplG3DXQfTaSeU4ngVmnHxE9uLt57FNBx/UJHfNaEPai7Eh0ufkNt2DgzqeU3Lw+a46mfLDY4g5NLFk8RZY6v1UwNjWqMy+kqM8C5PMK2tbVybJNMYzCzBZv8F8S1KH5VDZdFInAw/5QUm2peb+SmWN29gv3uzVsZVY/6Xrh6YcTTPtKOqgpOO4oWNuTyv+CGzbcw8q6rP34bSiG1fDBnslj6dSJjxzj0GZ+BhWDqqTaPJlJ2FUbAmaeH9gJ6+psXx01iqqLgFYtuuxiraJeZYYlGcKhtcAzy85g+aABOvTYQYBnchxcovpZEj2QAXQJ8Iz9dChkbJ9bD+rzErKpPGs2KItOAKwvbWqcAqtKI3E3GDeARPGOL8XjIzONA7F9cONCHDu4a7UzDOumD3LbbDPbWj1RJmvtZ0J40X/SRWGGf2MAg6wgXs4NVgr9Ffy+iCiPjYwMc8Fysx535evGLIN1trlfAPrDqX4rU7iIc5HSznorVKIEVKtssXbKKgkFG7fc+ppuqzzeo5xSm9u4XWJagdm2aWWrh5vcqGEoGPOt8u8tmYjvoKsLzBTYM04cBNZsRZvjkGzobDeIqaqz+g+kug8Yho29h/k6HlzuAHqUrDExyNBmjMSb6KBptp5pCi0ykjjtJqSulfFDayZnGWWyVz5gTSZe/UYrpwrOdD5xdvoyKUctKsjR8nGbDWBnyiwKn1ynzeqoGY86xCkwaLePi5NFV28PZgyUXu0xr2K7TGPS8siqbGjfuSvW3lTYOkK87FLnNc5feeiQJ1LmyeoHhAvkP8zev4MTrlYh0mOF9/gNFCWF9y6oENSOkiyFq2RUC7sT+GU+F/BqfClKLzglPtLOC2oIWC+QekkzQCXMuOPUPqa+wFKD+N/gdPi7UdI5Ka/CYgaLIfxa82JmwLdMWWY1n6Ro6FGJ16hxE9pfX6qoIaZo3M3iRFeNgCT5P2F3XT4j7umDAEH7kf/E9hs2cQbddpz+o4K41kS1ht1694hTExS52Phw7hgDKaH07V2q+44kbQ0H9eGFGeJf8pTZi9N8n66E3u07jo6vCtHkc83nG5ubsLKU+WbsHjzbtIs5ZM0RkKExNnpMN2E1guXrnliwMO9bylcmcor0jCaeZkUO03yucG46xRtvNTHe8me/CNAYUkGOc0HfNRRJ41AM8Xscfvc6hQW0hUfpfapSx2Rk1q3DxOyilTnaoNAtmNjfd70+TnI1tYKmwIRppB11uPjJUEZjS90Fol8YgP7hrLm91x4imVAgeJsYqNyFVpiiS9JgSsf8V12VvIQ3tYXrCTHHCNdWNzn7pocQE8SY9db/uO0Rft9zQscF+mn9AfYBCxDQ5zWPoBIegEkwwL+CZ4gfh5gJLD+AD8ID8gInQNzj87o5it0OSydSjwTWzXtJ3n196BrcAoHZgiR2zg3/DR+jD9G6I800Hdfqa7HwYOApmhE5hfjKMYvSFdpRhoFV4FlEZ34GThf84/LHRj/ue6aQa9f6RPslDT0cdIVVGbBBxgra24Pj5b+Zd/3jWnRZXJQD2vAbuaMyZF+b6/Hw/jaJaPpeNzfgbGhW1hIUS9wswwLCwILB5gca95c79fr50HzKN4dIRl8Gcb2iVYAG8fwFCLqo01qPT5UtCTlbT55uKlcjou3xAjCUuBW1UPkFQTINilAC6vJeTv04IrR7kbiheqwVGWYqKIep+klu08lry9PhC5Kcm0n9bJoYYwLXn7wPL6cmGsfrI4z5kdUqm+24eDN/dSXwVDcj8W84vjNTG21XmkI1UsYo12T22hdjFXXx8RQQd8tlcnLfG1vbFlnuKTD7RTRj5I6RnFo83d+YvPD4XBMwSOPZZ0QD5zVFL1vKuFBs5VTdJ60iCFsYg8zGCp0m+00H/HJK7h1IzoTY4+TT4WzUxSXqFiby/goSsDkAuFcZ4nLs6hCYYNlncKrb1lZOd9DafG2DRtMVB8keT/T7bGi62TzoA2GczHObzzhb4IKu8kgqHTiN1A5uWyGj4S5OpSxv5dTt1yJ6fz/FFTm/waV2/8KVJIv58cnqNh/YTfNwyn0CSdQpn8AKA9aH4tWP2LGIToeqrCR6cNmOQ+9SHrQ4vPAAyAIUTaoyuPICSEhgnOY6AJgH1OMT4Zd701MB3WzXBNIRTunywVvQ3gIdLpZaG4VEoH8Dns0H7fq3DDH5Ticej7ci1137TS2FBCBqG60hN6iywHzfXDKNqFOddR+3F4L50pEjl/3p9I4tCIv6/XiDQPenetgJ+5U7AxSEiW4GbGltNwivI+JYpSf98iK1A2qmoL4YpQoZRa9Fwa+IHQasU1uenOMPQgFehU1qsUPhOz1S+8RsSI46Ld7IDGmCEbWlKRuOj6ClTezOllzWM6YszHl9E1W2M1ZNcTIuvt4AGEcu2Aap3TRbOYRTh0vSMvUBmon96+Agq9Hj/1LMKG/Qgns0f4AJs9+46NrgnzCih5HPuNgB2F/jYbLs1g/S7XEKuuTznWSLmHPrgyC77e+TL/zY6N2WH2+XDdeed2QMPhix4utLPv52f511Lh6XFYE8Ghxx9XL1nQOTWbU+VV2WnmFoD493cY7G7TIMqKkWOzxB0dWGxtvK3o+47JLpsHxyFrgnF62FVYj/hVqx472qtHOorbRFarGYgyDSOcCdb4FeqV4vcaGtWtU55jqOdgrYMfcnrnGv/Ets5TmSthGHlcNw+INV7gwzJ3MFHv5BnvK44aZSyEQT+yyEFbopVfmbK6Ha10c++zuOpEo8tHq3rFC49108l5puMESNYwn9dJwkrUqZXdOr4HEuxlWOzmlW+vLTe6rgo2Z9mY9oHkNBHZZVGpZkTSf6PQxCBZOtGoXoR3BnxdvJX8lluYVbdrk4mJyaeybkbreQ67hUWLGSKquSY1KC3PUybXy07M98v3NHfLtbd7393yv7xmvDy7PV/A/foj5D/LlVqY= PARES end + def test_successful_verify_with_elo + response = @gateway.verify(@elo_credit_card, @options) + assert_successful_response(response) + end + def test_verify_credentials assert @gateway.verify_credentials @@ -445,4 +1290,34 @@ def test_verify_credentials assert !gateway.verify_credentials end + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match '1.00', response.params['amount'] + assert_equal 'Successful transaction', response.message + end + + def test_successful_verify_zero_amount_visa + @options[:zero_amount_auth] = true + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match '0.00', response.params['amount'] + assert_equal 'Successful transaction', response.message + end + + def test_successful_verify_zero_amount_master + @options[:zero_amount_auth] = true + response = @gateway.verify(@master_credit_card, @options) + assert_success response + assert_match '0.00', response.params['amount'] + assert_equal 'Successful transaction', response.message + end + + private + + def assert_successful_response(response) + assert_equal 'Successful transaction', response.message + assert_success response + assert response.test? + end end diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb index f704e54e5d4..89b516dd9d3 100644 --- a/test/remote/gateways/remote_d_local_test.rb +++ b/test/remote/gateways/remote_d_local_test.rb @@ -4,13 +4,15 @@ class RemoteDLocalTest < Test::Unit::TestCase def setup @gateway = DLocalGateway.new(fixtures(:d_local)) - @amount = 100 + @amount = 200 @credit_card = credit_card('4111111111111111') + @credit_card_naranja = credit_card('5895627823453005') + @cabal_credit_card = credit_card('5896 5700 0000 0004') # No test card numbers, all txns are approved by default, # but errors can be invoked directly with the `description` field @options = { billing_address: address(country: 'Brazil'), - document: '42243309114', + document: '71575743221', currency: 'BRL' } @options_colombia = { @@ -23,6 +25,13 @@ def setup document: '10563145', currency: 'ARS' } + @options_argentina_installments = { + billing_address: address(country: 'Argentina'), + document: '10563145', + currency: 'ARS', + installments: '3', + installments_id: 'INS54434' + } @options_mexico = { billing_address: address(country: 'Mexico'), document: '128475869794933', @@ -41,10 +50,95 @@ 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=') + 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')) + 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=') + response = @gateway.purchase(@amount, credit_card, options) + assert_success response + assert_match 'USED', response.params['card']['stored_credential_usage'] + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_with_installments + response = @gateway.purchase(@amount, @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) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_cabal + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_inquire_with_payment_id + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match 'The payment was paid', response.message + + authorization = response.params['id'] + response = @gateway.inquire(authorization, @options) + assert_success response + assert_match 'PAID', response.params['status'] + assert_match 'The payment was paid.', response.params['status_detail'] + end + + def test_successful_inquire_with_order_id + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match 'The payment was paid', response.message + + purchase_payment_id = response.params['id'] + order_id = response.params['order_id'] + + response = @gateway.inquire(nil, { order_id: order_id }) + check_payment_id = response.params['payment_id'] + assert_success response + assert_match purchase_payment_id, check_payment_id + end + + def test_successful_purchase_with_original_order_id + response = @gateway.purchase(@amount, @credit_card, @options.merge(original_order_id: '123ABC')) + assert_success response + assert_match 'The payment was paid', response.message + assert_match '123ABC', response.params['original_order_id'] + end + def test_successful_purchase_with_more_options options = @options.merge( order_id: '1', ip: '127.0.0.1', + device_id: '123', email: 'joe@example.com', birth_date: '03-01-1970', document2: '87648987569', @@ -57,6 +151,23 @@ def test_successful_purchase_with_more_options assert_match 'The payment was paid', response.message end + def test_successful_purchase_with_additional_data + options = @options.merge( + additional_data: { submerchant: { name: 'socks' } } + ) + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_with_force_type_debit + options = @options.merge(force_type: 'DEBIT') + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_match 'The payment was paid', response.message + end + # You may need dLocal to enable your test account to support individual countries def test_successful_purchase_colombia response = @gateway.purchase(100000, @credit_card, @options_colombia) @@ -82,12 +193,28 @@ def test_successful_purchase_peru assert_match 'The payment was paid', response.message end + def test_successful_purchase_partial_address + response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address(address1: 'My Street', country: 'Brazil'))) + assert_success response + assert_match 'The payment was paid', response.message + end + def test_failed_purchase response = @gateway.purchase(@amount, @credit_card, @options.merge(description: '300')) assert_failure response assert_match 'The payment was rejected', response.message end + def test_failed_purchase_with_network_tokens + 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 + end + def test_failed_document_format response = @gateway.purchase(@amount, @credit_card, @options.merge(document: 'bad_document')) assert_failure response @@ -104,6 +231,16 @@ def test_successful_authorize_and_capture assert_match 'The payment was paid', capture.message end + def test_successful_authorize_and_capture_with_cabal + auth = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + assert_match 'The payment was authorized', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_match 'The payment was paid', capture.message + end + def test_failed_authorize response = @gateway.authorize(@amount, @credit_card, @options.merge(description: '309')) assert_failure response @@ -115,7 +252,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) assert_success capture end @@ -140,7 +277,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(notification_url: 'http://example.com')) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options.merge(notification_url: 'http://example.com')) assert_success refund end @@ -148,7 +285,7 @@ def test_failed_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - response = @gateway.refund(@amount+1, purchase.authorization, @options.merge(notification_url: 'http://example.com')) + response = @gateway.refund(@amount + 100, purchase.authorization, @options.merge(notification_url: 'http://example.com')) assert_failure response assert_match 'Amount exceeded', response.message end @@ -163,21 +300,23 @@ def test_successful_void end def test_failed_void - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - - assert capture = @gateway.capture(@amount, auth.authorization, @options) - assert_success capture - - response = @gateway.void(auth.authorization) + response = @gateway.void('') assert_failure response - assert_match 'Invalid transaction status', response.message + assert_match 'Invalid request', response.message end def test_successful_verify response = @gateway.verify(@credit_card, @options) assert_success response - assert_match %r{The payment was authorized}, response.message + assert_equal 0, response.params['amount'] + assert_match %r{The payment was verified}, response.message + end + + def test_successful_verify_with_cabal + response = @gateway.verify(@cabal_credit_card, @options) + assert_success response + assert_equal 0, response.params['amount'] + assert_match %r{The payment was verified}, response.message end def test_failed_verify @@ -206,4 +345,31 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:trans_key], transcript) end + def test_successful_authorize_with_3ds_v1_options + @options[:three_d_secure] = { + version: '1.0', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + xid: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'true', + authentication_response_status: 'Y' + } + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_match 'The payment was authorized', auth.message + end + + def test_successful_authorize_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.2.0', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'Y', + authentication_response_status: 'Y' + } + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_match 'The payment was authorized', auth.message + end end diff --git a/test/remote/gateways/remote_data_cash_test.rb b/test/remote/gateways/remote_data_cash_test.rb index 9fb77b19a6f..c1297dca20b 100644 --- a/test/remote/gateways/remote_data_cash_test.rb +++ b/test/remote/gateways/remote_data_cash_test.rb @@ -1,64 +1,62 @@ require 'test_helper' class RemoteDataCashTest < Test::Unit::TestCase - def setup # gateway to connect to Datacash @gateway = DataCashGateway.new(fixtures(:data_cash)) @mastercard = CreditCard.new( - :number => '5120790000000034', - :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', - :last_name => 'McBride', - :brand => :master + number: '5120790000000034', + month: 3, + year: Date.today.year + 2, + first_name: 'Mark', + last_name: 'McBride', + brand: :master ) @mastercard_declined = CreditCard.new( - :number => '5473000000000106', - :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', - :last_name => 'McBride', - :brand => :master, - :verification_value => '547' + number: '5473000000000106', + month: 3, + year: Date.today.year + 2, + first_name: 'Mark', + last_name: 'McBride', + brand: :master, + verification_value: '547' ) @visa_delta = CreditCard.new( - :number => '4539792100000003', - :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', - :last_name => 'McBride', - :brand => :visa, - :verification_value => '444' + number: '4539792100000003', + month: 3, + year: Date.today.year + 2, + first_name: 'Mark', + last_name: 'McBride', + brand: :visa, + verification_value: '444' ) @solo = CreditCard.new( - :first_name => 'Cody', - :last_name => 'Fauser', - :number => '633499100000000004', - :month => 3, - :year => Date.today.year + 2, - :brand => :solo, - :issue_number => 5, - :start_month => 12, - :start_year => 2006 + first_name: 'Cody', + last_name: 'Fauser', + number: '633499100000000004', + month: 3, + year: Date.today.year + 2, + brand: :solo, + start_month: 12, + start_year: 2006 ) @address = { - :name => 'Mark McBride', - :address1 => 'Flat 12/3', - :address2 => '45 Main Road', - :city => 'Sometown', - :state => 'Somecounty', - :zip => 'A987AA', - :phone => '(555)555-5555' + name: 'Mark McBride', + address1: 'Flat 12/3', + address2: '45 Main Road', + city: 'Sometown', + state: 'Somecounty', + zip: 'A987AA', + phone: '(555)555-5555' } @params = { - :order_id => generate_unique_id + order_id: generate_unique_id } @amount = 198 @@ -110,7 +108,7 @@ def test_successful_purchase_with_account_set_up_and_repeat_payments assert response.test? # Make second payment on the continuous authorization that was set up in the first purchase - second_order_params = { :order_id => generate_unique_id } + second_order_params = { order_id: generate_unique_id } purchase = @gateway.purchase(201, response.authorization, second_order_params) assert_success purchase end @@ -122,7 +120,7 @@ def test_successful_purchase_with_account_set_up_and_repeat_payments_with_visa_d assert !response.authorization.to_s.split(';')[2].blank? # Make second payment on the continuous authorization that was set up in the first purchase - second_order_params = { :order_id => generate_unique_id } + second_order_params = { order_id: generate_unique_id } purchase = @gateway.purchase(201, response.authorization, second_order_params) assert_success purchase end @@ -148,7 +146,7 @@ def test_successful_authorization_and_capture_with_account_set_up_and_second_pur assert capture.test? # Collect second purchase - second_order_params = { :order_id => generate_unique_id } + second_order_params = { order_id: generate_unique_id } purchase = @gateway.purchase(201, first_authorization.authorization, second_order_params) assert_success purchase assert purchase.test? @@ -165,6 +163,7 @@ def test_duplicate_order_id end def test_invalid_verification_number + @mastercard.number = 1000350000000007 @mastercard.verification_value = 123 response = @gateway.purchase(@amount, @mastercard, @params) assert_failure response @@ -310,7 +309,7 @@ def test_successful_refund_of_a_repeat_payment assert response.test? # Make second payment on the continuous authorization that was set up in the first purchase - second_order_params = { :order_id => generate_unique_id } + second_order_params = { order_id: generate_unique_id } purchase = @gateway.purchase(201, response.authorization, second_order_params) assert_success purchase @@ -326,7 +325,7 @@ def test_order_id_that_is_too_short end def test_order_id_that_is_too_long - @params[:order_id] = "#{@params[:order_id]}1234356" + @params[:order_id] = "#{@params[:order_id]}1234356" response = @gateway.purchase(@amount, @mastercard, @params) assert_success response assert response.test? diff --git a/test/remote/gateways/remote_decidir_plus_test.rb b/test/remote/gateways/remote_decidir_plus_test.rb new file mode 100644 index 00000000000..0f36584dab5 --- /dev/null +++ b/test/remote/gateways/remote_decidir_plus_test.rb @@ -0,0 +1,334 @@ +require 'test_helper' +require 'securerandom' + +class RemoteDecidirPlusTest < Test::Unit::TestCase + def setup + @gateway_purchase = DecidirPlusGateway.new(fixtures(:decidir_plus)) + @gateway_auth = DecidirPlusGateway.new(fixtures(:decidir_plus_preauth)) + + @amount = 100 + @credit_card = credit_card('4484590159923090') + @american_express = credit_card('376414000000009') + @cabal = credit_card('5896570000000008') + @visa_debit = credit_card('4517721004856075') + @declined_card = credit_card('4000300011112220') + @options = { + billing_address: address, + description: 'Store Purchase', + card_brand: 'visa' + } + @sub_payments = [ + { + site_id: '04052018', + installments: 1, + amount: 1500 + }, + { + site_id: '04052018', + installments: '1', + amount: '1500' + } + ] + @fraud_detection = { + send_to_cs: 'false', + channel: 'Web', + dispatch_method: 'Store Pick Up', + csmdds: [ + { + code: '17', + description: 'Campo MDD17' + } + ] + } + @aggregate_data = { + indicator: '1', + identification_number: '308103480', + bill_to_pay: 'test1', + bill_to_refund: 'test2', + merchant_name: 'Heavenly Buffaloes', + street: 'Sesame', + number: '123', + postal_code: '22001', + category: 'yum', + channel: '005', + geographic_code: 'C1234', + city: 'Ciudad de Buenos Aires', + merchant_id: 'dec_agg', + province: 'Buenos Aires', + country: 'Argentina', + merchant_email: 'merchant@mail.com', + merchant_phone: '2678433111' + } + end + + def test_successful_purchase + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, @options) + assert_success response + assert_equal 'approved', response.message + end + + def test_failed_purchase + assert @gateway_purchase.store(@credit_card) + + response = @gateway_purchase.purchase(@amount, '', @options) + assert_failure response + assert_equal 'invalid_param: token', response.message + end + + def test_successful_authorize_and_capture + options = @options.merge(fraud_detection: @fraud_detection) + + assert response = @gateway_auth.store(@credit_card, options) + payment_reference = response.authorization + + response = @gateway_auth.authorize(@amount, payment_reference, options) + assert_success response + + assert capture_response = @gateway_auth.capture(@amount, response.authorization, options) + assert_success capture_response + end + + def test_failed_authorize + options = @options.merge(fraud_detection: @fraud_detection) + + assert response = @gateway_auth.store(@declined_card, options) + payment_reference = response.authorization + + response = @gateway_auth.authorize(@amount, payment_reference, options) + assert_failure response + assert_equal response.error_code, 3 + end + + def test_successful_refund + response = @gateway_purchase.store(@credit_card) + + purchase = @gateway_purchase.purchase(@amount, response.authorization, @options) + assert_success purchase + assert_equal 'approved', purchase.message + + assert refund = @gateway_purchase.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'approved', refund.message + end + + def test_partial_refund + assert response = @gateway_purchase.store(@credit_card) + + purchase = @gateway_purchase.purchase(@amount, response.authorization, @options) + assert_success purchase + + assert refund = @gateway_purchase.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway_purchase.refund(@amount, '') + assert_failure response + assert_equal 'not_found_error', response.message + end + + def test_successful_void + options = @options.merge(fraud_detection: @fraud_detection) + + assert response = @gateway_auth.store(@credit_card, options) + payment_reference = response.authorization + + response = @gateway_auth.authorize(@amount, payment_reference, options) + assert_success response + assert_equal 'pre_approved', response.message + authorization = response.authorization + + assert void_response = @gateway_auth.void(authorization) + assert_success void_response + end + + def test_failed_void + assert response = @gateway_auth.void('') + assert_failure response + assert_equal 'not_found_error', response.message + end + + def test_successful_verify + assert response = @gateway_auth.verify(@credit_card, @options.merge(fraud_detection: @fraud_detection)) + assert_success response + assert_equal 'active', response.message + end + + def test_failed_verify + assert response = @gateway_auth.verify(@declined_card, @options) + assert_failure response + assert_equal 'missing: fraud_detection', response.message + end + + def test_successful_store + assert response = @gateway_purchase.store(@credit_card) + assert_success response + assert_equal 'active', response.message + assert_equal @credit_card.number[0..5], response.authorization.split('|')[1] + end + + def test_successful_store_name_override + @credit_card.name = '' + options = { name_override: 'Rick Deckard' } + assert response = @gateway_purchase.store(@credit_card, options) + assert_success response + assert_equal 'active', response.message + assert_equal options[:name_override], response.params.dig('cardholder', 'name') + end + + def test_successful_unstore + customer = { + id: 'John', + email: 'decidir@decidir.com' + } + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, @options.merge({ customer: customer })) + assert_success response + + assert_equal 'approved', response.message + token_id = response.params['customer_token'] + + assert unstore_response = @gateway_purchase.unstore(token_id) + assert_success unstore_response + end + + def test_successful_purchase_with_options + options = @options.merge(sub_payments: @sub_payments) + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 'approved', response.message + end + + def test_successful_purchase_with_fraud_detection + options = @options.merge(fraud_detection: @fraud_detection) + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal({ 'status' => nil }, response.params['fraud_detection']) + end + + def test_successful_purchase_with_card_brand + options = @options.merge(card_brand: 'cabal') + + assert response = @gateway_purchase.store(@cabal) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 63, response.params['payment_method_id'] + end + + def test_successful_purchase_with_payment_method_id + options = @options.merge(payment_method_id: '63') + + assert response = @gateway_purchase.store(@cabal) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 63, response.params['payment_method_id'] + end + + def test_successful_purchase_with_establishment_name + establishment_name = 'Heavenly Buffaloes' + options = @options.merge(establishment_name: establishment_name) + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 'approved', response.message + end + + def test_successful_purchase_with_aggregate_data + options = @options.merge(aggregate_data: @aggregate_data) + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 'approved', response.message + end + + def test_successful_purchase_with_additional_data_validation + store_options = { + card_holder_identification_type: 'dni', + card_holder_identification_number: '44567890', + card_holder_door_number: '348', + card_holder_birthday: '01012017' + } + assert response = @gateway_purchase.store(@credit_card, store_options) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, @options) + assert_success response + assert_equal 'approved', response.message + end + + def test_failed_purchase_with_payment_method_id + options = @options.merge(payment_method_id: '1') + + assert response = @gateway_purchase.store(@cabal) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_failure response + end + + def test_successful_purchase_with_debit + options = @options.merge(debit: 'true', card_brand: 'visa') + + assert response = @gateway_purchase.store(@visa_debit) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_success response + assert_equal 31, response.params['payment_method_id'] + end + + def test_failed_purchase_with_debit + options = @options.merge(debit: 'true', card_brand: 'visa') + + assert response = @gateway_purchase.store(@credit_card) + payment_reference = response.authorization + + response = @gateway_purchase.purchase(@amount, payment_reference, options) + assert_failure response + assert_equal 'invalid_param: bin', response.message + end + + def test_invalid_login + gateway = DecidirPlusGateway.new(public_key: '12345', private_key: 'abcde') + + response = gateway.store(@credit_card, @options) + assert_failure response + assert_match %r{Invalid authentication credentials}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway_purchase) do + @gateway_purchase.store(@credit_card, @options) + end + transcript = @gateway_purchase.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway_purchase.options[:public_key], transcript) + assert_scrubbed(@gateway_purchase.options[:private_key], transcript) + end +end diff --git a/test/remote/gateways/remote_decidir_test.rb b/test/remote/gateways/remote_decidir_test.rb new file mode 100644 index 00000000000..22831af2ed7 --- /dev/null +++ b/test/remote/gateways/remote_decidir_test.rb @@ -0,0 +1,351 @@ +require 'test_helper' + +class RemoteDecidirTest < Test::Unit::TestCase + def setup + @gateway_for_purchase = DecidirGateway.new(fixtures(:decidir_purchase)) + @gateway_for_auth = DecidirGateway.new(fixtures(:decidir_authorize)) + + @amount = 100 + @credit_card = credit_card('4507990000004905') + @master_card_credit_card = credit_card('5299910010000015') + @amex_credit_card = credit_card('373953192351004') + @diners_club_credit_card = credit_card('36463664750005') + @cabal_credit_card = credit_card('5896570000000008') + @naranja_credit_card = credit_card('5895627823453005') + @declined_card = credit_card('4000300011112220') + @options = { + order_id: SecureRandom.uuid, + billing_address: address, + description: 'Store Purchase' + } + @sub_payments = [ + { + site_id: '04052018', + installments: '1', + amount: '1500' + }, + { + site_id: '04052018', + installments: 1, + 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 + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_master_card + response = @gateway_for_purchase.purchase(@amount, @master_card_credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_amex + response = @gateway_for_purchase.purchase(@amount, @amex_credit_card, @options) + assert_success response + assert_equal 'approved', response.message + 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. + # + # def test_successful_purchase_with_diners_club + # response = @gateway_for_purchase.purchase(@amount, @diners_club_credit_card, @options) + # assert_success response + # assert_equal 'approved', response.message + # assert response.authorization + # end + + def test_successful_purchase_with_cabal + response = @gateway_for_purchase.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_naranja + response = @gateway_for_purchase.purchase(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_more_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com', + card_holder_door_number: '1234', + card_holder_birthday: '01011980', + card_holder_identification_type: 'dni', + card_holder_identification_number: '123456', + establishment_name: 'Heavenly Buffaloes', + device_unique_identifier: '1', + fraud_detection: { + send_to_cs: false, + channel: 'Web', + dispatch_method: 'Store Pick Up', + csmdds: [ + { + code: 17, + description: 'Campo MDD17' + } + ], + device_unique_id: '1', + bill_to: { + postal_code: '12345', + last_name: 'Smith', + country: 'US', + street1: '123 Mockingbird Lane', + state: 'TN', + email: 'dootdoot@hotmail.com', + customer_id: '111111', + phone_number: '555-5555', + first_name: 'Joe', + city: 'Pantsville' + }, + customer_in_site: { + password: '', + is_guest: false, + street: '123 Mockingbird Lane', + cellphone_number: '555-1212', + num_of_transactions: 48, + date_of_birth: '8-4-80', + days_in_site: 105 + }, + purchase_totals: { + currency: 'USD', + amount: 100 + } + }, + installments: '12', + site_id: '99999999' + } + + response = @gateway_for_purchase.purchase(@amount, credit_card('4509790112684851'), @options.merge(options)) + assert_success response + assert_equal 'approved', response.message + assert_equal 'Heavenly Buffaloes', response.params['establishment_name'] + assert_equal '99999999', response.params['site_id'] + assert_equal({ 'status' => nil }, response.params['fraud_detection']) + assert response.authorization + end + + def test_successful_purchase_with_sub_payments + options = @options.merge(sub_payments: @sub_payments) + + assert response = @gateway_for_purchase.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'approved', response.message + end + + def test_failed_purchase_with_bad_csmdds + options = { + fraud_detection: { + send_to_cs: false, + channel: 'Web', + dispatch_method: 'Store Pick Up', + csmdds: [ + { + codee: 17, + descriptione: 'Campo MDD17' + } + ] + } + } + + response = @gateway_for_purchase.purchase(@amount, credit_card('4509790112684851'), @options.merge(options)) + assert_failure response + assert_equal 'param_required: fraud_detection.csmdds.[0].code, param_required: fraud_detection.csmdds.[0].description', response.message + assert_equal(nil, response.params['fraud_detection']) + end + + def test_failed_purchase + response = @gateway_for_purchase.purchase(@amount, @declined_card, @options) + assert_failure response + 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 + response = @gateway_for_purchase.purchase(@amount, @declined_card, @options.merge(installments: -1)) + assert_failure response + assert_equal 'invalid_param: installments', response.message + assert_equal 'invalid_request_error', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'pre_approved', auth.message + assert auth.authorization + + assert capture = @gateway_for_auth.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'approved', capture.message + assert capture.authorization + end + + def test_failed_authorize + response = @gateway_for_auth.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal @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 + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway_for_auth.capture(1, auth.authorization) + assert_failure capture + assert_equal 'amount: Amount out of ranges: 80 - 105', capture.message + assert_equal 'invalid_request_error', capture.error_code + assert_nil capture.authorization + end + + def test_failed_capture + response = @gateway_for_auth.capture(@amount, '') + + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway_for_purchase.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'approved', refund.message + assert refund.authorization + end + + def test_partial_refund + purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway_for_purchase.refund(@amount - 1, purchase.authorization) + assert_success refund + assert_equal 'approved', refund.message + assert refund.authorization + end + + def test_failed_refund + response = @gateway_for_purchase.refund(@amount, '') + assert_failure response + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_void + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway_for_auth.void(auth.authorization) + assert_success void + assert_equal 'approved', void.message + assert void.authorization + end + + def test_failed_void + response = @gateway_for_auth.void('') + assert_failure response + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_verify + response = @gateway_for_auth.verify(@credit_card, @options) + assert_success response + assert_match %r{pre_approved}, response.message + end + + def test_failed_verify + response = @gateway_for_auth.verify(@declined_card, @options) + assert_failure response + assert_match %r{PEDIR AUTORIZACION | request_authorization_card}, response.message + end + + def test_successful_inquire + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + + response = @gateway_for_purchase.inquire(response.params['id']) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_invalid_login_without_api_key + gateway = DecidirGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{No API key found in request}, response.message + end + + def test_invalid_login + gateway = DecidirGateway.new(api_key: 'xxxxxxx') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid authentication credentials}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway_for_purchase) do + @gateway_for_purchase.purchase(@amount, @credit_card, @options) + end + transcript = @gateway_for_purchase.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway_for_purchase.options[:api_key], transcript) + end +end diff --git a/test/remote/gateways/remote_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_dibs_test.rb b/test/remote/gateways/remote_dibs_test.rb index 1e6b16e36b1..daf302ac6df 100644 --- a/test/remote/gateways/remote_dibs_test.rb +++ b/test/remote/gateways/remote_dibs_test.rb @@ -5,11 +5,11 @@ def setup @gateway = DibsGateway.new(fixtures(:dibs)) cc_options = { - :month => 6, - :year => 24, - :verification_value => '684', - :brand => 'visa' - } + month: 6, + year: 24, + verification_value: '684', + brand: 'visa' + } @amount = 100 @credit_card = credit_card('4711100000000000', cc_options) diff --git a/test/remote/gateways/remote_digitzs_test.rb b/test/remote/gateways/remote_digitzs_test.rb index 285dd840f9d..271aa7f5f62 100644 --- a/test/remote/gateways/remote_digitzs_test.rb +++ b/test/remote/gateways/remote_digitzs_test.rb @@ -83,7 +83,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options) assert_success refund end @@ -99,17 +99,17 @@ def test_successful_store end def test_successful_store_without_billing_address - assert response = @gateway.store(@credit_card, {merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385'}) + assert response = @gateway.store(@credit_card, { merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385' }) assert_success response end def test_store_adds_card_to_existing_customer - assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575'})) + assert response = @gateway.store(@credit_card, @options.merge({ customer_id: 'spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575' })) assert_success response end def test_store_creates_new_customer_and_adds_card - assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'nonexistant'})) + assert response = @gateway.store(@credit_card, @options.merge({ customer_id: 'nonexistant' })) assert_success response end @@ -132,5 +132,4 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:api_key], transcript) assert_scrubbed(@gateway.options[:app_key], transcript) end - end diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb index d1035d9b62b..266c7b4e2ed 100644 --- a/test/remote/gateways/remote_ebanx_test.rb +++ b/test/remote/gateways/remote_ebanx_test.rb @@ -17,8 +17,19 @@ def setup phone_number: '8522847035' }), order_id: generate_unique_id, - document: '853.513.468-93' + document: '853.513.468-93', + device_id: '34c376b2767', + metadata: { + metadata_1: 'test', + metadata_2: 'test2' + }, + tags: EbanxGateway::TAGS, + soft_descriptor: 'ActiveMerchant', + email: 'neymar@test.com' } + + @hiper_card = credit_card('6062825624254001') + @elo_card = credit_card('6362970000457013') end def test_successful_purchase @@ -27,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, @@ -41,6 +70,13 @@ def test_successful_purchase_with_more_options assert_equal 'Accepted', response.message end + def test_successful_purchase_passing_processing_type_in_header + response = @gateway.purchase(@amount, @credit_card, @options.merge({ processing_type: 'local' })) + + assert_success response + assert_equal 'Accepted', response.message + end + def test_successful_purchase_as_brazil_business_with_responsible_fields options = @options.update(document: '32593371000110', person_type: 'business', @@ -71,13 +107,13 @@ def test_successful_purchase_as_colombian response = @gateway.purchase(500, @credit_card, options) assert_success response - assert_equal 'Sandbox - Test credit card, transaction captured', response.message + assert_equal 'Accepted', response.message end def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message + assert_equal 'Invalid card or card type', response.message assert_equal 'NOK', response.error_code end @@ -86,7 +122,7 @@ def test_successful_authorize_and_capture assert_success auth assert_equal 'Accepted', auth.message - assert capture = @gateway.capture(@amount, auth.authorization) + assert capture = @gateway.capture(@amount, auth.authorization, @options) assert_success capture assert_equal 'Accepted', capture.message end @@ -94,18 +130,35 @@ def test_successful_authorize_and_capture def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response - assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message + assert_equal 'Invalid card or card type', response.message assert_equal 'NOK', response.error_code end - def test_partial_capture + 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 - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end + # Partial capture is only available in Brazil and the EBANX Integration Team must be contacted to enable + def test_failed_partial_capture_when_include_capture_amount_is_passed + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options.merge(include_capture_amount: true)) + assert_failure capture + assert_equal 'Partial capture not available', capture.message + end + def test_failed_capture response = @gateway.capture(@amount, '') assert_failure response @@ -116,7 +169,7 @@ def test_successful_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund_options = @options.merge({description: 'full refund'}) + refund_options = @options.merge({ description: 'full refund' }) assert refund = @gateway.refund(@amount, purchase.authorization, refund_options) assert_success refund assert_equal 'Accepted', refund.message @@ -127,7 +180,7 @@ def test_partial_refund assert_success purchase refund_options = @options.merge(description: 'refund due to returned item') - assert refund = @gateway.refund(@amount-1, purchase.authorization, refund_options) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, refund_options) assert_success refund end @@ -149,7 +202,7 @@ def test_successful_void def test_failed_void response = @gateway.void('') assert_failure response - assert_equal 'Parameter hash not informed', response.message + assert_equal 'Parameters hash or merchant_payment_code not informed', response.message end def test_successful_store_and_purchase @@ -170,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 @@ -190,10 +260,62 @@ def test_successful_verify assert_match %r{Accepted}, response.message end + def test_successful_verify_for_chile + options = @options.merge({ + order_id: generate_unique_id, + ip: '127.0.0.1', + email: 'jose@example.com.cl', + birth_date: '10/11/1980', + billing_address: address({ + address1: '1040 Rua E', + city: 'Medellín', + state: 'AN', + zip: '29269', + country: 'CL', + phone_number: '8522847035' + }) + }) + + response = @gateway.verify(@credit_card, options) + assert_success response + assert_match %r{Accepted}, response.message + end + + def test_successful_verify_for_mexico + options = @options.merge({ + order_id: generate_unique_id, + ip: '127.0.0.1', + email: 'joao@example.com.mx', + birth_date: '10/11/1980', + billing_address: address({ + address1: '1040 Rua E', + city: 'Toluca de Lerdo', + state: 'MX', + zip: '29269', + country: 'MX', + phone_number: '8522847035' + }) + }) + response = @gateway.verify(@credit_card, options) + assert_success response + assert_match %r{Accepted}, response.message + end + def test_failed_verify - response = @gateway.verify(@declined_card, @options) + declined_card = credit_card('6011088896715918') + response = @gateway.verify(declined_card, @options) assert_failure response - assert_match %r{Accepted}, 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 @@ -215,4 +337,11 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:integration_key], transcript) end + def test_successful_purchase_with_long_order_id + options = @options.update(order_id: SecureRandom.hex(50)) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Accepted', response.message + end end diff --git a/test/remote/gateways/remote_efsnet_test.rb b/test/remote/gateways/remote_efsnet_test.rb index 4e6f22499b7..c2745e2551f 100644 --- a/test/remote/gateways/remote_efsnet_test.rb +++ b/test/remote/gateways/remote_efsnet_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteEfsnetTest < Test::Unit::TestCase - def setup Base.mode = :test @@ -12,9 +11,8 @@ def setup @amount = 100 @declined_amount = 156 - @options = { :order_id => generate_unique_id, - :billing_address => address - } + @options = { order_id: generate_unique_id, + billing_address: address } end def test_successful_purchase @@ -71,8 +69,8 @@ def test_failed_capture def test_invalid_login gateway = EfsnetGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Invalid credentials', response.message diff --git a/test/remote/gateways/remote_elavon_test.rb b/test/remote/gateways/remote_elavon_test.rb index 9c560e7ec77..f4c4356b404 100644 --- a/test/remote/gateways/remote_elavon_test.rb +++ b/test/remote/gateways/remote_elavon_test.rb @@ -3,15 +3,87 @@ class RemoteElavonTest < Test::Unit::TestCase def setup @gateway = ElavonGateway.new(fixtures(:elavon)) + @tokenization_gateway = all_fixtures[:elavon_tokenization] ? ElavonGateway.new(fixtures(:elavon_tokenization)) : ElavonGateway.new(fixtures(:elavon)) + @bad_creds_gateway = ElavonGateway.new(login: 'foo', password: 'bar', user: 'me') + @multi_currency_gateway = ElavonGateway.new(fixtures(:elavon_multi_currency)) - @credit_card = credit_card('4124939999999990') + @credit_card = credit_card('4000000000000002') @bad_credit_card = credit_card('invalid') @options = { - :email => 'paul@domain.com', - :description => 'Test Transaction', - :billing_address => address, - :ip => '203.0.113.0' + email: 'paul@domain.com', + description: 'Test Transaction', + billing_address: address, + ip: '203.0.113.0', + merchant_initiated_unscheduled: 'N' + } + @shipping_address = { + address1: '733 Foster St.', + city: 'Durham', + state: 'NC', + phone: '8887277750', + country: 'USA', + zip: '27701' + } + @level_3_data = { + customer_code: 'bob', + salestax: '3.45', + salestax_indicator: 'Y', + level3_indicator: 'Y', + ship_to_zip: '12345', + ship_to_country: 'US', + shipping_amount: '1234', + ship_from_postal_code: '54321', + discount_amount: '5', + duty_amount: '2', + national_tax_indicator: '0', + national_tax_amount: '10', + order_date: '280810', + other_tax: '3', + summary_commodity_code: '123', + merchant_vat_number: '222', + customer_vat_number: '333', + freight_tax_amount: '4', + vat_invoice_number: '26', + tracking_number: '45', + shipping_company: 'UFedzon', + other_fees: '2', + line_items: [ + { + description: 'thing', + product_code: '23', + commodity_code: '444', + quantity: '15', + unit_of_measure: 'kropogs', + unit_cost: '4.5', + discount_indicator: 'Y', + tax_indicator: 'Y', + discount_amount: '1', + tax_rate: '8.25', + tax_amount: '12', + tax_type: '000', + extended_total: '500', + total: '525', + alternative_tax: '111' + }, + { + description: 'thing2', + product_code: '23', + commodity_code: '444', + quantity: '15', + unit_of_measure: 'kropogs', + unit_cost: '4.5', + discount_indicator: 'Y', + tax_indicator: 'Y', + discount_amount: '1', + tax_rate: '8.25', + tax_amount: '12', + tax_type: '000', + extended_total: '500', + total: '525', + alternative_tax: '111' + } + ] } @amount = 100 end @@ -39,7 +111,7 @@ def test_authorize_and_capture assert_equal 'APPROVAL', auth.message assert auth.authorization - assert capture = @gateway.capture(@amount, auth.authorization, :credit_card => @credit_card) + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(credit_card: @credit_card)) assert_success capture end @@ -54,7 +126,7 @@ def test_authorize_and_capture_with_auth_code end def test_unsuccessful_capture - assert response = @gateway.capture(@amount, '', :credit_card => @credit_card) + assert response = @gateway.capture(@amount, '', credit_card: @credit_card) assert_failure response assert_equal 'The FORCE Approval Code supplied in the authorization request appears to be invalid or blank. The FORCE Approval Code must be 6 or less alphanumeric characters.', response.message end @@ -69,7 +141,6 @@ def test_successful_verify assert response = @gateway.verify(@credit_card, @options) assert_success response assert_equal 'APPROVAL', response.message - assert_success response.responses.last, 'The void should succeed' end def test_failed_verify @@ -136,75 +207,286 @@ def test_authorize_and_successful_void assert response.authorization end + def test_successful_auth_and_capture_with_recurring_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: auth.network_transaction_id + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_purchase_with_recurring_token + options = { + email: 'human@domain.com', + description: 'Test Transaction', + billing_address: address, + ip: '203.0.113.0', + merchant_initiated_unscheduled: 'Y', + add_recurring_token: 'Y' + } + + purchase = @gateway.purchase(@amount, @credit_card, options) + + assert_success purchase + assert_equal 'APPROVAL', purchase.message + end + + # This test is essentially replicating a test on line 373 in order to get it passing. + # This test was part of the work to enable recurring transactions for Elavon. Recurring + # transactions aren't possible with Elavon unless cards are stored there as well. This work + # will be removed in a later cleanup ticket. + def test_successful_purchase_with_ssl_token + store_response = @tokenization_gateway.store(@credit_card, @options) + token = store_response.params['token'] + options = { + email: 'paul@domain.com', + description: 'Test Transaction', + billing_address: address, + ip: '203.0.113.0', + merchant_initiated_unscheduled: 'Y', + ssl_token: token + } + + purchase = @tokenization_gateway.purchase(@amount, token, options) + + assert_success purchase + assert_equal 'APPROVAL', purchase.message + end + + def test_successful_auth_and_capture_with_unscheduled_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: auth.network_transaction_id + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_auth_and_capture_with_installment_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: nil + } + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: auth.network_transaction_id + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + def test_successful_store_without_verify - assert response = @gateway.store(@credit_card, @options) + assert response = @tokenization_gateway.store(@credit_card, @options) assert_success response assert_nil response.message assert response.test? end def test_successful_store_with_verify_false - assert response = @gateway.store(@credit_card, @options.merge(verify: false)) + assert response = @tokenization_gateway.store(@credit_card, @options.merge(verify: false)) assert_success response assert_nil response.message assert response.test? end def test_successful_store_with_verify_true - assert response = @gateway.store(@credit_card, @options.merge(verify: true)) + assert response = @tokenization_gateway.store(@credit_card, @options.merge(verify: true)) assert_success response assert_equal 'APPROVAL', response.message assert response.test? end def test_unsuccessful_store - assert response = @gateway.store(@bad_credit_card, @options) + assert response = @tokenization_gateway.store(@bad_credit_card, @options) assert_failure response assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message assert response.test? end def test_successful_update - store_response = @gateway.store(@credit_card, @options) + store_response = @tokenization_gateway.store(@credit_card, @options) token = store_response.params['token'] - credit_card = credit_card('4124939999999990', :month => 10) - assert response = @gateway.update(token, credit_card, @options) + credit_card = credit_card('4000000000000002', month: 10) + assert response = @tokenization_gateway.update(token, credit_card, @options) assert_success response assert response.test? end def test_unsuccessful_update - assert response = @gateway.update('ABC123', @credit_card, @options) + assert response = @tokenization_gateway.update('ABC123', @credit_card, @options) assert_failure response assert_match %r{invalid}i, response.message assert response.test? end def test_successful_purchase_with_token - store_response = @gateway.store(@credit_card, @options) + store_response = @tokenization_gateway.store(@credit_card, @options) token = store_response.params['token'] - assert response = @gateway.purchase(@amount, token, @options) + assert response = @tokenization_gateway.purchase(@amount, token, @options) assert_success response assert response.test? + assert_not_empty response.params['token'] assert_equal 'APPROVAL', response.message end def test_failed_purchase_with_token - assert response = @gateway.purchase(@amount, 'ABC123', @options) + assert response = @tokenization_gateway.purchase(@amount, 'ABC123', @options) assert_failure response assert response.test? assert_match %r{invalid}i, response.message end def test_successful_purchase_with_custom_fields - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: {a_key: 'a value'})) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: { my_field: 'a value' })) assert_success response + assert_match response.params['my_field'], 'a value' + assert_equal 'APPROVAL', response.message assert response.test? + assert response.authorization + end + + def test_failed_purchase_with_multi_currency_terminal_setting_disabled + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD', multi_currency: true)) + + assert_failure response + assert response.test? + assert_equal 'Transaction currency is not allowed for this terminal. Your terminal must be setup with Multi currency', response.message + assert response.authorization + end + + def test_successful_purchase_with_multi_currency_gateway_setting + assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY')) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_successful_purchase_with_multi_currency_transaction_setting + @multi_currency_gateway.options[:multi_currency] = false + assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY', multi_currency: true)) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_successful_purchase_with_level_3_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(level_3_data: @level_3_data)) + + assert_success response assert_equal 'APPROVAL', response.message assert response.authorization end + def test_successful_purchase_with_shipping_address + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: @shipping_address)) + + assert_success response + assert_equal 'APPROVAL', response.message + assert response.authorization + 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_success response + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_successful_purchase_with_truncated_data + credit_card = @credit_card + credit_card.first_name = 'Rick & ™ \" < > Martínez įncogníto' + credit_card.last_name = 'Lesly Andrea Mart™nez estrada the last name' + @options[:billing_address][:city] = 'Saint-François-Xavier-de-Brompton' + @options[:billing_address][:address1] = 'Bats & Cats' + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_special_character_encoding_truncation + special_card = credit_card('4000000000000002') + special_card.first_name = 'Césaire' + special_card.last_name = 'Castañeda&%' + + response = @gateway.purchase(@amount, special_card, @options) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert_equal 'Césaire', response.params['first_name'] + assert_equal 'Castañeda&%', response.params['last_name'] + assert response.authorization + end + + def test_invalid_byte_sequence + special_card = credit_card('4000000000000002') + special_card.last_name = "Castaneda \255" # add invalid utf-8 byte + + response = @gateway.purchase(@amount, special_card, @options) + assert_success response + assert response.test? + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) @@ -212,8 +494,15 @@ 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[:password], transcript) end + def test_invalid_login + assert response = @bad_creds_gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert response.test? + assert_equal 'The credentials supplied in the authorization request are invalid.', response.message + end end diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb index 4cc1b565471..3a9f2dbc42f 100644 --- a/test/remote/gateways/remote_element_test.rb +++ b/test/remote/gateways/remote_element_test.rb @@ -8,22 +8,49 @@ 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( + '4000100011112224', + 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: '07', + transaction_id: 'abc123', + 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 @@ -50,17 +77,147 @@ def test_successful_purchase_with_shipping_address 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 + 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 + 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 + 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 + 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_success response + assert_equal 'Approved', 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_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_duplicate_override_flag + options = { + order_id: '2', + billing_address: address.merge(zip: '87654'), + description: 'Store Purchase' + } + + 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: 'true')) + assert_success response + assert_equal 'Approved', response.message + + # 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 + + 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_merchant_descriptor + response = @gateway.purchase(@amount, @credit_card, @options.merge(merchant_descriptor: 'Flowerpot Florists')) + 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 + response = @gateway.purchase(@amount, @apple_pay_network_token, @options) + assert_success response + assert_equal 'Approved', response.message + end + def test_successful_authorize_and_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture - assert_equal 'Success', capture.message + 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 @@ -70,7 +227,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -93,7 +250,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -103,6 +260,20 @@ def test_failed_refund 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_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -121,7 +292,7 @@ def test_failed_void def test_successful_verify response = @gateway.verify(@credit_card, @options) assert_success response - assert_match %r{Approved}, response.message + assert_equal 'Success', response.message end def test_successful_store diff --git a/test/remote/gateways/remote_epay_test.rb b/test/remote/gateways/remote_epay_test.rb index 3cde00ce108..226f4d2132c 100644 --- a/test/remote/gateways/remote_epay_test.rb +++ b/test/remote/gateways/remote_epay_test.rb @@ -5,23 +5,22 @@ def setup Base.mode = :test @gateway = EpayGateway.new(fixtures(:epay)) - @credit_card = credit_card('3333333333333000') @credit_card_declined = credit_card('3333333333333102') - @amount = 100 - @options = {order_id: '1'} + @options_xid = { order_id: generate_unique_id, three_d_secure: { eci: '7', xid: '123', cavv: '456', version: '2', ds_transaction_id: nil } } + @options_ds_transaction_id = { order_id: generate_unique_id, three_d_secure: { eci: '7', xid: nil, cavv: '456', version: '2', ds_transaction_id: '798' } } end - def test_successful_purchase - response = @gateway.purchase(@amount, @credit_card, @options) + def test_successful_purchase_xid + response = @gateway.purchase(@amount, @credit_card, @options_xid) assert_success response assert !response.authorization.blank? assert response.test? end - def test_successful_authorize_and_capture - response = @gateway.authorize(@amount, @credit_card, @options) + def test_successful_authorize_and_capture_xid + response = @gateway.authorize(@amount, @credit_card, @options_xid) assert_success response assert !response.authorization.blank? @@ -29,42 +28,84 @@ def test_successful_authorize_and_capture assert_success capture_response end - def test_failed_authorization - response = @gateway.authorize(@amount, @credit_card_declined, @options) - assert_failure response - end - - def test_failed_purchase - response = @gateway.purchase(@amount, @credit_card_declined, @options) + def test_failed_authorization_xid + response = @gateway.authorize(@amount, @credit_card_declined, @options_xid) assert_failure response end - def test_failed_capture - response = @gateway.capture(@amount, 0) + def test_failed_purchase_xid + response = @gateway.purchase(@amount, @credit_card_declined, @options_xid) assert_failure response end - def test_successful_refund - response = @gateway.purchase(@amount, @credit_card, @options) + def test_successful_refund_xid + response = @gateway.purchase(@amount, @credit_card, @options_xid) assert_success response refund_response = @gateway.refund(@amount, response.authorization) assert_success refund_response end - def test_failed_refund - response = @gateway.refund(@amount, 0) + def test_successful_void_xid + response = @gateway.authorize(@amount, @credit_card, @options_xid) + assert_success response + + void_response = @gateway.void(response.authorization) + assert_success void_response + end + + def test_successful_purchase_ds_transaction_id + response = @gateway.purchase(@amount, @credit_card, @options_ds_transaction_id) + assert_success response + assert !response.authorization.blank? + assert response.test? + end + + def test_successful_authorize_and_capture_ds_transaction_id + response = @gateway.authorize(@amount, @credit_card, @options_ds_transaction_id) + assert_success response + assert !response.authorization.blank? + + capture_response = @gateway.capture(@amount, response.authorization) + assert_success capture_response + end + + def test_failed_authorization_ds_transaction_id + response = @gateway.authorize(@amount, @credit_card_declined, @options_ds_transaction_id) + assert_failure response + end + + def test_failed_purchase_ds_transaction_id + response = @gateway.purchase(@amount, @credit_card_declined, @options_ds_transaction_id) assert_failure response end - def test_successful_void - response = @gateway.authorize(@amount, @credit_card, @options) + def test_successful_refund_ds_transaction_id + response = @gateway.purchase(@amount, @credit_card, @options_ds_transaction_id) + assert_success response + + refund_response = @gateway.refund(@amount, response.authorization) + assert_success refund_response + end + + def test_successful_void_ds_transaction_id + response = @gateway.authorize(@amount, @credit_card, @options_ds_transaction_id) assert_success response void_response = @gateway.void(response.authorization) assert_success void_response end + def test_failed_capture + response = @gateway.capture(@amount, 0) + assert_failure response + end + + def test_failed_refund + response = @gateway.refund(@amount, 0) + assert_failure response + end + def test_failed_void response = @gateway.void(0) assert_failure response diff --git a/test/remote/gateways/remote_evo_ca_test.rb b/test/remote/gateways/remote_evo_ca_test.rb index 9bc99560d4a..f1b6cb32ebe 100644 --- a/test/remote/gateways/remote_evo_ca_test.rb +++ b/test/remote/gateways/remote_evo_ca_test.rb @@ -7,12 +7,12 @@ def setup @amount = 100 @credit_card = credit_card('4111111111111111') @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :invoice => 'AB-1234', - :email => 'evo@example.com', - :ip => '127.0.0.1' + order_id: '1', + billing_address: address, + description: 'Store Purchase', + invoice: 'AB-1234', + email: 'evo@example.com', + ip: '127.0.0.1' } end @@ -72,7 +72,7 @@ def test_purchase_and_void def test_purchase_and_update assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert response = @gateway.update(response.authorization, :shipping_carrier => 'fedex', :tracking_number => '12345') + assert response = @gateway.update(response.authorization, shipping_carrier: 'fedex', tracking_number: '12345') assert_success response assert_equal EvoCaGateway::MESSAGES[100], response.message end @@ -102,7 +102,7 @@ def test_successful_credit def test_avs_match # To simulate an AVS Match, pass 888 in the address1 field, 77777 for zip. - opts = @options.merge(:billing_address => address({:address1 => '888', :zip => '77777'})) + opts = @options.merge(billing_address: address({ address1: '888', zip: '77777' })) assert response = @gateway.purchase(@amount, @credit_card, opts) assert_success response assert_equal 'Y', response.avs_result['code'] @@ -112,7 +112,7 @@ def test_avs_match def test_cvv_match # To simulate a CVV Match, pass 999 in the cvv field. - assert response = @gateway.purchase(@amount, credit_card('4111111111111111', :verification_value => 999), @options) + assert response = @gateway.purchase(@amount, credit_card('4111111111111111', verification_value: 999), @options) assert_success response assert_equal 'M', response.cvv_result['code'] end diff --git a/test/remote/gateways/remote_eway_managed_test.rb b/test/remote/gateways/remote_eway_managed_test.rb index 1322e32676c..c36eca3c21a 100644 --- a/test/remote/gateways/remote_eway_managed_test.rb +++ b/test/remote/gateways/remote_eway_managed_test.rb @@ -2,17 +2,17 @@ class RemoteEwayManagedTest < Test::Unit::TestCase def setup - @gateway = EwayManagedGateway.new(fixtures(:eway_managed).merge({ :test => true })) + @gateway = EwayManagedGateway.new(fixtures(:eway_managed).merge({ test: true })) - @valid_card='4444333322221111' - @valid_customer_id='9876543211000' + @valid_card = '4444333322221111' + @valid_customer_id = '9876543211000' @credit_card = credit_card(@valid_card) @options = { - :billing_address => { - :country => 'au', - :title => 'Mr.' + billing_address: { + country: 'au', + title: 'Mr.' } } @@ -29,9 +29,9 @@ def test_successful_purchase def test_invalid_login gateway = EwayManagedGateway.new( - :login => '', - :password => '', - :username => '' + login: '', + password: '', + username: '' ) assert response = gateway.purchase(@amount, @valid_customer_id, @options) assert_equal 'Login failed. ', response.message diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb index f0c33ba61e8..3113dec205d 100644 --- a/test/remote/gateways/remote_eway_rapid_test.rb +++ b/test/remote/gateways/remote_eway_rapid_test.rb @@ -17,14 +17,104 @@ def setup } end - def test_successful_purchase + def test_successful_purchase_with_billing_address response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_purchase_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_purchase_without_address + email = 'test@example.com' + + @options[:billing_address] = nil + @options[:email] = email + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_purchase_with_3ds1 + eci = '05' + cavv = 'AgAAAAAA4n1uzQPRaATeQAAAAAA=' + xid = 'AAAAAAAA4n1uzQPRaATeQAAAAAA=' + authentication_response_status = 'Y' + @options[:three_d_secure] = { + eci: eci, + cavv: cavv, + xid: xid, + authentication_response_status: authentication_response_status + } + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + end + + def test_successful_purchase_with_3ds2 + eci = '05' + cavv = 'AgAAAAAA4n1uzQPRaATeQAAAAAA=' + authentication_response_status = 'Y' + version = '2.1.0' + ds_transaction_id = '8fe2e850-a028-407e-9a18-c8cf7598ca10' + + @options[:three_d_secure] = { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + authentication_response_status: authentication_response_status + } + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + end + + def test_successful_purchase_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. end def test_fully_loaded_purchase - 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', @@ -73,12 +163,12 @@ def test_successful_purchase_with_overly_long_fields billing_address: { address1: 'The Billing Address 1 Cannot Be More Than Fifty Characters.', address2: 'The Billing Address 2 Cannot Be More Than Fifty Characters.', - city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart', + city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart' }, shipping_address: { address1: 'The Shipping Address 1 Cannot Be More Than Fifty Characters.', address2: 'The Shipping Address 2 Cannot Be More Than Fifty Characters.', - city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart', + city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart' } } @credit_card.first_name = 'FullNameOnACardMustBeLessThanFiftyCharacters' @@ -95,6 +185,61 @@ def test_failed_purchase assert_equal 'Invalid Payment TotalAmount', response.message end + def test_successful_authorize_with_billing_address + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_authorize_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_authorize_without_address + email = 'test@example.com' + + @options[:billing_address] = nil + @options[:email] = email + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_authorize_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. + end + def test_successful_authorize_and_capture authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize @@ -107,7 +252,7 @@ def test_successful_authorize_and_capture def test_failed_authorize response = @gateway.authorize(@failed_amount, @credit_card, @options) assert_failure response - assert_equal 'Error Failed', response.message + assert_equal '', response.message end def test_failed_capture @@ -127,7 +272,7 @@ def test_successful_void def test_failed_void response = @gateway.void('bogus') assert_failure response - assert_equal 'Invalid Auth Transaction ID for Capture/Void', response.message + assert_equal 'Failed', response.message end def test_successful_refund @@ -150,6 +295,22 @@ def test_successful_store response = @gateway.store(@credit_card, @options) assert_success response assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_store_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.store(@credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. end def test_failed_store @@ -160,13 +321,72 @@ def test_failed_store assert_equal 'Customer CountryCode Required', response.message end - def test_successful_update + def test_successful_update_with_billing_address response = @gateway.store(@credit_card, @options) assert_success response assert_equal 'Transaction Approved Successful', response.message response = @gateway.update(response.authorization, @credit_card, @options) assert_success response assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_update_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_update_without_address + email = 'test@example.com' + @options[:email] = email + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + @options[:billing_address] = nil + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_update_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. end def test_successful_store_purchase @@ -190,12 +410,31 @@ def test_invalid_login end def test_transcript_scrubbing + credit_card_success = credit_card('4444333322221111', verification_value: 976225) + transcript = capture_transcript(@gateway) do - @gateway.purchase(100, @credit_card_success, @params) + @gateway.purchase(100, credit_card_success, {}) end + clean_transcript = @gateway.scrub(transcript) - assert_scrubbed(@credit_card_success.number, clean_transcript) - assert_scrubbed(@credit_card_success.verification_value.to_s, clean_transcript) + assert_scrubbed(credit_card_success.number, clean_transcript) + assert_scrubbed(credit_card_success.verification_value.to_s, clean_transcript) + end + + private + + def assert_address_match(customer, address) + assert_equal customer['FirstName'], address[:name].split[0] + assert_equal customer['LastName'], address[:name].split[1] + assert_equal customer['CompanyName'], address[:company] + assert_equal customer['Street1'], address[:address1] + assert_equal customer['Street2'], address[:address2] + assert_equal customer['City'], address[:city] + assert_equal customer['State'], address[:state] + assert_equal customer['PostalCode'], address[:zip] + assert_equal customer['Country'], address[:country].to_s.downcase + assert_equal customer['Phone'], address[:phone] + assert_equal customer['Fax'], address[:fax] end end diff --git a/test/remote/gateways/remote_eway_test.rb b/test/remote/gateways/remote_eway_test.rb index a8d7d583874..4c306709656 100644 --- a/test/remote/gateways/remote_eway_test.rb +++ b/test/remote/gateways/remote_eway_test.rb @@ -4,21 +4,17 @@ 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', - :email => 'bob@testbob.com', - :billing_address => { :address1 => '47 Bobway', - :city => 'Bobville', - :state => 'WA', - :country => 'AU', - :zip => '2000' - }, - :description => 'purchased items' + order_id: '1230123', + email: 'bob@testbob.com', + billing_address: { address1: '47 Bobway', + city: 'Bobville', + state: 'WA', + country: 'AU', + zip: '2000' }, + description: 'purchased items' } end @@ -72,7 +68,7 @@ def test_failed_refund end def test_transcript_scrubbing - @credit_card_success.verification_value = '431' + @credit_card_success.verification_value = '431' transcript = capture_transcript(@gateway) do @gateway.purchase(100, @credit_card_success, @params) end diff --git a/test/remote/gateways/remote_exact_test.rb b/test/remote/gateways/remote_exact_test.rb index 8843e170d74..c3a46234636 100644 --- a/test/remote/gateways/remote_exact_test.rb +++ b/test/remote/gateways/remote_exact_test.rb @@ -6,9 +6,9 @@ def setup @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -49,8 +49,8 @@ def test_failed_capture end def test_invalid_login - gateway = ExactGateway.new(:login => 'NotARealUser', - :password => 'NotARealPassword') + gateway = ExactGateway.new(login: 'NotARealUser', + password: 'NotARealPassword') assert response = gateway.purchase(@amount, @credit_card, @options) assert_match %r{^Invalid Login}, response.message assert_failure response diff --git a/test/remote/gateways/remote_ezic_test.rb b/test/remote/gateways/remote_ezic_test.rb index 7aacb3f3a74..85ec4c441d3 100644 --- a/test/remote/gateways/remote_ezic_test.rb +++ b/test/remote/gateways/remote_ezic_test.rb @@ -46,7 +46,7 @@ def test_failed_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount+30, auth.authorization) + assert capture = @gateway.capture(@amount + 30, auth.authorization) assert_failure capture assert_match(/Settlement amount cannot exceed authorized amount/, capture.message) end @@ -64,7 +64,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund assert_equal 'TEST RETURNED', refund.message assert_equal '-0.99', refund.params['settle_amount'] diff --git a/test/remote/gateways/remote_fat_zebra_test.rb b/test/remote/gateways/remote_fat_zebra_test.rb index fa0ad80219b..ff8afe7c584 100644 --- a/test/remote/gateways/remote_fat_zebra_test.rb +++ b/test/remote/gateways/remote_fat_zebra_test.rb @@ -9,8 +9,8 @@ def setup @declined_card = credit_card('4557012345678902') @options = { - :order_id => rand(100000).to_s, - :ip => '1.2.3.4' + order_id: generate_unique_id, + ip: '1.2.3.4' } end @@ -21,14 +21,14 @@ def test_successful_purchase end def test_successful_multi_currency_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) assert_success response assert_equal 'Approved', response.message assert_equal 'USD', response.params['response']['currency'] end def test_unsuccessful_multi_currency_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'XYZ')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'XYZ')) assert_failure response assert_match(/Currency XYZ is not valid for this merchant/, response.message) end @@ -64,12 +64,12 @@ def test_successful_authorize_and_capture end def test_multi_currency_authorize_and_capture - assert auth_response = @gateway.authorize(@amount, @credit_card, @options.merge(:currency => 'USD')) + assert auth_response = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD')) assert_success auth_response assert_equal 'Approved', auth_response.message assert_equal 'USD', auth_response.params['response']['currency'] - assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options.merge(:currency => 'USD')) + assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options.merge(currency: 'USD')) assert_success capture_response assert_equal 'Approved', capture_response.message assert_equal 'USD', capture_response.params['response']['currency'] @@ -118,8 +118,10 @@ def test_successful_void def test_successful_void_refund purchase = @gateway.purchase(@amount, @credit_card, @options) - puts purchase.inspect + assert_success purchase + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund assert response = @gateway.void(refund.authorization, @options) assert_success response @@ -139,35 +141,78 @@ def test_store assert_not_nil card.authorization end + def test_successful_store_without_cvv + credit_card = @credit_card + credit_card.verification_value = nil + assert card = @gateway.store(credit_card, recurring: true) + + assert_success card + assert_not_nil card.authorization + end + + def test_failed_store_without_cvv + credit_card = @credit_card + credit_card.verification_value = nil + assert card = @gateway.store(credit_card) + + assert_failure card + assert_match %r{CVV is required}, card.message + end + def test_purchase_with_token assert card = @gateway.store(@credit_card) - assert purchase = @gateway.purchase(@amount, card.authorization, @options.merge(:cvv => 123)) + assert purchase = @gateway.purchase(@amount, card.authorization, @options.merge(cvv: 123)) assert_success purchase end def test_successful_purchase_with_descriptor - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant => 'Merchant', :merchant_location => 'Location')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(merchant: 'Merchant', merchant_location: 'Location')) assert_success response assert_equal 'Approved', response.message end + def test_successful_purchase_with_metadata + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(metadata: { description: 'Invoice #1234356' })) + assert_success response + assert_equal 'Approved', response.message + assert_equal 'Invoice #1234356', response.params['response']['metadata']['description'] + end + def test_successful_purchase_with_3DS_information - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :xid => 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', :sli => '05')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(cavv: 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', xid: 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', sli: '05')) assert_success response assert_equal 'Approved', response.message end def test_failed_purchase_with_incomplete_3DS_information - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :sli => '05')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(xid: 'MGVmMmNlMzI4NjAyOWU2ZDgwNTZ=', sli: '05')) + assert_failure response + assert_match %r{Extra/cavv is required for SLI 05}, response.message + end + + def test_successful_purchase_with_3DS_information_using_standard_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure: { cavv: 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', xid: 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', eci: '05' })) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase_with_incomplete_3DS_information_using_standard_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure: { xid: 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', eci: '05' })) assert_failure response - assert_match %r{Extra/xid is required for SLI 05}, response.message + assert_match %r{Extra/cavv is required for SLI 05}, response.message + end + + def test_successful_purchase_with_card_on_file_information + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(recurring: true, extra: { card_on_file: true, auth_reason: 'U' })) + assert_success response + assert_equal 'Approved', response.message end def test_invalid_login gateway = FatZebraGateway.new( - :username => 'invalid', - :token => 'wrongtoken' - ) + username: 'invalid', + token: 'wrongtoken' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid Login', response.message diff --git a/test/remote/gateways/remote_federated_canada_test.rb b/test/remote/gateways/remote_federated_canada_test.rb index 3b48d1795af..94eb40e86c9 100644 --- a/test/remote/gateways/remote_federated_canada_test.rb +++ b/test/remote/gateways/remote_federated_canada_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteFederatedCanadaTest < Test::Unit::TestCase - def setup @gateway = FederatedCanadaGateway.new(fixtures(:federated_canada)) @@ -11,9 +10,9 @@ def setup @credit_card = credit_card('4111111111111111') # Visa @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Active Merchant Remote Test Purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Active Merchant Remote Test Purchase' } end @@ -77,9 +76,9 @@ def test_authorize_and_capture def test_invalid_login gateway = FederatedCanadaGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Error in transaction data or system error', response.message diff --git a/test/remote/gateways/remote_finansbank_test.rb b/test/remote/gateways/remote_finansbank_test.rb index 753df513fe9..df5da0c203b 100644 --- a/test/remote/gateways/remote_finansbank_test.rb +++ b/test/remote/gateways/remote_finansbank_test.rb @@ -12,10 +12,10 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :order_id => '#' + generate_unique_id, - :billing_address => address, - :description => 'Store Purchase', - :email => 'xyz@gmail.com' + order_id: '#' + generate_unique_id, + billing_address: address, + description: 'Store Purchase', + email: 'xyz@gmail.com' } end @@ -89,9 +89,9 @@ def test_void def test_invalid_login gateway = FinansbankGateway.new( - :login => '', - :password => '', - :client_id => '' + login: '', + password: '', + client_id: '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_first_giving_test.rb b/test/remote/gateways/remote_first_giving_test.rb index aa37014c925..e3a291058b8 100644 --- a/test/remote/gateways/remote_first_giving_test.rb +++ b/test/remote/gateways/remote_first_giving_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteFirstGivingTest < Test::Unit::TestCase - def setup @gateway = FirstGivingGateway.new(fixtures(:first_giving)) @@ -47,10 +46,10 @@ def test_failed_refund def test_invalid_login gateway = FirstGivingGateway.new( - application_key: '25151616', - security_token: '63131jnkj', - charity_id: '1234' - ) + application_key: '25151616', + security_token: '63131jnkj', + charity_id: '1234' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'An error occurred. Please check your input and try again.', response.message diff --git a/test/remote/gateways/remote_first_pay_test.rb b/test/remote/gateways/remote_first_pay_test.rb index 6e174f76976..3aa090b7869 100644 --- a/test/remote/gateways/remote_first_pay_test.rb +++ b/test/remote/gateways/remote_first_pay_test.rb @@ -112,7 +112,7 @@ def test_invalid_login end def test_recurring_payment - @options.merge!({recurring: 1, recurring_start_date: DateTime.now.strftime('%m/%d/%Y'), recurring_end_date: DateTime.now.strftime('%m/%d/%Y'), recurring_type: 'monthly'}) + @options.merge!({ recurring: 1, recurring_start_date: DateTime.now.strftime('%m/%d/%Y'), recurring_end_date: DateTime.now.strftime('%m/%d/%Y'), recurring_type: 'monthly' }) response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'Approved', response.message diff --git a/test/remote/gateways/remote_firstdata_e4_test.rb b/test/remote/gateways/remote_firstdata_e4_test.rb index f7d88e60197..e8a84ac81da 100755 --- a/test/remote/gateways/remote_firstdata_e4_test.rb +++ b/test/remote/gateways/remote_firstdata_e4_test.rb @@ -8,9 +8,9 @@ def setup @credit_card_with_track_data = credit_card_with_track_data('4003000123456781') @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @options_with_authentication_data = @options.merge({ eci: '5', @@ -26,7 +26,8 @@ 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 ) @@ -37,7 +38,7 @@ def test_successful_purchase_with_network_tokenization end def test_successful_purchase_with_specified_currency - options_with_specified_currency = @options.merge({currency: 'GBP'}) + options_with_specified_currency = @options.merge({ currency: 'GBP' }) assert response = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) assert_match(/Transaction Normal/, response.message) assert_success response @@ -119,7 +120,7 @@ def test_purchase_and_credit end def test_purchase_and_credit_with_specified_currency - options_with_specified_currency = @options.merge({currency: 'GBP'}) + options_with_specified_currency = @options.merge({ currency: 'GBP' }) assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) assert_success purchase assert purchase.authorization @@ -178,8 +179,8 @@ def test_failed_verify end def test_invalid_login - gateway = FirstdataE4Gateway.new(:login => 'NotARealUser', - :password => 'NotARealPassword') + gateway = FirstdataE4Gateway.new(login: 'NotARealUser', + password: 'NotARealPassword') assert response = gateway.purchase(@amount, @credit_card, @options) assert_match %r{Unauthorized Request}, response.message assert_failure response @@ -204,7 +205,7 @@ def test_refund end def test_refund_with_specified_currency - options_with_specified_currency = @options.merge({currency: 'GBP'}) + options_with_specified_currency = @options.merge({ currency: 'GBP' }) assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) assert_match(/Transaction Normal/, purchase.message) assert_success purchase @@ -248,5 +249,4 @@ def test_transcript_scrubbing assert_scrubbed(cc_with_different_cvc.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/test/remote/gateways/remote_firstdata_e4_v27_test.rb b/test/remote/gateways/remote_firstdata_e4_v27_test.rb index 0bf4fc8c79c..ef3f1ddb6ea 100644 --- a/test/remote/gateways/remote_firstdata_e4_v27_test.rb +++ b/test/remote/gateways/remote_firstdata_e4_v27_test.rb @@ -4,14 +4,14 @@ class RemoteFirstdataE4V27Test < Test::Unit::TestCase def setup @gateway = FirstdataE4V27Gateway.new(fixtures(:firstdata_e4_v27)) @credit_card = credit_card - @credit_card_master = credit_card('5500000000000004', :brand => 'master') + @credit_card_master = credit_card('5500000000000004', brand: 'master') @bad_credit_card = credit_card('4111111111111113') @credit_card_with_track_data = credit_card_with_track_data('4003000123456781') @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @options_with_authentication_data = @options.merge({ eci: '5', @@ -27,7 +27,8 @@ 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 ) @@ -91,6 +92,7 @@ def test_successful_purchase_with_stored_credentials_initial assert_match(/Transaction Normal/, response.message) assert_success response assert_equal '1', response.params['stored_credentials_indicator'] + assert_equal 'C', response.params['stored_credentials_initiation'] assert_equal 'U', response.params['stored_credentials_schedule'] assert_not_nil response.params['stored_credentials_transaction_id'] end @@ -208,10 +210,10 @@ def test_failed_verify end def test_invalid_login - gateway = FirstdataE4V27Gateway.new(:login => 'NotARealUser', - :password => 'NotARealPassword', - :key_id => 'NotARealKey', - :hmac_key => 'NotARealHMAC') + gateway = FirstdataE4V27Gateway.new(login: 'NotARealUser', + password: 'NotARealPassword', + key_id: 'NotARealKey', + hmac_key: 'NotARealHMAC') assert response = gateway.purchase(@amount, @credit_card, @options) assert_match %r{Unauthorized Request}, response.message assert_failure response @@ -267,5 +269,4 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:password], transcript) assert_scrubbed(@gateway.options[:hmac_key], transcript) end - end diff --git a/test/remote/gateways/remote_forte_test.rb b/test/remote/gateways/remote_forte_test.rb index 8a2df5bccc8..e38434eb795 100644 --- a/test/remote/gateways/remote_forte_test.rb +++ b/test/remote/gateways/remote_forte_test.rb @@ -10,13 +10,13 @@ def setup @check = check @bad_check = check({ - :name => 'Jim Smith', - :bank_name => 'Bank of Elbonia', - :routing_number => '1234567890', - :account_number => '0987654321', - :account_holder_type => '', - :account_type => 'checking', - :number => '0' + name: 'Jim Smith', + bank_name: 'Bank of Elbonia', + routing_number: '1234567890', + account_number: '0987654321', + account_holder_type: '', + account_type: 'checking', + number: '0' }) @options = { @@ -43,6 +43,38 @@ def test_successful_purchase_with_echeck response = @gateway.purchase(@amount, @check, @options) assert_success response assert_equal 'APPROVED', response.message + assert_equal 'PPD', response.params['echeck']['sec_code'] + end + + def test_successful_purchase_with_xdata + @options = @options.merge({ + xdata: { + xdata_1: 'some customer metadata', + xdata_2: 'some customer metadata', + xdata_3: 'some customer metadata', + xdata_4: 'some customer metadata', + xdata_5: 'some customer metadata', + xdata_6: 'some customer metadata', + xdata_7: 'some customer metadata', + xdata_8: 'some customer metadata', + xdata_9: 'some customer metadata' + } + }) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + (1..9).each { |n| assert_equal 'some customer metadata', response.params['xdata']["xdata_#{n}"] } + end + + def test_successful_purchase_with_echeck_with_more_options + options = { + sec_code: 'WEB' + } + + response = @gateway.purchase(@amount, @check, options) + assert_success response + assert_equal 'APPROVED', response.message + assert_equal 'WEB', response.params['echeck']['sec_code'] end def test_failed_purchase_with_echeck @@ -111,7 +143,7 @@ def test_partial_capture wait_for_authorization_to_clear - assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) assert_success capture end @@ -134,7 +166,7 @@ def test_partial_credit purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.credit(@amount-1, @credit_card, @options) + assert refund = @gateway.credit(@amount - 1, @credit_card, @options) assert_success refund end @@ -202,10 +234,19 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) end + def test_account_number_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + end + private def wait_for_authorization_to_clear sleep(10) end - end diff --git a/test/remote/gateways/remote_garanti_test.rb b/test/remote/gateways/remote_garanti_test.rb index 189fba6ddd0..df6922f6c73 100644 --- a/test/remote/gateways/remote_garanti_test.rb +++ b/test/remote/gateways/remote_garanti_test.rb @@ -2,7 +2,6 @@ # NOTE: tests may fail randomly because Garanti returns random(!) responses for their test server class RemoteGarantiTest < Test::Unit::TestCase - def setup @gateway = GarantiGateway.new(fixtures(:garanti)) @@ -11,9 +10,9 @@ def setup @credit_card = credit_card('4282209027132016', month: 5, year: 2018, verification_value: 358) @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store Purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' } end @@ -51,11 +50,11 @@ def test_failed_capture def test_invalid_login gateway = GarantiGateway.new( - :login => 'PROVAUT', - :terminal_id => '30691300', - :merchant_id => '', - :password => '' - ) + login: 'PROVAUT', + terminal_id: '30691300', + merchant_id: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal '0651', response.params['reason_code'] diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb index b7497b13f00..f8657744af1 100644 --- a/test/remote/gateways/remote_global_collect_test.rb +++ b/test/remote/gateways/remote_global_collect_test.rb @@ -3,10 +3,46 @@ class RemoteGlobalCollectTest < Test::Unit::TestCase def setup @gateway = GlobalCollectGateway.new(fixtures(:global_collect)) + @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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '01', + year: Time.new.year + 2, + first_name: 'John', + last_name: 'Smith', + eci: '05', + source: :apple_pay + ) + + @google_pay = network_tokenization_credit_card( + '4567350000427977', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '01', + year: Time.new.year + 2, + source: :google_pay, + transaction_id: '123456789', + eci: '05' + ) + + @google_pay_pan_only = credit_card( + '4567350000427977', + month: '01', + year: Time.new.year + 2 + ) + @accepted_amount = 4005 @rejected_amount = 2997 @options = { @@ -14,18 +50,115 @@ def setup billing_address: address, description: 'Store Purchase' } + @long_address = { + billing_address: { + address1: '1234 Supercalifragilisticexpialidociousthiscantbemorethanfiftycharacters', + city: 'Portland', + state: 'ME', + zip: '09901', + country: 'US' + } + } + @preprod_options = { + order_id: SecureRandom.hex(15), + email: 'email@example.com', + billing_address: address + } end def test_successful_purchase response = @gateway.purchase(@accepted_amount, @credit_card, @options) assert_success response assert_equal 'Succeeded', response.message + 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) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status'] + end + + def test_successful_purchase_with_cabal + options = @preprod_options.merge(requires_approval: false, currency: 'ARS') + response = @gateway_preprod.purchase(1000, @cabal_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status'] + end + + def test_successful_purchase_with_apple_pay + options = @preprod_options.merge(requires_approval: false, currency: 'USD') + response = @gateway_preprod.purchase(4500, @apple_pay, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status'] + end + + 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_apple_pay_ogone_direct + options = @preprod_options.merge(requires_approval: false, currency: 'USD') + response = @gateway_direct.purchase(100, @apple_pay, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'PENDING_CAPTURE', response.params['payment']['status'] + end + + def test_successful_authorize_and_capture_with_apple_pay_ogone_direct + options = @preprod_options.merge(requires_approval: false, currency: 'USD') + 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_successful_purchase_with_google_pay + options = @preprod_options.merge(requires_approval: false) + response = @gateway_preprod.purchase(4500, @google_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) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'CAPTURE_REQUESTED', 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) + + assert_failure response + assert_equal 'order.customer.merchantCustomerId is missing for UCOF', response.message end def test_successful_purchase_with_fraud_fields options = @options.merge( - fraud_fields: - { + fraud_fields: { 'website' => 'www.example.com', 'giftMessage' => 'Happy Day!' } @@ -55,20 +188,286 @@ def test_successful_purchase_with_more_options assert_equal 'Succeeded', response.message end + def test_successful_purchase_with_installments + 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 + + # When requires_approval is true (or not present), + # `purchase` will make both an `auth` and a `capture` call + def test_successful_purchase_with_requires_approval_true + options = @options.merge(requires_approval: true) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + 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_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: { + 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' + } + ) + + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_match 'jJ81HADVRtXfCBATEp01CJUAAAA=', response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['threeDSecureResults']['cavv'] + 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: { + code: 111, + name: 'Spreedly Airlines', + flight_date: '20190810', + passenger_name: 'Randi Smith', + is_eticket: 'true', + is_restricted_ticket: 'true', + is_third_party: 'true', + issue_date: 'tday', + merchant_customer_id: 'MIDs', + agent_numeric_code: '12345', + passengers: [ + { first_name: 'Randi', + surname: 'Smith', + surname_prefix: 'S', + title: 'Mr' }, + { first_name: 'Julia', + surname: 'Smith', + surname_prefix: 'S', + title: 'Mrs' } + ], + flight_legs: [ + { airline_class: 'ZZ', + arrival_airport: 'BDL', + arrival_time: '0520', + carrier_code: 'SA', + conjunction_ticket: 'ct-12', + coupon_number: '1', + date: '20190810', + departure_time: '1220', + endorsement_or_restriction: 'no', + exchange_ticket: 'no', + fare: '20000', + fare_basis: 'fareBasis', + fee: '12', + flight_number: '1', + number: 596, + origin_airport: 'RDU', + passenger_class: 'coach', + stopover_code: 'permitted', + taxes: '700' }, + { arrival_airport: 'RDU', + origin_airport: 'BDL', + date: '20190817', + carrier_code: 'SA', + number: 597, + airline_class: 'ZZ' } + ] + } + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase_with_insufficient_airline_data + options = @options.merge( + airline_data: { + flight_date: '20190810', + passenger_name: 'Randi Smith' + } + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_equal 'PARAMETER_NOT_FOUND_IN_REQUEST', response.message + property_names = response.params['errors'].collect { |e| e['propertyName'] } + assert property_names.include? 'order.additionalInput.airlineData.code' + assert property_names.include? 'order.additionalInput.airlineData.name' + end + + def test_successful_purchase_with_lodging_data + options = @options.merge( + lodging_data: { + charges: [ + { charge_amount: '1000', + charge_amount_currency_code: 'USD', + charge_type: 'giftshop' } + ], + check_in_date: '20211223', + check_out_date: '20211227', + folio_number: 'randAssortmentofChars', + is_confirmed_reservation: 'true', + is_facility_fire_safety_conform: 'true', + is_no_show: 'false', + is_preference_smoking_room: 'false', + number_of_adults: '2', + number_of_nights: '1', + number_of_rooms: '1', + program_code: 'advancedDeposit', + property_customer_service_phone_number: '5555555555', + property_phone_number: '5555555555', + renter_name: 'Guy', + rooms: [ + { daily_room_rate: '25000', + daily_room_rate_currency_code: 'USD', + daily_room_tax_amount: '5', + daily_room_tax_amount_currency_code: 'USD', + number_of_nights_at_room_rate: '1', + room_location: 'Courtyard', + type_of_bed: 'Queen', + type_of_room: 'Walled' } + ] + } + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + end + def test_successful_purchase_with_very_long_name - credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname'}) + credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname' }) + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_blank_name + credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil }) response = @gateway.purchase(@amount, credit_card, @options) assert_success response assert_equal 'Succeeded', response.message end + def test_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 + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_payment_product_id + options = @preprod_options.merge(requires_approval: false, currency: 'ARS') + response = @gateway_preprod.purchase(1000, @cabal_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 135, response.params['payment']['paymentOutput']['cardPaymentMethodSpecificOutput']['paymentProductId'] + end + + def test_successful_purchase_with_truncated_split_address + response = @gateway.purchase(@amount, @credit_card, @long_address) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_failed_purchase response = @gateway.purchase(@rejected_amount, @declined_card, @options) assert_failure response assert_equal 'Not authorised', response.message end + def test_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 @@ -78,19 +477,30 @@ def test_successful_authorize_and_capture assert_equal 'Succeeded', capture.message end + def test_authorize_with_optional_idempotency_key_header + response = @gateway.authorize(@accepted_amount, @credit_card, @options.merge(idempotency_key: 'test123')) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response assert_equal '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 @@ -132,30 +542,72 @@ 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 assert_match %r{UNKNOWN_PAYMENT_ID}, response.message end + def test_failed_repeat_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Succeeded', void.message + + assert repeat_void = @gateway.void(auth.authorization) + assert_failure repeat_void + end + + def test_successful_inquire + response = @gateway.purchase(@accepted_amount, @credit_card, @options) + assert_success response + + response = @gateway.inquire(response.params['payment']['id']) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + def test_successful_verify response = @gateway.verify(@credit_card, @options) assert_success response 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) assert_failure response - assert_match %r{UNKNOWN_SERVER_ERROR}, response.message + assert_match %r{MISSING_OR_INVALID_AUTHORIZATION}, response.message end def test_transcript_scrubbing @@ -168,4 +620,49 @@ 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 + @gateway_preprod.purchase(@amount, @apple_pay, options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@apple_pay.payment_cryptogram, transcript) + assert_scrubbed(@apple_pay.number, transcript) + end + + def test_successful_preprod_auth_and_capture + options = @preprod_options.merge(requires_approval: true) + auth = @gateway_preprod.authorize(@accepted_amount, @preprod_card, options) + assert_success auth + + assert capture = @gateway_preprod.capture(@amount, auth.authorization, options) + assert_success capture + assert_equal 'CAPTURE_REQUESTED', capture.params['payment']['status'] + end + + def test_successful_preprod_purchase + options = @preprod_options.merge(requires_approval: false) + assert purchase = @gateway_preprod.purchase(@accepted_amount, @preprod_card, options) + assert_success purchase + end + + def test_successful_preprod_void + options = @preprod_options.merge(requires_approval: true) + auth = @gateway_preprod.authorize(@amount, @preprod_card, options) + assert_success auth + + assert void = @gateway_preprod.void(auth.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end end diff --git a/test/remote/gateways/remote_global_transport_test.rb b/test/remote/gateways/remote_global_transport_test.rb index eac5264fe9c..22ca103956f 100644 --- a/test/remote/gateways/remote_global_transport_test.rb +++ b/test/remote/gateways/remote_global_transport_test.rb @@ -9,7 +9,7 @@ def setup @options = { email: 'john@example.com', order_id: '1', - billing_address: address, + billing_address: address } end @@ -137,5 +137,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:global_password], transcript) end - end diff --git a/test/remote/gateways/remote_hdfc_test.rb b/test/remote/gateways/remote_hdfc_test.rb index e3f2ed395d5..2b92b92fea6 100644 --- a/test/remote/gateways/remote_hdfc_test.rb +++ b/test/remote/gateways/remote_hdfc_test.rb @@ -11,12 +11,12 @@ def setup # Use an American Express card to simulate a failure since HDFC does not # support any proper decline cards outside of 3D secure failures. - @declined_card = credit_card('377182068239368', :brand => :american_express) + @declined_card = credit_card('377182068239368', brand: :american_express) @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store Purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' } end @@ -61,15 +61,15 @@ def test_successful_refund end def test_passing_billing_address - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => address)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address)) assert_success response end def test_invalid_login gateway = HdfcGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'TranPortal ID required.', response.message diff --git a/test/remote/gateways/remote_hps_test.rb b/test/remote/gateways/remote_hps_test.rb index 2650965ecc5..9f7a0e08c24 100644 --- a/test/remote/gateways/remote_hps_test.rb +++ b/test/remote/gateways/remote_hps_test.rb @@ -3,10 +3,13 @@ class RemoteHpsTest < Test::Unit::TestCase def setup @gateway = HpsGateway.new(fixtures(:hps)) + @check_gateway = HpsGateway.new(fixtures(:hps_echeck)) @amount = 100 + @check_amount = 2000 @declined_amount = 1034 @credit_card = credit_card('4000100011112224') + @check = check(account_number: '1357902468', routing_number: '122000030', number: '1234', account_type: 'SAVINGS') @options = { order_id: '1', @@ -21,6 +24,13 @@ def test_successful_purchase assert_equal 'Success', response.message end + def test_successful_check_purchase + options = @options.merge(company_name: 'Hot Buttered Toast Incorporated') + response = @check_gateway.purchase(@check_amount, @check, options) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_purchase_without_cardholder response = @gateway.purchase(@amount, @credit_card) assert_success response @@ -43,6 +53,13 @@ def test_successful_purchase_with_descriptor assert_equal 'Success', response.message end + def test_successful_purchase_with_hyphenated_zip + @options[:billing_address][:zip] = '12345-1234' + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_purchase_no_address options = { order_id: '1', @@ -97,7 +114,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -106,7 +123,7 @@ def test_failed_capture assert_failure response end - def test_successful_refund + def test_successful_purchase_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -116,11 +133,37 @@ def test_successful_refund assert_equal '0', refund.params['GatewayRspCode'] end - def test_partial_refund + def test_successful_capture_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount, capture.authorization) + assert_success refund + assert_equal 'Success', refund.params['GatewayRspMsg'] + assert_equal '0', refund.params['GatewayRspCode'] + end + + def test_partial_purchase_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.params['GatewayRspMsg'] + assert_equal '0', refund.params['GatewayRspCode'] + end + + def test_partial_capture_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount - 1, capture.authorization) assert_success refund assert_equal 'Success', refund.params['GatewayRspMsg'] assert_equal '0', refund.params['GatewayRspCode'] @@ -131,6 +174,17 @@ def test_failed_refund assert_failure response end + def test_successful_credit + credit = @gateway.credit(@amount, @credit_card, @options) + assert_success credit + assert_equal 'Success', credit.params['GatewayRspMsg'] + end + + def test_failed_credit + credit = @gateway.credit(nil, @credit_card) + assert_failure credit + end + def test_successful_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -140,6 +194,16 @@ def test_successful_void assert_equal 'Success', void.params['GatewayRspMsg'] end + def test_successful_check_void + options = @options.merge(company_name: 'Hot Buttered Toast Incorporated') + purchase = @check_gateway.purchase(@check_amount, @check, options) + assert_success purchase + + assert void = @check_gateway.void(purchase.authorization, @options.merge(check_void: true)) + assert_success void + assert_equal 'Success', void.params['GatewayRspMsg'] + end + def test_failed_void response = @gateway.void('123') assert_failure response @@ -207,6 +271,31 @@ def test_successful_purchase_with_swipe_no_encryption assert_equal 'Success', response.message end + def test_successful_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Success', initial_response.message + assert_not_nil initial_response.params['CardBrandTxnId'] + network_transaction_id = initial_response.params['CardBrandTxnId'] + + used_options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, used_options) + assert_success response + assert_equal 'Success', response.message + end + def test_failed_purchase_with_swipe_bad_track_data @credit_card.track_data = '%B547888879888877776?;?' response = @gateway.purchase(@amount, @credit_card, @options) @@ -235,6 +324,13 @@ def test_successful_purchase_with_swipe_encryption_type_02 assert_equal 'Success', response.message end + def test_successful_purchase_with_truncated_invoicenbr + response = @gateway.purchase(@amount, @credit_card, @options.merge(order_id: '04863692e6b56aaed85760b3d0879afd18b980da0521f6454c007a838435e561')) + + assert_success response + assert_equal 'Success', response.message + end + def tests_successful_verify response = @gateway.verify(@credit_card, @options) @@ -261,4 +357,266 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:secret_api_key], transcript) end + + def test_transcript_scrubbing_with_cryptogram + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(@gateway.options[:secret_api_key], transcript) + assert_scrubbed(credit_card.payment_cryptogram, transcript) + end + + def test_account_number_scrubbing + options = @options.merge(company_name: 'Hot Buttered Toast Incorporated') + transcript = capture_transcript(@check_gateway) do + @check_gateway.purchase(@check_amount, @check, options) + end + clean_transcript = @check_gateway.scrub(transcript) + assert_scrubbed(@check.account_number, clean_transcript) + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :google_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_visa + @credit_card.number = '4012002000060016' + @credit_card.brand = 'visa' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_mastercard + @credit_card.number = '5473500000000014' + @credit_card.brand = 'master' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_discover + @credit_card.number = '6011000990156527' + @credit_card.brand = 'discover' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_amex + @credit_card.number = '372700699251018' + @credit_card.brand = 'american_express' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_jcb + @credit_card.number = '372700699251018' + @credit_card.brand = 'jcb' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end end diff --git a/test/remote/gateways/remote_iats_payments_test.rb b/test/remote/gateways/remote_iats_payments_test.rb index 34d604b7b92..f7b4bfe2ec1 100644 --- a/test/remote/gateways/remote_iats_payments_test.rb +++ b/test/remote/gateways/remote_iats_payments_test.rb @@ -9,9 +9,14 @@ def setup @credit_card = credit_card('4222222222222220') @check = check(routing_number: '111111111', account_number: '12345678') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Store purchase' + } + @customer_details = { + phone: '5555555555', + email: 'test@example.com', + country: 'US' } end @@ -23,6 +28,14 @@ def test_successful_purchase assert response.authorization end + def test_successful_purchase_with_customer_details + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@customer_details)) + assert_success response + assert response.test? + assert_equal 'Success', response.message + assert response.authorization + end + def test_failed_purchase credit_card = credit_card('4111111111111111') assert response = @gateway.purchase(200, credit_card, @options) @@ -61,7 +74,7 @@ def test_failed_refund purchase = @gateway.purchase(@amount, credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount, purchase.authorization) + assert refund = @gateway.refund(@amount + 50, purchase.authorization) assert_failure refund end @@ -71,11 +84,8 @@ def test_successful_check_refund assert refund = @gateway.refund(@amount, purchase.authorization) - # This is a dubious test. Basically testing that the refund failed b/c - # the original purchase hadn't yet cleared. No way to test immediate failure - # due to the delay in original tx processing, even for text txs. - assert_failure refund - assert_equal 'REJECT: 3', refund.message + assert_success refund + assert_equal 'Success', refund.message end def test_failed_check_refund @@ -95,6 +105,21 @@ def test_successful_store_and_unstore assert_equal 'Success', unstore.message end + def test_successful_store_and_purchase_and_refund + assert store = @gateway.store(@credit_card, @options) + assert_success store + assert store.authorization + assert_equal 'Success', store.message + + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert purchase.authorization + assert_equal 'Success', purchase.message + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + def test_failed_store credit_card = credit_card('4111') assert store = @gateway.store(credit_card, @options) @@ -104,9 +129,9 @@ def test_failed_store def test_invalid_login gateway = IatsPaymentsGateway.new( - :agent_code => 'X', - :password => 'Y', - :region => 'na' + agent_code: 'X', + password: 'Y', + region: 'na' ) assert response = gateway.purchase(@amount, @credit_card) @@ -136,5 +161,4 @@ def test_check_purchase_scrubbing assert_scrubbed(@gateway.options[:agent_code], transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/test/remote/gateways/remote_inspire_test.rb b/test/remote/gateways/remote_inspire_test.rb index 4e137b4d3ff..7541d44aa2a 100644 --- a/test/remote/gateways/remote_inspire_test.rb +++ b/test/remote/gateways/remote_inspire_test.rb @@ -5,11 +5,10 @@ def setup @gateway = InspireGateway.new(fixtures(:inspire)) @amount = rand(1001..11000) - @credit_card = credit_card('4111111111111111', :brand => 'visa') + @credit_card = credit_card('4111111111111111', brand: 'visa') @declined_amount = rand(99) - @options = { :order_id => generate_unique_id, - :billing_address => address - } + @options = { order_id: generate_unique_id, + billing_address: address } end def test_successful_purchase @@ -20,12 +19,12 @@ def test_successful_purchase def test_successful_purchase_with_echeck check = ActiveMerchant::Billing::Check.new( - :name => 'Fredd Bloggs', - :routing_number => '111000025', # Valid ABA # - Bank of America, TX - :account_number => '999999999999', - :account_holder_type => 'personal', - :account_type => 'checking' - ) + name: 'Fredd Bloggs', + routing_number: '111000025', # Valid ABA # - Bank of America, TX + account_number: '999999999999', + account_holder_type: 'personal', + account_type: 'checking' + ) response = @gateway.purchase(@amount, check, @options) assert_success response assert_equal 'This transaction has been approved', response.message @@ -51,7 +50,7 @@ def test_successful_add_to_vault_and_use assert_equal 'This transaction has been approved', response.message assert_not_nil customer_id = response.params['customer_vault_id'] - second_response = @gateway.purchase(@amount*2, customer_id, @options) + second_response = @gateway.purchase(@amount * 2, customer_id, @options) assert_equal 'This transaction has been approved', second_response.message assert_success second_response end @@ -74,7 +73,7 @@ def test_add_to_vault_with_custom_vault_id_with_store_method def test_update_vault test_add_to_vault_with_custom_vault_id - @credit_card = credit_card('4111111111111111', :month => 10) + @credit_card = credit_card('4111111111111111', month: 10) response = @gateway.update(@options[:store], @credit_card) assert_success response assert_equal 'Customer Update Successful', response.message @@ -140,7 +139,7 @@ def test_partial_refund response = @gateway.purchase(@amount, @credit_card) assert_success response - response = @gateway.refund(@amount-500, response.authorization) + response = @gateway.refund(@amount - 500, response.authorization) assert_success response end @@ -151,9 +150,9 @@ def test_failed_refund def test_invalid_login gateway = InspireGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Invalid Username', response.message assert_failure response diff --git a/test/remote/gateways/remote_instapay_test.rb b/test/remote/gateways/remote_instapay_test.rb index 5a8286cf234..3565cfaaae8 100644 --- a/test/remote/gateways/remote_instapay_test.rb +++ b/test/remote/gateways/remote_instapay_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteInstapayTest < Test::Unit::TestCase - def setup @gateway = InstapayGateway.new(fixtures(:instapay)) @@ -10,10 +9,10 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :description => 'Store Purchase' + order_id: generate_unique_id, + billing_address: address, + shipping_address: address, + description: 'Store Purchase' } end @@ -24,12 +23,12 @@ def test_successful_purchase end def test_failed_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) + assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response end def test_succesful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal InstapayGateway::SUCCESS_MESSAGE, response.message end @@ -50,8 +49,8 @@ def test_authorization_and_capture def test_invalid_login gateway = InstapayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card) diff --git a/test/remote/gateways/remote_ipg_test.rb b/test/remote/gateways/remote_ipg_test.rb new file mode 100644 index 00000000000..9a81291b0c5 --- /dev/null +++ b/test/remote/gateways/remote_ipg_test.rb @@ -0,0 +1,187 @@ +require 'test_helper' + +class RemoteIpgTest < Test::Unit::TestCase + def setup + @gateway = IpgGateway.new(fixtures(:ipg)) + + @amount = 100 + @credit_card = credit_card('5165850000000008', brand: 'mastercard', verification_value: '987', 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: '2029') + @options = { + currency: 'ARS' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_purchase_with_store + payment_token = generate_unique_id + response = @gateway.store(@credit_card, @options.merge({ hosted_data_id: payment_token })) + assert_success response + assert_equal 'true', response.params['successfully'] + + response = @gateway.purchase(@amount, payment_token, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_purchase_with_store_without_passing_hosted + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'true', response.params['successfully'] + payment_token = response.authorization + assert payment_token + + response = @gateway.purchase(@amount, payment_token, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_unstore + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'true', response.params['successfully'] + payment_token = response.authorization + assert payment_token + + response = @gateway.unstore(payment_token) + assert_success response + assert_equal 'true', response.params['successfully'] + end + + def test_successful_purchase_with_stored_credential + @options[:stored_credential] = { + initial_transaction: true, + reason_type: '', + initiator: 'merchant', + network_transaction_id: nil + } + order_id = generate_unique_id + assert response = @gateway.purchase(@amount, @credit_card, @options.merge({ order_id: order_id })) + assert_success response + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: '', + initiator: 'merchant', + network_transaction_id: response.params['IpgTransactionId'] + } + + assert recurring_purchase = @gateway.purchase(@amount, @credit_card, @options.merge({ order_id: order_id })) + assert_success recurring_purchase + assert_equal 'APPROVED', recurring_purchase.message + end + + def test_successful_purchase_with_3ds2_options + options = @options.merge( + three_d_secure: { + version: '2.1.0', + cavv: 'jEET5Odser3oCRAyNTY5BVgAAAA=', + xid: 'jHDMyjJJF9bLBCFT/YUbqMhoQ0s=', + directory_response_status: 'Y', + authentication_response_status: 'Y', + ds_transaction_id: '925a0317-9143-5130-8000-0000000f8742' + } + ) + response = @gateway.purchase(@amount, @visa_card, options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_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 + end + + def test_successful_authorize_and_capture + order_id = generate_unique_id + response = @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: order_id })) + assert_success response + assert_equal 'APPROVED', response.message + + capture = @gateway.capture(@amount, response.authorization, @options) + assert_success capture + assert_equal 'APPROVED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal '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_match 'FAILED', response.message + assert_equal 'SGS-005001', response.error_code + end + + def test_successful_void + order_id = generate_unique_id + response = @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: order_id })) + assert_success response + + void = @gateway.void(response.authorization, @options) + assert_success void + assert_equal 'APPROVED', void.message + end + + def test_failed_void + response = @gateway.void('', @options) + assert_failure response + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization, @options) + assert_success refund + assert_equal 'APPROVED', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_match 'FAILED', response.message + assert_equal 'SGS-005001', response.error_code + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match 'DECLINED', response.message + assert_equal 'SGS-050005', response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end +end diff --git a/test/remote/gateways/remote_ipp_test.rb b/test/remote/gateways/remote_ipp_test.rb index f7e62cad46b..443e7b186eb 100644 --- a/test/remote/gateways/remote_ipp_test.rb +++ b/test/remote/gateways/remote_ipp_test.rb @@ -9,7 +9,7 @@ def setup @options = { order_id: '1', billing_address: address, - description: 'Store Purchase', + description: 'Store Purchase' } end diff --git a/test/remote/gateways/remote_iridium_test.rb b/test/remote/gateways/remote_iridium_test.rb index dc033cabe3a..24460d4c943 100644 --- a/test/remote/gateways/remote_iridium_test.rb +++ b/test/remote/gateways/remote_iridium_test.rb @@ -7,21 +7,21 @@ def setup @gateway = IridiumGateway.new(fixtures(:iridium)) @amount = 100 - @avs_card = credit_card('4921810000005462', {:verification_value => '441'}) - @cv2_card = credit_card('4976000000003436', {:verification_value => '777'}) - @avs_cv2_card = credit_card('4921810000005462', {:verification_value => '777'}) - @credit_card = credit_card('4976000000003436', {:verification_value => '452'}) + @avs_card = credit_card('4921810000005462', { verification_value: '441' }) + @cv2_card = credit_card('4976000000003436', { verification_value: '777' }) + @avs_cv2_card = credit_card('4921810000005462', { verification_value: '777' }) + @credit_card = credit_card('4976000000003436', { verification_value: '452' }) @declined_card = credit_card('4221690000004963') - our_address = address(:address1 => '32 Edward Street', - :address2 => 'Camborne', - :state => 'Cornwall', - :zip => 'TR14 8PA', - :country => '826') + our_address = address(address1: '32 Edward Street', + address2: 'Camborne', + state: 'Cornwall', + zip: 'TR14 8PA', + country: '826') @options = { - :order_id => generate_unique_id, - :billing_address => our_address, - :description => 'Store Purchase' + order_id: generate_unique_id, + billing_address: our_address, + description: 'Store Purchase' } end @@ -111,7 +111,7 @@ def test_successful_purchase_by_reference assert_success response assert(reference = response.authorization) - assert response = @gateway.purchase(@amount, reference, {:order_id => generate_unique_id}) + assert response = @gateway.purchase(@amount, reference, { order_id: generate_unique_id }) assert_success response end @@ -120,7 +120,7 @@ def test_failed_purchase_by_reference assert_success response assert response.authorization - assert response = @gateway.purchase(@amount, 'bogusref', {:order_id => generate_unique_id}) + assert response = @gateway.purchase(@amount, 'bogusref', { order_id: generate_unique_id }) assert_failure response end @@ -129,7 +129,7 @@ def test_successful_authorize_by_reference assert_success response assert(reference = response.authorization) - assert response = @gateway.authorize(@amount, reference, {:order_id => generate_unique_id}) + assert response = @gateway.authorize(@amount, reference, { order_id: generate_unique_id }) assert_success response end @@ -145,7 +145,7 @@ def test_failed_credit assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert response = @gateway.credit(@amount*2, response.authorization) + assert response = @gateway.credit(@amount * 2, response.authorization) assert_failure response end @@ -164,9 +164,9 @@ def test_failed_void def test_invalid_login gateway = IridiumGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_itransact_test.rb b/test/remote/gateways/remote_itransact_test.rb index d949b17232d..55fafbb0388 100644 --- a/test/remote/gateways/remote_itransact_test.rb +++ b/test/remote/gateways/remote_itransact_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteItransactTest < Test::Unit::TestCase - def setup @gateway = ItransactGateway.new(fixtures(:itransact)) @@ -10,9 +9,9 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -77,10 +76,10 @@ def test_refund_partial def test_invalid_login gateway = ItransactGateway.new( - :login => 'x', - :password => 'x', - :gateway_id => 'x' - ) + login: 'x', + password: 'x', + gateway_id: 'x' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid login credentials', response.message diff --git a/test/remote/gateways/remote_iveri_test.rb b/test/remote/gateways/remote_iveri_test.rb index 16733284a46..0ced8b40be3 100644 --- a/test/remote/gateways/remote_iveri_test.rb +++ b/test/remote/gateways/remote_iveri_test.rb @@ -24,6 +24,16 @@ def test_successful_purchase assert_equal '100', response.params['amount'] end + def test_successful_purchase_with_iveri_url + credentials = fixtures(:iveri_url).merge(url_override: 'iveri') + @gateway = IveriGateway.new(credentials) + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '100', response.params['amount'] + end + def test_successful_purchase_with_more_options options = { ip: '127.0.0.1', @@ -73,7 +83,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -96,7 +106,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -124,6 +134,10 @@ def test_failed_void def test_successful_verify response = @gateway.verify(@credit_card, @options) assert_success response + assert_equal 'Authorisation', response.responses[0].params['transaction_command'] + assert_equal '0', response.responses[0].params['result_status'] + assert_equal 'Void', response.responses[1].params['transaction_command'] + assert_equal '0', response.responses[1].params['result_status'] assert_equal 'Succeeded', response.message end @@ -161,4 +175,35 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:cert_id], transcript) end + def test_successful_authorize_with_3ds_v1_options + @options[:three_d_secure] = { + version: '1.0', + cavv: 'FHhirTpN0Aefs4rIzTlheBByD77J', + eci: '02', + xid: SecureRandom.alphanumeric(28), + enrolled: 'true', + authentication_response_status: 'Y' + } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.1.0', + cavv: 'FHhirTpN0Aefs4rIzTlheBByD77J', + eci: '02', + ds_transaction_id: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'Y', + authentication_response_status: 'Y' + } + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '100', response.params['amount'] + end end diff --git a/test/remote/gateways/remote_ixopay_test.rb b/test/remote/gateways/remote_ixopay_test.rb new file mode 100644 index 00000000000..b695de1a0e1 --- /dev/null +++ b/test/remote/gateways/remote_ixopay_test.rb @@ -0,0 +1,248 @@ +require 'test_helper' + +class RemoteIxopayTest < Test::Unit::TestCase + def setup + @gateway = IxopayGateway.new(fixtures(:ixopay)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4000300011112220') + + @options = { + billing_address: address, + shipping_address: address, + email: 'test@example.com', + description: 'Store Purchase', + ip: '192.168.1.1', + stored_credential: stored_credential(:initial) + } + + @extra_data = { extra_data: { customData1: 'some data', customData2: 'Can be anything really' } } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'FINISHED', response.message + assert_match(/[0-9a-zA-Z]+(|[0-9a-zA-Z]+)*/, response.authorization) + + assert_equal @credit_card.name, response.params.dig('return_data', 'creditcard_data', 'card_holder') + assert_equal '%02d' % @credit_card.month, response.params.dig('return_data', 'creditcard_data', 'expiry_month') + assert_equal @credit_card.year.to_s, response.params.dig('return_data', 'creditcard_data', 'expiry_year') + assert_equal @credit_card.number[0..5], response.params.dig('return_data', 'creditcard_data', 'first_six_digits') + assert_equal @credit_card.number.split(//).last(4).join, response.params.dig('return_data', 'creditcard_data', 'last_four_digits') + assert_equal 'FINISHED', response.params['return_type'] + + assert_not_nil response.params['purchase_id'] + assert_not_nil response.params['reference_id'] + end + + def test_successful_purchase_with_extra_data + response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_data)) + assert_success response + assert_equal 'FINISHED', response.message + assert_match(/[0-9a-zA-Z]+(|[0-9a-zA-Z]+)*/, response.authorization) + + assert_equal @credit_card.name, response.params.dig('return_data', 'creditcard_data', 'card_holder') + assert_equal '%02d' % @credit_card.month, response.params.dig('return_data', 'creditcard_data', 'expiry_month') + assert_equal @credit_card.year.to_s, response.params.dig('return_data', 'creditcard_data', 'expiry_year') + assert_equal @credit_card.number[0..5], response.params.dig('return_data', 'creditcard_data', 'first_six_digits') + assert_equal @credit_card.number.split(//).last(4).join, response.params.dig('return_data', 'creditcard_data', 'last_four_digits') + assert_equal 'FINISHED', response.params['return_type'] + + assert_not_nil response.params['purchase_id'] + assert_not_nil response.params['reference_id'] + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, {}) + + assert_failure response + assert_equal 'The transaction was declined', response.message + assert_equal '2003', response.error_code + end + + def test_failed_authentication + gateway = IxopayGateway.new( + username: 'baduser', + password: 'badpass', + secret: 'badsecret', + api_key: 'badapikey' + ) + + response = gateway.purchase(@amount, @credit_card, {}) + + assert_failure response + + assert_equal 'Invalid Signature', response.message + assert_equal '9999', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + + assert_success auth + assert_equal 'FINISHED', auth.message + assert_not_nil auth.params['purchase_id'] + assert_not_nil auth.params['reference_id'] + assert_not_nil auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'FINISHED', capture.message + end + + def test_successful_authorize_and_capture_with_extra_data + auth = @gateway.authorize(@amount, @credit_card, @options.merge(@extra_data)) + + assert_success auth + assert_equal 'FINISHED', auth.message + assert_not_nil auth.params['purchase_id'] + assert_not_nil auth.params['reference_id'] + assert_not_nil auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'FINISHED', capture.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + assert_equal 'FINISHED', capture.message + end + + def test_partial_capture_with_extra_data + auth = @gateway.authorize(@amount, @credit_card, @options.merge(@extra_data)) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + assert_equal 'FINISHED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The transaction was declined', response.message + assert_equal 'ERROR', response.params['return_type'] + assert_equal response.error_code, '2003' + end + + def test_failed_capture + response = @gateway.capture(@amount, nil) + + assert_failure response + assert_equal 'Transaction of type "capture" requires a referenceTransactionId', response.message + end + + def test_successful_refund + options = @options.update(currency: 'USD') + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, options) + assert_success refund + assert_equal 'FINISHED', refund.message + end + + def test_successful_refund_with_extra_data + options = @options.update(currency: 'USD') + + purchase = @gateway.purchase(@amount, @credit_card, options.merge(@extra_data)) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, options) + assert_success refund + assert_equal 'FINISHED', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_partial_refund_with_extra_data + purchase = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_data)) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, nil) + assert_failure response + assert_equal 'Transaction of type "refund" requires a referenceTransactionId', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'FINISHED', void.message + end + + def test_successful_void_with_extra_data + auth = @gateway.authorize(@amount, @credit_card, @options.merge(@extra_data)) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'FINISHED', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Transaction of type "void" requires a referenceTransactionId', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{FINISHED}, response.message + end + + def test_successful_verify_with_extra_data + response = @gateway.verify(@credit_card, @options.merge(@extra_data)) + assert_success response + assert_match %r{FINISHED}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{The transaction was declined}, response.message + end + + def test_invalid_login + gateway = IxopayGateway.new(username: '', password: '', secret: '', api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid Signature: Invalid authorization header}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_jetpay_test.rb b/test/remote/gateways/remote_jetpay_test.rb index d5d331a72f1..7f4a38c2539 100644 --- a/test/remote/gateways/remote_jetpay_test.rb +++ b/test/remote/gateways/remote_jetpay_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteJetpayTest < Test::Unit::TestCase - def setup @gateway = JetpayGateway.new(fixtures(:jetpay)) @@ -9,12 +8,12 @@ def setup @declined_card = credit_card('4000300020001000') @options = { - :billing_address => address(:country => 'US', :zip => '75008'), - :shipping_address => address(:country => 'US'), - :email => 'test@test.com', - :ip => '127.0.0.1', - :order_id => '12345', - :tax => 7 + billing_address: address(country: 'US', zip: '75008'), + shipping_address: address(country: 'US'), + email: 'test@test.com', + ip: '127.0.0.1', + order_id: '12345', + tax: 7 } end @@ -33,7 +32,7 @@ def test_unsuccessful_purchase end def test_successful_purchase_with_origin - assert response = @gateway.purchase(9900, @credit_card, {:origin => 'RECURRING'}) + assert response = @gateway.purchase(9900, @credit_card, { origin: 'RECURRING' }) assert_success response assert_equal 'APPROVED', response.message assert_not_nil response.authorization @@ -123,7 +122,7 @@ def test_capture_refund_with_token def test_refund_backwards_compatible # no need for csv - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) assert response = @gateway.purchase(9900, card, @options) assert_success response @@ -134,7 +133,7 @@ def test_refund_backwards_compatible old_authorization = [response.params['transaction_id'], response.params['approval'], 9900].join(';') # linked to a specific transaction_id - assert credit = @gateway.refund(9900, old_authorization, :credit_card => card) + assert credit = @gateway.refund(9900, old_authorization, credit_card: card) assert_success credit assert_not_nil(credit.authorization) assert_not_nil(response.params['approval']) @@ -143,7 +142,7 @@ def test_refund_backwards_compatible def test_credit # no need for csv - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) # no link to a specific transaction_id assert credit = @gateway.credit(9900, card) @@ -159,7 +158,7 @@ def test_failed_capture end def test_invalid_login - gateway = JetpayGateway.new(:login => 'bogus') + gateway = JetpayGateway.new(login: 'bogus') assert response = gateway.purchase(9900, @credit_card, @options) assert_failure response @@ -167,7 +166,7 @@ def test_invalid_login end def test_missing_login - gateway = JetpayGateway.new(:login => '') + gateway = JetpayGateway.new(login: '') assert response = gateway.purchase(9900, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_jetpay_v2_certification_test.rb b/test/remote/gateways/remote_jetpay_v2_certification_test.rb index be182a8be37..a213fc82691 100644 --- a/test/remote/gateways/remote_jetpay_v2_certification_test.rb +++ b/test/remote/gateways/remote_jetpay_v2_certification_test.rb @@ -1,20 +1,19 @@ require 'test_helper' class RemoteJetpayV2CertificationTest < Test::Unit::TestCase - def setup @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2)) @unique_id = '' @options = { - :device => 'spreedly', - :application => 'spreedly', - :developer_id => 'GenkID', - :billing_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'), - :shipping_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'), - :email => 'test@test.com', - :ip => '127.0.0.1' + device: 'spreedly', + application: 'spreedly', + developer_id: 'GenkID', + billing_address: address(address1: '1234 Fifth Street', address2: '', city: 'Beaumont', state: 'TX', country: 'US', zip: '77708'), + shipping_address: address(address1: '1234 Fifth Street', address2: '', city: 'Beaumont', state: 'TX', country: 'US', zip: '77708'), + email: 'test@test.com', + ip: '127.0.0.1' } end @@ -25,7 +24,7 @@ def teardown def test_certification_cnp1_authorize_mastercard @options[:order_id] = 'CNP1' amount = 1000 - master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '121') + master = credit_card('5111111111111118', month: 12, year: 2017, brand: 'master', verification_value: '121') assert response = @gateway.authorize(amount, master, @options) assert_success response assert_equal 'APPROVED', response.message @@ -35,7 +34,7 @@ def test_certification_cnp1_authorize_mastercard def test_certification_cnp2_authorize_visa @options[:order_id] = 'CNP2' amount = 1105 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '121') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '121') assert response = @gateway.authorize(amount, visa, @options) assert_failure response assert_equal 'Do not honor.', response.message @@ -45,7 +44,7 @@ def test_certification_cnp2_authorize_visa def test_certification_cnp3_cnp4_authorize_and_capture_amex @options[:order_id] = 'CNP3' amount = 1200 - amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221') + amex = credit_card('378282246310005', month: 12, year: 2017, brand: 'american_express', verification_value: '1221') assert response = @gateway.authorize(amount, amex, @options) assert_success response assert_equal 'APPROVED', response.message @@ -61,7 +60,7 @@ def test_certification_cnp3_cnp4_authorize_and_capture_amex def test_certification_cnp5_purchase_discover @options[:order_id] = 'CNP5' amount = 1300 - discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '121') + discover = credit_card('6011111111111117', month: 12, year: 2017, brand: 'discover', verification_value: '121') assert response = @gateway.purchase(amount, discover, @options) assert_success response assert_equal 'APPROVED', response.message @@ -71,7 +70,7 @@ def test_certification_cnp5_purchase_discover def test_certification_cnp6_purchase_visa @options[:order_id] = 'CNP6' amount = 1405 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '120') assert response = @gateway.purchase(amount, visa, @options) assert_failure response assert_equal 'Do not honor.', response.message @@ -81,7 +80,7 @@ def test_certification_cnp6_purchase_visa def test_certification_cnp7_authorize_mastercard @options[:order_id] = 'CNP7' amount = 1500 - master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120') + master = credit_card('5111111111111118', month: 12, year: 2017, brand: 'master', verification_value: '120') assert response = @gateway.authorize(amount, master, @options) assert_success response assert_equal 'APPROVED', response.message @@ -91,7 +90,7 @@ def test_certification_cnp7_authorize_mastercard def test_certification_cnp8_authorize_visa @options[:order_id] = 'CNP8' amount = 1605 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '120') assert response = @gateway.authorize(amount, visa, @options) assert_failure response assert_equal 'Do not honor.', response.message @@ -101,7 +100,7 @@ def test_certification_cnp8_authorize_visa def test_certification_cnp9_cnp10_authorize_and_capture_amex @options[:order_id] = 'CNP9' amount = 1700 - amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220') + amex = credit_card('378282246310005', month: 12, year: 2017, brand: 'american_express', verification_value: '1220') assert response = @gateway.authorize(amount, amex, @options) assert_success response assert_equal 'APPROVED', response.message @@ -117,7 +116,7 @@ def test_certification_cnp9_cnp10_authorize_and_capture_amex def test_certification_cnp11_purchase_discover @options[:order_id] = 'CNP11' amount = 1800 - discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120') + discover = credit_card('6011111111111117', month: 12, year: 2017, brand: 'discover', verification_value: '120') assert response = @gateway.purchase(amount, discover, @options) assert_success response assert_equal 'APPROVED', response.message @@ -130,7 +129,7 @@ def test_certification_rec01_recurring_mastercard @options[:billing_address] = nil @options[:shipping_address] = nil amount = 2000 - master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120') + master = credit_card('5111111111111118', month: 12, year: 2017, brand: 'master', verification_value: '120') assert response = @gateway.purchase(amount, master, @options) assert_success response assert_equal 'APPROVED', response.message @@ -141,7 +140,7 @@ def test_certification_rec02_recurring_visa @options[:order_id] = 'REC02' @options[:origin] = 'RECURRING' amount = 2100 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '') assert response = @gateway.purchase(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -152,7 +151,7 @@ def test_certification_rec03_recurring_amex @options[:order_id] = 'REC03' @options[:origin] = 'RECURRING' amount = 2200 - amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221') + amex = credit_card('378282246310005', month: 12, year: 2017, brand: 'american_express', verification_value: '1221') assert response = @gateway.purchase(amount, amex, @options) assert_success response assert_equal 'APPROVED', response.message @@ -162,7 +161,7 @@ def test_certification_rec03_recurring_amex def test_certification_corp07_corp08_authorize_and_capture_discover @options[:order_id] = 'CORP07' amount = 2500 - discover = credit_card('6011111111111117', :month => 12, :year => 2018, :brand => 'discover', :verification_value => '120') + discover = credit_card('6011111111111117', month: 12, year: 2018, brand: 'discover', verification_value: '120') assert response = @gateway.authorize(amount, discover, @options) assert_success response assert_equal 'APPROVED', response.message @@ -170,7 +169,7 @@ def test_certification_corp07_corp08_authorize_and_capture_discover puts "\n#{@options[:order_id]}: #{@unique_id}" @options[:order_id] = 'CORP08' - assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '200')) + assert response = @gateway.capture(amount, response.authorization, @options.merge(tax_amount: '200')) assert_success response @unique_id = response.params['unique_id'] end @@ -178,7 +177,7 @@ def test_certification_corp07_corp08_authorize_and_capture_discover def test_certification_corp09_corp10_authorize_and_capture_visa @options[:order_id] = 'CORP09' amount = 5000 - visa = credit_card('4111111111111111', :month => 12, :year => 2018, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2018, brand: 'visa', verification_value: '120') assert response = @gateway.authorize(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -186,7 +185,7 @@ def test_certification_corp09_corp10_authorize_and_capture_visa puts "\n#{@options[:order_id]}: #{@unique_id}" @options[:order_id] = 'CORP10' - assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'true')) + assert response = @gateway.capture(amount, response.authorization, @options.merge(tax_amount: '0', tax_exempt: 'true')) assert_success response @unique_id = response.params['unique_id'] end @@ -194,7 +193,7 @@ def test_certification_corp09_corp10_authorize_and_capture_visa def test_certification_corp11_corp12_authorize_and_capture_mastercard @options[:order_id] = 'CORP11' amount = 7500 - master = credit_card('5111111111111118', :month => 12, :year => 2018, :brand => 'master', :verification_value => '120') + master = credit_card('5111111111111118', month: 12, year: 2018, brand: 'master', verification_value: '120') assert response = @gateway.authorize(amount, master, @options) assert_success response assert_equal 'APPROVED', response.message @@ -202,7 +201,7 @@ def test_certification_corp11_corp12_authorize_and_capture_mastercard puts "\n#{@options[:order_id]}: #{@unique_id}" @options[:order_id] = 'CORP12' - assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'false', :purchase_order => '456456')) + assert response = @gateway.capture(amount, response.authorization, @options.merge(tax_amount: '0', tax_exempt: 'false', purchase_order: '456456')) assert_success response @unique_id = response.params['unique_id'] end @@ -210,7 +209,7 @@ def test_certification_corp11_corp12_authorize_and_capture_mastercard def test_certification_cred02_credit_visa @options[:order_id] = 'CRED02' amount = 100 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '120') assert response = @gateway.credit(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -220,7 +219,7 @@ def test_certification_cred02_credit_visa def test_certification_cred03_credit_amex @options[:order_id] = 'CRED03' amount = 200 - amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220') + amex = credit_card('378282246310005', month: 12, year: 2017, brand: 'american_express', verification_value: '1220') assert response = @gateway.credit(amount, amex, @options) assert_success response assert_equal 'APPROVED', response.message @@ -230,7 +229,7 @@ def test_certification_cred03_credit_amex def test_certification_void03_void04_purchase_void_visa @options[:order_id] = 'VOID03' amount = 300 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '120') assert response = @gateway.purchase(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -249,7 +248,7 @@ def test_certification_void03_void04_purchase_void_visa def test_certification_void07_void08_void09_authorize_capture_void_discover @options[:order_id] = 'VOID07' amount = 400 - discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120') + discover = credit_card('6011111111111117', month: 12, year: 2017, brand: 'discover', verification_value: '120') assert response = @gateway.authorize(amount, discover, @options) assert_success response assert_equal 'APPROVED', response.message @@ -272,7 +271,7 @@ def test_certification_void07_void08_void09_authorize_capture_void_discover def test_certification_void12_void13_credit_void_visa @options[:order_id] = 'VOID12' amount = 800 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '120') assert response = @gateway.credit(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -287,7 +286,7 @@ def test_certification_void12_void13_credit_void_visa def test_certification_tok15_tokenize_mastercard @options[:order_id] = 'TOK15' - master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101') + master = credit_card('5111111111111118', month: 12, year: 2017, brand: 'master', verification_value: '101') assert response = @gateway.store(master, @options) assert_success response assert_equal 'APPROVED', response.message @@ -298,7 +297,7 @@ def test_certification_tok15_tokenize_mastercard def test_certification_tok16_authorize_with_token_request_visa @options[:order_id] = 'TOK16' amount = 3100 - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '101') assert response = @gateway.authorize(amount, visa, @options) assert_success response assert_equal 'APPROVED', response.message @@ -310,7 +309,7 @@ def test_certification_tok16_authorize_with_token_request_visa def test_certification_tok17_purchase_with_token_request_amex @options[:order_id] = 'TOK17' amount = 3200 - amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1001') + amex = credit_card('378282246310005', month: 12, year: 2017, brand: 'american_express', verification_value: '1001') assert response = @gateway.purchase(amount, amex, @options) assert_success response assert_equal 'APPROVED', response.message @@ -320,7 +319,7 @@ def test_certification_tok17_purchase_with_token_request_amex end def test_certification_tok18_authorize_using_token_mastercard - master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101') + master = credit_card('5111111111111118', month: 12, year: 2017, brand: 'master', verification_value: '101') assert response = @gateway.store(master, @options) assert_success response @@ -333,7 +332,7 @@ def test_certification_tok18_authorize_using_token_mastercard end def test_certification_tok19_purchase_using_token_visa - visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101') + visa = credit_card('4111111111111111', month: 12, year: 2017, brand: 'visa', verification_value: '101') assert response = @gateway.store(visa, @options) assert_success response @@ -344,5 +343,4 @@ def test_certification_tok19_purchase_using_token_visa assert_equal 'APPROVED', response.message @unique_id = response.params['unique_id'] end - end diff --git a/test/remote/gateways/remote_jetpay_v2_test.rb b/test/remote/gateways/remote_jetpay_v2_test.rb index 10f1ea6322f..f2502bc7f32 100644 --- a/test/remote/gateways/remote_jetpay_v2_test.rb +++ b/test/remote/gateways/remote_jetpay_v2_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteJetpayV2Test < Test::Unit::TestCase - def setup @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2)) @@ -11,14 +10,14 @@ def setup @amount_declined = 5205 @options = { - :device => 'spreedly', - :application => 'spreedly', - :developer_id => 'GenkID', - :billing_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'), - :shipping_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'), - :email => 'test@test.com', - :ip => '127.0.0.1', - :order_id => '12345' + device: 'spreedly', + application: 'spreedly', + developer_id: 'GenkID', + billing_address: address(city: 'Durham', state: 'NC', country: 'US', zip: '27701'), + shipping_address: address(city: 'Durham', state: 'NC', country: 'US', zip: '27701'), + email: 'test@test.com', + ip: '127.0.0.1', + order_id: '12345' } end @@ -37,7 +36,7 @@ def test_failed_purchase end def test_successful_purchase_with_minimal_options - assert response = @gateway.purchase(@amount_approved, @credit_card, {:device => 'spreedly', :application => 'spreedly'}) + assert response = @gateway.purchase(@amount_approved, @credit_card, { device: 'spreedly', application: 'spreedly' }) assert_success response assert_equal 'APPROVED', response.message assert_not_nil response.authorization @@ -72,7 +71,7 @@ def test_successful_authorize_and_capture_with_tax assert_not_nil auth.authorization assert_not_nil auth.params['approval'] - assert capture = @gateway.capture(@amount_approved, auth.authorization, @options.merge(:tax_amount => '990', :purchase_order => 'ABC12345')) + assert capture = @gateway.capture(@amount_approved, auth.authorization, @options.merge(tax_amount: '990', purchase_order: 'ABC12345')) assert_success capture end @@ -147,7 +146,7 @@ def test_failed_refund end def test_successful_credit - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) assert credit = @gateway.credit(@amount_approved, card, @options) assert_success credit @@ -156,7 +155,7 @@ def test_successful_credit end def test_failed_credit - card = credit_card('2424242424242424', :verification_value => nil) + card = credit_card('2424242424242424', verification_value: nil) assert credit = @gateway.credit(@amount_approved, card, @options) assert_failure credit @@ -169,7 +168,7 @@ def test_successful_verify end def test_failed_verify - card = credit_card('2424242424242424', :verification_value => nil) + card = credit_card('2424242424242424', verification_value: nil) assert verify = @gateway.verify(card, @options) assert_failure verify @@ -177,7 +176,7 @@ def test_failed_verify end def test_invalid_login - gateway = JetpayV2Gateway.new(:login => 'bogus') + gateway = JetpayV2Gateway.new(login: 'bogus') assert response = gateway.purchase(@amount_approved, @credit_card, @options) assert_failure response @@ -185,7 +184,7 @@ def test_invalid_login end def test_missing_login - gateway = JetpayV2Gateway.new(:login => '') + gateway = JetpayV2Gateway.new(login: '') assert response = gateway.purchase(@amount_approved, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_komoju_test.rb b/test/remote/gateways/remote_komoju_test.rb index 5214eb01fbf..9a5213fcd27 100644 --- a/test/remote/gateways/remote_komoju_test.rb +++ b/test/remote/gateways/remote_komoju_test.rb @@ -11,13 +11,13 @@ def setup @fraudulent_card = credit_card('4123111111111083') @options = { - :order_id => generate_unique_id, - :description => 'Store Purchase', - :tax => '10.0', - :ip => '192.168.0.1', - :email => 'valid@email.com', - :browser_language => 'en', - :browser_user_agent => 'user_agent' + order_id: generate_unique_id, + description: 'Store Purchase', + tax: '10.0', + ip: '192.168.0.1', + email: 'valid@email.com', + browser_language: 'en', + browser_user_agent: 'user_agent' } end @@ -65,7 +65,7 @@ def test_detected_fraud end def test_invalid_login - gateway = KomojuGateway.new(:login => 'abc') + gateway = KomojuGateway.new(login: 'abc') response = gateway.purchase(@amount, @credit_card, @options) assert_failure response end diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb index b4eb060de00..8527c769aea 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', @@ -23,7 +31,24 @@ def test_successful_purchase_with_options subtotal_iva: '10', iva: '1.54', ice: '3.50' - } + }, + contact_details: { + document_type: 'CC', + document_number: '123456', + email: 'who_dis@monkeys.tv', + first_name: 'Who', + last_name: 'Dis', + second_last_name: 'Buscemi', + phone_number: '+13125556789' + }, + metadata: { + productos: 'bananas', + nombre_apellido: 'Kirk' + }, + months: 2, + deferred_grace_months: '05', + deferred_credit_type: '01', + deferred_months: 3 } amount = 100 * ( @@ -39,6 +64,74 @@ def test_successful_purchase_with_options assert_match %r(^\d+$), response.authorization end + def test_successful_purchase_with_extra_taxes_cop + options = { + currency: 'COP', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50', + extra_taxes: { + propina: 0.1, + tasa_aeroportuaria: 0.2, + agencia_de_viaje: 0.3, + iac: 0.4 + } + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + + options[:amount][:extra_taxes][:propina].to_f + + options[:amount][:extra_taxes][:tasa_aeroportuaria].to_f + + options[:amount][:extra_taxes][:agencia_de_viaje].to_f + + options[:amount][:extra_taxes][:iac].to_f + ) + + response = @gateway.purchase(amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + + def test_successful_purchase_with_extra_taxes_usd + options = { + currency: 'USD', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50', + extra_taxes: { + propina: 0.1, + tasa_aeroportuaria: 0.2, + agencia_de_viaje: 0.3, + iac: 0.4 + } + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + + options[:amount][:extra_taxes][:propina].to_f + + options[:amount][:extra_taxes][:tasa_aeroportuaria].to_f + + options[:amount][:extra_taxes][:agencia_de_viaje].to_f + + options[:amount][:extra_taxes][:iac].to_f + ) + + response = @gateway.purchase(amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + def test_failed_purchase options = { amount: { @@ -51,6 +144,161 @@ def test_failed_purchase assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message end + def test_successful_authorize + 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 + end + + def test_approval_code_comes_back_when_passing_full_response + options = { + full_response: true + } + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_not_empty response.params.dig('details', 'approvalCode') + assert_equal 'Succeeded', response.message + end + + def test_failed_authorize + options = { + amount: { + subtotal_iva: '200' + } + } + response = @gateway.authorize(@amount, @credit_card, options) + assert_failure response + assert_equal 'K220', response.responses.last.error_code + 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 + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_capture + options = { + amount: { + subtotal_iva: '200' + } + } + auth = @gateway.authorize(@amount, @credit_card) + assert_success auth + + capture = @gateway.capture(@amount, auth.authorization, options) + assert_failure capture + assert_equal 'K012', capture.error_code + assert_equal 'Monto de captura inválido.', capture.message + end + def test_successful_refund purchase = @gateway.purchase(@amount, @credit_card) assert_success purchase @@ -69,6 +317,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 @@ -81,7 +349,7 @@ def test_successful_void def test_failed_void response = @gateway.void('000') assert_failure response - assert_equal 'El monto de la transacción es requerido', response.message + assert_equal 'Cuerpo de la petición inválido.', response.message end def test_invalid_login @@ -89,7 +357,7 @@ def test_invalid_login response = gateway.purchase(@amount, @credit_card) assert_failure response - assert_match %r{ID de comercio no válido}, response.message + assert_match %r{Unauthorized}, response.message end def test_transcript_scrubbing diff --git a/test/remote/gateways/remote_linkpoint_test.rb b/test/remote/gateways/remote_linkpoint_test.rb index 1a51911712d..efcffa07dec 100644 --- a/test/remote/gateways/remote_linkpoint_test.rb +++ b/test/remote/gateways/remote_linkpoint_test.rb @@ -36,7 +36,7 @@ def setup @amount = 100 @credit_card = credit_card('4111111111111111') - @options = { :order_id => generate_unique_id, :billing_address => address } + @options = { order_id: generate_unique_id, billing_address: address } end def test_successful_authorization @@ -91,21 +91,23 @@ def test_successfull_purchase_and_credit def test_successfull_purchase_with_item_entity @options[:line_items] = [ - {:id => '123456', :description => 'Logo T-Shirt', :price => '12.00', :quantity => '1', - :options => [{:name => 'Color', :value => 'Red'}, {:name => 'Size', :value => 'XL'}]}, - {:id => '111', :description => 'keychain', :price => '3.00', :quantity => '1'} + { id: '123456', description: 'Logo T-Shirt', price: '12.00', quantity: '1', + options: [{ name: 'Color', value: 'Red' }, { name: 'Size', value: 'XL' }] }, + { id: '111', description: 'keychain', price: '3.00', quantity: '1' } ] assert purchase = @gateway.purchase(1500, @credit_card, @options) assert_success purchase end def test_successful_recurring_payment - assert response = @gateway.recurring(2400, @credit_card, - :order_id => generate_unique_id, - :installments => 12, - :startdate => 'immediate', - :periodicity => :monthly, - :billing_address => address + assert response = @gateway.recurring( + 2400, + @credit_card, + order_id: generate_unique_id, + installments: 12, + startdate: 'immediate', + periodicity: :monthly, + billing_address: address ) assert_success response diff --git a/test/remote/gateways/remote_litle_certification_test.rb b/test/remote/gateways/remote_litle_certification_test.rb index 3a9558c701f..ee177ce116a 100644 --- a/test/remote/gateways/remote_litle_certification_test.rb +++ b/test/remote/gateways/remote_litle_certification_test.rb @@ -9,146 +9,146 @@ def setup def test1 credit_card = CreditCard.new( - :number => '4457010000000009', - :month => '01', - :year => '2021', - :verification_value => '349', - :brand => 'visa' + number: '4457010000000009', + month: '01', + year: '2021', + verification_value: '349', + brand: 'visa' ) options = { - :order_id => '1', - :billing_address => { - :name => 'John & Mary Smith', - :address1 => '1 Main St.', - :city => 'Burlington', - :state => 'MA', - :zip => '01803-3747', - :country => 'US' + order_id: '1', + billing_address: { + name: 'John & Mary Smith', + address1: '1 Main St.', + city: 'Burlington', + state: 'MA', + zip: '01803-3747', + country: 'US' } } - auth_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M') + auth_assertions(10100, credit_card, options, avs: 'X', cvv: 'M') - authorize_avs_assertions(credit_card, options, :avs => 'X', :cvv => 'M') + authorize_avs_assertions(credit_card, options, avs: 'X', cvv: 'M') - sale_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M') + sale_assertions(10100, credit_card, options, avs: 'X', cvv: 'M') end def test2 - credit_card = CreditCard.new(:number => '5112010000000003', :month => '02', - :year => '2021', :brand => 'master', - :verification_value => '261', - :name => 'Mike J. Hammer') + credit_card = CreditCard.new(number: '5112010000000003', month: '02', + year: '2021', brand: 'master', + verification_value: '261', + name: 'Mike J. Hammer') options = { - :order_id => '2', - :billing_address => { - :address1 => '2 Main St.', - :address2 => 'Apt. 222', - :city => 'Riverside', - :state => 'RI', - :zip => '02915', - :country => 'US' + order_id: '2', + billing_address: { + address1: '2 Main St.', + address2: 'Apt. 222', + city: 'Riverside', + state: 'RI', + zip: '02915', + country: 'US' } } - auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') + auth_assertions(10100, credit_card, options, avs: 'Z', cvv: 'M') - authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M') + authorize_avs_assertions(credit_card, options, avs: 'Z', cvv: 'M') - sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') + sale_assertions(10100, credit_card, options, avs: 'Z', cvv: 'M') end def test3 credit_card = CreditCard.new( - :number => '6011010000000003', - :month => '03', - :year => '2021', - :verification_value => '758', - :brand => 'discover' + number: '6011010000000003', + month: '03', + year: '2021', + verification_value: '758', + brand: 'discover' ) options = { - :order_id => '3', - :billing_address => { - :name => 'Eileen Jones', - :address1 => '3 Main St.', - :city => 'Bloomfield', - :state => 'CT', - :zip => '06002', - :country => 'US' + order_id: '3', + billing_address: { + name: 'Eileen Jones', + address1: '3 Main St.', + city: 'Bloomfield', + state: 'CT', + zip: '06002', + country: 'US' } } - auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') + auth_assertions(10100, credit_card, options, avs: 'Z', cvv: 'M') - authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M') + authorize_avs_assertions(credit_card, options, avs: 'Z', cvv: 'M') - sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') + sale_assertions(10100, credit_card, options, avs: 'Z', cvv: 'M') end def test4 credit_card = CreditCard.new( - :number => '375001000000005', - :month => '04', - :year => '2021', - :brand => 'american_express' + number: '375001000000005', + month: '04', + year: '2021', + brand: 'american_express' ) options = { - :order_id => '4', - :billing_address => { - :name => 'Bob Black', - :address1 => '4 Main St.', - :city => 'Laurel', - :state => 'MD', - :zip => '20708', - :country => 'US' + order_id: '4', + billing_address: { + name: 'Bob Black', + address1: '4 Main St.', + city: 'Laurel', + state: 'MD', + zip: '20708', + country: 'US' } } - auth_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil) + auth_assertions(10100, credit_card, options, avs: 'A', cvv: nil) - authorize_avs_assertions(credit_card, options, :avs => 'A') + authorize_avs_assertions(credit_card, options, avs: 'A') - sale_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil) + sale_assertions(10100, credit_card, options, avs: 'A', cvv: nil) end def test5 credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( - :number => '4100200300011001', - :month => '05', - :year => '2021', - :verification_value => '463', - :brand => 'visa', - :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + number: '4100200300011001', + month: '05', + year: '2021', + verification_value: '463', + brand: 'visa', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' ) options = { - :order_id => '5' + order_id: '5' } - auth_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M') + auth_assertions(10100, credit_card, options, avs: 'U', cvv: 'M') - authorize_avs_assertions(credit_card, options, :avs => 'U', :cvv => 'M') + authorize_avs_assertions(credit_card, options, avs: 'U', cvv: 'M') - sale_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M') + sale_assertions(10100, credit_card, options, avs: 'U', cvv: 'M') end def test6 - credit_card = CreditCard.new(:number => '4457010100000008', :month => '06', - :year => '2021', :brand => 'visa', - :verification_value => '992') + credit_card = CreditCard.new(number: '4457010100000008', month: '06', + year: '2021', brand: 'visa', + verification_value: '992') options = { - :order_id => '6', - :billing_address => { - :name => 'Joe Green', - :address1 => '6 Main St.', - :city => 'Derry', - :state => 'NH', - :zip => '03038', - :country => 'US' + order_id: '6', + billing_address: { + name: 'Joe Green', + address1: '6 Main St.', + city: 'Derry', + state: 'NH', + zip: '03038', + country: 'US' } } @@ -171,26 +171,26 @@ def test6 puts "Test #{options[:order_id]} Sale: #{txn_id(response)}" # 6A. void - assert response = @gateway.void(response.authorization, {:order_id => '6A'}) + assert response = @gateway.void(response.authorization, { order_id: '6A' }) assert_equal '360', response.params['response'] assert_equal 'No transaction found with specified transaction Id', response.message puts "Test #{options[:order_id]}A: #{txn_id(response)}" end def test7 - credit_card = CreditCard.new(:number => '5112010100000002', :month => '07', - :year => '2021', :brand => 'master', - :verification_value => '251') + credit_card = CreditCard.new(number: '5112010100000002', month: '07', + year: '2021', brand: 'master', + verification_value: '251') options = { - :order_id => '7', - :billing_address => { - :name => 'Jane Murray', - :address1 => '7 Main St.', - :city => 'Amesbury', - :state => 'MA', - :zip => '01913', - :country => 'US' + order_id: '7', + billing_address: { + name: 'Jane Murray', + address1: '7 Main St.', + city: 'Amesbury', + state: 'MA', + zip: '01913', + country: 'US' } } @@ -204,7 +204,7 @@ def test7 puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 7: authorize avs - authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'N', :message => 'Invalid Account Number', :success => false) + authorize_avs_assertions(credit_card, options, avs: 'I', cvv: 'N', message: 'Invalid Account Number', success: false) # 7. sale assert response = @gateway.purchase(10100, credit_card, options) @@ -217,19 +217,19 @@ def test7 end def test8 - credit_card = CreditCard.new(:number => '6011010100000002', :month => '08', - :year => '2021', :brand => 'discover', - :verification_value => '184') + credit_card = CreditCard.new(number: '6011010100000002', month: '08', + year: '2021', brand: 'discover', + verification_value: '184') options = { - :order_id => '8', - :billing_address => { - :name => 'Mark Johnson', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US' + order_id: '8', + billing_address: { + name: 'Mark Johnson', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US' } } @@ -243,7 +243,7 @@ def test8 puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 8: authorize avs - authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'P', :message => 'Call Discover', :success => false) + authorize_avs_assertions(credit_card, options, avs: 'I', cvv: 'P', message: 'Call Discover', success: false) # 8: sale assert response = @gateway.purchase(80080, credit_card, options) @@ -256,19 +256,19 @@ def test8 end def test9 - credit_card = CreditCard.new(:number => '375001010000003', :month => '09', - :year => '2021', :brand => 'american_express', - :verification_value => '0421') + credit_card = CreditCard.new(number: '375001010000003', month: '09', + year: '2021', brand: 'american_express', + verification_value: '0421') options = { - :order_id => '9', - :billing_address => { - :name => 'James Miller', - :address1 => '9 Main St.', - :city => 'Boston', - :state => 'MA', - :zip => '02134', - :country => 'US' + order_id: '9', + billing_address: { + name: 'James Miller', + address1: '9 Main St.', + city: 'Boston', + state: 'MA', + zip: '02134', + country: 'US' } } @@ -281,7 +281,7 @@ def test9 puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 9: authorize avs - authorize_avs_assertions(credit_card, options, :avs => 'I', :message => 'Pick Up Card', :success => false) + authorize_avs_assertions(credit_card, options, avs: 'I', message: 'Pick Up Card', success: false) # 9: sale assert response = @gateway.purchase(10100, credit_card, options) @@ -294,19 +294,19 @@ def test9 # Authorization Reversal Tests def test32 - credit_card = CreditCard.new(:number => '4457010000000009', :month => '01', - :year => '2021', :brand => 'visa', - :verification_value => '349') + credit_card = CreditCard.new(number: '4457010000000009', month: '01', + year: '2021', brand: 'visa', + verification_value: '349') options = { - :order_id => '32', - :billing_address => { - :name => 'John Smith', - :address1 => '1 Main St.', - :city => 'Burlington', - :state => 'MA', - :zip => '01803-3747', - :country => 'US' + order_id: '32', + billing_address: { + name: 'John Smith', + address1: '1 Main St.', + city: 'Burlington', + state: 'MA', + zip: '01803-3747', + country: 'US' } } @@ -326,21 +326,21 @@ def test32 end def test33 - credit_card = CreditCard.new(:number => '5112010000000003', :month => '01', - :year => '2021', :brand => 'master', - :verification_value => '261') + credit_card = CreditCard.new(number: '5112010000000003', month: '01', + year: '2021', brand: 'master', + verification_value: '261') options = { - :order_id => '33', - :billing_address => { - :name => 'Mike J. Hammer', - :address1 => '2 Main St.', - :address2 => 'Apt. 222', - :city => 'Riverside', - :state => 'RI', - :zip => '02915', - :country => 'US', - :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + order_id: '33', + billing_address: { + name: 'Mike J. Hammer', + address1: '2 Main St.', + address2: 'Apt. 222', + city: 'Riverside', + state: 'RI', + zip: '02915', + country: 'US', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' } } @@ -355,19 +355,19 @@ def test33 end def test34 - credit_card = CreditCard.new(:number => '6011010000000003', :month => '01', - :year => '2021', :brand => 'discover', - :verification_value => '758') + credit_card = CreditCard.new(number: '6011010000000003', month: '01', + year: '2021', brand: 'discover', + verification_value: '758') options = { - :order_id => '34', - :billing_address => { - :name => 'Eileen Jones', - :address1 => '3 Main St.', - :city => 'Bloomfield', - :state => 'CT', - :zip => '06002', - :country => 'US' + order_id: '34', + billing_address: { + name: 'Eileen Jones', + address1: '3 Main St.', + city: 'Bloomfield', + state: 'CT', + zip: '06002', + country: 'US' } } @@ -382,18 +382,18 @@ def test34 end def test35 - credit_card = CreditCard.new(:number => '375001000000005', :month => '01', - :year => '2021', :brand => 'american_express') + credit_card = CreditCard.new(number: '375001000000005', month: '01', + year: '2021', brand: 'american_express') options = { - :order_id => '35', - :billing_address => { - :name => 'Bob Black', - :address1 => '4 Main St.', - :city => 'Laurel', - :state => 'MD', - :zip => '20708', - :country => 'US' + order_id: '35', + billing_address: { + name: 'Bob Black', + address1: '4 Main St.', + city: 'Laurel', + state: 'MD', + zip: '20708', + country: 'US' } } @@ -414,12 +414,12 @@ def test35 end def test36 - credit_card = CreditCard.new(:number => '375000026600004', :month => '01', - :year => '2021', :brand => 'american_express') + credit_card = CreditCard.new(number: '375000026600004', month: '01', + year: '2021', brand: 'american_express') options = { - :order_id => '36' - } + order_id: '36' + } assert auth_response = @gateway.authorize(20500, credit_card, options) assert_success auth_response @@ -440,16 +440,16 @@ def test37 account_type: 'Checking' ) options = { - :order_id => '37', - :billing_address => { - :name => 'Tom Black', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '37', + billing_address: { + name: 'Tom Black', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert auth_response = @gateway.authorize(3001, check, options) @@ -467,16 +467,16 @@ def test38 account_type: 'Checking' ) options = { - :order_id => '38', - :billing_address => { - :name => 'John Smith', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '38', + billing_address: { + name: 'John Smith', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert auth_response = @gateway.authorize(3002, check, options) @@ -494,17 +494,17 @@ def test39 account_type: 'Corporate' ) options = { - :order_id => '39', - :billing_address => { - :name => 'John Smith', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Good Goods Inc', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '39', + billing_address: { + name: 'John Smith', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Good Goods Inc', + email: 'test@test.com', + phone: '2233334444' } } assert auth_response = @gateway.authorize(3003, check, options) @@ -522,17 +522,17 @@ def test40 account_type: 'Corporate' ) options = { - :order_id => '40', - :billing_address => { - :name => 'Peter Green', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Green Co', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '40', + billing_address: { + name: 'Peter Green', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Green Co', + email: 'test@test.com', + phone: '2233334444' } } assert auth_response = @gateway.authorize(3004, declined_authorize_check, options) @@ -550,16 +550,16 @@ def test41 account_type: 'Checking' ) options = { - :order_id => '41', - :billing_address => { - :name => 'Mike Hammer', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '41', + billing_address: { + name: 'Mike Hammer', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2008, check, options) @@ -577,16 +577,16 @@ def test42 account_type: 'Checking' ) options = { - :order_id => '42', - :billing_address => { - :name => 'Tom Black', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '42', + billing_address: { + name: 'Tom Black', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2004, check, options) @@ -604,17 +604,17 @@ def test43 account_type: 'Corporate' ) options = { - :order_id => '43', - :billing_address => { - :name => 'Peter Green', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Green Co', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '43', + billing_address: { + name: 'Peter Green', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Green Co', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2007, check, options) @@ -632,17 +632,17 @@ def test44 account_type: 'Corporate' ) options = { - :order_id => '44', - :billing_address => { - :name => 'Peter Green', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Green Co', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '44', + billing_address: { + name: 'Peter Green', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Green Co', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2009, check, options) @@ -660,16 +660,16 @@ def test45 account_type: 'Checking' ) options = { - :order_id => '45', - :billing_address => { - :name => 'John Smith', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '45', + billing_address: { + name: 'John Smith', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert refund_response = @gateway.refund(1001, check, options) @@ -687,18 +687,18 @@ def test46 account_type: 'Corporate' ) options = { - :order_id => '46', - :order_source => 'telephone', - :billing_address => { - :name => 'Robert Jones', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444', - :company => 'Widget Inc' + order_id: '46', + order_source: 'telephone', + billing_address: { + name: 'Robert Jones', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444', + company: 'Widget Inc' } } assert purchase_response = @gateway.purchase(1003, check, options) @@ -718,17 +718,17 @@ def test47 account_type: 'Corporate' ) options = { - :order_id => '47', - :billing_address => { - :name => 'Peter Green', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Green Co', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '47', + billing_address: { + name: 'Peter Green', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Green Co', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(1007, check, options) @@ -747,17 +747,17 @@ def test48 account_type: 'Corporate' ) options = { - :order_id => '43', - :billing_address => { - :name => 'Peter Green', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :company => 'Green Co', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '43', + billing_address: { + name: 'Peter Green', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + company: 'Green Co', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2007, check, options) @@ -783,17 +783,17 @@ def test_echeck_void1 account_type: 'Checking' ) options = { - :order_id => '42', - :id => '236222', - :billing_address => { - :name => 'Tom Black', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '42', + id: '236222', + billing_address: { + name: 'Tom Black', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(2004, check, options) @@ -812,17 +812,17 @@ def test_echeck_void2 account_type: 'Checking' ) options = { - :order_id => '46', - :id => '232222', - :billing_address => { - :name => 'Robert Jones', - :address1 => '8 Main St.', - :city => 'Manchester', - :state => 'NH', - :zip => '03101', - :country => 'US', - :email => 'test@test.com', - :phone => '2233334444' + order_id: '46', + id: '232222', + billing_address: { + name: 'Robert Jones', + address1: '8 Main St.', + city: 'Manchester', + state: 'NH', + zip: '03101', + country: 'US', + email: 'test@test.com', + phone: '2233334444' } } assert purchase_response = @gateway.purchase(1003, check, options) @@ -843,9 +843,9 @@ def test_echeck_void3 # Explicit Token Registration Tests def test50 - credit_card = CreditCard.new(:number => '4457119922390123') + credit_card = CreditCard.new(number: '4457119922390123') options = { - :order_id => '50' + order_id: '50' } # store @@ -861,9 +861,9 @@ def test50 end def test51 - credit_card = CreditCard.new(:number => '4457119999999999') + credit_card = CreditCard.new(number: '4457119999999999') options = { - :order_id => '51' + order_id: '51' } # store @@ -876,9 +876,9 @@ def test51 end def test52 - credit_card = CreditCard.new(:number => '4457119922390123') + credit_card = CreditCard.new(number: '4457119922390123') options = { - :order_id => '52' + order_id: '52' } # store @@ -898,8 +898,8 @@ def test53 routing_number: '011100012', account_number: '1099999998' ) - options = { - :order_id => '53' + options = { + order_id: '53' } store_response = @gateway.store(check, options) @@ -917,8 +917,8 @@ def test54 routing_number: '1145_7895', account_number: '1022222102' ) - options = { - :order_id => '54' + options = { + order_id: '54' } store_response = @gateway.store(check, options) @@ -931,13 +931,13 @@ def test54 # Implicit Token Registration Tests def test55 - credit_card = CreditCard.new(:number => '5435101234510196', - :month => '11', - :year => '2014', - :brand => 'master', - :verification_value => '987') + credit_card = CreditCard.new(number: '5435101234510196', + month: '11', + year: '2014', + brand: 'master', + verification_value: '987') options = { - :order_id => '55' + order_id: '55' } # authorize @@ -952,13 +952,13 @@ def test55 end def test56 - credit_card = CreditCard.new(:number => '5435109999999999', - :month => '11', - :year => '2014', - :brand => 'master', - :verification_value => '987') + credit_card = CreditCard.new(number: '5435109999999999', + month: '11', + year: '2014', + brand: 'master', + verification_value: '987') options = { - :order_id => '56' + order_id: '56' } # authorize @@ -970,13 +970,13 @@ def test56 end def test57_58 - credit_card = CreditCard.new(:number => '5435101234510196', - :month => '11', - :year => '2014', - :brand => 'master', - :verification_value => '987') + credit_card = CreditCard.new(number: '5435101234510196', + month: '11', + year: '2014', + brand: 'master', + verification_value: '987') options = { - :order_id => '57' + order_id: '57' } # authorize card @@ -993,10 +993,10 @@ def test57_58 # authorize token token = response.params['tokenResponse_litleToken'] options = { - :order_id => '58', - :token => { - :month => credit_card.month, - :year => credit_card.year + order_id: '58', + token: { + month: credit_card.month, + year: credit_card.year } } @@ -1011,10 +1011,10 @@ def test57_58 def test59 token = '1111000100092332' options = { - :order_id => '59', - :token => { - :month => '11', - :year => '2021' + order_id: '59', + token: { + month: '11', + year: '2021' } } @@ -1030,10 +1030,10 @@ def test59 def test60 token = '171299999999999' options = { - :order_id => '60', - :token => { - :month => '11', - :year => '2014' + order_id: '60', + token: { + month: '11', + year: '2014' } } @@ -1048,7 +1048,7 @@ def test60 def test_apple_pay_purchase options = { - :order_id => transaction_id, + order_id: transaction_id } decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( { @@ -1057,7 +1057,8 @@ def test_apple_pay_purchase brand: 'visa', number: '4457000300000007', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) assert response = @gateway.purchase(10010, decrypted_apple_pay, options) assert_success response @@ -1066,7 +1067,7 @@ def test_apple_pay_purchase def test_android_pay_purchase options = { - :order_id => transaction_id, + order_id: transaction_id } decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( { @@ -1076,7 +1077,8 @@ def test_android_pay_purchase brand: 'visa', number: '4457000300000007', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) assert response = @gateway.purchase(10010, decrypted_android_pay, options) assert_success response @@ -1104,22 +1106,22 @@ def test_three_d_secure def test_authorize_and_purchase_and_credit_with_token options = { - :order_id => transaction_id, - :billing_address => { - :name => 'John Smith', - :address1 => '1 Main St.', - :city => 'Burlington', - :state => 'MA', - :zip => '01803-3747', - :country => 'US' + order_id: transaction_id, + billing_address: { + name: 'John Smith', + address1: '1 Main St.', + city: 'Burlington', + state: 'MA', + zip: '01803-3747', + country: 'US' } } - credit_card = CreditCard.new(:number => '5435101234510196', - :month => '11', - :year => '2014', - :brand => 'master', - :verification_value => '987') + credit_card = CreditCard.new(number: '5435101234510196', + month: '11', + year: '2014', + brand: 'master', + verification_value: '987') # authorize assert auth_response = @gateway.authorize(0, credit_card, options) @@ -1132,12 +1134,12 @@ def test_authorize_and_purchase_and_credit_with_token # purchase purchase_options = options.merge({ - :order_id => transaction_id, - :token => { - :month => credit_card.month, - :year => credit_card.year - } - }) + order_id: transaction_id, + token: { + month: credit_card.month, + year: credit_card.year + } + }) assert purchase_response = @gateway.purchase(100, token, purchase_options) assert_success purchase_response @@ -1146,12 +1148,12 @@ def test_authorize_and_purchase_and_credit_with_token # credit credit_options = options.merge({ - :order_id => transaction_id, - :token => { - :month => credit_card.month, - :year => credit_card.year - } - }) + order_id: transaction_id, + token: { + month: credit_card.month, + year: credit_card.year + } + }) assert credit_response = @gateway.credit(500, token, credit_options) assert_success credit_response @@ -1161,7 +1163,7 @@ def test_authorize_and_purchase_and_credit_with_token private - def auth_assertions(amount, card, options, assertions={}) + def auth_assertions(amount, card, options, assertions = {}) # 1: authorize assert response = @gateway.authorize(amount, card, options) assert_success response @@ -1171,19 +1173,19 @@ def auth_assertions(amount, card, options, assertions={}) assert_equal auth_code(options[:order_id]), response.params['authCode'] # 1A: capture - assert response = @gateway.capture(amount, response.authorization, {:id => transaction_id}) + assert response = @gateway.capture(amount, response.authorization, { id: transaction_id }) assert_equal 'Approved', response.message # 1B: credit - assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id}) + assert response = @gateway.credit(amount, response.authorization, { id: transaction_id }) assert_equal 'Approved', response.message # 1C: void - assert response = @gateway.void(response.authorization, {:id => transaction_id}) + assert response = @gateway.void(response.authorization, { id: transaction_id }) assert_equal 'Approved', response.message end - def authorize_avs_assertions(credit_card, options, assertions={}) + def authorize_avs_assertions(credit_card, options, assertions = {}) assert response = @gateway.authorize(000, credit_card, options) assert_equal assertions.key?(:success) ? assertions[:success] : true, response.success? assert_equal assertions[:message] || 'Approved', response.message @@ -1191,7 +1193,7 @@ def authorize_avs_assertions(credit_card, options, assertions={}) assert_equal assertions[:cvv], response.cvv_result['code'], caller.inspect if assertions[:cvv] end - def sale_assertions(amount, card, options, assertions={}) + def sale_assertions(amount, card, options, assertions = {}) # 1: sale assert response = @gateway.purchase(amount, card, options) assert_success response @@ -1201,19 +1203,19 @@ def sale_assertions(amount, card, options, assertions={}) # assert_equal auth_code(options[:order_id]), response.params['authCode'] # 1B: credit - assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id}) + assert response = @gateway.credit(amount, response.authorization, { id: transaction_id }) assert_equal 'Approved', response.message # 1C: void - assert response = @gateway.void(response.authorization, {:id => transaction_id}) + assert response = @gateway.void(response.authorization, { id: transaction_id }) assert_equal 'Approved', response.message end def three_d_secure_assertions(test_id, card, type, source, result) - credit_card = CreditCard.new(:number => card, :month => '01', - :year => '2021', :brand => type, - :verification_value => '261', - :name => 'Mike J. Hammer') + credit_card = CreditCard.new(number: card, month: '01', + year: '2021', brand: type, + verification_value: '261', + name: 'Mike J. Hammer') options = { order_id: test_id, diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb index fb7759cde92..80241a8b361 100644 --- a/test/remote/gateways/remote_litle_test.rb +++ b/test/remote/gateways/remote_litle_test.rb @@ -7,7 +7,7 @@ def setup first_name: 'John', last_name: 'Smith', month: '01', - year: '2012', + year: '2024', brand: 'visa', number: '4457010000000009', verification_value: '349' @@ -53,7 +53,8 @@ def setup brand: 'visa', number: '44444444400009', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) @decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( { source: :android_pay, @@ -62,7 +63,19 @@ def setup brand: 'visa', number: '4457000300000007', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) + + @decrypted_google_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + source: :google_pay, + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + } + ) @check = check( name: 'Tom Black', routing_number: '011075150', @@ -79,6 +92,8 @@ def setup routing_number: '011100012', account_number: '1099999998' ) + + @declined_card = credit_card('4488282659650110', first_name: nil, last_name: 'REFUSED') end def test_successful_authorization @@ -96,6 +111,30 @@ def test_successful_authorization_with_merchant_data assert @gateway.authorize(10010, @credit_card1, options) end + def test_successful_capture_with_customer_id + options = @options.merge(customer_id: '8675309') + assert response = @gateway.authorize(1000, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_succesful_purchase_with_customer_id + options = @options.merge(customer_id: '8675309') + assert response = @gateway.purchase(1000, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_refund_with_customer_id + options = @options.merge(customer_id: '8675309') + + assert purchase = @gateway.purchase(100, @credit_card1, options) + + assert refund = @gateway.refund(444, purchase.authorization, options) + assert_success refund + assert_equal 'Approved', refund.message + end + def test_successful_authorization_with_echeck options = @options.merge({ order_id: '38', @@ -106,24 +145,34 @@ 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=>{ - :name => 'Joe Green', - :address1 => '6 Main St.', - :city => 'Derry', - :state => 'NH', - :zip => '03038', - :country => 'US' - }, + order_id: '6', + billing_address: { + name: 'Joe Green', + address1: '6 Main St.', + city: 'Derry', + state: 'NH', + zip: '03038', + country: 'US' + } } ) assert_failure response @@ -147,12 +196,40 @@ def test_successful_purchase_with_some_empty_address_parts assert_equal 'Approved', response.message end + def test_successful_purchase_with_truncated_billing_address + assert response = @gateway.purchase(10010, @credit_card1, { + order_id: '1', + email: 'test@example.com', + billing_address: { + address1: '1234 Supercalifragilisticexpialidocious', + address2: 'Unit 6', + city: '‎Lake Chargoggagoggmanchauggagoggchaubunagungamaugg', + state: 'ME', + zip: '09901', + country: 'US' + } + }) + assert_success response + assert_equal 'Approved', response.message + end + def test_successful_purchase_with_debt_repayment_flag assert response = @gateway.purchase(10010, @credit_card1, @options.merge(debt_repayment: true)) assert_success response assert_equal 'Approved', response.message end + def test_successful_purchase_with_fraud_filter_override_flag + assert response = @gateway.purchase(10010, @credit_card1, @options.merge(fraud_filter_override: true)) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase_when_fraud_filter_override_flag_not_sent_as_boolean + assert response = @gateway.purchase(10010, @credit_card1, @options.merge(fraud_filter_override: 'hey')) + assert_failure response + end + def test_successful_purchase_with_3ds_fields options = @options.merge({ order_source: '3dsAuthenticated', @@ -167,7 +244,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 @@ -176,6 +253,114 @@ def test_successful_purchase_with_android_pay assert_equal 'Approved', response.message end + def test_successful_purchase_with_google_pay + assert response = @gateway.purchase(10000, @decrypted_google_pay) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_two_data_visa + options = @options.merge( + level_2_data: { + sales_tax: 200 + } + ) + assert response = @gateway.purchase(10010, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_two_data_master + credit_card = CreditCard.new( + first_name: 'John', + last_name: 'Smith', + month: '01', + year: '2024', + brand: 'master', + number: '5555555555554444', + verification_value: '349' + ) + + options = @options.merge( + level_2_data: { + total_tax_amount: 200, + customer_code: 'PO12345', + card_acceptor_tax_id: '011234567', + tax_included_in_total: 'true', + tax_amount: 50 + } + ) + assert response = @gateway.purchase(10010, credit_card, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_three_data_visa + options = @options.merge( + level_3_data: { + discount_amount: 50, + shipping_amount: 50, + duty_amount: 20, + tax_included_in_total: true, + tax_amount: 100, + tax_rate: 0.05, + tax_type_identifier: '01', + card_acceptor_tax_id: '361531321', + line_items: [{ + item_sequence_number: 1, + item_commodity_code: 300, + item_description: 'ramdom-object', + product_code: 'TB123', + quantity: 2, + unit_of_measure: 'EACH', + unit_cost: 25, + discount_per_line_item: 5, + line_item_total: 300, + tax_included_in_total: true, + tax_amount: 100, + tax_rate: 0.05, + tax_type_identifier: '01', + card_acceptor_tax_id: '361531321' + }] + } + ) + assert response = @gateway.purchase(10010, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_three_data_master + credit_card = CreditCard.new( + first_name: 'John', + last_name: 'Smith', + month: '01', + year: '2024', + brand: 'master', + number: '5555555555554444', + verification_value: '349' + ) + + options = @options.merge( + level_3_data: { + total_tax_amount: 200, + customer_code: 'PO12345', + card_acceptor_tax_id: '011234567', + tax_amount: 50, + line_items: [{ + item_description: 'ramdom-object', + product_code: 'TB123', + quantity: 2, + unit_of_measure: 'EACH', + line_item_total: 300 + }] + } + ) + + assert response = @gateway.purchase(10010, credit_card, options) + assert_success response + assert_equal 'Approved', response.message + end + def test_successful_purchase_with_merchant_data options = @options.merge( affiliate: 'some-affiliate', @@ -198,16 +383,16 @@ def test_successful_purchase_with_echeck end def test_unsuccessful_purchase - assert response = @gateway.purchase(60060, @credit_card2, { - :order_id=>'6', - :billing_address=>{ - :name => 'Joe Green', - :address1 => '6 Main St.', - :city => 'Derry', - :state => 'NH', - :zip => '03038', - :country => 'US' - }, + assert response = @gateway.purchase(60060, @declined_card, { + order_id: '6', + billing_address: { + name: 'Joe Green', + address1: '6 Main St.', + city: 'Derry', + state: 'NH', + zip: '03038', + country: 'US' + } }) assert_failure response assert_equal 'Insufficient Funds', response.message @@ -226,6 +411,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 @@ -233,11 +420,11 @@ def test_authorize_capture_refund_void def test_authorize_and_capture_with_stored_credential_recurring credit_card = CreditCard.new(@credit_card_hash.merge( - number: '4100200300011001', - month: '05', - year: '2021', - verification_value: '463' - )) + number: '4100200300011001', + month: '05', + year: '2021', + verification_value: '463' + )) initial_options = @options.merge( order_id: 'Net_Id1', @@ -250,7 +437,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) @@ -268,7 +455,7 @@ def test_authorize_and_capture_with_stored_credential_recurring ) 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 @@ -277,11 +464,11 @@ def test_authorize_and_capture_with_stored_credential_recurring def test_authorize_and_capture_with_stored_credential_installment credit_card = CreditCard.new(@credit_card_hash.merge( - number: '4457010000000009', - month: '01', - year: '2021', - verification_value: '349' - )) + number: '4457010000000009', + month: '01', + year: '2021', + verification_value: '349' + )) initial_options = @options.merge( order_id: 'Net_Id2', @@ -321,11 +508,11 @@ def test_authorize_and_capture_with_stored_credential_installment def test_authorize_and_capture_with_stored_credential_mit_card_on_file credit_card = CreditCard.new(@credit_card_hash.merge( - number: '4457000800000002', - month: '01', - year: '2021', - verification_value: '349' - )) + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) initial_options = @options.merge( order_id: 'Net_Id3', @@ -365,11 +552,11 @@ def test_authorize_and_capture_with_stored_credential_mit_card_on_file def test_authorize_and_capture_with_stored_credential_cit_card_on_file credit_card = CreditCard.new(@credit_card_hash.merge( - number: '4457000800000002', - month: '01', - year: '2021', - verification_value: '349' - )) + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) initial_options = @options.merge( order_id: 'Net_Id3', @@ -409,11 +596,11 @@ def test_authorize_and_capture_with_stored_credential_cit_card_on_file def test_purchase_with_stored_credential_cit_card_on_file_non_ecommerce credit_card = CreditCard.new(@credit_card_hash.merge( - number: '4457000800000002', - month: '01', - year: '2021', - verification_value: '349' - )) + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) initial_options = @options.merge( order_id: 'Net_Id3', @@ -470,9 +657,21 @@ 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 litleTxnId', void.message + assert_match(/^Error validating xml data against the schema/, void.message) + end + + def test_successful_credit + assert credit = @gateway.credit(123456, @credit_card1, @options) + assert_success credit + assert_equal 'Approved', credit.message + end + + def test_failed_credit + @credit_card1.number = '1234567890' + assert credit = @gateway.credit(1, @credit_card1, @options) + assert_failure credit end def test_partial_refund @@ -526,38 +725,36 @@ 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 litleTxnId', 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 litleTxnId', credit_response.message + assert_match(/^Error validating xml data against the schema/, credit_response.message) end def test_void_unsuccessful assert void_response = @gateway.void('123456789012345360') assert_failure void_response - assert_equal 'No transaction found with specified litleTxnId', void_response.message + assert_equal 'No transaction found with specified Transaction Id', void_response.message end def test_store_successful - credit_card = CreditCard.new(@credit_card_hash.merge(:number => '4457119922390123')) - assert store_response = @gateway.store(credit_card, :order_id => '50') + credit_card = CreditCard.new(@credit_card_hash.merge(number: '4457119922390123')) + assert store_response = @gateway.store(credit_card, order_id: '50') 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 paypage_registration_id = 'cDZJcmd1VjNlYXNaSlRMTGpocVZQY1NNlYE4ZW5UTko4NU9KK3p1L1p1VzE4ZWVPQVlSUHNITG1JN2I0NzlyTg=' - assert store_response = @gateway.store(paypage_registration_id, :order_id => '50') + assert store_response = @gateway.store(paypage_registration_id, order_id: '50') assert_success store_response assert_equal 'Account number was successfully registered', store_response.message @@ -566,17 +763,17 @@ def test_store_with_paypage_registration_id_successful end def test_store_unsuccessful - credit_card = CreditCard.new(@credit_card_hash.merge(:number => '4457119999999999')) - assert store_response = @gateway.store(credit_card, :order_id => '51') + 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 def test_store_and_purchase_with_token_successful - credit_card = CreditCard.new(@credit_card_hash.merge(:number => '4100280190123000')) - assert store_response = @gateway.store(credit_card, :order_id => '50') + credit_card = CreditCard.new(@credit_card_hash.merge(number: '4100280190123000')) + assert store_response = @gateway.store(credit_card, order_id: '50') assert_success store_response token = store_response.authorization @@ -584,7 +781,19 @@ 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 + assert store_response = @gateway.store(@credit_card1, order_id: '50') + assert_success store_response + + token = store_response.authorization + assert_equal store_response.params['litleToken'], token + + assert response = @gateway.purchase(10010, token, { basis_expiration_month: '01', basis_expiration_year: '2024' }) + assert_success response + assert_equal 'Partially Approved: The authorized amount is less than the requested amount.', response.message end def test_echeck_store_and_purchase @@ -597,7 +806,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 @@ -615,16 +824,16 @@ def test_unsuccessful_verify def test_successful_purchase_with_dynamic_descriptors assert response = @gateway.purchase(10010, @credit_card1, @options.merge( - descriptor_name: 'SuperCompany', - descriptor_phone: '9193341121' - )) + descriptor_name: 'SuperCompany', + descriptor_phone: '9193341121' + )) assert_success response assert_equal 'Approved', response.message end def test_unsuccessful_xml_schema_validation - credit_card = CreditCard.new(@credit_card_hash.merge(:number => '123456')) - assert store_response = @gateway.store(credit_card, :order_id => '51') + credit_card = CreditCard.new(@credit_card_hash.merge(number: '123456')) + assert store_response = @gateway.store(credit_card, order_id: '51') assert_failure store_response assert_match(/^Error validating xml data against the schema/, store_response.message) diff --git a/test/remote/gateways/remote_mercado_pago_test.rb b/test/remote/gateways/remote_mercado_pago_test.rb index d715d49d04d..9aab14911f3 100644 --- a/test/remote/gateways/remote_mercado_pago_test.rb +++ b/test/remote/gateways/remote_mercado_pago_test.rb @@ -2,18 +2,39 @@ class RemoteMercadoPagoTest < Test::Unit::TestCase def setup + exp_year = Time.now.year + 1 @gateway = MercadoPagoGateway.new(fixtures(:mercado_pago)) + @argentina_gateway = MercadoPagoGateway.new(fixtures(:mercado_pago_argentina)) + @colombian_gateway = MercadoPagoGateway.new(fixtures(:mercado_pago_colombia)) @amount = 500 - @credit_card = credit_card('4509953566233704') - @elo_credit_card = credit_card('5067268650517446', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737' + @credit_card = credit_card('5031433215406351') + @colombian_card = credit_card('4013540682746260') + @elo_credit_card = credit_card( + '5067268650517446', + month: 10, + year: exp_year, + first_name: 'John', + last_name: 'Smith', + verification_value: '737' ) - @declined_card = credit_card('4000300011112220') + @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', + month: 10, + year: exp_year, + first_name: 'John', + last_name: 'Smith', + verification_value: '123' + ) + @declined_card = credit_card('5031433215406351', first_name: 'OTHE') @options = { billing_address: address, shipping_address: address, @@ -25,7 +46,15 @@ def setup processing_mode: 'gateway', merchant_account_id: fixtures(:mercado_pago)[:merchant_account_id], fraud_scoring: true, - fraud_manual_review: true + fraud_manual_review: true, + payment_method_option_id: '123abc' + } + @payer = { + entity_type: 'individual', + type: 'customer', + identification: {}, + first_name: 'Longbob', + last_name: 'Longsen' } end @@ -41,6 +70,18 @@ def test_successful_purchase_with_elo assert_equal 'accredited', response.message end + def test_successful_purchase_with_cabal + response = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_naranja + response = @argentina_gateway.purchase(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + def test_successful_purchase_with_binary_false @options.update(binary_mode: false) response = @gateway.authorize(@amount, @credit_card, @options) @@ -63,6 +104,42 @@ def test_successful_purchase_with_american_express assert_equal 'accredited', response.message end + def test_successful_purchase_with_taxes_and_net_amount + # Minimum transaction amount is 0.30 EUR or ~1112 $COL on 1/27/20. + # This value must exceed that + amount = 10000_00 + + # These values need to be represented as dollars, so divide them by 100 + tax_amount = amount * 0.19 + @options[:net_amount] = (amount - tax_amount) / 100 + @options[:taxes] = [{ value: tax_amount / 100, type: 'IVA' }] + + response = @colombian_gateway.purchase(amount, @colombian_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_notification_url + response = @gateway.purchase(@amount, @credit_card, @options.merge(notification_url: 'https://www.spreedly.com/')) + assert_success response + assert_equal 'https://www.spreedly.com/', response.params['notification_url'] + end + + def test_successful_purchase_with_payer + response = @gateway.purchase(@amount, @credit_card, @options.merge({ payer: @payer })) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_metadata_passthrough + metadata = { 'key_1' => 'value_1', + 'key_2' => 'value_2', + 'key_3' => { 'nested_key_1' => 'value_3' } } + response = @gateway.purchase(@amount, @credit_card, @options.merge({ metadata: metadata })) + assert_success response + assert_equal metadata, response.params['metadata'] + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -90,6 +167,32 @@ def test_successful_authorize_and_capture_with_elo assert_equal 'accredited', capture.message end + def test_successful_authorize_and_capture_with_cabal + auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert capture = @argentina_gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_successful_authorize_and_capture_with_naranja + auth = @argentina_gateway.authorize(@amount, @naranja_credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert capture = @argentina_gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_successful_authorize_with_capture_option + auth = @gateway.authorize(@amount, @credit_card, @options.merge(capture: true)) + assert_success auth + assert_equal 'accredited', auth.message + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response @@ -97,7 +200,7 @@ def test_failed_authorize end def test_partial_capture - auth = @gateway.authorize(@amount+1, @credit_card, @options) + auth = @gateway.authorize(@amount + 1, @credit_card, @options) assert_success auth assert capture = @gateway.capture(@amount, auth.authorization) @@ -129,11 +232,29 @@ def test_successful_refund_with_elo assert_equal nil, refund.message end + def test_successful_refund_with_cabal + purchase = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success purchase + + assert refund = @argentina_gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal nil, refund.message + end + + def test_successful_refund_with_naranja + purchase = @argentina_gateway.purchase(@amount, @naranja_credit_card, @options) + assert_success purchase + + assert refund = @argentina_gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal nil, refund.message + end + def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -161,6 +282,24 @@ def test_successful_void_with_elo assert_equal 'by_collector', void.message end + def test_successful_void_with_cabal + auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + + assert void = @argentina_gateway.void(auth.authorization) + assert_success void + assert_equal 'by_collector', void.message + end + + def test_successful_void_with_naranja + auth = @argentina_gateway.authorize(@amount, @naranja_credit_card, @options) + assert_success auth + + assert void = @argentina_gateway.void(auth.authorization) + assert_success void + assert_equal 'by_collector', void.message + end + def test_failed_void response = @gateway.void('') assert_failure response @@ -173,12 +312,39 @@ def test_successful_verify assert_match %r{pending_capture}, response.message end + def test_successful_verify_with_amount + @options[:amount] = 200 + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{pending_capture}, response.message + end + def test_failed_verify response = @gateway.verify(@declined_card, @options) assert_failure response assert_match %r{cc_rejected_other_reason}, response.message end + def test_successful_inquire_with_id + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert inquire = @gateway.inquire(auth.authorization) + assert_success inquire + assert_equal auth.message, inquire.message + end + + def test_successful_inquire_with_external_reference + auth = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: 'abcd1234')) + assert_success auth + assert auth.params['external_reference'] = 'abcd1234' + + assert inquire = @gateway.inquire(nil, { external_reference: 'abcd1234' }) + assert_success inquire + assert_equal auth.authorization, inquire.authorization + end + def test_invalid_login gateway = MercadoPagoGateway.new(access_token: '') @@ -197,5 +363,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:access_token], transcript) end - end diff --git a/test/remote/gateways/remote_merchant_e_solutions_test.rb b/test/remote/gateways/remote_merchant_e_solutions_test.rb index a9504dac516..c514aa6e3a2 100644 --- a/test/remote/gateways/remote_merchant_e_solutions_test.rb +++ b/test/remote/gateways/remote_merchant_e_solutions_test.rb @@ -11,18 +11,29 @@ def setup @declined_card = credit_card('4111111111111112') @options = { - :order_id => '123', - :billing_address => { - :name => 'John Doe', - :address1 => '123 State Street', - :address2 => 'Apartment 1', - :city => 'Nowhere', - :state => 'MT', - :country => 'US', - :zip => '55555', - :phone => '555-555-5555' + order_id: '123', + billing_address: { + name: 'John Doe', + address1: '123 State Street', + address2: 'Apartment 1', + city: 'Nowhere', + state: 'MT', + country: 'US', + zip: '55555', + phone: '555-555-5555', + recurring_pmt_num: 11, + recurring_pmt_count: 10 } } + @stored_credential_options = { + moto_ecommerce_ind: '7', + client_reference_number: '345892', + recurring_pmt_num: 11, + recurring_pmt_count: 10, + card_on_file: 'Y', + cit_mit_indicator: 'C101', + account_data_source: 'Y' + } end # MES has a race condition with immediately trying to operate on an @@ -37,6 +48,12 @@ def test_successful_purchase assert_equal 'This transaction has been approved', response.message end + def test_successful_purchase_with_moto_ecommerce_ind + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@stored_credential_options)) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -44,7 +61,7 @@ def test_unsuccessful_purchase end def test_purchase_with_long_order_id - options = {order_id: 'thisislongerthan17characters'} + options = { order_id: 'thisislongerthan17characters' } assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'This transaction has been approved', response.message @@ -71,7 +88,8 @@ def test_failed_capture def test_store_purchase_unstore assert store = @gateway.store(@credit_card) assert_success store - assert_equal 'This transaction has been approved', store.message + assert_equal 'Card Ok', store.message + assert_equal store.authorization, store.params['card_id'] assert purchase = @gateway.purchase(@amount, store.authorization, @options) assert_success purchase assert_equal 'This transaction has been approved', purchase.message @@ -116,15 +134,15 @@ def test_successful_avs_check def test_unsuccessful_avs_check_with_bad_street_address options = { - :billing_address => { - :name => 'John Doe', - :address1 => '124 State Street', - :address2 => 'Apartment 1', - :city => 'Nowhere', - :state => 'MT', - :country => 'US', - :zip => '55555', - :phone => '555-555-5555' + billing_address: { + name: 'John Doe', + address1: '124 State Street', + address2: 'Apartment 1', + city: 'Nowhere', + state: 'MT', + country: 'US', + zip: '55555', + phone: '555-555-5555' } } assert response = @gateway.purchase(@amount, @credit_card, options) @@ -136,20 +154,20 @@ def test_unsuccessful_avs_check_with_bad_street_address def test_unsuccessful_avs_check_with_bad_zip options = { - :billing_address => { - :name => 'John Doe', - :address1 => '123 State Street', - :address2 => 'Apartment 1', - :city => 'Nowhere', - :state => 'MT', - :country => 'US', - :zip => '55554', - :phone => '555-555-5555' + billing_address: { + name: 'John Doe', + address1: '123 State Street', + address2: 'Apartment 1', + city: 'Nowhere', + state: 'MT', + country: 'US', + zip: '55554', + phone: '555-555-5555' } } assert response = @gateway.purchase(@amount, @credit_card, options) assert_equal 'A', response.avs_result['code'] - assert_equal 'Street address matches, but 5-digit and 9-digit postal code do not match.', response.avs_result['message'] + assert_equal 'Street address matches, but postal code does not match.', response.avs_result['message'] assert_equal 'Y', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] end @@ -162,13 +180,13 @@ def test_successful_cvv_check def test_unsuccessful_cvv_check credit_card = ActiveMerchant::Billing::CreditCard.new({ - :first_name => 'John', - :last_name => 'Doe', - :number => '4111111111111111', - :month => '11', - :year => (Time.now.year + 1).to_s, - :verification_value => '555' - }) + first_name: 'John', + last_name: 'Doe', + number: '4111111111111111', + month: '11', + year: (Time.now.year + 1).to_s, + verification_value: '555' + }) assert response = @gateway.purchase(@amount, credit_card, @options) assert_equal 'N', response.cvv_result['code'] assert_equal 'CVV does not match', response.cvv_result['message'] @@ -176,29 +194,29 @@ def test_unsuccessful_cvv_check def test_invalid_login gateway = MerchantESolutionsGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response end - def test_connection_failure_404_notfound_with_purchase - @gateway.test_url = 'https://cert.merchante-solutions.com/mes-api/tridentApiasdasd' - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal 'Failed with 404 Not Found', response.message - end - def test_successful_purchase_with_3dsecure_params - assert response = @gateway.purchase(@amount, @credit_card, @options.merge( - { :xid => 'ERERERERERERERERERERERERERE=', - :cavv => 'ERERERERERERERERERERERERERE=' - })) + options = @options.merge( + { xid: 'ERERERERERERERERERERERERERE=', + cavv: 'ERERERERERERERERERERERERERE=' } + ) + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'This transaction has been approved', response.message end + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options.merge({ verify_amount: 0 })) + assert_success response + assert_equal 'Card Ok', response.message + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_merchant_one_test.rb b/test/remote/gateways/remote_merchant_one_test.rb index 0e4f98fb23e..bad6b46aaae 100644 --- a/test/remote/gateways/remote_merchant_one_test.rb +++ b/test/remote/gateways/remote_merchant_one_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteMerchantOneTest < Test::Unit::TestCase - def setup @gateway = MerchantOneGateway.new(fixtures(:merchant_one)) @@ -10,9 +9,9 @@ def setup @declined_card = credit_card('1111111111111111') @options = { - :order_id => '1', - :description => 'Store Purchase', - :billing_address => { + order_id: '1', + description: 'Store Purchase', + billing_address: { name: 'Jim Smith', address1: '1234 My Street', address2: 'Apt 1', @@ -54,9 +53,9 @@ def test_failed_capture def test_invalid_login gateway = MerchantOneGateway.new( - :username => 'nnn', - :password => 'nnn' - ) + username: 'nnn', + password: 'nnn' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Authentication Failed', response.message diff --git a/test/remote/gateways/remote_merchant_ware_test.rb b/test/remote/gateways/remote_merchant_ware_test.rb index b4b4dc2c899..1e46bcd8ccf 100644 --- a/test/remote/gateways/remote_merchant_ware_test.rb +++ b/test/remote/gateways/remote_merchant_ware_test.rb @@ -6,11 +6,11 @@ def setup @amount = rand(200..1199) - @credit_card = credit_card('5424180279791732', {:brand => 'master'}) + @credit_card = credit_card('5424180279791732', { brand: 'master' }) @options = { - :order_id => generate_unique_id, - :billing_address => address + order_id: generate_unique_id, + billing_address: address } end @@ -93,10 +93,10 @@ def test_failed_capture def test_invalid_login gateway = MerchantWareGateway.new( - :login => '', - :password => '', - :name => '' - ) + login: '', + password: '', + name: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Server was unable to process request. ---> Invalid Credentials.', response.message 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 155bdc2e460..b553611b7e3 100644 --- a/test/remote/gateways/remote_merchant_ware_version_four_test.rb +++ b/test/remote/gateways/remote_merchant_ware_version_four_test.rb @@ -4,16 +4,16 @@ class RemoteMerchantWareVersionFourTest < Test::Unit::TestCase def setup @gateway = MerchantWareVersionFourGateway.new(fixtures(:merchant_ware_version_four)) @amount = rand(200..1199) - @credit_card = credit_card('5424180279791732', {:brand => 'master'}) + @credit_card = credit_card('5424180279791732', { brand: 'master' }) @declined_card = credit_card('1234567890123') @options = { - :order_id => generate_unique_id[0, 8], - :billing_address => address + order_id: generate_unique_id[0, 8], + billing_address: address } @reference_purchase_options = { - :order_id => generate_unique_id[0, 8] + order_id: generate_unique_id[0, 8] } end @@ -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 @@ -92,10 +94,10 @@ def test_failed_capture def test_invalid_login gateway = MerchantWareVersionFourGateway.new( - :login => '', - :password => '', - :name => '' - ) + login: '', + password: '', + name: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid Credentials.', response.message diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb index d9c5e826c4a..5cd61daff72 100644 --- a/test/remote/gateways/remote_merchant_warrior_test.rb +++ b/test/remote/gateways/remote_merchant_warrior_test.rb @@ -2,29 +2,37 @@ class RemoteMerchantWarriorTest < Test::Unit::TestCase def setup - @gateway = MerchantWarriorGateway.new(fixtures(:merchant_warrior).merge(:test => true)) + @gateway = MerchantWarriorGateway.new(fixtures(:merchant_warrior).merge(test: true)) @success_amount = 100 @failure_amount = 205 @credit_card = credit_card( - '5123456789012346', - :month => 5, - :year => Time.now.year + 2, - :verification_value => '123', - :brand => 'master' + '4564710000000004', + month: '2', + year: '29', + verification_value: '847', + brand: 'visa' + ) + + @expired_card = credit_card( + '4564710000000012', + month: '2', + year: '05', + verification_value: '963', + brand: 'visa' ) @options = { - :billing_address => { - :name => 'Longbob Longsen', - :country => 'AU', - :state => 'Queensland', - :city => 'Brisbane', - :address1 => '123 test st', - :zip => '4000' + billing_address: { + name: 'Longbob Longsen', + country: 'AU', + state: 'Queensland', + city: 'Brisbane', + address1: '123 test st', + zip: '4000' }, - :description => 'TestProduct' + description: 'TestProduct' } end @@ -44,15 +52,15 @@ def test_successful_authorize def test_successful_purchase assert purchase = @gateway.purchase(@success_amount, @credit_card, @options) - assert_equal 'Transaction approved', purchase.message assert_success purchase + assert_equal 'Transaction approved', purchase.message assert_not_nil purchase.params['transaction_id'] assert_equal purchase.params['transaction_id'], purchase.authorization end def test_failed_purchase - assert purchase = @gateway.purchase(@failure_amount, @credit_card, @options) - assert_equal 'Transaction declined', purchase.message + assert purchase = @gateway.purchase(@success_amount, @expired_card, @options) + assert_match 'Card has expired', purchase.message assert_failure purchase assert_not_nil purchase.params['transaction_id'] assert_equal purchase.params['transaction_id'], purchase.authorization @@ -68,10 +76,25 @@ def test_successful_refund def test_failed_refund assert refund = @gateway.refund(@success_amount, 'invalid-transaction-id') - assert_match %r{Invalid transactionID}, refund.message + assert_match %r{MW - 011:Invalid transactionID}, refund.message assert_failure refund end + def test_successful_void + assert purchase = @gateway.purchase(@success_amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, amount: @success_amount) + assert_success void + assert_equal 'Transaction approved', void.message + end + + def test_failed_void + assert void = @gateway.void('invalid-transaction-id', amount: @success_amount) + assert_match %r{MW - 011:Invalid transactionID}, void.message + assert_failure void + end + def test_capture_too_much assert auth = @gateway.authorize(300, @credit_card, @options) assert_success auth @@ -114,6 +137,37 @@ def test_successful_purchase_with_funky_names assert_success purchase end + def test_successful_purchase_with_recurring_flag + @options[:recurring_flag] = 1 + test_successful_purchase + end + + def test_successful_authorize_with_recurring_flag + @options[:recurring_flag] = 1 + test_successful_authorize + end + + def test_successful_authorize_with_soft_descriptors + @options[:descriptor_name] = 'FOO*Test' + @options[:descriptor_city] = 'Melbourne' + @options[:descriptor_state] = 'VIC' + test_successful_authorize + end + + def test_successful_purchase_with_soft_descriptors + @options[:descriptor_name] = 'FOO*Test' + @options[:descriptor_city] = 'Melbourne' + @options[:descriptor_state] = 'VIC' + test_successful_purchase + end + + def test_successful_refund_with_soft_descriptors + @options[:descriptor_name] = 'FOO*Test' + @options[:descriptor_city] = 'Melbourne' + @options[:descriptor_state] = 'VIC' + test_successful_refund + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@success_amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_mercury_certification_test.rb b/test/remote/gateways/remote_mercury_certification_test.rb index 56cb9de3234..9422b396241 100644 --- a/test/remote/gateways/remote_mercury_certification_test.rb +++ b/test/remote/gateways/remote_mercury_certification_test.rb @@ -13,7 +13,7 @@ def test_sale_and_reversal assert_success sale assert_equal 'AP', sale.params['text_response'] - reversal = tokenization_gateway.void(sale.authorization, options.merge(:try_reversal => true)) + reversal = tokenization_gateway.void(sale.authorization, options.merge(try_reversal: true)) assert_success reversal assert_equal 'REVERSED', reversal.params['text_response'] end @@ -45,7 +45,7 @@ def test_preauth_and_reversal assert_success preauth assert_equal 'AP', preauth.params['text_response'] - reversal = tokenization_gateway.void(preauth.authorization, options.merge(:try_reversal => true)) + reversal = tokenization_gateway.void(preauth.authorization, options.merge(try_reversal: true)) assert_success reversal assert_equal 'REVERSED', reversal.params['text_response'] end @@ -70,45 +70,45 @@ def test_preauth_capture_and_reversal def tokenization_gateway @tokenization_gateway ||= MercuryGateway.new( - :login => '023358150511666', - :password => 'xyz' + login: '023358150511666', + password: 'xyz' ) end def visa @visa ||= credit_card( '4003000123456781', - :brand => 'visa', - :month => '12', - :year => '15', - :verification_value => '123' + brand: 'visa', + month: '12', + year: '15', + verification_value: '123' ) end def disc @disc ||= credit_card( '6011000997235373', - :brand => 'discover', - :month => '12', - :year => '15', - :verification_value => '362' + brand: 'discover', + month: '12', + year: '15', + verification_value: '362' ) end def mc @mc ||= credit_card( '5439750001500248', - :brand => 'master', - :month => '12', - :year => '15', - :verification_value => '123' + brand: 'master', + month: '12', + year: '15', + verification_value: '123' ) end - def options(order_id=nil, other={}) + def options(order_id = nil, other = {}) { - :order_id => order_id, - :description => 'ActiveMerchant', + order_id: order_id, + description: 'ActiveMerchant' }.merge(other) end end diff --git a/test/remote/gateways/remote_mercury_test.rb b/test/remote/gateways/remote_mercury_test.rb index 4dd1a95b80c..37079eaf852 100644 --- a/test/remote/gateways/remote_mercury_test.rb +++ b/test/remote/gateways/remote_mercury_test.rb @@ -9,27 +9,27 @@ def setup @amount = 100 - @credit_card = credit_card('4003000123456781', :brand => 'visa', :month => '12', :year => '18') + @credit_card = credit_card('4003000123456781', brand: 'visa', month: '12', year: '18') @track_1_data = '%B4003000123456781^LONGSEN/L. ^18121200000000000000**123******?*' @track_2_data = ';5413330089010608=2512101097750213?' @options = { - :order_id => 'c111111111.1', - :description => 'ActiveMerchant' + order_id: 'c111111111.1', + description: 'ActiveMerchant' } @options_with_billing = @options.merge( - :merchant => '999', - :billing_address => { - :address1 => '4 Corporate SQ', - :zip => '30329' + merchant: '999', + billing_address: { + address1: '4 Corporate SQ', + zip: '30329' } ) @full_options = @options_with_billing.merge( - :ip => '123.123.123.123', - :merchant => 'Open Dining', - :customer => 'Tim', - :tax => '5' + ip: '123.123.123.123', + merchant: 'Open Dining', + customer: 'Tim', + tax: '5' ) close_batch @@ -101,7 +101,7 @@ def test_avs_and_cvv_results }, response.avs_result ) - assert_equal({'code'=>'M', 'message'=>'CVV matches'}, response.cvv_result) + assert_equal({ 'code' => 'M', 'message' => 'CVV matches' }, response.cvv_result) end def test_avs_and_cvv_results_with_track_data @@ -118,7 +118,7 @@ def test_avs_and_cvv_results_with_track_data }, response.avs_result ) - assert_equal({'code'=>'P', 'message'=>'CVV not processed'}, response.cvv_result) + assert_equal({ 'code' => 'P', 'message' => 'CVV not processed' }, response.cvv_result) end def test_partial_capture @@ -144,7 +144,7 @@ def test_authorize_with_bad_expiration_date end def test_mastercard_authorize_and_capture_with_refund - mc = credit_card('5499990123456781', :brand => 'master') + mc = credit_card('5499990123456781', brand: 'master') response = @gateway.authorize(200, mc, @options) assert_success response @@ -161,7 +161,7 @@ def test_mastercard_authorize_and_capture_with_refund end def test_amex_authorize_and_capture_with_refund - amex = credit_card('373953244361001', :brand => 'american_express', :verification_value => '1234') + amex = credit_card('373953244361001', brand: 'american_express', verification_value: '1234') response = @gateway.authorize(201, amex, @options) assert_success response @@ -177,7 +177,7 @@ def test_amex_authorize_and_capture_with_refund end def test_discover_authorize_and_capture - discover = credit_card('6011000997235373', :brand => 'discover') + discover = credit_card('6011000997235373', brand: 'discover') response = @gateway.authorize(225, discover, @options_with_billing) assert_success response @@ -206,7 +206,7 @@ def test_authorize_and_capture_without_tokenization assert_success response assert_equal '1.00', response.params['authorize'] - capture = gateway.capture(nil, response.authorization, :credit_card => @credit_card) + capture = gateway.capture(nil, response.authorization, credit_card: @credit_card) assert_success capture assert_equal '1.00', capture.params['authorize'] end diff --git a/test/remote/gateways/remote_metrics_global_test.rb b/test/remote/gateways/remote_metrics_global_test.rb index 3669fd41a11..329c735622f 100644 --- a/test/remote/gateways/remote_metrics_global_test.rb +++ b/test/remote/gateways/remote_metrics_global_test.rb @@ -6,11 +6,11 @@ def setup @gateway = MetricsGlobalGateway.new(fixtures(:metrics_global)) @amount = 100 - @credit_card = credit_card('4111111111111111', :verification_value => '999') + @credit_card = credit_card('4111111111111111', verification_value: '999') @options = { - :order_id => generate_unique_id, - :billing_address => address(:address1 => '888 Test Street', :zip => '77777'), - :description => 'Store purchase' + order_id: generate_unique_id, + billing_address: address(address1: '888 Test Street', zip: '77777'), + description: 'Store purchase' } end @@ -57,19 +57,19 @@ def test_authorization_and_void def test_bad_login gateway = MetricsGlobalGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card) assert_equal Response, response.class - assert_equal ['avs_result_code', - 'card_code', - 'response_code', - 'response_reason_code', - 'response_reason_text', - 'transaction_id'], response.params.keys.sort + assert_equal %w[avs_result_code + card_code + response_code + response_reason_code + response_reason_text + transaction_id], response.params.keys.sort assert_match(/Authentication Failed/, response.message) @@ -78,19 +78,19 @@ def test_bad_login def test_using_test_request gateway = MetricsGlobalGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card) assert_equal Response, response.class - assert_equal ['avs_result_code', - 'card_code', - 'response_code', - 'response_reason_code', - 'response_reason_text', - 'transaction_id'], response.params.keys.sort + assert_equal %w[avs_result_code + card_code + response_code + response_reason_code + response_reason_text + transaction_id], response.params.keys.sort assert_match(/Authentication Failed/, response.message) diff --git a/test/remote/gateways/remote_micropayment_test.rb b/test/remote/gateways/remote_micropayment_test.rb index fb4589e7d71..d5c49cf3787 100644 --- a/test/remote/gateways/remote_micropayment_test.rb +++ b/test/remote/gateways/remote_micropayment_test.rb @@ -89,7 +89,7 @@ def test_successful_void_for_purchase end def test_successful_authorize_and_capture_and_refund - response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: false)) + response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: false)) assert_success response assert_equal 'Succeeded', response.message assert_match %r(^\w+\|.+$), response.authorization diff --git a/test/remote/gateways/remote_migs_test.rb b/test/remote/gateways/remote_migs_test.rb index 077762dc30b..a920aec863d 100644 --- a/test/remote/gateways/remote_migs_test.rb +++ b/test/remote/gateways/remote_migs_test.rb @@ -10,15 +10,18 @@ def setup @amount = 100 @declined_amount = 105 - @visa = credit_card('4987654321098769', :month => 5, :year => 2021, :brand => 'visa') - @master = credit_card('5123456789012346', :month => 5, :year => 2021, :brand => 'master') - @amex = credit_card('371449635311004', :month => 5, :year => 2021, :brand => 'american_express') - @diners = credit_card('30123456789019', :month => 5, :year => 2021, :brand => 'diners_club') + @visa = credit_card('4987654321098769', month: 5, year: 2021, brand: 'visa') + @master = credit_card('5123456789012346', month: 5, year: 2021, brand: 'master') + @amex = credit_card('371449635311004', month: 5, year: 2021, brand: 'american_express') + @diners = credit_card('30123456789019', month: 5, year: 2021, brand: 'diners_club') @credit_card = @visa + @valid_tx_source = 'MOTO' + @invalid_tx_source = 'penguin' + @options = { - :order_id => '1', - :currency => 'SAR' + order_id: '1', + currency: 'SAR' } @three_ds_options = { @@ -33,10 +36,10 @@ def setup def test_server_purchase_url options = { - :order_id => 1, - :unique_id => 9, - :return_url => 'http://localhost:8080/payments/return', - :currency => 'SAR' + order_id: 1, + unique_id: 9, + return_url: 'http://localhost:8080/payments/return', + currency: 'SAR' } choice_url = @gateway.purchase_offsite_url(@amount, options) @@ -49,7 +52,7 @@ def test_server_purchase_url } responses.each_pair do |card_type, response_text| - url = @gateway.purchase_offsite_url(@amount, options.merge(:card_type => card_type)) + url = @gateway.purchase_offsite_url(@amount, options.merge(card_type: card_type)) assert_response_match response_text, url end end @@ -104,6 +107,48 @@ def test_refund # assert_equal 'Approved', response.message end + def test_purchase_passes_tx_source + # returns a successful response when a valid tx_source parameter is sent + assert good_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + assert_equal 'Approved', good_response.message + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response + end + + def test_capture_passes_tx_source + # authorize the credit card in order to then run capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + # returns a successful response when a valid tx_source paramater is sent + assert good_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response + end + + def test_void_passes_tx_source + # authorize the credit card in order to then run capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + # returns a successful response when a valid tx_source paramater is sent + assert good_response = @gateway.void(auth.authorization, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + assert_equal 'Approved', good_response.message + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.void(auth.authorization, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response + end + def test_status purchase_response = @gateway.purchase(@declined_amount, @credit_card, @options) assert response = @gateway.status(purchase_response.params['MerchTxnRef']) @@ -112,7 +157,7 @@ def test_status end def test_invalid_login - gateway = MigsGateway.new(:login => '', :password => '') + gateway = MigsGateway.new(login: '', password: '') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Required field vpc_Merchant was not present in the request', response.message @@ -166,7 +211,7 @@ def assert_response_match(regexp, url) def https_response(url, cookie = nil) retry_exceptions do - headers = cookie ? {'Cookie' => cookie} : {} + headers = cookie ? { 'Cookie' => cookie } : {} response = raw_ssl_request(:get, url, nil, headers) if response.is_a?(Net::HTTPRedirection) new_cookie = [cookie, response['Set-Cookie']].compact.join(';') diff --git a/test/remote/gateways/remote_mit_test.rb b/test/remote/gateways/remote_mit_test.rb new file mode 100644 index 00000000000..db58072b039 --- /dev/null +++ b/test/remote/gateways/remote_mit_test.rb @@ -0,0 +1,129 @@ +require 'test_helper' + +class RemoteMitTest < Test::Unit::TestCase + def setup + @gateway = MitGateway.new(fixtures(:mit)) + + @amount = 1115 + @amount_fail = 11165 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: '5555555555555557', + verification_value: '261', + month: '09', + year: '2025', + first_name: 'Pedro', + last_name: 'Flores Valdes' + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + number: '4111111111111111', + verification_value: '318', + month: '09', + year: '2025', + first_name: 'Pedro', + last_name: 'Flores Valdes' + ) + + @options_success = { + order_id: '721', + transaction_id: '721', # unique id for every transaction, needs to be generated for every test + billing_address: address, + description: 'Store Purchase' + } + + @options = { + order_id: '721', + transaction_id: '721', # unique id for every transaction, needs to be generated for every test + billing_address: address, + description: 'Store Purchase', + api_key: fixtures(:mit)[:apikey] + } + end + + def test_successful_purchase + # ############################################################### + # create unique id based on timestamp for testing purposes + # Each order / transaction passed to the gateway must be unique + time = Time.now.to_i.to_s + @options_success[:order_id] = 'TID|' + time + response = @gateway.purchase(@amount, @credit_card, @options_success) + assert_success response + assert_equal 'approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount_fail, @declined_card, @options) + assert_failure response + assert_not_equal 'approved', response.message + end + + def test_successful_authorize_and_capture + # ############################################################### + # create unique id based on timestamp for testing purposes + # Each order / transaction passed to the gateway must be unique + time = Time.now.to_i.to_s + @options_success[:order_id] = 'TID|' + time + auth = @gateway.authorize(@amount, @credit_card, @options_success) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options_success) + assert_success capture + assert_equal 'approved', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount_fail, @declined_card, @options) + assert_failure response + assert_not_equal 'approved', response.message + end + + def test_failed_capture + # ############################################################### + # create unique id based on timestamp for testing purposes + # Each order / transaction passed to the gateway must be unique + time = Time.now.to_i.to_s + @options[:order_id] = 'TID|' + time + response = @gateway.capture(@amount_fail, 'requiredauth', @options) + assert_failure response + assert_not_equal 'approved', response.message + end + + def test_successful_refund + # ############################################################### + # create unique id based on timestamp for testing purposes + # Each order / transaction passed to the gateway must be unique + time = Time.now.to_i.to_s + @options_success[:order_id] = 'TID|' + time + purchase = @gateway.purchase(@amount, @credit_card, @options_success) + assert_success purchase + + # authorization is required + assert refund = @gateway.refund(@amount, purchase.authorization, @options_success) + assert_success refund + assert_equal 'approved', refund.message + end + + def test_failed_refund + # ############################################################### + # create unique id based on timestamp for testing purposes + # Each order / transaction passed to the gateway must be unique + time = Time.now.to_i.to_s + @options[:order_id] = 'TID|' + time + response = @gateway.refund(@amount, 'invalidauth', @options) + assert_failure response + assert_not_equal 'approved', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options_success) + end + + clean_transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value, clean_transcript) + assert_scrubbed(@gateway.options[:api_key], clean_transcript) + assert_scrubbed(@gateway.options[:key_session], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_modern_payments_cim_test.rb b/test/remote/gateways/remote_modern_payments_cim_test.rb index 8c310d0fe17..ed480d5fa65 100644 --- a/test/remote/gateways/remote_modern_payments_cim_test.rb +++ b/test/remote/gateways/remote_modern_payments_cim_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteModernPaymentsCimTest < Test::Unit::TestCase - def setup @gateway = ModernPaymentsCimGateway.new(fixtures(:modern_payments)) @@ -10,8 +9,8 @@ def setup @declined_card = credit_card('4000000000000000') @options = { - :billing_address => address, - :customer => 'JIMSMITH2000' + billing_address: address, + customer: 'JIMSMITH2000' } end @@ -47,9 +46,9 @@ def test_succsessful_authorize_credit_card_payment def test_invalid_login gateway = ModernPaymentsCimGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.create_customer(@options) assert_failure response assert_equal ModernPaymentsCimGateway::ERROR_MESSAGE, response.message diff --git a/test/remote/gateways/remote_modern_payments_test.rb b/test/remote/gateways/remote_modern_payments_test.rb index e0fff6eeb95..a5467e42fc0 100644 --- a/test/remote/gateways/remote_modern_payments_test.rb +++ b/test/remote/gateways/remote_modern_payments_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteModernPaymentTest < Test::Unit::TestCase - def setup @gateway = ModernPaymentsGateway.new(fixtures(:modern_payments)) @@ -10,9 +9,9 @@ def setup @declined_card = credit_card('4000000000000000') @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -34,9 +33,9 @@ def test_unsuccessful_purchase def test_invalid_login gateway = ModernPaymentsGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert_raises(ActiveMerchant::ResponseError) do gateway.purchase(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_moka_test.rb b/test/remote/gateways/remote_moka_test.rb new file mode 100644 index 00000000000..8dbac306cf3 --- /dev/null +++ b/test/remote/gateways/remote_moka_test.rb @@ -0,0 +1,274 @@ +require 'test_helper' + +class RemoteMokaTest < Test::Unit::TestCase + def setup + @gateway = MokaGateway.new(fixtures(:moka)) + + @amount = 100 + @credit_card = credit_card('5269111122223332') + @declined_card = credit_card('4000300011112220') + @options = { + description: 'Store Purchase' + } + @three_ds_options = @options.merge({ + execute_threed: true, + redirect_type: 1, + redirect_url: 'www.example.com' + }) + end + + def test_invalid_login + gateway = MokaGateway.new(dealer_code: '', username: '', password: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'PaymentDealer.CheckPaymentDealerAuthentication.InvalidAccount', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_single_digit_exp_month + @credit_card.month = 1 + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + sub_merchant_name: 'Example Co.', + is_pool_payment: 1 + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_buyer_information + options = { + billing_address: address, + email: 'safiye.ali@example.com' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_basket_products + # Basket Products must be on the list of Merchant Products for your Moka account. + # To see this list or add products to it, log in to your Moka Dashboard + options = { + basket_product: [ + { + product_id: 333, + product_code: '0173', + unit_price: 19900, + quantity: 1 + }, + { + product_id: 281, + product_code: '38', + unit_price: 5000, + quantity: 1 + } + ] + } + + response = @gateway.purchase(24900, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_nil_cvv + test_card = credit_card('5269111122223332') + test_card.verification_value = nil + + response = @gateway.purchase(@amount, test_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_installments + options = @options.merge(installment_number: 12) + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + + assert_equal 'PaymentDealer.DoDirectPayment.VirtualPosNotAvailable', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_and_capture_using_non_default_currency + options = @options.merge(currency: 'USD') + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, currency: 'USD') + assert_success capture + assert_equal 'Success', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'PaymentDealer.DoDirectPayment.VirtualPosNotAvailable', response.error_code + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 0.1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'PaymentDealer.DoCapture.OtherTrxCodeOrVirtualPosOrderIdMustGiven', response.message + end + + # # Moka does not allow a same-day refund on a purchase/capture. In order to test refund, + # # you must pass a reference that has 'matured' at least one day. + # def test_successful_refund + # my_matured_reference = 'REPLACE ME' + # assert refund = @gateway.refund(0, my_matured_reference) + # assert_success refund + # assert_equal 'Success', refund.message + # end + + # # Moka does not allow a same-day refund on a purchase/capture. In order to test refund, + # # you must pass a reference that has 'matured' at least one day. For the purposes of testing + # # a partial refund, make sure the original transaction being referenced was for an amount + # # greater than the 'partial_amount' supplied in the test. + # def test_partial_refund + # my_matured_reference = 'REPLACE ME' + # partial_amount = 50 + # assert refund = @gateway.refund(partial_amount, my_matured_reference) + # assert_success refund + # assert_equal 'Success', refund.message + # end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'PaymentDealer.DoCreateRefundRequest.OtherTrxCodeOrVirtualPosOrderIdMustGiven', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'PaymentDealer.DoVoid.InvalidRequest', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Success', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match 'PaymentDealer.DoDirectPayment.VirtualPosNotAvailable', response.message + end + + # 3ds Tests + + def test_successful_initiation_of_3ds_authorize + response = @gateway.authorize(@amount, @credit_card, @three_ds_options) + + assert_success response + assert_equal 'Success', response.message + assert response.params['Data']['Url'].present? + assert response.params['Data']['CodeForHash'].present? + end + + def test_failed_3ds_authorize + response = @gateway.authorize(@amount, @declined_card, @three_ds_options) + + assert_failure response + assert_equal 'PaymentDealer.DoDirectPayment3dRequest.VirtualPosNotAvailable', response.message + end + + def test_successful_initiation_of_3ds_purchase + response = @gateway.purchase(@amount, @credit_card, @three_ds_options) + + assert_success response + assert_equal 'Success', response.message + assert response.params['Data']['Url'].present? + assert response.params['Data']['CodeForHash'].present? + end + + def test_failed_3ds_purchase + response = @gateway.purchase(@amount, @declined_card, @three_ds_options) + + assert_failure response + assert_equal 'PaymentDealer.DoDirectPayment3dRequest.VirtualPosNotAvailable', response.message + end + + # Scrubbing Tests + + def test_transcript_scrubbing_with_string_dealer_code + gateway = MokaGateway.new(fixtures(:moka)) + gateway.options[:dealer_code] = gateway.options[:dealer_code].to_s + + capture_transcript_and_assert_scrubbed(gateway) + end + + def test_transcript_scrubbing_with_integer_dealer_code + gateway = MokaGateway.new(fixtures(:moka)) + gateway.options[:dealer_code] = gateway.options[:dealer_code].to_i + + capture_transcript_and_assert_scrubbed(gateway) + end + + def capture_transcript_and_assert_scrubbed(gateway) + 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[:dealer_code], transcript) + assert_scrubbed(gateway.options[:username], transcript) + assert_scrubbed(gateway.options[:password], transcript) + assert_scrubbed(check_key, transcript) + end + + def check_key + str = "#{@gateway.options[:dealer_code]}MK#{@gateway.options[:username]}PD#{@gateway.options[:password]}" + Digest::SHA256.hexdigest(str) + end +end diff --git a/test/remote/gateways/remote_monei_test.rb b/test/remote/gateways/remote_monei_test.rb index 474f317c76a..9d49ade34f9 100755 --- a/test/remote/gateways/remote_monei_test.rb +++ b/test/remote/gateways/remote_monei_test.rb @@ -7,31 +7,175 @@ def setup ) @amount = 100 - @credit_card = credit_card('4000100011112224') - @declined_card = credit_card('5453010000059675') + @credit_card = credit_card('4548812049400004', month: 12, year: 2034, verification_value: '123') + @declined_card = credit_card('5453010000059675', month: 12, year: 2034, verification_value: '123') + + @three_ds_declined_card = credit_card('4444444444444505', month: 12, year: 2034, verification_value: '123') + @three_ds_2_enrolled_card = credit_card('4444444444444406', month: 12, year: 2034, verification_value: '123') @options = { - order_id: '1', billing_address: address, description: 'Store Purchase' } end + def random_order_id + SecureRandom.hex(16) + end + def test_successful_purchase - response = @gateway.purchase(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_successful_purchase_with_no_billing_address + options = { + order_id: random_order_id, + description: 'Store Purchase' + } + response = @gateway.purchase(@amount, @credit_card, options) assert_success response - assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message + assert_equal 'Transaction approved', response.message + end + + def test_successful_purchase_with_partial_billing_address + partial_address = { + name: 'Jim Smith', + address1: '456 My Street', + city: 'MIlan', + zip: 'K1C2N6' + } + options = { + billing_address: partial_address, + order_id: random_order_id, + description: 'Store Purchase' + } + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_successful_purchase_with_3ds + options = @options.merge!({ + order_id: random_order_id, + three_d_secure: { + eci: '05', + cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=', + xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + }, + ip: '77.110.174.153', + user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', + lang: 'en' + }) + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_successful_purchase_with_3ds_v2 + options = @options.merge!({ + order_id: random_order_id, + three_d_secure: { + eci: '05', + cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=', + ds_transaction_id: '7eac9571-3533-4c38-addd-00cf34af6a52' + }, + ip: '77.110.174.153', + user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', + lang: 'en' + }) + response = @gateway.purchase(@amount, @three_ds_2_enrolled_card, options) + + assert_success response + assert_equal 'Transaction approved', response.message end def test_failed_purchase - response = @gateway.purchase(@amount, @declined_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = @gateway.purchase(@amount, @declined_card, options) + assert_failure response + assert_equal 'Card rejected: invalid card number', response.message + end + + def test_failed_purchase_with_3ds + options = @options.merge!({ + order_id: random_order_id, + three_d_secure: { + eci: '05', + cavv: 'INVALID_Verification_ID', + xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + }, + ip: '77.110.174.153', + user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', + lang: 'en' + }) + response = @gateway.purchase(@amount, @three_ds_declined_card, options) assert_failure response - assert_equal 'invalid cc number/brand combination', response.message + assert_equal 'Invalid 3DSecure Verification ID', response.message + end + + def test_successful_store + options = @options.merge({ order_id: random_order_id }) + response = @gateway.store(@credit_card, options) + + assert_success response + assert(!response.params['paymentToken'].nil?) + assert_equal response.authorization, response.params['paymentToken'] + assert_equal 'Transaction approved', response.message + end + + def test_successful_store_with_3ds_v2 + options = @options.merge!({ + order_id: random_order_id, + three_d_secure: { + eci: '05', + cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=', + ds_transaction_id: '7eac9571-3533-4c38-addd-00cf34af6a52' + }, + ip: '77.110.174.153', + user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', + lang: 'en' + }) + response = @gateway.store(@three_ds_2_enrolled_card, options) + + assert_success response + assert(!response.params['paymentToken'].nil?) + assert_equal response.authorization, response.params['paymentToken'] + assert_equal 'Transaction approved', response.message + end + + def test_successful_store_and_purchase + options = @options.merge({ order_id: random_order_id }) + stored = @gateway.store(@credit_card, options) + assert_success stored + + options = @options.merge({ order_id: random_order_id }) + assert purchase = @gateway.purchase(@amount, stored.authorization, options) + assert_success purchase + end + + def test_successful_store_authorize_and_capture + options = @options.merge({ order_id: random_order_id }) + stored = @gateway.store(@credit_card, options) + assert_success stored + + options = @options.merge({ order_id: random_order_id }) + authorize = @gateway.authorize(@amount, stored.authorization, options) + assert_success authorize + + assert capture = @gateway.capture(@amount, authorize.authorization) + assert_success capture end def test_successful_authorize_and_capture - auth = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + auth = @gateway.authorize(@amount, @credit_card, options) assert_success auth assert capture = @gateway.capture(@amount, auth.authorization) @@ -39,26 +183,29 @@ def test_successful_authorize_and_capture end def test_failed_authorize - response = @gateway.authorize(@amount, @declined_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = @gateway.authorize(@amount, @declined_card, options) assert_failure response end def test_partial_capture - auth = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + auth = @gateway.authorize(@amount, @credit_card, options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end def test_multi_partial_capture - auth = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + auth = @gateway.authorize(@amount, @credit_card, options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_failure capture end @@ -68,7 +215,8 @@ def test_failed_capture end def test_successful_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + purchase = @gateway.purchase(@amount, @credit_card, options) assert_success purchase assert refund = @gateway.refund(@amount, purchase.authorization) @@ -76,21 +224,23 @@ def test_successful_refund end def test_partial_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + purchase = @gateway.purchase(@amount, @credit_card, options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end def test_multi_partial_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + purchase = @gateway.purchase(@amount, @credit_card, options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_failure refund end @@ -100,7 +250,8 @@ def test_failed_refund end def test_successful_void - auth = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + auth = @gateway.authorize(@amount, @credit_card, options) assert_success auth assert void = @gateway.void(auth.authorization) @@ -113,27 +264,26 @@ def test_failed_void end def test_successful_verify - response = @gateway.verify(@credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = @gateway.verify(@credit_card, options) assert_success response - assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message + assert_equal 'Transaction approved', response.message end def test_failed_verify - response = @gateway.verify(@declined_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = @gateway.verify(@declined_card, options) assert_failure response - assert_equal 'invalid cc number/brand combination', response.message + assert_equal 'Card rejected: invalid card number', response.message end def test_invalid_login gateway = MoneiGateway.new( - :sender_id => 'mother', - :channel_id => 'there is no other', - :login => 'like mother', - :pwd => 'so treat Her right' + api_key: 'invalid' ) - response = gateway.purchase(@amount, @credit_card, @options) + options = @options.merge({ order_id: random_order_id }) + response = gateway.purchase(@amount, @credit_card, options) assert_failure response end - end diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb index 50a36e13d26..f9ed446aef4 100644 --- a/test/remote/gateways/remote_moneris_test.rb +++ b/test/remote/gateways/remote_moneris_test.rb @@ -5,12 +5,30 @@ def setup Base.mode = :test @gateway = MonerisGateway.new(fixtures(:moneris)) + + # https://developer.moneris.com/More/Testing/Penny%20Value%20Simulator @amount = 100 - @credit_card = credit_card('4242424242424242') + @fail_amount = 105 + + # https://developer.moneris.com/livedemo/3ds2/reference/guide/php + @fully_authenticated_eci = 5 + @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, - :customer => generate_unique_id, - :billing_address => address + order_id: generate_unique_id, + customer: generate_unique_id, + billing_address: address } end @@ -21,6 +39,26 @@ def test_successful_purchase assert_false response.authorization.blank? end + 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, + @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 'Approved', response.message + assert_false response.authorization.blank? + end + def test_successful_first_purchase_with_credential_on_file gateway = MonerisGateway.new(fixtures(:moneris)) assert response = gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '', payment_indicator: 'C', payment_information: '0')) @@ -78,25 +116,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 + 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? @@ -109,7 +196,7 @@ def test_successful_authorization end def test_failed_authorization - response = @gateway.authorize(105, @credit_card, @options) + response = @gateway.authorize(@fail_amount, @credit_card, @options) assert_failure response end @@ -130,7 +217,7 @@ def test_successful_authorization_and_capture_and_void response = @gateway.capture(@amount, response.authorization) assert_success response - void = @gateway.void(response.authorization, :purchasecorrection => true) + void = @gateway.void(response.authorization, purchasecorrection: true) assert_success void end @@ -143,6 +230,72 @@ def test_successful_authorization_and_void assert_success void end + 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, + @options.merge( + three_d_secure: { + version: '2', + cavv: 'AAABBJg0VhI0VniQEjRWAAAAAAA=', + eci: '7', + 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? + end + + 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, + @options.merge( + three_d_secure: { + version: '2', + cavv: 'AAABBJg0VhI0VniQEjRWAAAAAAA=', + eci: @fully_authenticated_eci, + 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? + + response = @gateway.capture(@amount, response.authorization) + assert_success response + end + + 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, + @options.merge( + three_d_secure: { + version: '2', + cavv: 'AAABBJg0VhI0VniQEjRWAAAAAAA=', + eci: @no_liability_shift_eci, + three_ds_server_trans_id: 'e11d4985-8d25-40ed-99d6-c3803fe5e68f', + ds_transaction_id: '12345' + } + ) + ) + + assert_failure response + end + def test_successful_authorization_with_network_tokenization @credit_card = network_tokenization_credit_card( '4242424242424242', @@ -159,7 +312,7 @@ def test_successful_purchase_and_void purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - void = @gateway.void(purchase.authorization, :purchasecorrection => true) + void = @gateway.void(purchase.authorization, purchasecorrection: true) assert_success void end @@ -182,7 +335,7 @@ def test_successful_purchase_and_refund def test_failed_purchase_from_error assert response = @gateway.purchase(150, @credit_card, @options) assert_failure response - assert_equal 'Declined', response.message + assert_equal 'Card declined do not retry card declined do not retry', response.message end def test_successful_verify @@ -199,6 +352,45 @@ def test_successful_store @data_key = response.params['data_key'] end + def test_successful_store_with_duration + assert response = @gateway.store(@credit_card, duration: 600) + assert_success response + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + end + + # AVS result fields are stored in the vault and returned as part of the + # XML response under (which isn't parsed by ActiveMerchant so + # we can't test for it). + # + # Actual AVS results aren't returned processed until an actual transaction is made + # so we make a second purchase request. + def test_successful_store_and_purchase_with_avs + gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) + + # card number triggers AVS match + @credit_card = credit_card('4761739012345637', verification_value: '012') + assert response = gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + data_key = response.params['data_key'] + + options_without_address = @options.dup + options_without_address.delete(:address) + assert response = gateway.purchase(@amount, data_key, options_without_address) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + + assert_equal(response.avs_result, { + 'code' => 'Y', + 'message' => 'Street address and 5-digit postal code match.', + 'street_match' => 'Y', + 'postal_match' => 'Y' + }) + end + def test_successful_unstore test_successful_store assert response = @gateway.unstore(@data_key) @@ -232,27 +424,27 @@ def test_successful_authorization_with_vault def test_failed_authorization_with_vault test_successful_store - response = @gateway.authorize(105, @data_key, @options) + response = @gateway.authorize(@fail_amount, @data_key, @options) assert_failure response end def test_cvv_match_when_not_enabled assert response = @gateway.purchase(1039, @credit_card, @options) assert_success response - assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) + assert_equal({ 'code' => nil, 'message' => nil }, response.cvv_result) end def test_cvv_no_match_when_not_enabled assert response = @gateway.purchase(1053, @credit_card, @options) assert_success response - assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) + assert_equal({ 'code' => nil, 'message' => nil }, response.cvv_result) end def test_cvv_match_when_enabled gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) assert response = gateway.purchase(1039, @credit_card, @options) assert_success response - assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result) + assert_equal({ 'code' => 'M', 'message' => 'CVV matches' }, response.cvv_result) end def test_avs_result_valid_when_enabled @@ -261,10 +453,10 @@ def test_avs_result_valid_when_enabled assert response = gateway.purchase(1010, @credit_card, @options) assert_success response assert_equal(response.avs_result, { - 'code' => 'A', - 'message' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', - 'street_match' => 'Y', - 'postal_match' => 'N' + 'code' => 'A', + 'message' => 'Street address matches, but postal code does not match.', + 'street_match' => 'Y', + 'postal_match' => 'N' }) end @@ -274,10 +466,10 @@ def test_avs_result_nil_when_address_absent assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) }) assert_success response assert_equal(response.avs_result, { - 'code' => nil, - 'message' => nil, - 'street_match' => nil, - 'postal_match' => nil + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil }) end @@ -285,21 +477,148 @@ def test_avs_result_nil_when_efraud_disabled assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal(response.avs_result, { - 'code' => nil, - 'message' => nil, - 'street_match' => nil, - 'postal_match' => nil + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil }) end + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:merchant, :recurring, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_purchase_using_stored_credential_installment_cit + initial_options = stored_credential_options(:cardholder, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:installment, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_purchase_using_stored_credential_installment_mit + initial_options = stored_credential_options(:merchant, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_purchase_using_stored_credential_unscheduled_cit + initial_options = stored_credential_options(:cardholder, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:unscheduled, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_purchase_using_stored_credential_unscheduled_mit + initial_options = stored_credential_options(:merchant, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['issuer_id'] + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + + used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert_false purchase.authorization.blank? + assert_not_empty purchase.params['issuer_id'] + end + + def test_authorize_and_capture_with_stored_credential + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert authorization = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success authorization + assert network_transaction_id = authorization.params['issuer_id'] + assert_equal 'Approved', authorization.message + assert_not_empty authorization.params['issuer_id'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + + used_options = stored_credential_options(:cardholder, :recurring, id: network_transaction_id) + assert authorization = @gateway.authorize(@amount, @credit_card, used_options) + assert_success authorization + assert @gateway.capture(@amount, authorization.authorization) + end + def test_purchase_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) end transcript = @gateway.scrub(transcript) - assert_scrubbed(credit_card.number, transcript) - assert_scrubbed(credit_card.verification_value, transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end + + private + + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id), + issuer_id: '') + end end diff --git a/test/remote/gateways/remote_moneris_us_test.rb b/test/remote/gateways/remote_moneris_us_test.rb deleted file mode 100644 index 75bbf7cf52d..00000000000 --- a/test/remote/gateways/remote_moneris_us_test.rb +++ /dev/null @@ -1,261 +0,0 @@ -require 'test_helper' - -class MonerisUsRemoteTest < Test::Unit::TestCase - def setup - Base.mode = :test - - @gateway = MonerisUsGateway.new(fixtures(:moneris_us)) - @amount = 100 - @credit_card = credit_card('4242424242424242') - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store Purchase' - } - @check = check({ - routing_number: '011000015', - account_number: '1234455', - number: 123 - }) - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal 'Approved', response.message - assert_false response.authorization.blank? - end - - def test_successful_echeck_purchase - response = @gateway.purchase(@amount, @check, @options) - assert_success response - assert response.test? - assert_equal 'Registered', response.message - assert response.authorization - end - - def test_failed_echeck_purchase - response = @gateway.purchase(105, check(routing_number: 5), @options) - assert_failure response - assert response.test? - assert_equal 'Unspecified error', response.message - end - - def test_successful_authorization - response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert_false response.authorization.blank? - end - - def test_successful_verify - response = @gateway.verify(@credit_card, @options) - assert_success response - assert_equal 'Approved', response.message - assert response.authorization - end - - def test_failed_verify - response = @gateway.verify(credit_card(nil), @options) - assert_failure response - assert_match %r{Invalid pan parameter}, response.message - end - - def test_failed_authorization - response = @gateway.authorize(105, @credit_card, @options) - assert_failure response - end - - def test_successful_authorization_and_capture - response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert response.authorization - - response = @gateway.capture(@amount, response.authorization) - assert_success response - end - - def test_successful_authorization_and_void - response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert response.authorization - - # Moneris cannot void a preauthorization - # You must capture the auth transaction with an amount of $0.00 - void = @gateway.capture(0, response.authorization) - assert_success void - end - - def test_successful_purchase_and_void - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase - - void = @gateway.void(purchase.authorization) - assert_success void - end - - def test_failed_purchase_and_void - purchase = @gateway.purchase(101, @credit_card, @options) - assert_failure purchase - - void = @gateway.void(purchase.authorization) - assert_failure void - end - - def test_successful_purchase_and_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase - - refund = @gateway.refund(@amount, purchase.authorization) - assert_success refund - end - - def test_failed_purchase_from_error - assert response = @gateway.purchase(150, @credit_card, @options) - assert_failure response - assert_equal 'Declined', response.message - end - - def test_successful_store - assert response = @gateway.store(@credit_card) - assert_success response - assert_equal 'Successfully registered cc details', response.message - assert response.params['data_key'].present? - @data_key = response.params['data_key'] - end - - def test_successful_echeck_store - assert response = @gateway.store(@check) - assert_success response - assert_equal 'Successfully registered ach details', response.message - assert response.params['data_key'].present? - @data_key_echeck = response.params['data_key'] - end - - def test_successful_unstore - test_successful_store - assert response = @gateway.unstore(@data_key) - assert_success response - assert_equal 'Successfully deleted cc details', response.message - assert response.params['data_key'].present? - end - - def test_successful_echeck_unstore - test_successful_echeck_store - assert response = @gateway.unstore(@data_key_echeck) - assert_success response - assert_equal 'Successfully deleted ach details', response.message - assert response.params['data_key'].present? - end - - def test_update - test_successful_store - assert response = @gateway.update(@data_key, @credit_card) - assert_success response - assert_equal 'Successfully updated cc details', response.message - assert response.params['data_key'].present? - end - - def test_echeck_update - test_successful_echeck_store - assert response = @gateway.update(@data_key_echeck, @check) - assert_success response - assert_equal 'Successfully updated ach details', response.message - assert response.params['data_key'].present? - end - - def test_successful_purchase_with_vault - test_successful_store - assert response = @gateway.purchase(@amount, @data_key, @options) - assert_success response - assert_equal 'Approved', response.message - assert_false response.authorization.blank? - end - - def test_successful_authorization_with_vault - test_successful_store - assert response = @gateway.authorize(@amount, @data_key, @options) - assert_success response - assert_false response.authorization.blank? - end - - def test_failed_authorization_with_vault - test_successful_store - response = @gateway.authorize(105, @data_key, @options) - assert_failure response - end - - def test_cvv_match_when_not_enabled - assert response = @gateway.purchase(1039, @credit_card, @options) - assert_success response - assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) - end - - def test_cvv_no_match_when_not_enabled - assert response = @gateway.purchase(1053, @credit_card, @options) - assert_success response - assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) - end - - def test_cvv_match_when_enabled - gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) - assert response = gateway.purchase(1039, @credit_card, @options) - assert_success response - assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result) - end - - def test_cvv_no_match_when_enabled - gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) - assert response = gateway.purchase(1053, @credit_card, @options) - assert_success response - assert_equal({'code' => 'N', 'message' => 'CVV does not match'}, response.cvv_result) - end - - def test_avs_result_valid_when_enabled - gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) - - assert response = gateway.purchase(1010, @credit_card, @options) - assert_success response - assert_equal(response.avs_result, { - 'code' => 'A', - 'message' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', - 'street_match' => 'Y', - 'postal_match' => 'N' - }) - end - - def test_avs_result_nil_when_address_absent - gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) - - assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) }) - assert_success response - assert_equal(response.avs_result, { - 'code' => nil, - 'message' => nil, - 'street_match' => nil, - 'postal_match' => nil - }) - end - - def test_avs_result_nil_when_efraud_disabled - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal(response.avs_result, { - 'code' => nil, - 'message' => nil, - 'street_match' => nil, - 'postal_match' => nil - }) - end - - def test_transcript_scrubbing - transcript = capture_transcript(@gateway) do - @gateway.purchase(@amount, @credit_card, @options) - end - transcript = @gateway.scrub(transcript) - - assert_scrubbed(@credit_card.number, transcript) - assert_scrubbed(@credit_card.verification_value, transcript) - assert_scrubbed(@gateway.options[:password], transcript) - end - -end diff --git a/test/remote/gateways/remote_money_movers_test.rb b/test/remote/gateways/remote_money_movers_test.rb index 64165cb50d8..ddbc976fdb0 100644 --- a/test/remote/gateways/remote_money_movers_test.rb +++ b/test/remote/gateways/remote_money_movers_test.rb @@ -10,9 +10,9 @@ def setup @credit_card = credit_card('4111111111111111') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Active Merchant Remote Test Purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Active Merchant Remote Test Purchase' } end @@ -72,9 +72,9 @@ def test_authorize_and_capture def test_invalid_login gateway = MoneyMoversGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Error in transaction data or system error', response.message diff --git a/test/remote/gateways/remote_mundipagg_test.rb b/test/remote/gateways/remote_mundipagg_test.rb index 39846fea222..2eacfc42fa1 100644 --- a/test/remote/gateways/remote_mundipagg_test.rb +++ b/test/remote/gateways/remote_mundipagg_test.rb @@ -5,26 +5,68 @@ def setup @gateway = MundipaggGateway.new(fixtures(:mundipagg)) @amount = 100 - @credit_card = credit_card('4000100011112224') + @credit_card = credit_card('4000000000000010') @declined_card = credit_card('4000300011112220') - @voucher = credit_card('60607044957644', brand: 'sodexo') + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') + + # Mundipagg only allows certain card numbers for success and failure scenarios. + # As such, we cannot use card numbers with BINs belonging to VR or Alelo. + # See https://docs.mundipagg.com/docs/simulador-de-voucher. + @vr_voucher = credit_card('4000000000000010', brand: 'vr') + @alelo_voucher = credit_card('4000000000000010', brand: 'alelo') + @declined_alelo_voucher = credit_card('4000000000000028', brand: 'alelo') + @options = { - billing_address: address({neighborhood: 'Sesame Street'}), + gateway_affiliation_id: fixtures(:mundipagg)[:gateway_affiliation_id], + billing_address: address({ neighborhood: 'Sesame Street' }), description: 'Store Purchase' } + + @authorization_secret_options = { authorization_secret_key: fixtures(:mundipagg)[:api_key] } + + @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' + }, + "address": { + "street": 'Malibu Point', + "number": '10880', + "complement": 'A', + "neighborhood": 'Central Malibu', + "city": 'Malibu', + "state": 'CA', + "country": 'US', + "zip_code": '24210-460' + } + } + } + + @excess_length_neighborhood = address({ neighborhood: 'Super Long Neighborhood Name' * 5 }) + @neighborhood_length_error = 'Invalid parameters; The request is invalid. | The field neighborhood must be a string with a maximum length of 64.' end def test_successful_purchase - response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + test_successful_purchase_with(@credit_card) + end + + def test_successful_purchase_with_alelo_card + test_successful_purchase_with(@alelo_voucher) end def test_successful_purchase_no_address @options.delete(:billing_address) response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + assert_equal 'Transação capturada com sucesso', response.message end def test_successful_purchase_with_more_options @@ -39,40 +81,82 @@ def test_successful_purchase_with_more_options assert_success response end - def test_successful_purchase_with_voucher + def test_successful_purchase_with_sodexo_voucher @options.update(holder_document: '93095135270') - response = @gateway.purchase(@amount, @voucher, @options) + response = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_success response + assert_equal 'Transação capturada com sucesso', response.message + end + + def test_successful_purchase_with_vr_voucher + @options.update(holder_document: '93095135270') + response = @gateway.purchase(@amount, @vr_voucher, @options) + assert_success response + assert_equal 'Transação capturada com sucesso', response.message + end + + def test_successful_purchase_with_submerchant + options = @options.update(@submerchant_options) + response = @gateway.purchase(@amount, @credit_card, options) assert_success response - assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + assert_equal 'Transação capturada com sucesso', response.message end def test_failed_purchase - response = @gateway.purchase(105200, @declined_card, @options) + test_failed_purchase_with(@declined_card) + end + + def test_failed_purchase_with_top_level_errors + @options[:billing_address] = @excess_length_neighborhood + + response = @gateway.purchase(105200, @credit_card, @options) + assert_failure response - assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message + assert_equal @neighborhood_length_error, response.message + end + + def test_failed_purchase_with_alelo_card + test_failed_purchase_with(@declined_alelo_voucher) end def test_successful_authorize_and_capture - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth + test_successful_authorize_and_capture_with(@credit_card) + end - assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture - assert_equal 'Simulator|Transação de simulação capturada com sucesso', capture.message + def test_successful_authorize_and_capture_with_alelo_card + test_successful_authorize_and_capture_with(@alelo_voucher) + end + + def test_successful_authorize_with_submerchant + options = @options.update(@submerchant_options) + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'Transação authorizada com sucesso', response.message end def test_failed_authorize - response = @gateway.authorize(105200, @declined_card, @options) + test_failed_authorize_with(@declined_card) + end + + def test_failed_authorize_with_alelo_card + test_failed_authorize_with(@declined_alelo_voucher) + end + + def test_failed_authorize_with_top_level_errors + @options[:billing_address] = @excess_length_neighborhood + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response - assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message + assert_equal @neighborhood_length_error, response.message end def test_partial_capture - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth + test_partial_capture_with(@credit_card) + end - assert capture = @gateway.capture(@amount-1, auth.authorization) - assert_success capture + def test_partial_capture_with_alelo_card + test_partial_capture_with(@alelo_voucher) end def test_failed_capture @@ -82,19 +166,19 @@ def test_failed_capture end def test_successful_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase + test_successful_refund_with(@credit_card) + end - assert refund = @gateway.refund(@amount, purchase.authorization) - assert_success refund + def test_successful_refund_with_alelo_card + test_successful_refund_with(@alelo_voucher) end def test_partial_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) - assert_success purchase + test_partial_refund_with(@credit_card) + end - assert refund = @gateway.refund(@amount - 1, purchase.authorization) - assert_success refund + def test_partial_refund_with_alelo_card + test_partial_refund_with(@alelo_voucher) end def test_failed_refund @@ -104,25 +188,43 @@ def test_failed_refund end def test_successful_void - auth = @gateway.authorize(@amount, @credit_card, @options) + test_successful_void_with(@credit_card) + end + + def test_successful_void_with_alelo_card + test_successful_void_with(@alelo_voucher) + end + + def test_successful_void_with_sodexo_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @sodexo_voucher, @options) assert_success auth assert void = @gateway.void(auth.authorization) assert_success void end - def test_successful_void_with_voucher + def test_successful_void_with_vr_voucher @options.update(holder_document: '93095135270') - auth = @gateway.purchase(@amount, @voucher, @options) + auth = @gateway.purchase(@amount, @vr_voucher, @options) assert_success auth assert void = @gateway.void(auth.authorization) assert_success void end - def test_successful_refund_with_voucher + def test_successful_refund_with_sodexo_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_success auth + + assert void = @gateway.refund(1, auth.authorization) + assert_success void + end + + def test_successful_refund_with_vr_voucher @options.update(holder_document: '93095135270') - auth = @gateway.purchase(@amount, @voucher, @options) + auth = @gateway.purchase(@amount, @vr_voucher, @options) assert_success auth assert void = @gateway.refund(1, auth.authorization) @@ -136,18 +238,40 @@ def test_failed_void end def test_successful_verify - response = @gateway.verify(@credit_card, @options) - assert_success response - assert_match %r{Simulator|Transação de simulação autorizada com sucesso}, response.message + test_successful_verify_with(@credit_card) + end + + def test_successful_verify_with_alelo_card + test_successful_verify_with(@alelo_voucher) end def test_successful_store_and_purchase - store = @gateway.store(@credit_card, @options) - assert_success store + test_successful_store_and_purchase_with(@credit_card) + end - assert purchase = @gateway.purchase(@amount, store.authorization, @options) - assert_success purchase - assert_equal 'Simulator|Transação de simulação autorizada com sucesso', purchase.message + def test_successful_store_and_purchase_with_alelo_card + test_successful_store_and_purchase_with(@alelo_voucher) + end + + def test_invalid_login_with_bad_api_key_overwrite + response = @gateway.purchase(@amount, @credit_card, @options.merge({ authorization_secret_key: 'bad_key' })) + assert_failure response + assert_match %r{Invalid API key; Authorization has been denied for this request.}, response.message + end + + def test_successful_purchase_with_api_key_overwrite + response = @gateway.purchase(@amount, @credit_card, @options.merge(@authorization_secret_options)) + assert_success response + assert_equal 'Transação capturada com sucesso', response.message + end + + def test_failed_store_with_top_level_errors + @options[:billing_address] = @excess_length_neighborhood + + response = @gateway.store(@credit_card, @options) + + assert_failure response + assert_equal @neighborhood_length_error, response.message end def test_invalid_login @@ -158,6 +282,16 @@ def test_invalid_login assert_match %r{Invalid API key; Authorization has been denied for this request.}, response.message end + def test_gateway_id_fallback + gateway = MundipaggGateway.new(api_key: fixtures(:mundipagg)[:api_key], gateway_id: fixtures(:mundipagg)[:gateway_id]) + options = { + billing_address: address({ neighborhood: 'Sesame Street' }), + description: 'Store Purchase' + } + response = gateway.purchase(@amount, @credit_card, options) + assert_success response + end + def test_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) @@ -168,4 +302,80 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:api_key], transcript) end + + private + + def test_successful_purchase_with(card) + response = @gateway.purchase(@amount, card, @options) + assert_success response + assert_equal 'Transação capturada com sucesso', response.message + end + + def test_failed_purchase_with(card) + response = @gateway.purchase(105200, card, @options) + assert_failure response + assert_equal 'Transação não autorizada', response.message + end + + def test_successful_authorize_and_capture_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Transação capturada com sucesso', capture.message + end + + def test_failed_authorize_with(card) + response = @gateway.authorize(105200, card, @options) + assert_failure response + assert_equal 'Transação não autorizada', response.message + end + + def test_partial_capture_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + end + + def test_successful_refund_with(card) + purchase = @gateway.purchase(@amount, card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund_with(card) + purchase = @gateway.purchase(@amount, card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_successful_void_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_verify_with(card) + response = @gateway.verify(card, @options) + assert_success response + assert_match %r{Transação authorizada com sucesso}, response.message + end + + def test_successful_store_and_purchase_with(card) + store = @gateway.store(card, @options) + assert_success store + + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert_equal 'Transação capturada com sucesso', purchase.message + end end diff --git a/test/remote/gateways/remote_nab_transact_test.rb b/test/remote/gateways/remote_nab_transact_test.rb index 55289ec9ff0..19052fe001c 100644 --- a/test/remote/gateways/remote_nab_transact_test.rb +++ b/test/remote/gateways/remote_nab_transact_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteNabTransactTest < Test::Unit::TestCase - def setup @gateway = NabTransactGateway.new(fixtures(:nab_transact)) @privileged_gateway = NabTransactGateway.new(fixtures(:nab_transact_privileged)) @@ -12,9 +11,9 @@ def setup @declined_card = credit_card('4111111111111234') @options = { - :order_id => '1', - :billing_address => address, - :description => 'NAB Transact Purchase' + order_id: '1', + billing_address: address, + description: 'NAB Transact Purchase' } end @@ -64,11 +63,11 @@ def test_unsuccessful_purchase_bad_credit_card # ensure we get the error. def test_successful_purchase_with_card_acceptor card_acceptor_options = { - :merchant_name => 'ActiveMerchant', - :merchant_location => 'Melbourne' + merchant_name: 'ActiveMerchant', + merchant_location: 'Melbourne' } card_acceptor_options.each do |key, value| - options = @options.merge({key => value}) + options = @options.merge({ key => value }) assert response = @gateway.purchase(@amount, @credit_card, options) assert_failure response assert_equal 'Permission denied', response.message @@ -122,18 +121,18 @@ def test_unsuccessful_capture_amount_greater_than_authorized authorization = auth.authorization - assert capture = @gateway.capture(@amount+100, authorization) + assert capture = @gateway.capture(@amount + 100, authorization) assert_failure capture assert_equal 'Preauth was done for smaller amount', capture.message end def test_authorize_and_capture_with_card_acceptor card_acceptor_options = { - :merchant_name => 'ActiveMerchant', - :merchant_location => 'Melbourne' + merchant_name: 'ActiveMerchant', + merchant_location: 'Melbourne' } card_acceptor_options.each do |key, value| - options = @options.merge({key => value}) + options = @options.merge({ key => value }) assert response = @gateway.authorize(@amount, @credit_card, options) assert_failure response assert_equal 'Permission denied', response.message @@ -162,11 +161,11 @@ def test_successful_refund # You need to speak to NAB Transact to have this feature enabled on # your account otherwise you will receive a "Permission denied" error def test_credit - assert response = @gateway.credit(@amount, @credit_card, {:order_id => '1'}) + assert response = @gateway.credit(@amount, @credit_card, { order_id: '1' }) assert_failure response assert_equal 'Permission denied', response.message - assert response = @privileged_gateway.credit(@amount, @credit_card, {:order_id => '1'}) + assert response = @privileged_gateway.credit(@amount, @credit_card, { order_id: '1' }) assert_success response assert_equal 'Approved', response.message end @@ -175,16 +174,16 @@ def test_failed_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response authorization = response.authorization - assert response = @gateway.refund(@amount+1, authorization) + assert response = @gateway.refund(@amount + 1, authorization) assert_failure response assert_equal 'Only 2.00 AUD available for refund', response.message end def test_invalid_login gateway = NabTransactGateway.new( - :login => 'ABCFAKE', - :password => 'changeit' - ) + login: 'ABCFAKE', + password: 'changeit' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid merchant ID', response.message @@ -205,11 +204,11 @@ def test_unsuccessful_store def test_duplicate_store @gateway.unstore(1236) - assert response = @gateway.store(@credit_card, {:billing_id => 1236}) + assert response = @gateway.store(@credit_card, { billing_id: 1236 }) assert_success response assert_equal 'Successful', response.message - assert response = @gateway.store(@credit_card, {:billing_id => 1236}) + assert response = @gateway.store(@credit_card, { billing_id: 1236 }) assert_failure response assert_equal 'Duplicate CRN Found', response.message end @@ -240,7 +239,7 @@ def test_failure_trigger_purchase trigger_amount = 0 @gateway.unstore(gateway_id) - assert response = @gateway.store(@credit_card, {:billing_id => gateway_id, :amount => 150}) + assert response = @gateway.store(@credit_card, { billing_id: gateway_id, amount: 150 }) assert_success response assert_equal 'Successful', response.message @@ -260,5 +259,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.number, clean_transcript) assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end - end diff --git a/test/remote/gateways/remote_ncr_secure_pay_test.rb b/test/remote/gateways/remote_ncr_secure_pay_test.rb index 239063f06e8..ddd21d8cb85 100644 --- a/test/remote/gateways/remote_ncr_secure_pay_test.rb +++ b/test/remote/gateways/remote_ncr_secure_pay_test.rb @@ -44,7 +44,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -118,5 +118,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/test/remote/gateways/remote_net_registry_test.rb b/test/remote/gateways/remote_net_registry_test.rb index aeadc59b83d..dfbb80c9ac1 100644 --- a/test/remote/gateways/remote_net_registry_test.rb +++ b/test/remote/gateways/remote_net_registry_test.rb @@ -10,15 +10,14 @@ # All purchases made in these tests are $1, so hopefully you won't be # sent broke if you forget... class NetRegistryTest < Test::Unit::TestCase - def setup @gateway = NetRegistryGateway.new(fixtures(:net_registry)) @amount = 100 @valid_creditcard = credit_card @invalid_creditcard = credit_card('41111111111111111') - @expired_creditcard = credit_card('4111111111111111', :year => '2000') - @invalid_month_creditcard = credit_card('4111111111111111', :month => '13') + @expired_creditcard = credit_card('4111111111111111', year: '2000') + @invalid_month_creditcard = credit_card('4111111111111111', month: '13') end def test_successful_purchase_and_credit @@ -57,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 @@ -88,9 +85,9 @@ def test_purchase_with_invalid_month def test_bad_login gateway = NetRegistryGateway.new( - :login => 'bad-login', - :password => 'bad-login' - ) + login: 'bad-login', + password: 'bad-login' + ) response = gateway.purchase(@amount, @valid_creditcard) assert_equal 'failed', response.params['status'] assert_failure response diff --git a/test/remote/gateways/remote_netaxept_test.rb b/test/remote/gateways/remote_netaxept_test.rb index 0ae763f5876..b4dd8771771 100644 --- a/test/remote/gateways/remote_netaxept_test.rb +++ b/test/remote/gateways/remote_netaxept_test.rb @@ -9,7 +9,7 @@ def setup @declined_card = credit_card('4925000000000087') @options = { - :order_id => generate_unique_id + order_id: generate_unique_id } end @@ -52,7 +52,7 @@ def test_failed_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - response = @gateway.refund(@amount+100, response.authorization) + response = @gateway.refund(@amount + 100, response.authorization) assert_failure response assert_equal 'Unable to credit more than captured amount', response.message end @@ -75,20 +75,20 @@ def test_failed_void end def test_error_in_transaction_setup - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'BOGG')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'BOGG')) assert_failure response assert_match(/currency code/, response.message) end def test_successful_amex_purchase - credit_card = credit_card('378282246310005', :brand => 'american_express') + credit_card = credit_card('378282246310005', brand: 'american_express') assert response = @gateway.purchase(@amount, credit_card, @options) assert_success response assert_equal 'OK', response.message end def test_successful_master_purchase - credit_card = credit_card('5413000000000000', :brand => 'master') + credit_card = credit_card('5413000000000000', brand: 'master') assert response = @gateway.purchase(@amount, credit_card, @options) assert_success response assert_equal 'OK', response.message @@ -116,9 +116,9 @@ def test_query_fails def test_invalid_login gateway = NetaxeptGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_match(/Unable to authenticate merchant/, response.message) diff --git a/test/remote/gateways/remote_netbanx_test.rb b/test/remote/gateways/remote_netbanx_test.rb index 19b4faddd5c..491b0d39c03 100644 --- a/test/remote/gateways/remote_netbanx_test.rb +++ b/test/remote/gateways/remote_netbanx_test.rb @@ -5,12 +5,24 @@ def setup @gateway = NetbanxGateway.new(fixtures(:netbanx)) @amount = 100 @credit_card = credit_card('4530910000012345') + @credit_card_no_match_cvv = credit_card('4530910000012345', { verification_value: 666 }) @declined_amount = 11 @options = { billing_address: address, description: 'Store Purchase', currency: 'CAD' } + + @options_3ds2 = @options.merge( + three_d_secure: { + version: '2.1.0', + eci: '05', + cavv: 'AAABCIEjYgAAAAAAlCNiENiWiV+=', + ds_transaction_id: 'a3a721f3-b6fa-4cb5-84ea-c7b5c39890a2', + xid: 'OU9rcTRCY1VJTFlDWTFESXFtTHU=', + directory_response_status: 'Y' + } + ) end def test_successful_purchase @@ -18,6 +30,24 @@ def test_successful_purchase assert_success response assert_equal 'OK', response.message assert_equal response.authorization, response.params['id'] + assert_equal 'MATCH', response.params['cvvVerification'] + assert_equal 'MATCH', response.params['avsResponse'] + end + + def test_successful_purchase_avs_no_match_cvv + response = @gateway.purchase(@amount, @credit_card_no_match_cvv, @options) + assert_success response + assert_equal 'X', response.avs_result['code'] + assert_equal 'N', response.cvv_result['code'] + end + + def split_names(full_name) + names = (full_name || '').split + return [nil, nil] if names.size == 0 + + last_name = names.pop + first_name = names.join(' ') + [first_name, last_name] end def test_successful_purchase_with_more_options @@ -28,9 +58,22 @@ def test_successful_purchase_with_more_options email: 'joe@example.com' } + first_name, last_name = split_names(address[:name]) + response = @gateway.purchase(@amount, @credit_card, options) assert_equal 'OK', response.message assert_equal response.authorization, response.params['id'] + assert_equal first_name, response.params['profile']['firstName'] + assert_equal last_name, response.params['profile']['lastName'] + assert_equal options[:email], response.params['profile']['email'] + assert_equal options[:ip], response.params['customerIp'] + end + + def test_successful_purchase_with_3ds2_auth + assert response = @gateway.purchase(@amount, @credit_card, @options_3ds2) + assert_success response + assert_equal 'OK', response.message + assert_equal response.authorization, response.params['id'] end def test_failed_purchase @@ -39,6 +82,15 @@ def test_failed_purchase assert_equal 'The card has been declined due to insufficient funds.', response.message end + def test_failed_verify_before_purchase + options = { + verification_value: '' + } + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_equal 'The zip/postal code must be provided for an AVS check request.', response.message + end + def test_successful_authorize auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -63,7 +115,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) assert_success capture end @@ -73,6 +125,15 @@ def test_failed_capture assert_equal 'The authorization ID included in this settlement request could not be found.', response.message end + def test_successful_authorize_and_capture_with_3ds2_auth + auth = @gateway.authorize(@amount, @credit_card, @options_3ds2) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options_3ds2) + assert_success capture + assert_equal 'OK', capture.message + end + # def test_successful_refund # # Unfortunately when testing a refund, you need to wait until the transaction # # if batch settled by the test system, this can take up to 2h. @@ -122,7 +183,9 @@ def test_failed_capture # assert_equal 'OK', refund.message # end - def test_failed_refund + # Changed test_failed_refund to test_cancelled_refund + # Because We added the checking status. If the transactions that are pending, API call needs to be Cancellation + def test_cancelled_refund # Read comment in `test_successful_refund` method. auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -130,11 +193,23 @@ def test_failed_refund assert capture = @gateway.capture(@amount, auth.authorization, @options) assert_success capture - # the following shall fail if you run it immediately after the capture - # as noted in the comment from `test_successful_refund` - assert refund = @gateway.refund(@amount, capture.authorization) - assert_failure refund - assert_equal 'The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund.', refund.message + # The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund. + # So the following refund shall be cancelled if you run it immediately after the capture + assert cancelled_response = @gateway.refund(@amount, capture.authorization) + assert_success cancelled_response + assert_equal 'CANCELLED', cancelled_response.params['status'] + end + + def test_reject_partial_refund_on_pending_status + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + assert rejected_response = @gateway.refund(90, capture.authorization) + assert_failure rejected_response + assert_equal 'Transaction not settled. Either do a full refund or try partial refund after settlement.', rejected_response.message end def test_successful_void @@ -195,11 +270,34 @@ def test_successful_unstore def test_successful_purchase_using_stored_card merchant_customer_id = SecureRandom.hex - assert store = @gateway.store(@credit_card, @options.merge({locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com'})) + assert store = @gateway.store(@credit_card, @options.merge({ locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com' })) assert_success store assert response = @gateway.purchase(@amount, store.authorization.split('|').last) assert_success response assert_equal 'OK', response.message end + + def test_successful_verify + verify = @gateway.verify(@credit_card, @options) + assert_success verify + end + + def test_failed_verify + options = { + verification_value: '' + } + verify = @gateway.verify(@credit_card, options) + assert_failure verify + assert_equal 'The zip/postal code must be provided for an AVS check request.', verify.message + end + + def test_successful_cancel_settlement + response = @gateway.purchase(@amount, @credit_card, @options) + authorization = response.authorization + + assert cancelled_response = @gateway.refund(@amount, authorization) + assert_success cancelled_response + assert_equal 'CANCELLED', cancelled_response.params['status'] + end end diff --git a/test/remote/gateways/remote_netbilling_test.rb b/test/remote/gateways/remote_netbilling_test.rb index 7004e213627..46cafebd62b 100644 --- a/test/remote/gateways/remote_netbilling_test.rb +++ b/test/remote/gateways/remote_netbilling_test.rb @@ -6,18 +6,17 @@ def setup @credit_card = credit_card('4444111111111119') - @address = { :address1 => '1600 Amphitheatre Parkway', - :city => 'Mountain View', - :state => 'CA', - :country => 'US', - :zip => '94043', - :phone => '650-253-0001' - } + @address = { address1: '1600 Amphitheatre Parkway', + city: 'Mountain View', + state: 'CA', + country: 'US', + zip: '94043', + phone: '650-253-0001' } @options = { - :billing_address => @address, - :description => 'Internet purchase', - :order_id => 987654321 + billing_address: @address, + description: 'Internet purchase', + order_id: 987654321 } @amount = 100 @@ -89,9 +88,9 @@ def test_failed_capture def test_invalid_login gateway = NetbillingGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_match(/missing/i, response.message) assert_failure response diff --git a/test/remote/gateways/remote_netpay_test.rb b/test/remote/gateways/remote_netpay_test.rb index cb87c5ca3f7..148773c4f28 100644 --- a/test/remote/gateways/remote_netpay_test.rb +++ b/test/remote/gateways/remote_netpay_test.rb @@ -9,7 +9,7 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :description => 'Store Purchase' + description: 'Store Purchase' } end @@ -44,47 +44,45 @@ def test_successful_purchase_and_refund assert_equal 'Aprobada', refund.message end -=begin - # Netpay are currently adding support for authorize and capture. - # When this is complete, the following remote calls should work. - - def test_successful_authorize - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert_equal 'Aprobada', response.message - end - - def test_unsuccessful_authorize - # We have to force a decline using the mode option - opts = @options.clone - opts[:mode] = 'D' - assert response = @gateway.authorize(@amount, @declined_card, opts) - assert_failure response - assert_match %r{Declinada}, response.message - end - - def test_successful_authorize_and_capture - assert purchase = @gateway.authorize(@amount, @credit_card, @options) - assert_success purchase - assert capture = @gateway.capture(@amount, purchase.authorization) - assert_success capture - assert_equal 'Aprobada', capture.message - end - - def test_failed_capture - assert response = @gateway.capture(@amount, '') - assert_failure response - assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message - end - - def test_invalid_login - gateway = NetpayGateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal 'REPLACE WITH FAILURE MESSAGE', response.message - end -=end + # # Netpay are currently adding support for authorize and capture. + # # When this is complete, the following remote calls should work. + # + # def test_successful_authorize + # assert response = @gateway.authorize(@amount, @credit_card, @options) + # assert_success response + # assert_equal 'Aprobada', response.message + # end + # + # def test_unsuccessful_authorize + # # We have to force a decline using the mode option + # opts = @options.clone + # opts[:mode] = 'D' + # assert response = @gateway.authorize(@amount, @declined_card, opts) + # assert_failure response + # assert_match %r{Declinada}, response.message + # end + # + # def test_successful_authorize_and_capture + # assert purchase = @gateway.authorize(@amount, @credit_card, @options) + # assert_success purchase + # assert capture = @gateway.capture(@amount, purchase.authorization) + # assert_success capture + # assert_equal 'Aprobada', capture.message + # end + # + # def test_failed_capture + # assert response = @gateway.capture(@amount, '') + # assert_failure response + # assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message + # end + # + # def test_invalid_login + # gateway = NetpayGateway.new( + # :login => '', + # :password => '' + # ) + # assert response = gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # assert_equal 'REPLACE WITH FAILURE MESSAGE', response.message + # end end diff --git a/test/remote/gateways/remote_network_merchants_test.rb b/test/remote/gateways/remote_network_merchants_test.rb index 290a0cad4c6..c4a43d5661c 100644 --- a/test/remote/gateways/remote_network_merchants_test.rb +++ b/test/remote/gateways/remote_network_merchants_test.rb @@ -11,9 +11,9 @@ def setup @check = check @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -54,7 +54,7 @@ def test_unsuccessful_purchase_with_track_data end def test_purchase_and_store - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:store => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true)) assert_success response assert_equal response.params['transactionid'], response.authorization assert response.params['customer_vault_id'] @@ -153,9 +153,9 @@ def test_purchase_on_stored_card def test_invalid_login gateway = NetworkMerchantsGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid Username', response.message @@ -163,16 +163,16 @@ def test_invalid_login def test_successful_purchase_without_state @options[:billing_address] = { - :name => 'Jim Smith', - :address1 => 'Gullhauggrenda 30', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Baerums Verk', - :state => nil, - :zip => '1354', - :country => 'NO', - :phone => '(555)555-5555', - :fax => '(555)555-6666' + name: 'Jim Smith', + address1: 'Gullhauggrenda 30', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Baerums Verk', + state: nil, + zip: '1354', + country: 'NO', + phone: '(555)555-5555', + fax: '(555)555-6666' } assert response = @gateway.purchase(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb index b4fbc9ff01a..7814d39dd92 100644 --- a/test/remote/gateways/remote_nmi_test.rb +++ b/test/remote/gateways/remote_nmi_test.rb @@ -3,24 +3,41 @@ class RemoteNmiTest < Test::Unit::TestCase def setup @gateway = NmiGateway.new(fixtures(:nmi)) + @gateway_secure = NmiGateway.new(fixtures(:nmi_secure)) @amount = Random.rand(100...1000) @credit_card = credit_card('4111111111111111', verification_value: 917) @check = check( - :routing_number => '123123123', - :account_number => '123123123' + routing_number: '123123123', + account_number: '123123123' ) - @apple_pay_card = network_tokenization_credit_card('4111111111111111', - :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', - :month => '01', - :year => '2024', - :source => :apple_pay, - :eci => '5', - :transaction_id => '123456789' + @apple_pay_card = network_tokenization_credit_card( + '4111111111111111', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + month: '01', + year: '2024', + source: :apple_pay, + eci: '5', + transaction_id: '123456789' ) @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase' + order_id: generate_unique_id, + billing_address: address, + description: 'Store purchase' + } + @level3_options = { + tax: 5.25, shipping: 10.51, ponumber: 1002 + } + @descriptor_options = { + descriptor: 'test', + descriptor_phone: '123', + descriptor_address: 'address', + descriptor_city: 'city', + descriptor_state: 'state', + descriptor_postal: 'postal', + descriptor_country: 'country', + descriptor_mcc: 'mcc', + descriptor_merchant_id: '120', + descriptor_url: 'url' } end @@ -31,9 +48,52 @@ def test_invalid_login assert_equal 'Authentication Failed', response.message end - def test_successful_purchase + def test_invalid_login_security_key_empty + gateway_secure = NmiGateway.new(security_key: '') + assert response = gateway_secure.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication Failed', response.message + end + + def test_valid_login_username_password + @gateway = NmiGateway.new(login: 'demo', password: 'password') assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response + end + + def test_valid_login_security_key + gateway_secure = NmiGateway.new(fixtures(:nmi_secure)) + assert response = gateway_secure.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_authorization_security_key + assert response = @gateway_secure.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_purchase_using_security_key + assert response = @gateway_secure.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_transcript_scrubbing_using_security_key + transcript = capture_transcript(@gateway_secure) do + @gateway_secure.purchase(@amount, @credit_card, @options) + end + transcript = @gateway_secure.scrub(transcript) + assert_scrubbed(@gateway_secure.options[:security_key], transcript) + end + + def test_successful_purchase + options = @options.merge(@level3_options) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response assert response.test? assert_equal 'Succeeded', response.message assert response.authorization @@ -90,7 +150,7 @@ def test_successful_purchase_with_additional_options options = @options.merge({ customer_id: '234', vendor_id: '456', - recurring: true, + recurring: true }) assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response @@ -99,9 +159,58 @@ def test_successful_purchase_with_additional_options assert response.authorization end + def test_successful_purchase_with_three_d_secure + three_d_secure_options = @options.merge({ + three_d_secure: { + version: '2.1.0', + authentication_response_status: 'Y', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC' + } + }) + + assert response = @gateway.purchase(@amount, @credit_card, three_d_secure_options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + def test_successful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge(@level3_options) + + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_purchase_with_descriptors + options = @options.merge({ descriptors: @descriptor_options }) + + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_purchase_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 @@ -174,7 +283,9 @@ def test_successful_refund_with_echeck end def test_successful_credit - response = @gateway.credit(@amount, @credit_card, @options) + options = @options.merge(@level3_options) + + response = @gateway.credit(@amount, @credit_card, options) assert_success response assert_equal 'Succeeded', response.message end @@ -186,7 +297,9 @@ def test_failed_credit end def test_successful_verify - response = @gateway.verify(@credit_card, @options) + options = @options.merge(@level3_options) + + response = @gateway.verify(@credit_card, options) assert_success response assert_match 'Succeeded', response.message end @@ -253,17 +366,95 @@ def test_verify_credentials assert !gateway.verify_credentials end + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :recurring, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_cit + initial_options = stored_credential_options(:cardholder, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:cardholder, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_mit + initial_options = stored_credential_options(:merchant, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_cit + initial_options = stored_credential_options(:cardholder, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:cardholder, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_mit + initial_options = stored_credential_options(:merchant, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_authorize_and_capture_with_stored_credential + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert authorization = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success authorization + assert network_transaction_id = authorization.params['transactionid'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + + used_options = stored_credential_options(:cardholder, :recurring, id: network_transaction_id) + assert authorization = @gateway.authorize(@amount, @credit_card, used_options) + assert_success authorization + assert @gateway.capture(@amount, authorization.authorization) + end + def test_card_transcript_scrubbing transcript = capture_transcript(@gateway) do @gateway.purchase(@amount, @credit_card, @options) end clean_transcript = @gateway.scrub(transcript) - assert_scrubbed(@credit_card.number, clean_transcript) - assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) - - # "password=password is filtered, but can't be tested b/c of key match" - # assert_scrubbed(@gateway.options[:password], clean_transcript) + assert_cvv_scrubbed(clean_transcript) + assert_password_scrubbed(clean_transcript) end def test_check_transcript_scrubbing @@ -274,9 +465,7 @@ def test_check_transcript_scrubbing assert_scrubbed(@check.account_number, clean_transcript) assert_scrubbed(@check.routing_number, clean_transcript) - - # "password=password is filtered, but can't be tested b/c of key match" - # assert_scrubbed(@gateway.options[:password], clean_transcript) + assert_password_scrubbed(clean_transcript) end def test_network_tokenization_transcript_scrubbing @@ -287,8 +476,30 @@ def test_network_tokenization_transcript_scrubbing assert_scrubbed(@apple_pay_card.number, clean_transcript) assert_scrubbed(@apple_pay_card.payment_cryptogram, clean_transcript) + assert_password_scrubbed(clean_transcript) + end + + private + + # "password=password is filtered, but can't be tested via normal + # `assert_scrubbed` b/c of key match" + def assert_password_scrubbed(transcript) + assert_match(/password=\[FILTERED\]/, transcript) + end + + # Because the cvv is a simple three digit number, sometimes there are random + # failures using `assert_scrubbed` because of natural collisions with a + # substring within orderid in transcript; e.g. + # + # Expected the value to be scrubbed out of the transcript. + # was expected to not match + # <"opening connection to secure.nmi.com:443...\nopened\nstarting SSL for secure.nmi.com:443...\nSSL established\n<- \"POST /api/transact.php HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: secure.nmi.com\\r\\nContent-Length: 394\\r\\n\\r\\n\"\n<- \"amount=7.96&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&orderdescription=Store+purchase¤cy=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=[FILTERED]&cvv=[FILTERED]&ccexp=0920&email=&ipaddress=&customer_id=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=[FILTERED]\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Wed, 12 Jun 2019 21:10:29 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Content-Length: 169\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n-> \"\\r\\n\"\nreading 169 bytes...\n-> \"response=1&responsetext=SUCCESS&authcode=123456&transactionid=4743046890&avsresponse=N&cvvresponse=N&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&type=sale&response_code=100\"\nread 169 bytes\nConn close\n">. + def assert_cvv_scrubbed(transcript) + assert_match(/cvv=\[FILTERED\]/, transcript) + end - # "password=password is filtered, but can't be tested b/c of key match" - # assert_scrubbed(@gateway.options[:password], clean_transcript) + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id)) end end diff --git a/test/remote/gateways/remote_ogone_test.rb b/test/remote/gateways/remote_ogone_test.rb index 3d2b878db33..41ce461866d 100644 --- a/test/remote/gateways/remote_ogone_test.rb +++ b/test/remote/gateways/remote_ogone_test.rb @@ -3,38 +3,59 @@ require 'test_helper' 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') + @mastercard = credit_card('5399999999999999', brand: 'mastercard') @declined_card = credit_card('1111111111111111') - @credit_card_d3d = credit_card('4000000000000002', :verification_value => '111') + @credit_card_d3d = credit_card('4000000000000002', verification_value: '111') + @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], - :billing_address => address, - :description => 'Store Purchase', - :currency => fixtures(:ogone)[:currency] || 'EUR', - :origin => 'STORE' + order_id: generate_unique_id[0...30], + billing_address: address, + description: 'Store Purchase', + currency: fixtures(:ogone)[:currency] || 'EUR', + origin: 'STORE' + } + @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' + } + } } 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 @@ -62,37 +83,96 @@ def test_successful_purchase_with_utf8_encoding_2 # NOTE: You have to set the "Hash algorithm" to "SHA-512" in the "Technical information"->"Global security parameters" # section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test def test_successful_purchase_with_signature_encryptor_to_sha512 - gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha512')) + gateway = OgoneGateway.new(fixtures(:ogone).merge(signature_encryptor: 'sha512')) assert response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message end # 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 - assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(:d3d => true)) + def test_successful_purchase_with_3d_secure_v1 + 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 Base64.decode64(response.params['HTML_ANSWER']) + end + + def test_successful_purchase_with_3d_secure_v2 + 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 Base64.decode64(response.params['HTML_ANSWER']) + end + + def test_successful_purchase_with_3d_secure_v2_frictionless + 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'] + assert_equal '9', response.params['STATUS'] + assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message + end + + def test_successful_purchase_with_3d_secure_v2_recomended_parameters + options = @options.merge(@options_browser_info) + 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 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_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 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_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' + assert_equal response.params['STATUS'], '2' + assert_equal response.params['NCERRORPLUS'], 'Authentication failed. Please retry or cancel.' end 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 @@ -100,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 @@ -109,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', response.message + 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', response.message + 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 @@ -208,36 +285,36 @@ 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', response.message + 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 def test_invalid_login gateway = OgoneGateway.new( - login: 'login', - user: 'user', - password: 'password', - signature: 'signature' - ) + login: 'login', + user: 'user', + password: 'password', + signature: 'signature' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response end diff --git a/test/remote/gateways/remote_omise_test.rb b/test/remote/gateways/remote_omise_test.rb index 4b5be3064b0..95ae0d30b66 100644 --- a/test/remote/gateways/remote_omise_test.rb +++ b/test/remote/gateways/remote_omise_test.rb @@ -6,7 +6,7 @@ def setup @amount = 8888 @credit_card = credit_card('4242424242424242') @declined_card = credit_card('4255555555555555') - @invalid_cvc = credit_card('4111111111160001', {verification_value: ''}) + @invalid_cvc = credit_card('4111111111160001', { verification_value: '' }) @options = { description: 'Active Merchant', email: 'active.merchant@testing.test', @@ -54,7 +54,7 @@ def test_successful_purchase_after_store end def test_failed_purchase_with_token - response = @gateway.purchase(@amount, nil, {token_id: 'tokn_invalid_12345'}) + response = @gateway.purchase(@amount, nil, { token_id: 'tokn_invalid_12345' }) assert_failure response end @@ -95,9 +95,8 @@ def test_successful_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase assert_equal purchase.params['amount'], @amount - response = @gateway.refund(@amount-1000, purchase.authorization) + response = @gateway.refund(@amount - 1000, purchase.authorization) assert_success response - assert_equal @amount-1000, response.params['amount'] + assert_equal @amount - 1000, response.params['amount'] end - end diff --git a/test/remote/gateways/remote_openpay_test.rb b/test/remote/gateways/remote_openpay_test.rb index 079b4d09279..b7d336d78d6 100644 --- a/test/remote/gateways/remote_openpay_test.rb +++ b/test/remote/gateways/remote_openpay_test.rb @@ -16,6 +16,19 @@ def setup end def test_successful_purchase + @options[:email] = '%d@example.org' % Time.now + @options[:name] = 'Customer name' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + end + + def test_successful_purchase_with_mexico_url + @options[:email] = '%d@example.org' % Time.now + @options[:name] = 'Customer name' + credentials = fixtures(:openpay).merge(merchant_country: 'MX') + @gateway = OpenpayGateway.new(credentials) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_nil response.message @@ -31,7 +44,7 @@ def test_successful_purchase_with_email def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'The card was declined', response.message + assert_equal 'The card was declined by the bank', response.message end def test_successful_refund @@ -48,7 +61,7 @@ def test_successful_refund end def test_unsuccessful_refund - assert response = @gateway.refund(@amount, '1', @options) + assert response = @gateway.refund(@amount, '1', @options) assert_failure response assert_not_nil response.message end @@ -69,7 +82,7 @@ def test_successful_authorize_with_email def test_unsuccessful_authorize assert response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response - assert_equal 'The card was declined', response.message + assert_equal 'The card was declined by the bank', response.message end def test_successful_capture diff --git a/test/remote/gateways/remote_opp_test.rb b/test/remote/gateways/remote_opp_test.rb index 652059e87ad..fa744bbd9ab 100644 --- a/test/remote/gateways/remote_opp_test.rb +++ b/test/remote/gateways/remote_opp_test.rb @@ -1,14 +1,13 @@ require 'test_helper' class RemoteOppTest < Test::Unit::TestCase - def setup @gateway = OppGateway.new(fixtures(:opp)) @amount = 100 - @valid_card = credit_card('4200000000000000', month: 05, year: 2018) - @invalid_card = credit_card('4444444444444444', month: 05, year: 2018) - @amex_card = credit_card('377777777777770 ', month: 05, year: 2018, brand: 'amex', verification_value: '1234') + @valid_card = credit_card('4200000000000000', month: 05, year: Date.today.year + 2) + @invalid_card = credit_card('4444444444444444', month: 05, year: Date.today.year + 2) + @amex_card = credit_card('377777777777770 ', month: 05, year: Date.today.year + 2, brand: 'amex', verification_value: '1234') request_type = 'complete' # 'minimal' || 'complete' time = Time.now.to_i @@ -26,7 +25,7 @@ def setup city: 'Test', state: 'TE', zip: 'AB12CD', - country: 'GB', + country: 'GB' }, shipping_address: { name: 'Muton DeMicelis', @@ -34,7 +33,7 @@ def setup city: 'Munich', state: 'Bov', zip: '81675', - country: 'DE', + country: 'DE' }, customer: { merchant_customer_id: 'your merchant/customer id', @@ -47,13 +46,13 @@ def setup company_name: 'JJ Ltd.', identification_doctype: 'PASSPORT', identification_docid: 'FakeID2342431234123', - ip: ip, - }, + ip: ip + } } @minimal_request_options = { order_id: "Order #{time}", - description: 'Store Purchase - Books', + description: 'Store Purchase - Books' } @complete_request_options['customParameters[SHOPPER_test124TestName009]'] = 'customParameters_test' @@ -140,7 +139,7 @@ def test_successful_partial_capture auth = @gateway.authorize(@amount, @valid_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture assert_match %r{Request successfully processed}, capture.message end @@ -150,7 +149,7 @@ def test_successful_partial_refund purchase = @gateway.purchase(@amount, @valid_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund assert_match %r{Request successfully processed}, refund.message end @@ -211,6 +210,6 @@ def test_transcript_scrubbing assert_scrubbed(@valid_card.number, transcript) assert_scrubbed(@valid_card.verification_value, transcript) - assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:access_token], transcript) end end diff --git a/test/remote/gateways/remote_optimal_payment_test.rb b/test/remote/gateways/remote_optimal_payment_test.rb index 6061a306300..045f9e22938 100644 --- a/test/remote/gateways/remote_optimal_payment_test.rb +++ b/test/remote/gateways/remote_optimal_payment_test.rb @@ -7,13 +7,14 @@ def setup @amount = 100 @declined_amount = 5 @credit_card = credit_card('4387751111011') + @expired_card = credit_card('4387751111011', month: 12, year: 2019) @options = { - :order_id => '1', - :billing_address => address, - :description => 'Basic Subscription', - :email => 'email@example.com', - :ip => '1.2.3.4' + order_id: '1', + billing_address: address, + description: 'Basic Subscription', + email: 'email@example.com', + ip: '1.2.3.4' } end @@ -57,6 +58,51 @@ def test_successful_verify assert_equal 'no_error', response.message end + def test_stored_data_auth_and_capture_after_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'no_error', response.message + + assert auth = @gateway.stored_authorize(@amount, response.authorization) + assert_success auth + assert_equal 'no_error', auth.message + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_stored_data_purchase_after_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'no_error', response.message + + assert stored_purchase = @gateway.stored_purchase(@amount, response.authorization) + assert_success stored_purchase + end + + def test_stored_data_auth_after_failed_store + response = @gateway.store(@expired_card, @options) + assert_failure response + assert_not_nil response.authorization + assert_equal 'ERROR', response.params['decision'] + + assert auth = @gateway.stored_authorize(@amount, response.authorization) + assert_failure auth + assert_equal 'ERROR', auth.params['decision'] + end + + def test_stored_data_purchase_after_failed_store + response = @gateway.store(@expired_card, @options) + assert_failure response + assert_not_nil response.authorization + assert_equal 'ERROR', response.params['decision'] + + assert stored_purchase = @gateway.stored_purchase(@amount, response.authorization) + assert_failure stored_purchase + assert_equal 'ERROR', stored_purchase.params['decision'] + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -141,10 +187,10 @@ def test_overloaded_stored_data_authorize_and_capture def test_invalid_login gateway = OptimalPaymentGateway.new( - :account_number => '1', - :store_id => 'bad', - :password => 'bad' - ) + account_number: '1', + store_id: 'bad', + password: 'bad' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'invalid merchant account', response.message diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb index f85e53c28d3..6ae0530fd6d 100644 --- a/test/remote/gateways/remote_orbital_test.rb +++ b/test/remote/gateways/remote_orbital_test.rb @@ -4,24 +4,39 @@ class RemoteOrbitalGatewayTest < Test::Unit::TestCase def setup Base.mode = :test @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 - @credit_card = credit_card('4112344112344113') + @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') + @google_pay_card = network_tokenization_credit_card( + '4777777777777778', + payment_cryptogram: 'BwAQCFVQdwEAABNZI1B3EGLyGC8=', + verification_value: '987', + source: :google_pay, + brand: 'visa', + eci: '5' + ) @options = { - :order_id => generate_unique_id, - :address => address, - :merchant_id => 'merchant1234' + order_id: generate_unique_id, + address: address, + merchant_id: 'merchant1234' } @cards = { - :visa => '4788250000028291', - :mc => '5454545454545454', - :amex => '371449635398431', - :ds => '6011000995500000', - :diners => '36438999960016', - :jcb => '3566002020140006'} + visa: '4556761029983886', + mc: '5454545454545454', + amex: '371449635398431', + ds: '6011000995500000', + diners: '36438999960016', + jcb: '3566002020140006' + } @level_2_options = { tax_indicator: '1', @@ -36,19 +51,71 @@ def setup address2: address[:address2], city: address[:city], state: address[:state], - zip: address[:zip], + zip: address[:zip] + } + + @level_3_options_visa = { + freight_amount: 1, + duty_amount: 1, + ship_from_zip: 27604, + dest_country: 'USA', + discount_amount: 1, + vat_tax: 1, + vat_rate: 25 + } + + @level_2_options_master = { + freight_amount: 1, + duty_amount: 1, + ship_from_zip: 27604, + dest_country: 'USA', + alt_tax: 1, + alt_ind: 25 } + @line_items_visa = [ + { + desc: 'another item', + prod_cd: generate_unique_id[0, 11], + qty: 1, + u_o_m: 'LBR', + tax_amt: 250, + tax_rate: 10000, + line_tot: 2500, + disc: 250, + comm_cd: '00584', + unit_cost: 2500, + gross_net: 'Y', + tax_type: 'sale', + debit_ind: 'C' + }, + { + desc: 'something else', + prod_cd: generate_unique_id[0, 11], + qty: 1, + u_o_m: 'LBR', + tax_amt: 125, + tax_rate: 5000, + line_tot: 2500, + disc: 250, + comm_cd: '00584', + unit_cost: 250000, + gross_net: 'Y', + tax_type: 'sale', + debit_ind: 'C' + } + ] + @test_suite = [ - {:card => :visa, :AVSzip => 11111, :CVD => 111, :amount => 3000}, - {:card => :visa, :AVSzip => 33333, :CVD => nil, :amount => 3801}, - {:card => :mc, :AVSzip => 44444, :CVD => nil, :amount => 4100}, - {:card => :mc, :AVSzip => 88888, :CVD => 666, :amount => 1102}, - {:card => :amex, :AVSzip => 55555, :CVD => nil, :amount => 105500}, - {:card => :amex, :AVSzip => 66666, :CVD => 2222, :amount => 7500}, - {:card => :ds, :AVSzip => 77777, :CVD => nil, :amount => 1000}, - {:card => :ds, :AVSzip => 88888, :CVD => 444, :amount => 6303}, - {:card => :jcb, :AVSzip => 33333, :CVD => nil, :amount => 2900} + { card: :visa, AVSzip: 11111, CVD: 111, amount: 3000 }, + { card: :visa, AVSzip: 33333, CVD: nil, amount: 3801 }, + { card: :mc, AVSzip: 44444, CVD: nil, amount: 4100 }, + { card: :mc, AVSzip: 88888, CVD: 666, amount: 1102 }, + { card: :amex, AVSzip: 55555, CVD: nil, amount: 105500 }, + { card: :amex, AVSzip: 66666, CVD: 2222, amount: 7500 }, + { card: :ds, AVSzip: 77777, CVD: nil, amount: 1000 }, + { card: :ds, AVSzip: 88888, CVD: 444, amount: 6303 }, + { card: :jcb, AVSzip: 33333, CVD: nil, amount: 2900 } ] end @@ -59,15 +126,33 @@ def test_successful_purchase end def test_successful_purchase_with_soft_descriptor_hash - assert response = @gateway.purchase( - @amount, @credit_card, @options.merge( - soft_descriptors: { - merchant_name: 'Merch', - product_description: 'Description', - merchant_email: 'email@example', - } - ) + options = @options.merge( + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example' + } + ) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_card_indicators + options = @options.merge( + card_indicators: 'y' + ) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_card_indicators_and_line_items + options = @options.merge( + line_items: @line_items, + card_indicators: 'y' ) + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'Approved', response.message end @@ -79,22 +164,40 @@ def test_successful_purchase_with_level_2_data assert_equal 'Approved', response.message end + def test_successful_purchase_with_level_3_data + response = @gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options_visa, line_items: @line_items_visa)) + + assert_success response + assert_equal 'Approved', response.message + end + def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci - network_card = network_tokenization_credit_card('4788250000028291', + network_card = network_tokenization_credit_card( + '4788250000028291', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', verification_value: '111', brand: 'visa', eci: '5' ) - assert response = @gateway.purchase(3000, network_card, @options) + # Ensure that soft descriptor fields don't conflict with network token data in schema + options = @options.merge( + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example' + } + ) + + assert response = @gateway.purchase(3000, network_card, options) assert_success response assert_equal 'Approved', response.message assert_false response.authorization.blank? end def test_successful_purchase_with_master_card_network_tokenization_credit_card - network_card = network_tokenization_credit_card('4788250000028291', + network_card = network_tokenization_credit_card( + '4788250000028291', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', verification_value: '111', @@ -106,8 +209,51 @@ def test_successful_purchase_with_master_card_network_tokenization_credit_card assert_false response.authorization.blank? end + def test_successful_purchase_with_sca_recurring_master_card + cc = credit_card('5555555555554444', first_name: 'Joe', last_name: 'Smith', + month: '12', year: '2022', brand: 'master', verification_value: '999') + options_local = { + three_d_secure: { + eci: '7', + xid: 'TESTXID', + cavv: 'AAAEEEDDDSSSAAA2243234', + ds_transaction_id: '97267598FAE648F28083C23433990FBC', + version: '2.2.0' + }, + sca_recurring: 'Y' + } + + assert response = @three_ds_gateway.purchase(100, cc, @options.merge(options_local)) + + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_sca_merchant_initiated_master_card + cc = credit_card('5555555555554444', first_name: 'Joe', last_name: 'Smith', + month: '12', year: '2022', brand: 'master', verification_value: '999') + options_local = { + three_d_secure: { + eci: '7', + xid: 'TESTXID', + cavv: 'AAAEEEDDDSSSAAA2243234', + ds_transaction_id: '97267598FAE648F28083C23433990FBC', + version: '2.2.0' + }, + sca_merchant_initiated: 'Y' + } + + assert response = @three_ds_gateway.purchase(100, cc, @options.merge(options_local)) + + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + def test_successful_purchase_with_american_express_network_tokenization_credit_card - network_card = network_tokenization_credit_card('4788250000028291', + network_card = network_tokenization_credit_card( + '4788250000028291', payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', verification_value: '111', @@ -120,7 +266,8 @@ 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', @@ -132,6 +279,92 @@ def test_successful_purchase_with_discover_network_tokenization_credit_card assert_false response.authorization.blank? end + def test_successful_purchase_with_echeck + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_echeck_having_written_authorization + @options[:auth_method] = 'W' + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_echeck_having_internet_authorization + @options[:auth_method] = 'I' + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_echeck_having_telephonic_authorization + @options[:auth_method] = 'T' + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_echeck_having_arc_authorization + test_check = check(account_number: '000000000', account_type: 'checking', routing_number: '072403004') + assert response = @echeck_gateway.purchase(20, test_check, @options.merge({ auth_method: 'A' })) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_failed_missing_serial_for_arc_with_echeck + assert_raise do + test_check = { account_type: 'savings', routing_number: '072403004' } + @echeck_gateway.purchase(20, test_check, @options.merge({ auth_method: 'A' })) + end + end + + def test_successful_purchase_with_echeck_having_pop_authorization + test_check = check(account_number: '000000000', account_type: 'savings', routing_number: '072403004') + assert response = @echeck_gateway.purchase(20, test_check, @options.merge({ auth_method: 'P', terminal_city: 'CO', terminal_state: 'IL', image_reference_number: '00000' })) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_failed_missing_serial_for_pop_with_echeck + assert_raise do + test_check = { account_type: 'savings', routing_number: '072403004' } + @echeck_gateway.purchase(20, test_check, @options.merge({ auth_method: 'P' })) + end + end + + def test_successful_purchase_with_echeck_on_same_day + @options[:same_day] = 'Y' + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_echeck_on_next_day + @options[:same_day] = 'N' + assert response = @echeck_gateway.purchase(20, @echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_commercial_echeck + commercial_echeck = check(account_number: '072403004', account_type: 'checking', account_holder_type: 'business', routing_number: '072403004') + + assert response = @echeck_gateway.purchase(20, commercial_echeck, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + def test_successful_purchase_with_mit_stored_credentials mit_stored_credentials = { mit_msg_type: 'MUSE', @@ -157,35 +390,30 @@ def test_successful_purchase_with_cit_stored_credentials assert_equal 'Approved', response.message end - def test_successful_purchase_with_normalized_mit_stored_credentials - stored_credential = { - stored_credential: { - initial_transaction: false, - initiator: 'merchant', - reason_type: 'unscheduled', - network_transaction_id: 'abcdefg12345678' - } - } - - response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert network_transaction_id = purchase.params['mit_received_transaction_id'] - assert_success response - assert_equal 'Approved', response.message + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message end - def test_successful_purchase_with_normalized_cit_stored_credentials - stored_credential = { - stored_credential: { - initial_transaction: true, - initiator: 'customer', - reason_type: 'unscheduled' - } - } - - response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert network_transaction_id = purchase.params['mit_received_transaction_id'] - assert_success response - assert_equal 'Approved', response.message + used_options = stored_credential_options(:recurring, :merchant, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message end def test_successful_purchase_with_overridden_normalized_stored_credentials @@ -205,6 +433,48 @@ def test_successful_purchase_with_overridden_normalized_stored_credentials assert_equal 'Approved', response.message end + def test_successful_purchase_with_google_pay + response = @gateway.purchase(@google_pay_amount, @google_pay_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_force_capture_with_echeck + @options[:force_capture] = true + assert response = @echeck_gateway.purchase(@amount, @echeck, @options) + assert_success response + assert_match 'APPROVAL', response.message + assert_equal 'Approved and Completed', response.params['status_msg'] + assert_false response.authorization.blank? + end + + def test_failed_force_capture_with_echeck_due_to_invalid_amount + @options[:force_capture] = true + assert capture = @echeck_gateway.purchase(-1, @echeck, @options.merge(order_id: '2')) + assert_failure capture + assert_equal '801', capture.params['proc_status'] + assert_equal 'Error validating amount. Must be numerical and greater than 0 [-1]', capture.message + end + + def test_successful_force_capture_with_echeck_prenote_valid_action_code + @options[:force_capture] = true + @options[:action_code] = 'W8' + assert response = @echeck_gateway.authorize(0, @echeck, @options) + assert_success response + assert_match 'APPROVAL', response.message + assert_equal 'Approved and Completed', response.params['status_msg'] + assert_false response.authorization.blank? + end + + def test_failed_force_capture_with_echeck_prenote_invalid_action_code + @options[:force_capture] = true + @options[:action_code] = 'W7' + assert authorize = @echeck_gateway.authorize(0, @echeck, @options) + assert_failure authorize + assert_equal '19784', authorize.params['proc_status'] + assert_equal ' EWS: Invalid Action Code [W7], For Transaction Type [A].', authorize.message + end + # Amounts of x.01 will fail def test_unsuccessful_purchase assert response = @gateway.purchase(101, @declined_card, @options) @@ -214,11 +484,11 @@ def test_unsuccessful_purchase def test_authorize_and_capture amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options.merge(:order_id => '2')) + assert auth = @gateway.authorize(amount, @credit_card, @options.merge(order_id: '2')) assert_success auth assert_equal 'Approved', auth.message assert auth.authorization - assert capture = @gateway.capture(amount, auth.authorization, :order_id => '2') + assert capture = @gateway.capture(amount, auth.authorization, order_id: '2') assert_success capture end @@ -231,16 +501,78 @@ def test_successful_authorize_and_capture_with_level_2_data assert_success capture end + def test_successful_authorize_and_capture_with_level_3_data + auth = @gateway.authorize(@amount, @credit_card, @options.merge(level_3_data: @level_3_options)) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options.merge(level_3_data: @level_3_options)) + assert_success capture + end + + def test_successful_authorize_and_capture_with_echeck + assert auth = @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert capture = @echeck_gateway.capture(@amount, auth.authorization, order_id: '2') + assert_success capture + end + + def test_successful_authorize_and_capture_with_line_items + auth = @gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options_visa, line_items: @line_items_visa)) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options_visa, line_items: @line_items_visa)) + assert_success capture + end + + def test_successful_authorize_and_capture_with_google_pay + auth = @gateway.authorize(@amount, @google_pay_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + end + + def test_failed_authorize_with_echeck_due_to_invalid_amount + assert auth = @echeck_gateway.authorize(-1, @echeck, @options.merge(order_id: '2')) + assert_failure auth + assert_equal '885', auth.params['proc_status'] + assert_equal 'Error validating amount. Must be numeric, equal to zero or greater [-1]', auth.message + end + def test_authorize_and_void - assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(:order_id => '2')) + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert void = @gateway.void(auth.authorization, order_id: '2') + assert_success void + end + + def test_successful_authorize_and_void_with_echeck + assert auth = @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert void = @echeck_gateway.void(auth.authorization, order_id: '2') + assert_success void + end + + def test_authorize_and_void_using_google_pay + assert auth = @gateway.authorize(@amount, @google_pay_card, @options) assert_success auth assert_equal 'Approved', auth.message + assert auth.authorization - assert void = @gateway.void(auth.authorization, :order_id => '2') + assert void = @gateway.void(auth.authorization) assert_success void end - def test_refund + def test_successful_refund amount = @amount assert response = @gateway.purchase(amount, @credit_card, @options) assert_success response @@ -249,6 +581,50 @@ def test_refund assert_success refund end + def test_successful_refund_with_payment_source + amount = @amount + assert response = @gateway.purchase(amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert refund = @gateway.refund(amount, '', @options.merge({ payment_method: @credit_card })) + assert_success refund + end + + def test_failed_refund + assert refund = @gateway.refund(@amount, '123;123', @options) + assert_failure refund + assert_equal '881', refund.params['proc_status'] + end + + def test_successful_refund_with_google_pay + auth = @gateway.authorize(@amount, @google_pay_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + assert capture.authorization + assert refund = @gateway.refund(@amount, capture.authorization, @options) + assert_success refund + end + + def test_successful_refund_with_echeck + assert response = @echeck_gateway.purchase(@amount, @echeck, @options) + assert_success response + assert response.authorization + assert refund = @echeck_gateway.refund(@amount, response.authorization, @options) + assert_success refund + end + + def test_failed_refund_with_echeck_due_to_invalid_authorization + assert refund = @echeck_gateway.refund(@amount, '123;123', @options) + assert_failure refund + assert_equal 'The LIDM you supplied (3F3F3F) does not match with any existing transaction', refund.message + assert_equal '881', refund.params['proc_status'] + end + def test_successful_refund_with_level_2_data amount = @amount assert response = @gateway.purchase(amount, @credit_card, @options.merge(level_2_data: @level_2_options)) @@ -258,19 +634,120 @@ def test_successful_refund_with_level_2_data assert_success refund end + def test_successful_credit + payment_method = credit_card('5454545454545454') + assert response = @gateway.credit(@amount, payment_method, @options) + assert_success response + end + def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response assert_equal 'Bad data error', response.message end + def test_authorize_sends_with_retry + assert auth = @echeck_gateway.authorize(@amount, @credit_card, @options.merge(order_id: '4', retry_logic: 'true', trace_number: '989898')) + assert_success auth + assert_equal 'Approved', auth.message + end + + def test_authorize_sends_with_payment_delivery + assert auth = @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '4', payment_delivery: 'A')) + assert_success auth + assert_equal 'Approved', auth.message + end + + def test_default_payment_delivery_with_no_payment_delivery_sent + transcript = capture_transcript(@echeck_gateway) do + @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '4')) + end + + assert_match(/B/, transcript) + assert_match(/A/, transcript) + assert_match(/1/, transcript) + assert_match(/00/, transcript) + end + + def test_sending_echeck_adds_ecp_details_for_refund + assert auth = @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @echeck_gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + transcript = capture_transcript(@echeck_gateway) do + refund = @echeck_gateway.refund(@amount, capture.authorization, @options.merge(payment_method: @echeck, action_code: 'W6', auth_method: 'I')) + assert_success refund + end + + assert_match(/W6/, transcript) + assert_match(/I/, transcript) + assert_match(/R/, transcript) + assert_match(/1/, transcript) + end + + def test_sending_credit_card_performs_correct_refund + assert auth = @echeck_gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @echeck_gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + refund = @echeck_gateway.refund(@amount, capture.authorization, @options) + assert_success refund + end + + def test_echeck_purchase_with_address_responds_with_name + transcript = capture_transcript(@echeck_gateway) do + @echeck_gateway.authorize(@amount, @echeck, @options.merge(order_id: '2')) + end + + assert_match(/Jim Smith/, transcript) + assert_match(/00/, transcript) + assert_match(/atusMsg>ApprovedTest McTest/, transcript) + assert_match(/00/, transcript) + assert_match(/atusMsg>ApprovedLongbob Longsen/, transcript) + assert_match(/00/, transcript) + assert_match(/Approved/, transcript) + end + + def test_credit_purchase_with_no_address_responds_with_no_name + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2', address: nil, billing_address: nil)) + end + + assert_match(/00/, transcript) + assert_match(/Approved/, transcript) + end + # == Certification Tests # ==== Section A def test_auth_only_transactions for suite in @test_suite do amount = suite[:amount] - card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD]) + card = credit_card(@cards[suite[:card]], verification_value: suite[:CVD]) @options[:address][:zip] = suite[:AVSzip] assert response = @gateway.authorize(amount, card, @options) assert_kind_of Response, response @@ -288,7 +765,7 @@ def test_auth_only_transactions def test_auth_capture_transactions for suite in @test_suite do amount = suite[:amount] - card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD]) + card = credit_card(@cards[suite[:card]], verification_value: suite[:CVD]) options = @options; options[:address][:zip] = suite[:AVSzip] assert response = @gateway.purchase(amount, card, options) assert_kind_of Response, response @@ -338,7 +815,7 @@ def test_refund_transactions def test_void_transactions [3000, 105500, 2900].each do |amount| assert auth_response = @gateway.authorize(amount, @credit_card, @options) - assert void_response = @gateway.void(auth_response.authorization, @options.merge(:transaction_index => 1)) + assert void_response = @gateway.void(auth_response.authorization, @options.merge(transaction_index: 1)) assert_kind_of Response, void_response # Makes it easier to fill in cert sheet if you print these to the command line @@ -350,7 +827,120 @@ def test_void_transactions def test_successful_verify response = @gateway.verify(@credit_card, @options) assert_success response - assert_equal 'Approved', response.message + 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 + # The 'UT' value means use token, To tell Orbital we want to use the stored payment method + # The 'VI' value means an abbreviation for the card brand 'VISA'. + response = @tpv_orbital_gateway.purchase(@amount, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT')) + 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', token_txn_type: 'UT')) + assert_success auth + end + + def test_successful_authorize_stored_token_mastercard + store = @tpv_orbital_gateway.store(@credit_card, @options) + assert_success store + response = @tpv_orbital_gateway.authorize(29, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT')) + assert_success response + assert_equal response.params['card_brand'], 'VI' + end + + def test_failed_authorize_and_capture + store = @tpv_orbital_gateway.store(@credit_card, @options) + assert_success store + response = @tpv_orbital_gateway.capture(39, store.authorization, @options.merge(card_brand: 'VI', token_txn_type: 'UT')) + assert_failure response + assert_equal response.params['status_msg'], "The LIDM you supplied (#{store.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', token_txn_type: 'UT')) + assert_success auth + assert_equal auth.params['card_brand'], 'MC' + response = @tpv_orbital_gateway.capture(28, auth.authorization, @options) + 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', token_txn_type: 'UT')) + assert_success auth + response = @tpv_orbital_gateway.refund(38, auth.authorization, @options) + 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', token_txn_type: 'UT')) + assert_success auth + response = @tpv_orbital_gateway.refund(38, store.authorization, @options) + assert_failure response + assert_equal response.params['status_msg'], "The LIDM you supplied (#{store.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', token_txn_type: 'UT')) + assert_success purchase + response = @tpv_orbital_gateway.refund(38, purchase.authorization, @options) + 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. The Orbital Gateway has received a badly formatted message. The account number is required for this transaction' + end + + def test_successful_different_cards + @credit_card.brand = 'master' + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'No reason to decline', response.message + end + + def test_successful_verify_with_discover_brand + @credit_card.brand = 'discover' + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_verify_with_invalid_discover_card + @declined_card.brand = 'discover' + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Invalid CC Number', response.message end def test_failed_verify @@ -384,4 +974,694 @@ def test_transcript_scrubbing_profile assert_scrubbed(@gateway.options[:login], transcript) assert_scrubbed(@gateway.options[:merchant_id], transcript) end + + def test_transcript_scrubbing_echeck + transcript = capture_transcript(@echeck_gateway) do + @echeck_gateway.purchase(20, @echeck, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@echeck.account_number, transcript) + assert_scrubbed(@echeck_gateway.options[:password], transcript) + assert_scrubbed(@echeck_gateway.options[:login], transcript) + assert_scrubbed(@echeck_gateway.options[:merchant_id], transcript) + end + + def test_transcript_scrubbing_network_card + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'visa', + eci: '5' + ) + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, network_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(network_card.payment_cryptogram, transcript) + end + + private + + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id)) + end +end + +class BrandSpecificOrbitalTests < RemoteOrbitalGatewayTest + # Additional class for a subset of tests that share setup logic. + # This will run automatically with the rest of the tests in this file, + # or you can specify individual tests by name as you usually would. + def setup + super + + @brand_specific_fixtures = { + visa: { + card: { + number: '4112344112344113', + verification_value: '411', + brand: 'visa' + }, + three_d_secure: { + eci: '5', + cavv: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=', + xid: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=' + }, + address: { + address1: '55 Forever Ave', + address2: '', + city: 'Concord', + state: 'NH', + zip: '03301', + country: 'US' + } + }, + master: { + card: { + number: '5112345112345114', + verification_value: '823', + brand: 'master' + }, + three_d_secure: { + eci: '5', + cavv: 'AAAEEEDDDSSSAAA2243234', + xid: 'Asju1ljfl86bAAAAAACm9zU6aqY=', + version: '2.2.0', + ds_transaction_id: '8dh4htokdf84jrnxyemfiosheuyfjt82jiek' + }, + address: { + address1: 'Byway Street', + address2: '', + city: 'Portsmouth', + state: 'MA', + zip: '67890', + country: 'US', + phone: '5555555555' + } + }, + american_express: { + card: { + number: '371144371144376', + verification_value: '1234', + brand: 'american_express' + }, + three_d_secure: { + eci: '5', + cavv: 'AAABBWcSNIdjeUZThmNHAAAAAAA=', + xid: 'AAABBWcSNIdjeUZThmNHAAAAAAA=' + }, + address: { + address1: '4 Northeastern Blvd', + address2: '', + city: 'Salem', + state: 'NH', + zip: '03105', + country: 'US' + } + }, + discover: { + card: { + number: '6011016011016011', + verification_value: '613', + brand: 'discover' + }, + three_d_secure: { + eci: '6', + cavv: 'Asju1ljfl86bAAAAAACm9zU6aqY=', + ds_transaction_id: '32b274ee-582d-4232-b20a-363f2acafa5a' + }, + address: { + address1: '1 Northeastern Blvd', + address2: '', + city: 'Bedford', + state: 'NH', + zip: '03109', + country: 'US' + } + } + } + end + + def test_successful_3ds_authorization_with_visa + cc = brand_specific_card(@brand_specific_fixtures[:visa][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:visa]) + + assert response = @three_ds_gateway.authorize(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_purchase_with_visa + cc = brand_specific_card(@brand_specific_fixtures[:visa][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:visa]) + + assert response = @three_ds_gateway.purchase(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_authorization_with_mastercard + cc = brand_specific_card(@brand_specific_fixtures[:master][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:master]) + + assert response = @three_ds_gateway.authorize(100, cc, options) + assert_success_with_authorization(response) + end + + def test_succesful_3ds_purchase_with_mastercard + cc = brand_specific_card(@brand_specific_fixtures[:master][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:master]) + + assert response = @three_ds_gateway.purchase(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_authorization_with_american_express + cc = brand_specific_card(@brand_specific_fixtures[:american_express][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:american_express]) + + assert response = @three_ds_gateway.authorize(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_purchase_with_american_express + cc = brand_specific_card(@brand_specific_fixtures[:american_express][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:american_express]) + + assert response = @three_ds_gateway.purchase(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_authorization_with_discover + cc = brand_specific_card(@brand_specific_fixtures[:discover][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:discover]) + + assert response = @three_ds_gateway.authorize(100, cc, options) + assert_success_with_authorization(response) + end + + def test_successful_3ds_purchase_with_discover + cc = brand_specific_card(@brand_specific_fixtures[:discover][:card]) + options = brand_specific_3ds_options(@brand_specific_fixtures[:discover]) + + assert response = @three_ds_gateway.purchase(100, cc, options) + assert_success_with_authorization(response) + end + + private + + def assert_success_with_authorization(response) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def brand_specific_3ds_options(data) + @options.merge( + order_id: '2', + currency: 'USD', + three_d_secure: data[:three_d_secure], + address: data[:address], + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example' + } + ) + end + + def brand_specific_card(card_data) + credit_card( + card_data[:number], + { + verification_value: card_data[:verification_value], + brand: card_data[:brand] + } + ) + end +end + +class TandemOrbitalTests < Test::Unit::TestCase + # Additional test cases to verify tandem integration + def setup + Base.mode = :test + @tandem_gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_tandem_gateway)) + + @amount = 100 + @google_pay_amount = 10000 + @credit_card = credit_card('4556761029983886') + @declined_card = credit_card('4011361100000012') + @google_pay_card = network_tokenization_credit_card( + '4777777777777778', + payment_cryptogram: 'BwAQCFVQdwEAABNZI1B3EGLyGC8=', + verification_value: '987', + source: :google_pay, + brand: 'visa', + eci: '5' + ) + + @options = { + order_id: generate_unique_id, + address: address, + merchant_id: 'merchant1234' + } + + @level_2_options = { + tax_indicator: '1', + tax: '75', + purchase_order: '123abc', + zip: address[:zip] + } + + @level_3_options = { + freight_amount: 1, + duty_amount: 1, + ship_from_zip: 27604, + dest_country: 'USA', + discount_amount: 1, + vat_tax: 1, + vat_rate: 25 + } + + @line_items = [ + { + desc: 'another item', + prod_cd: generate_unique_id[0, 11], + qty: 1, + u_o_m: 'LBR', + tax_amt: 250, + tax_rate: 10000, + comm_cd: '00584', + unit_cost: 2500, + gross_net: 'Y', + tax_type: 'sale', + debit_ind: 'C' + }, + { + desc: 'something else', + prod_cd: generate_unique_id[0, 11], + qty: 1, + u_o_m: 'LBR', + tax_amt: 125, + tax_rate: 5000, + comm_cd: '00584', + unit_cost: 1000, + gross_net: 'Y', + tax_type: 'sale', + debit_ind: 'C' + } + ] + end + + def test_successful_purchase + assert response = @tandem_gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_soft_descriptor + options = @options.merge( + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example' + } + ) + assert response = @tandem_gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_2_data + response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_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)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'visa', + eci: '5' + ) + + assert response = @tandem_gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_master_card_network_tokenization_credit_card + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'master' + ) + assert response = @tandem_gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_american_express_network_tokenization_credit_card + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'american_express' + ) + assert response = @tandem_gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_discover_network_tokenization_credit_card + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'discover' + ) + assert response = @tandem_gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + # verify stored credential flows in tandem support + + def test_successful_purchase_with_mit_stored_credentials + mit_stored_credentials = { + mit_msg_type: 'MUSE', + mit_stored_credential_ind: 'Y', + mit_submitted_transaction_id: '111222333444555' + } + + response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(mit_stored_credentials)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_cit_stored_credentials + cit_options = { + mit_msg_type: 'CUSE', + mit_stored_credential_ind: 'Y' + } + + response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(cit_options)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @tandem_gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert network_transaction_id = purchase.params['mit_received_transaction_id'] + + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @tandem_gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + end + + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @tandem_gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert_equal 'Approved', purchase.message + assert network_transaction_id = purchase.params['mit_received_transaction_id'] + + used_options = stored_credential_options(:recurring, :merchant, id: network_transaction_id) + assert purchase = @tandem_gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + assert_equal 'Approved', purchase.message + end + + def test_successful_purchase_with_overridden_normalized_stored_credentials + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'unscheduled', + network_transaction_id: '111222333444555' + }, + mit_msg_type: 'MRSB' + } + + response = @tandem_gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Approved', response.message + end + + # verify google pay transactions on tandem account + + def test_successful_purchase_with_google_pay + response = @tandem_gateway.purchase(@google_pay_amount, @google_pay_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_purchase + assert response = @tandem_gateway.purchase(101, @declined_card, @options) + assert_failure response + assert_match 'AUTH DECLINED', response.message + end + + def test_authorize_and_capture + amount = @amount + assert auth = @tandem_gateway.authorize(amount, @credit_card, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert capture = @tandem_gateway.capture(amount, auth.authorization, order_id: '2') + assert_success capture + end + + def test_successful_authorize_and_capture_with_level_2_data + auth = @tandem_gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @tandem_gateway.capture(@amount, auth.authorization, @options.merge(level_2_data: @level_2_options)) + assert_success capture + end + + def test_successful_authorize_and_capture_with_line_items + auth = @tandem_gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options, line_items: @line_items)) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @tandem_gateway.capture(@amount, auth.authorization, @options.merge(level_2_data: @level_2_options, level_3_data: @level_3_options, line_items: @line_items)) + assert_success capture + end + + def test_successful_authorize_and_capture_with_google_pay + auth = @tandem_gateway.authorize(@amount, @google_pay_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @tandem_gateway.capture(@amount, auth.authorization, @options) + assert_success capture + end + + def test_authorize_and_void + assert auth = @tandem_gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2')) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert void = @tandem_gateway.void(auth.authorization, order_id: '2') + assert_success void + end + + def test_authorize_and_void_using_google_pay + assert auth = @tandem_gateway.authorize(@amount, @google_pay_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + assert auth.authorization + assert void = @tandem_gateway.void(auth.authorization) + assert_success void + end + + def test_successful_refund + amount = @amount + assert response = @tandem_gateway.purchase(amount, @credit_card, @options) + assert_success response + assert response.authorization + assert refund = @tandem_gateway.refund(amount, response.authorization, @options) + assert_success refund + end + + def test_failed_refund + assert refund = @tandem_gateway.refund(@amount, '123;123', @options) + assert_failure refund + assert_equal '881', refund.params['proc_status'] + end + + def test_successful_refund_with_google_pay + auth = @tandem_gateway.authorize(@amount, @google_pay_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @tandem_gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + assert capture.authorization + assert refund = @tandem_gateway.refund(@amount, capture.authorization, @options) + assert_success refund + end + + def test_successful_refund_with_level_2_data + amount = @amount + assert response = @tandem_gateway.purchase(amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + assert_success response + assert response.authorization + assert refund = @tandem_gateway.refund(amount, response.authorization, @options.merge(level_2_data: @level_2_options)) + assert_success refund + end + + def test_successful_credit + payment_method = credit_card('5454545454545454') + assert response = @tandem_gateway.credit(@amount, payment_method, @options) + assert_success response + end + + def test_failed_capture + assert response = @tandem_gateway.capture(@amount, '') + assert_failure response + assert_equal 'Bad data error', response.message + end + + def test_credit_purchase_with_address_responds_with_name + transcript = capture_transcript(@tandem_gateway) do + @tandem_gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2')) + end + + assert_match(/Longbob Longsen/, transcript) + assert_match(/00/, transcript) + assert_match(/Approved/, transcript) + end + + def test_credit_purchase_with_no_address_responds_with_no_name + transcript = capture_transcript(@tandem_gateway) do + @tandem_gateway.authorize(@amount, @credit_card, @options.merge(order_id: '2', address: nil, billing_address: nil)) + end + + assert_match(/00/, transcript) + assert_match(/Approved/, transcript) + end + + def test_void_transactions + [3000, 105500, 2900].each do |amount| + assert auth_response = @tandem_gateway.authorize(amount, @credit_card, @options) + assert void_response = @tandem_gateway.void(auth_response.authorization, @options.merge(transaction_index: 1)) + assert_kind_of Response, void_response + end + end + + def test_successful_verify + response = @tandem_gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_different_cards + @credit_card.brand = 'master' + response = @tandem_gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_verify_with_discover_brand + @credit_card.brand = 'discover' + response = @tandem_gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_verify_with_invalid_discover_card + @declined_card.brand = 'discover' + response = @tandem_gateway.verify(@declined_card, @options.merge({ verify_amount: '101' })) + assert_failure response + assert_match 'AUTH DECLINED', response.message + end + + def test_failed_verify + response = @tandem_gateway.verify(@declined_card, @options.merge({ verify_amount: '101' })) + assert_failure response + assert_match 'AUTH DECLINED', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@tandem_gateway) do + @tandem_gateway.purchase(@amount, @credit_card, @options) + end + transcript = @tandem_gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@tandem_gateway.options[:password], transcript) + assert_scrubbed(@tandem_gateway.options[:login], transcript) + assert_scrubbed(@tandem_gateway.options[:merchant_id], transcript) + end + + def test_transcript_scrubbing_profile + transcript = capture_transcript(@tandem_gateway) do + @tandem_gateway.add_customer_profile(@credit_card, @options) + end + transcript = @tandem_gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@tandem_gateway.options[:password], transcript) + assert_scrubbed(@tandem_gateway.options[:login], transcript) + assert_scrubbed(@tandem_gateway.options[:merchant_id], transcript) + end + + def test_transcript_scrubbing_network_card + network_card = network_tokenization_credit_card( + '4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'visa', + eci: '5' + ) + transcript = capture_transcript(@tandem_gateway) do + @tandem_gateway.purchase(@tandem_gateway, network_card, @options) + end + transcript = @tandem_gateway.scrub(transcript) + + assert_scrubbed(network_card.payment_cryptogram, transcript) + end + + private + + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id)) + end end diff --git a/test/remote/gateways/remote_pagarme_test.rb b/test/remote/gateways/remote_pagarme_test.rb index 868ff0a48c3..0679cbd43c6 100644 --- a/test/remote/gateways/remote_pagarme_test.rb +++ b/test/remote/gateways/remote_pagarme_test.rb @@ -14,7 +14,7 @@ def setup @declined_card = credit_card('4242424242424242', { first_name: 'Richard', last_name: 'Deschamps', - :verification_value => '688' + verification_value: '688' }) @options = { @@ -153,5 +153,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:api_key], transcript) end - end diff --git a/test/remote/gateways/remote_pago_facil_test.rb b/test/remote/gateways/remote_pago_facil_test.rb index 254ebd33dec..b672f9e0e06 100644 --- a/test/remote/gateways/remote_pago_facil_test.rb +++ b/test/remote/gateways/remote_pago_facil_test.rb @@ -86,7 +86,7 @@ def successful_response_to random_response = yield if random_response.success? return random_response - elsif(attempts > 2) + elsif attempts > 2 raise 'Unable to get a successful response' else assert_equal 'Declined_(General).', random_response.params.fetch('error') diff --git a/test/remote/gateways/remote_pay_arc_test.rb b/test/remote/gateways/remote_pay_arc_test.rb new file mode 100644 index 00000000000..bc98c11e7e9 --- /dev/null +++ b/test/remote/gateways/remote_pay_arc_test.rb @@ -0,0 +1,262 @@ +require 'test_helper' + +class RemotePayArcTest < Test::Unit::TestCase + def setup + @gateway = PayArcGateway.new(fixtures(:pay_arc)) + credit_card_options = { + month: '12', + year: '2022', + first_name: 'Rex Joseph', + last_name: '', + verification_value: '999' + } + @credit_card = credit_card('4111111111111111', credit_card_options) + @invalid_credit_card = credit_card('3111111111111111', credit_card_options) + @invalid_cvv_card = credit_card('4111111111111111', credit_card_options.update(verification_value: '123')) + + @amount = 100 + + @options = { + description: 'Store Purchase', + card_source: 'INTERNET', + billing_address: address.update(phone: '8772036624'), + email: 'testy@test.com', + phone: '8772036624' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_successful_purchase_with_more_options + extra_options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + tip_amount: 10 + } + response = @gateway.purchase(1500, @credit_card, @options.merge(extra_options)) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_successful_two_digit_card_exp_month + credit_card_options = { + month: '02', + year: '2022', + first_name: 'Rex Joseph', + last_name: '', + verification_value: '999' + } + credit_card = credit_card('4111111111111111', credit_card_options) + + response = @gateway.purchase(1022, credit_card, @options) + assert_success response + + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_successful_one_digit_card_exp_month + credit_card_options = { + month: '2', + year: '2022', + first_name: 'Rex Joseph', + last_name: '', + verification_value: '999' + } + credit_card = credit_card('4111111111111111', credit_card_options) + + response = @gateway.purchase(1022, credit_card, @options) + assert_success response + + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_failed_three_digit_card_exp_month + credit_card_options = { + month: '200', + year: '2022', + first_name: 'Rex Joseph', + last_name: '', + verification_value: '999' + } + credit_card = credit_card('4111111111111111', credit_card_options) + + response = @gateway.purchase(1022, credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_adds_phone_number_for_purchase + response = @gateway.purchase(250, @credit_card, @options) + assert_success response + + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + + assert_equal '8772036624', response.params['data']['phone_number'] + end + + def test_successful_purchase_without_billing_address + @options.delete(:billing_address) + response = @gateway.purchase(250, @credit_card, @options) + + assert_nil response.params['data']['card']['data']['address1'] + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(1300, @invalid_credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_authorize + response = @gateway.authorize(1100, @credit_card, @options) + assert_success response + assert_equal 'authorized', response.message + end + + # Failed due to invalid CVV + def test_failed_authorize + response = @gateway.authorize(500, @invalid_cvv_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_authorize_and_capture + authorize_response = @gateway.authorize(2000, @credit_card, @options) + assert_success authorize_response + response = @gateway.capture(2000, authorize_response.authorization, @options) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_failed_capture + response = @gateway.capture(2000, 'invalid_txn_refernece', @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_void + @options.update(reason: 'duplicate') + charge_response = @gateway.purchase(1200, @credit_card, @options) + assert_success charge_response + + assert void = @gateway.void(charge_response.authorization, @options) + assert_success void + assert_block do + PayArcGateway::SUCCESS_STATUS.include? void.message + end + end + + def test_failed_void + response = @gateway.void('invalid_txn_reference', @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_partial_capture + authorize_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize_response + + response = @gateway.capture(@amount - 1, authorize_response.authorization, @options) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_successful_refund + purchase = @gateway.purchase(900, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(900, purchase.authorization) + assert_success refund + assert_block do + PayArcGateway::SUCCESS_STATUS.include? refund.message + end + assert_equal 'refunded', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + assert_block do + PayArcGateway::SUCCESS_STATUS.include? refund.message + end + assert_equal 'partial_refund', refund.message + end + + def test_failed_refund + response = @gateway.refund(1200, '') + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_credit + response = @gateway.credit(250, @credit_card, @options) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + assert_equal 'refunded', response.message + end + + def test_failed_credit + response = @gateway.credit('', @invalid_credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + response = @gateway.verify(@invalid_credit_card, @options) + assert_failure response + assert_block do + !(200..299).cover? response.error_code + end + end + + def test_invalid_login + gateway = PayArcGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + assert_block do + !(200..299).cover? response.error_code + end + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(fixtures(:pay_arc), transcript) + assert_scrubbed(/card_number=#{@credit_card.number}/, transcript) + assert_scrubbed(/cvv=#{@credit_card.verification_value}/, transcript) + end +end diff --git a/test/remote/gateways/remote_pay_conex_test.rb b/test/remote/gateways/remote_pay_conex_test.rb index 22ed010bcbd..a87790f8068 100644 --- a/test/remote/gateways/remote_pay_conex_test.rb +++ b/test/remote/gateways/remote_pay_conex_test.rb @@ -29,6 +29,17 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:api_accesskey], transcript) end + def test_transcript_scrubbing_with_check + 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[:api_accesskey], transcript) + end + def test_successful_purchase response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -61,7 +72,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture assert_equal 'CAPTURED', capture.message end @@ -85,7 +96,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund assert_equal 'REFUND', refund.message end diff --git a/test/remote/gateways/remote_pay_gate_xml_test.rb b/test/remote/gateways/remote_pay_gate_xml_test.rb index b16cedb0678..02d23f0e8b7 100644 --- a/test/remote/gateways/remote_pay_gate_xml_test.rb +++ b/test/remote/gateways/remote_pay_gate_xml_test.rb @@ -9,11 +9,11 @@ def setup @declined_card = credit_card('4000000000000036') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :email => 'john.doe@example.com', - :ip => '127.0.0.1', - :description => 'Store Purchase', + order_id: generate_unique_id, + billing_address: address, + email: 'john.doe@example.com', + ip: '127.0.0.1', + description: 'Store Purchase' } end @@ -47,8 +47,8 @@ def test_failed_capture def test_invalid_login gateway = PayGateXmlGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.authorize(@amount, @credit_card, @options) assert_failure response @@ -59,7 +59,7 @@ def test_successful_purchase_and_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry') + credit = @gateway.refund(@amount, purchase.authorization, note: 'Sorry') assert_success credit assert credit.test? end diff --git a/test/remote/gateways/remote_pay_hub_test.rb b/test/remote/gateways/remote_pay_hub_test.rb index e57fe048e4a..ae79455b5a2 100644 --- a/test/remote/gateways/remote_pay_hub_test.rb +++ b/test/remote/gateways/remote_pay_hub_test.rb @@ -7,14 +7,14 @@ def setup @credit_card = credit_card('5466410004374507', verification_value: '998') @invalid_card = credit_card('371449635398431', verification_value: '9997') @options = { - :first_name => 'Garrya', - :last_name => 'Barrya', - :email => 'payhubtest@mailinator.com', - :address => { - :address1 => '123a ahappy St.', - :city => 'Happya City', - :state => 'CA', - :zip => '94901' + first_name: 'Garrya', + last_name: 'Barrya', + email: 'payhubtest@mailinator.com', + address: { + address1: '123a ahappy St.', + city: 'Happya City', + state: 'CA', + zip: '94901' } } end diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb index 1db822d8bb1..280e89e3883 100644 --- a/test/remote/gateways/remote_pay_junction_test.rb +++ b/test/remote/gateways/remote_pay_junction_test.rb @@ -10,28 +10,28 @@ class PayJunctionTest < Test::Unit::TestCase def setup @gateway = PayJunctionGateway.new(fixtures(:pay_junction)) - @credit_card = credit_card('4444333322221111', :verification_value => '999') + @credit_card = credit_card('4444333322221111', verification_value: '999') @valid_verification_value = '999' @invalid_verification_value = '1234' @valid_address = { - :address1 => '123 Test St.', - :address2 => nil, - :city => 'Somewhere', - :state => 'CA', - :zip => '90001' + address1: '123 Test St.', + address2: nil, + city: 'Somewhere', + state: 'CA', + zip: '90001' } @invalid_address = { - :address1 => '187 Apple Tree Lane.', - :address2 => nil, - :city => 'Woodside', - :state => 'CA', - :zip => '94062' + address1: '187 Apple Tree Lane.', + address2: nil, + city: 'Woodside', + state: 'CA', + zip: '94062' } - @options = { :billing_address => @valid_address, :order_id => generate_unique_id } + @options = { billing_address: @valid_address, order_id: generate_unique_id } end def test_successful_purchase @@ -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 @@ -90,11 +89,10 @@ def test_successful_void purchase = @gateway.purchase(AMOUNT, @credit_card, @options) assert_success purchase - assert response = @gateway.void(purchase.authorization, :order_id => order_id) + 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 @@ -105,22 +103,23 @@ def test_successful_instant_purchase purchase = @gateway.purchase(AMOUNT, @credit_card, @options) assert_success purchase - assert response = @gateway.purchase(AMOUNT, purchase.authorization, :order_id => generate_unique_id) + assert response = @gateway.purchase(AMOUNT, purchase.authorization, order_id: generate_unique_id) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message assert_equal 'capture', response.params['posture'], 'Should be captured funds' assert_equal 'charge', response.params['transaction_action'] - assert_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, - :periodicity => :monthly, - :payments => 12, - :order_id => generate_unique_id[0..15] + assert response = @gateway.recurring( + AMOUNT, + @credit_card, + periodicity: :monthly, + payments: 12, + order_id: generate_unique_id[0..15] ) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message diff --git a/test/remote/gateways/remote_pay_junction_v2_test.rb b/test/remote/gateways/remote_pay_junction_v2_test.rb index 62954ff4496..8d6ae6987bf 100644 --- a/test/remote/gateways/remote_pay_junction_v2_test.rb +++ b/test/remote/gateways/remote_pay_junction_v2_test.rb @@ -5,9 +5,10 @@ def setup @gateway = PayJunctionV2Gateway.new(fixtures(:pay_junction_v2)) @amount = 99 - @credit_card = credit_card('4444333322221111', month: 01, year: 2020, verification_value: 999) + @credit_card = credit_card('4444333322221111', month: 01, year: 2021, verification_value: 999) @options = { - order_id: generate_unique_id + order_id: generate_unique_id, + billing_address: address() } end @@ -24,6 +25,13 @@ def test_successful_purchase assert_success response assert_equal 'Approved', response.message assert response.test? + + assert_match @options[:billing_address][:company], response.params['billing']['companyName'] + assert_match @options[:billing_address][:address1], response.params['billing']['address']['address'] + assert_match @options[:billing_address][:city], response.params['billing']['address']['city'] + assert_match @options[:billing_address][:state], response.params['billing']['address']['state'] + assert_match @options[:billing_address][:country], response.params['billing']['address']['country'] + assert_match @options[:billing_address][:zip], response.params['billing']['address']['zip'] end def test_successful_purchase_sans_options @@ -60,9 +68,9 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture - assert_equal sprintf('%.2f', (@amount-1).to_f / 100), capture.params['amountTotal'] + assert_equal sprintf('%.2f', (@amount - 1).to_f / 100), capture.params['amountTotal'] end def test_failed_capture @@ -81,13 +89,13 @@ def test_successful_refund end def test_partial_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) + purchase = @gateway.purchase(@amount + 50, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-50, purchase.authorization) + assert refund = @gateway.refund(@amount, purchase.authorization) assert_success refund assert_equal 'Approved', refund.message - assert_equal sprintf('%.2f', (@amount-50).to_f / 100), refund.params['amountTotal'] + assert_equal sprintf('%.2f', @amount.to_f / 100), refund.params['amountTotal'] end def test_failed_refund diff --git a/test/remote/gateways/remote_pay_secure_test.rb b/test/remote/gateways/remote_pay_secure_test.rb index ee153752bb9..4e82df22a9a 100644 --- a/test/remote/gateways/remote_pay_secure_test.rb +++ b/test/remote/gateways/remote_pay_secure_test.rb @@ -1,14 +1,13 @@ require 'test_helper' class RemotePaySecureTest < Test::Unit::TestCase - def setup @gateway = PaySecureGateway.new(fixtures(:pay_secure)) @credit_card = credit_card('4000100011112224') @options = { - :billing_address => address, - :order_id => generate_unique_id + billing_address: address, + order_id: generate_unique_id } @amount = 100 end @@ -29,9 +28,9 @@ def test_unsuccessful_purchase def test_invalid_login gateway = PaySecureGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal "MissingField: 'MERCHANT_ID'", response.message assert_failure response diff --git a/test/remote/gateways/remote_pay_trace_test.rb b/test/remote/gateways/remote_pay_trace_test.rb new file mode 100644 index 00000000000..6c56f840353 --- /dev/null +++ b/test/remote/gateways/remote_pay_trace_test.rb @@ -0,0 +1,429 @@ +require 'test_helper' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayTraceGateway < Gateway + def settle + post = {} + response = commit('transactions/settle', post) + check_token_response(response, 'transactions/settle', post, options) + end + end + end +end + +class RemotePayTraceTest < Test::Unit::TestCase + def setup + @gateway = PayTraceGateway.new(fixtures(:pay_trace)) + + @amount = 100 + @credit_card = credit_card('4012000098765439') + @mastercard = credit_card('5499740000000057') + @invalid_card = credit_card('54545454545454', month: '14', year: '1999') + @discover = credit_card('6011000993026909') + @amex = credit_card('371449635392376') + @echeck = check(account_number: '123456', routing_number: '325070760') + @options = { + billing_address: { + address1: '8320 This Way Lane', + city: 'Placeville', + state: 'CA', + zip: '85284' + }, + description: 'Store Purchase' + } + end + + def test_acquire_token + response = @gateway.acquire_access_token + assert_not_nil response['access_token'] + end + + def test_failed_access_token + assert_raises(ActiveMerchant::OAuthResponseError) do + gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator') + gateway.send :acquire_access_token + end + end + + def test_failed_purchase_with_failed_access_token + gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator') + + error = assert_raises(ActiveMerchant::OAuthResponseError) do + gateway.purchase(1000, @credit_card, @options) + 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 + assert_equal 'Your transaction was successfully approved.', response.message + end + + def test_successful_purchase_with_customer_id + create = @gateway.store(@mastercard, @options) + customer_id = create.params['customer_id'] + response = @gateway.purchase(500, customer_id, @options) + assert_success response + assert_equal 'Your transaction was successfully approved.', response.message + end + + def test_successful_purchase_with_ach + @echeck.account_number = rand.to_s[2..7] + response = @gateway.purchase(1000, @echeck, @options) + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_successful_purchase_by_customer_with_ach + @echeck.account_number = rand.to_s[2..7] + create = @gateway.store(@echeck, @options) + assert_success create + customer_id = create.params['customer_id'] + response = @gateway.purchase(500, customer_id, @options.merge({ check_transaction: 'true' })) + assert_success response + end + + def test_successful_purchase_with_more_options + options = { + email: 'joe@example.com' + } + + response = @gateway.purchase(200, @discover, options) + assert_success response + assert_equal 'Your transaction was successfully approved.', response.message + end + + def test_successful_purchase_with_level_3_data_visa + options = { + visa_or_mastercard: 'visa', + invoice_id: 'inv12345', + customer_reference_id: '123abcd', + tax_amount: 499, + national_tax_amount: 172, + merchant_tax_id: '3456defg', + customer_tax_id: '3456test', + commodity_code: '4321', + discount_amount: 99, + freight_amount: 75, + duty_amount: 32, + source_address: { + zip: '94947' + }, + shipping_address: { + zip: '94948', + country: 'US' + }, + additional_tax_amount: 4, + additional_tax_rate: 1, + line_items: [ + { + additional_tax_amount: 0, + additional_tax_rate: 8, + amount: 1999, + commodity_code: '123commodity', + description: 'plumbing', + discount_amount: 327, + product_id: 'skucode123', + quantity: 4, + unit_of_measure: 'EACH', + unit_cost: 424 + } + ] + } + + response = @gateway.purchase(3000, @credit_card, options) + assert_success response + assert_equal 101, response.params['response_code'] + assert_not_nil response.authorization + end + + def test_successful_purchase_with_level_3_data_mastercard + options = { + visa_or_mastercard: 'mastercard', + invoice_id: 'inv1234', + customer_reference_id: 'PO123456', + tax_amount: 810, + source_address: { + zip: '99201' + }, + shipping_address: { + zip: '85284', + country: 'US' + }, + additional_tax_amount: 40, + additional_tax_included: true, + line_items: [ + { + additional_tax_amount: 40, + additional_tax_included: true, + additional_tax_rate: 8, + amount: 1999, + debit_or_credit: 'D', + description: 'business services', + discount_amount: 327, + discount_rate: 1, + discount_included: true, + merchant_tax_id: '12-123456', + product_id: 'sku1245', + quantity: 4, + tax_included: true, + unit_of_measure: 'EACH', + unit_cost: 524 + } + ] + } + + response = @gateway.purchase(250, @mastercard, options) + assert_success response + assert_equal 101, response.params['response_code'] + end + + # Level three data can only be added to approved sale transactions done with visa or mastercard. + # This test is to show that if a transaction were to come through with a different card type, + # the gateway integration would ignore the attempt to add level three data, but could still approve the sale transaction. + def test_successful_purchase_with_attempted_level_3 + options = { + visa_or_mastercard: 'discover', + invoice_id: 'inv1234', + customer_reference_id: 'PO123456' + } + + response = @gateway.purchase(300, @discover, @options.merge(options)) + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(29, @amex, @options) + assert_failure response + assert_equal false, response.success? + end + + def test_failed_purchase_with_multiple_errors + response = @gateway.purchase(25000, @invalid_card, @options) + assert_failure response + assert_equal 'Errors- code:35, message:["Please provide a valid Credit Card Number."] code:43, message:["Please provide a valid Expiration Month."]', response.message + end + + def test_successful_authorize_and_full_capture + auth = @gateway.authorize(4000, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(4000, auth.authorization, @options) + assert_success capture + assert_equal 'Your transaction was successfully captured.', capture.message + end + + def test_successful_authorize_with_customer_id + store = @gateway.store(@mastercard, @options) + assert_success store + customer_id = store.params['customer_id'] + + response = @gateway.authorize(200, customer_id, @options) + assert_success response + assert_equal 'Your transaction was successfully approved.', response.message + end + + def test_successful_authorize_with_ach + @echeck.account_number = rand.to_s[2..7] + response = @gateway.authorize(1000, @echeck, @options) + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_successful_authorize_by_customer_with_ach + @echeck.account_number = rand.to_s[2..7] + store = @gateway.store(@echeck, @options) + assert_success store + customer_id = store.params['customer_id'] + + response = @gateway.authorize(200, customer_id, @options.merge({ check_transaction: 'true' })) + assert_success response + end + + def test_successful_authorize_and_capture_with_level_3_data + options = { + visa_or_mastercard: 'mastercard', + address: { + zip: '99201' + }, + shipping_address: { + zip: '85284', + country: 'US' + }, + line_items: [ + { + description: 'office supplies', + product_id: 'sku9876' + }, + { + description: 'business services', + product_id: 'sku3456' + } + ] + } + auth = @gateway.authorize(@amount, @mastercard, options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, options) + assert_success capture + + transaction_id = auth.authorization + assert_equal "Visa/MasterCard enhanced data was successfully added to Transaction ID #{transaction_id}. 2 line item records were created.", capture.message + end + + def test_failed_authorize + response = @gateway.authorize(29, @mastercard, @options) + assert_failure response + assert_equal 'Your transaction was not approved. EXPIRED CARD - Expired card', response.message + end + + def test_partial_capture + auth = @gateway.authorize(500, @amex, @options) + assert_success auth + + assert capture = @gateway.capture(200, auth.authorization, @options) + assert_success capture + end + + def test_authorize_and_capture_with_ach + @echeck.account_number = rand.to_s[2..7] + auth = @gateway.authorize(500, @echeck, @options) + assert_success auth + + assert capture = @gateway.capture(500, auth.authorization, @options.merge({ check_transaction: 'true' })) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Errors- code:58, message:["Please provide a valid Transaction ID."]', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(200, @credit_card, @options) + assert_success purchase + authorization = purchase.authorization + + settle = @gateway.settle() + assert_success settle + + refund = @gateway.refund(200, authorization, @options) + assert_success refund + assert_equal 'Your transaction was successfully refunded.', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(400, @mastercard, @options) + assert_success purchase + authorization = purchase.authorization + + settle = @gateway.settle() + assert_success settle + + refund = @gateway.refund(300, authorization, @options) + assert_success refund + assert_equal 'Your transaction was successfully refunded.', refund.message + end + + def test_refund_without_amount + purchase = @gateway.purchase(@amount, @discover, @options) + assert_success purchase + authorization = purchase.authorization + + settle = @gateway.settle() + assert_success settle + + refund = @gateway.refund(@amount, authorization) + assert_success refund + assert_equal 'Your transaction was successfully refunded.', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(2000, @credit_card, @options) + assert_success purchase + authorization = purchase.authorization + + response = @gateway.refund(2000, authorization, @options) + assert_failure response + assert_equal 'Errors- code:817, message:["The Transaction ID that you provided could not be refunded. Only settled transactions can be refunded. Please try to void the transaction instead."]', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @amex, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Your transaction was successfully voided.', void.message + end + + def test_successful_void_with_ach + @echeck.account_number = rand.to_s[2..7] + auth = @gateway.authorize(@amount, @echeck, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, { check_transaction: 'true' }) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Errors- code:58, message:["Please provide a valid Transaction ID."]', response.message + end + + def test_successful_verify + response = @gateway.verify(@mastercard, @options) + assert_success response + assert_equal 'Your transaction was successfully approved.', response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_successful_store_and_redact_customer_profile + response = @gateway.store(@mastercard, @options) + assert_success response + customer_id = response.params['customer_id'] + redact = @gateway.unstore(customer_id) + assert_success redact + assert_equal true, redact.success? + end + + def test_duplicate_customer_creation + create = @gateway.store(@discover, @options) + customer_id = create.params['customer_id'] + response = @gateway.store(@discover, @options.merge(customer_id: customer_id)) + assert_failure response + end + + # Not including a test_failed_verify since the only way to force a failure on this + # 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) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@amex.number, transcript) + assert_scrubbed(@amex.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:username], transcript) + assert_scrubbed(@gateway.options[:integrator_id], transcript) + end +end diff --git a/test/remote/gateways/remote_paybox_direct_3ds_test.rb b/test/remote/gateways/remote_paybox_direct_3ds_test.rb new file mode 100644 index 00000000000..a8fa0589d8f --- /dev/null +++ b/test/remote/gateways/remote_paybox_direct_3ds_test.rb @@ -0,0 +1,140 @@ +# encoding: utf-8 + +require 'test_helper' + +class RemotePayboxDirect3DSTest < Test::Unit::TestCase + def setup + fixtures = fixtures(:paybox_direct) + @gateway = PayboxDirectGateway.new(fixtures) + + @amount = 100 + @credit_card = credit_card(fixtures[:credit_card_ok_3ds]) + @declined_card = credit_card(fixtures[:credit_card_nok_3ds]) + @unenrolled_card = credit_card(fixtures[:credit_card_ok_3ds_not_enrolled]) + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + three_d_secure: { + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501', + cavv_algorithm: '1' + } + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'The transaction was approved', response.message + end + + def test_successful_purchase_other_eci + options = @options + options[:three_d_secure][:eci] = '05' + + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'The transaction was approved', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal "PAYBOX : Num\xE9ro de porteur invalide".force_encoding('ASCII-8BIT'), response.message + end + + def test_successful_unenrolled_3ds_purchase + assert response = @gateway.purchase(@amount, @unenrolled_card, @options) + assert_success response + assert_equal 'The transaction was approved', response.message + end + + def test_authorize_and_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'The transaction was approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization, order_id: '1') + assert_success capture + end + + def test_purchase_and_void + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal 'The transaction was approved', purchase.message + assert purchase.authorization + # Paybox requires you to remember the expiration date + assert void = @gateway.void(purchase.authorization, order_id: '1', amount: @amount) + assert_equal 'The transaction was approved', void.message + assert_success void + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '', order_id: '1') + assert_failure response + assert_equal 'Mandatory values missing keyword:13 Type:1', response.message + end + + def test_purchase_and_partial_credit + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal 'The transaction was approved', purchase.message + assert purchase.authorization + assert credit = @gateway.refund(@amount / 2, purchase.authorization, order_id: '1') + assert_equal 'The transaction was approved', credit.message + assert_success credit + end + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, order_id: '1') + assert_success refund + end + + def test_partial_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount / 2, purchase.authorization, order_id: '1') + assert_success refund + end + + def test_failed_refund + refund = @gateway.refund(@amount, '', order_id: '2') + assert_failure refund + assert_equal 'Mandatory values missing keyword:13 Type:13', refund.message + end + + def test_failed_purchase_invalid_eci + options = @options + options[:three_d_secure][:eci] = '00' + + assert purchase = @gateway.purchase(@amount, @credit_card, options) + assert_failure purchase + assert_equal "PAYBOX : Transaction refus\xE9e".force_encoding('ASCII-8BIT'), purchase.message + end + + def test_failed_purchase_invalid_cavv + options = @options + options[:three_d_secure][:cavv] = 'jJ81HADVRtXfCBATEp01CJUAAAAVZQGY=' + + assert purchase = @gateway.purchase(@amount, @credit_card, options) + assert_failure purchase + assert_equal 'Some values exceed max length', purchase.message + end + + def test_failed_purchase_invalid_xid + options = @options + options[:three_d_secure][:xid] = '00000000000000000510123123123456789' + + assert purchase = @gateway.purchase(@amount, @credit_card, options) + assert_failure purchase + assert_equal 'Some values exceed max length', purchase.message + end +end diff --git a/test/remote/gateways/remote_paybox_direct_test.rb b/test/remote/gateways/remote_paybox_direct_test.rb index 25c03d67804..6650a2f6a1f 100644 --- a/test/remote/gateways/remote_paybox_direct_test.rb +++ b/test/remote/gateways/remote_paybox_direct_test.rb @@ -3,18 +3,18 @@ require 'test_helper' class RemotePayboxDirectTest < Test::Unit::TestCase - def setup - @gateway = PayboxDirectGateway.new(fixtures(:paybox_direct)) + fixtures = fixtures(:paybox_direct) + @gateway = PayboxDirectGateway.new(fixtures) @amount = 100 - @credit_card = credit_card('1111222233334444') - @declined_card = credit_card('1111222233334445') + @credit_card = credit_card(fixtures[:credit_card_ok]) + @declined_card = credit_card(fixtures[:credit_card_nok]) @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -36,7 +36,7 @@ def test_authorize_and_capture assert_success auth assert_equal 'The transaction was approved', auth.message assert auth.authorization - assert capture = @gateway.capture(amount, auth.authorization, :order_id => '1') + assert capture = @gateway.capture(amount, auth.authorization, order_id: '1') assert_success capture end @@ -46,15 +46,15 @@ def test_purchase_and_void assert_equal 'The transaction was approved', purchase.message assert purchase.authorization # Paybox requires you to remember the expiration date - assert void = @gateway.void(purchase.authorization, :order_id => '1', :amount => @amount) + assert void = @gateway.void(purchase.authorization, order_id: '1', amount: @amount) assert_equal 'The transaction was approved', void.message assert_success void end def test_failed_capture - assert response = @gateway.capture(@amount, '', :order_id => '1') + assert response = @gateway.capture(@amount, '', order_id: '1') assert_failure response - assert_equal 'Invalid data', response.message + assert_equal 'Mandatory values missing keyword:13 Type:1', response.message end def test_purchase_and_partial_credit @@ -62,7 +62,7 @@ def test_purchase_and_partial_credit assert_success purchase assert_equal 'The transaction was approved', purchase.message assert purchase.authorization - assert credit = @gateway.credit(@amount / 2, purchase.authorization, :order_id => '1') + assert credit = @gateway.refund(@amount / 2, purchase.authorization, order_id: '1') assert_equal 'The transaction was approved', credit.message assert_success credit end @@ -79,34 +79,34 @@ def test_partial_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount/2, purchase.authorization, order_id: '1') + assert refund = @gateway.refund(@amount / 2, purchase.authorization, order_id: '1') assert_success refund end def test_failed_refund refund = @gateway.refund(@amount, '', order_id: '2') assert_failure refund - assert_equal 'Invalid data', refund.message + assert_equal 'Mandatory values missing keyword:13 Type:13', refund.message end def test_invalid_login gateway = PayboxDirectGateway.new( - login: '199988899', - password: '1999888F', - rang: 100 - ) + login: '199988899', + password: '1999888F', + rang: 100 + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'Non autorise', response.message + assert_equal 'HMAC requis', response.message end def test_invalid_login_without_rang gateway = PayboxDirectGateway.new( - login: '199988899', - password: '1999888F' - ) + login: '199988899', + password: '1999888F' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'Non autorise', response.message + assert_equal "PAYBOX : Acc\xE8s refus\xE9 ou site/rang/cl\xE9 invalide".force_encoding('ASCII-8BIT'), response.message end end diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb index 789e500a456..da2fd0fae93 100644 --- a/test/remote/gateways/remote_payeezy_test.rb +++ b/test/remote/gateways/remote_payeezy_test.rb @@ -9,9 +9,9 @@ def setup @amount = 100 @reversal_id = "REV-#{SecureRandom.random_number(1000000)}" @options = { - :billing_address => address, - :merchant_ref => 'Store Purchase', - :ta_token => 'NOIW' + billing_address: address, + merchant_ref: 'Store Purchase', + ta_token: 'NOIW' } @options_mdd = { soft_descriptors: { @@ -33,6 +33,33 @@ def setup initiator: 'MERCHANT', auth_type_override: 'A' } + @options_standardized_stored_credentials = { + stored_credential: { + network_transaction_id: 'abc123', # Not checked if initial_transaction == true; not valid if initial_transaction == false. + initial_transaction: true, + reason_type: 'recurring', + initiator: 'cardholder' + } + } + @apple_pay_card = network_tokenization_credit_card( + '4761209980011439', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '11', + year: Time.now.year + 1, + eci: 5, + 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 @@ -59,11 +86,31 @@ def test_unsuccessful_store def test_successful_purchase 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_successful_purchase_with_apple_pay + assert response = @gateway.purchase(@amount, @apple_pay_card, @options) + 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'}) + options = @options.merge({ customer_id_type: '1', customer_id_number: '1', client_email: 'test@example.com' }) assert response = @gateway.purchase(@amount, @check, options) assert_match(/Transaction Normal/, response.message) assert_success response @@ -75,12 +122,61 @@ 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) + assert_success response + end + def test_successful_purchase_with_stored_credentials assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials)) assert_match(/Transaction Normal/, response.message) assert_success response end + def test_successful_purchase_with_standardized_stored_credentials + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_standardized_stored_credentials)) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_apple_pay_name_from_billing_address + @apple_pay_card.first_name = nil + @apple_pay_card.last_name = nil + assert response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_success response + assert_equal 'Jim Smith', response.params['card']['cardholder_name'] + end + + def test_failed_purchase_with_apple_pay_no_name + @options[:billing_address] = nil + @apple_pay_card.first_name = nil + @apple_pay_card.last_name = nil + assert response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_failure response + assert_equal 'Bad Request (27) - Invalid Card Holder', response.message + end + def test_failed_purchase @amount = 501300 assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -88,6 +184,55 @@ def test_failed_purchase assert_failure response end + def test_failed_purchase_with_insufficient_funds + assert response = @gateway.purchase(530200, @credit_card, @options) + assert_failure response + assert_equal '302', response.error_code + 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 @@ -119,7 +264,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -139,6 +284,28 @@ def test_successful_refund assert response.authorization end + def test_successful_refund_with_soft_descriptors + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization, @options.merge(@options_mdd)) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_successful_refund_with_order_id + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization, @options.merge(order_id: '1234')) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + def test_successful_refund_with_echeck assert purchase = @gateway.purchase(@amount, @check, @options) assert_match(/Transaction Normal/, purchase.message) @@ -168,7 +335,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -183,6 +350,22 @@ def test_failed_refund assert response.authorization end + def test_successful_general_credit + assert response = @gateway.credit(@amount, @credit_card, @options.merge(@options_mdd)) + 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_successful_general_credit_with_order_id + assert response = @gateway.credit(@amount, @credit_card, @options.merge(order_id: '1234')) + 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_successful_void auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -278,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 @@ -327,4 +510,14 @@ def test_transcript_scrubbing_echeck assert_scrubbed(@check.routing_number, transcript) assert_scrubbed(@gateway.options[:token], transcript) end + + def test_transcript_scrubbing_network_token + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @apple_pay_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@apple_pay_card.payment_cryptogram, transcript) + assert_scrubbed(@apple_pay_card.verification_value, transcript) + end end diff --git a/test/remote/gateways/remote_payex_test.rb b/test/remote/gateways/remote_payex_test.rb index fd37ddb6164..0eb8903fbce 100644 --- a/test/remote/gateways/remote_payex_test.rb +++ b/test/remote/gateways/remote_payex_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemotePayexTest < Test::Unit::TestCase - def setup @gateway = PayexGateway.new(fixtures(:payex)) @@ -10,7 +9,7 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :order_id => '1234', + order_id: '1234' } end @@ -80,7 +79,7 @@ def test_successful_store_and_purchase assert_success response assert_equal 'OK', response.message - assert response = @gateway.purchase(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert response = @gateway.purchase(@amount, response.authorization, @options.merge({ order_id: '5678' })) assert_success response assert_equal 'OK', response.message end @@ -90,7 +89,7 @@ def test_successful_store_and_authorize_and_capture assert_success response assert_equal 'OK', response.message - assert response = @gateway.authorize(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert response = @gateway.authorize(@amount, response.authorization, @options.merge({ order_id: '5678' })) assert_success response assert_equal 'OK', response.message assert response.authorization @@ -109,9 +108,9 @@ def test_successful_unstore def test_invalid_login gateway = PayexGateway.new( - :account => '1', - :encryption_key => '1' - ) + account: '1', + encryption_key: '1' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_not_equal 'OK', response.message diff --git a/test/remote/gateways/remote_payflow_express_test.rb b/test/remote/gateways/remote_payflow_express_test.rb index 827907d02d9..7a0377e0087 100644 --- a/test/remote/gateways/remote_payflow_express_test.rb +++ b/test/remote/gateways/remote_payflow_express_test.rb @@ -7,17 +7,16 @@ def setup @gateway = PayflowExpressGateway.new(fixtures(:payflow)) @options = { - :billing_address => - { - :name => 'Cody Fauser', - :address1 => '1234 Shady Brook Lane', - :city => 'Ottawa', - :state => 'ON', - :country => 'CA', - :zip => '90210', - :phone => '555-555-5555' - }, - :email => 'cody@example.com' + billing_address: { + name: 'Cody Fauser', + address1: '1234 Shady Brook Lane', + city: 'Ottawa', + state: 'ON', + country: 'CA', + zip: '90210', + phone: '555-555-5555' + }, + email: 'cody@example.com' } end @@ -28,9 +27,9 @@ def setup # the tests to work correctly def test_set_express_authorization @options.update( - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com', - :email => 'Buyer1@paypal.com' + return_url: 'http://example.com', + cancel_return_url: 'http://example.com', + email: 'Buyer1@paypal.com' ) response = @gateway.setup_authorization(500, @options) assert response.success? @@ -40,9 +39,9 @@ def test_set_express_authorization def test_set_express_purchase @options.update( - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com', - :email => 'Buyer1@paypal.com' + return_url: 'http://example.com', + cancel_return_url: 'http://example.com', + email: 'Buyer1@paypal.com' ) response = @gateway.setup_purchase(500, @options) assert response.success? @@ -53,25 +52,25 @@ def test_set_express_purchase def test_setup_authorization_discount_taxes_included_free_shipping amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'USD', - :subtotal=>2798, - :items => [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'USD', + subtotal: 2798, + items: [ { - :name => 'test4', - :description => 'test4', - :quantity=>2, - :amount=> 1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>true + discount: 280, + no_shipping: true } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message @@ -80,25 +79,25 @@ def test_setup_authorization_discount_taxes_included_free_shipping def test_setup_authorization_with_discount_taxes_additional amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'USD', - :subtotal=>2798, - :items => [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'USD', + subtotal: 2798, + items: [ { - :name => 'test4', - :description => 'test4', - :quantity=>2, - :amount=> 1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>true + discount: 280, + no_shipping: true } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message @@ -107,25 +106,25 @@ def test_setup_authorization_with_discount_taxes_additional def test_setup_authorization_with_discount_taxes_and_shipping_addtiional amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'USD', - :subtotal=>2798, - :items => [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'USD', + subtotal: 2798, + items: [ { - :name => 'test4', - :description => 'test4', - :quantity=>2, - :amount=> 1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>false + discount: 280, + no_shipping: false } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message @@ -133,7 +132,6 @@ def test_setup_authorization_with_discount_taxes_and_shipping_addtiional end class RemotePayflowExpressUkTest < Test::Unit::TestCase - def setup @gateway = PayflowExpressUkGateway.new(fixtures(:payflow_uk)) end @@ -141,25 +139,25 @@ def setup def test_setup_authorization_discount_taxes_included_free_shipping amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'GBP', - :subtotal=>2798, - :items=> [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'GBP', + subtotal: 2798, + items: [ { - :name=>'test4', - :description=>'test4', - :quantity=>2, - :amount=>1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>true + discount: 280, + no_shipping: true } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message @@ -168,25 +166,25 @@ def test_setup_authorization_discount_taxes_included_free_shipping def test_setup_authorization_with_discount_taxes_additional amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'GBP', - :subtotal=>2798, - :items=> [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'GBP', + subtotal: 2798, + items: [ { - :name=>'test4', - :description=>'test4', - :quantity=>2, - :amount=>1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>true + discount: 280, + no_shipping: true } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message @@ -195,25 +193,25 @@ def test_setup_authorization_with_discount_taxes_additional def test_setup_authorization_with_discount_taxes_and_shipping_addtiional amount = 2518 options = { - :ip=>'127.0.0.1', - :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', - :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', - :customer=>'test6@test.com', - :email=>'test6@test.com', - :order_id=>'#1092', - :currency=>'GBP', - :subtotal=>2798, - :items=> [ + ip: '127.0.0.1', + return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + cancel_return_url: 'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + customer: 'test6@test.com', + email: 'test6@test.com', + order_id: '#1092', + currency: 'GBP', + subtotal: 2798, + items: [ { - :name=>'test4', - :description=>'test4', - :quantity=>2, - :amount=>1399, - :url=>'http://localhost:3000/products/test4' + name: 'test4', + description: 'test4', + quantity: 2, + amount: 1399, + url: 'http://localhost:3000/products/test4' } ], - :discount=>280, - :no_shipping=>false + discount: 280, + no_shipping: false } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb index 6104f8e45bd..ed12025cd79 100644 --- a/test/remote/gateways/remote_payflow_test.rb +++ b/test/remote/gateways/remote_payflow_test.rb @@ -8,27 +8,49 @@ def setup @credit_card = credit_card( '5105105105105100', - :brand => 'master' + brand: 'master' ) @options = { - :billing_address => address, - :email => 'cody@example.com', - :customer => 'codyexample' + billing_address: address, + email: 'cody@example.com', + customer: 'codyexample' } @extra_options = { - :order_id => '123', - :description => 'Description string', - :order_desc => 'OrderDesc string', - :comment => 'Comment string', - :comment2 => 'Comment2 string' + order_id: '123', + description: 'Description string', + order_desc: 'OrderDesc string', + comment: 'Comment string', + comment2: 'Comment2 string', + merch_descr: 'MerchDescr string' } @check = check( - :routing_number => '111111118', - :account_number => '1111111111' + routing_number: '111111118', + account_number: '1111111111' ) + + @l2_json = '{ + "Tender": { + "ACH": { + "AcctType": "C", + "AcctNum": "6355059797", + "ABA": "021000021" + } + } + }' + + @l3_json = '{ + "Invoice": { + "Date": "20190104", + "Level3Invoice": { + "CountyTax": {"Amount": "3.23"} + }, + "Items": + "11119999Widget2INQ49.999.983.008.00101.00
500 Main St.AnytownNY67890US
2003063024680
ABC01232003071454.10.15.05
22228888Gizmo5INQ9.992.503.002.5052.95
500 Main St.AnytownNY67890US
2003062813579
XYZ78902003071154.10.16.05
" + } + }' end def test_successful_purchase @@ -40,6 +62,28 @@ def test_successful_purchase assert !response.fraud_review? end + def test_successful_purchase_with_stored_credential + @options[:stored_credential] = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'cardholder', + network_transaction_id: nil + } + assert response = @gateway.purchase(100000, @credit_card, @options) + assert_equal 'Approved', response.message + assert_success response + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: response.authorization + } + assert response = @gateway.purchase(100000, @credit_card, @options) + assert_equal 'Approved', response.message + assert_success response + end + def test_successful_purchase_with_extra_options assert response = @gateway.purchase(100000, @credit_card, @options.merge(@extra_options)) assert_equal 'Approved', response.message @@ -49,6 +93,19 @@ def test_successful_purchase_with_extra_options assert !response.fraud_review? end + def test_successful_purchase_with_application_id + ActiveMerchant::Billing::PayflowGateway.application_id = 'partner_id' + + assert response = @gateway.purchase(100000, @credit_card, @options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + assert !response.fraud_review? + ensure + ActiveMerchant::Billing::PayflowGateway.application_id = nil + end + # In order for this remote test to pass, you must go into your Payflow test # backend and enable the correct filter. Once logged in: # "Service Settings" -> @@ -68,6 +125,50 @@ def test_successful_purchase_with_fraud_review assert response.fraud_review? end + def test_successful_purchase_with_l2_fields + options = @options.merge(level_two_fields: @l2_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + + def test_successful_purchase_with_l3_fields + options = @options.merge(level_three_fields: @l3_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + + def test_successful_purchase_with_l2_l3_fields + options = @options.merge(level_two_fields: @l2_json).merge(level_three_fields: @l3_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + + def test_successful_purchase_with_l3_fields_and_application_id + ActiveMerchant::Billing::PayflowGateway.application_id = 'partner_id' + + options = @options.merge(level_three_fields: @l3_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + ensure + ActiveMerchant::Billing::PayflowGateway.application_id = nil + end + def test_declined_purchase assert response = @gateway.purchase(210000, @credit_card, @options) assert_equal 'Declined', response.message @@ -125,6 +226,19 @@ def test_authorize_and_capture_with_three_d_secure_option assert_success capture end + def test_successful_authorize_with_application_id + ActiveMerchant::Billing::PayflowGateway.application_id = 'partner_id' + + assert response = @gateway.authorize(100000, @credit_card, @options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + assert !response.fraud_review? + ensure + ActiveMerchant::Billing::PayflowGateway.application_id = nil + end + def test_authorize_and_partial_capture assert auth = @gateway.authorize(100 * 2, @credit_card, @options) assert_success auth @@ -141,7 +255,7 @@ def test_authorize_and_complete_capture assert_equal 'Approved', auth.message assert auth.authorization - assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'Y') + assert capture = @gateway.capture(100, auth.authorization, capture_complete: 'Y') assert_success capture assert capture = @gateway.capture(100, auth.authorization) @@ -154,7 +268,7 @@ def test_authorize_and_uncomplete_capture assert_equal 'Approved', auth.message assert auth.authorization - assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'N') + assert capture = @gateway.capture(100, auth.authorization, capture_complete: 'N') assert_success capture assert capture = @gateway.capture(100, auth.authorization) @@ -185,7 +299,7 @@ def test_successful_verify def test_successful_verify_amex @amex_credit_card = credit_card( '378282246310005', - :brand => 'american_express' + brand: 'american_express' ) assert response = @gateway.verify(@amex_credit_card, @options) assert_success response @@ -200,8 +314,8 @@ def test_failed_verify def test_invalid_login gateway = PayflowGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(100, @credit_card, @options) assert_equal 'Invalid vendor account', response.message @@ -223,7 +337,7 @@ def test_duplicate_request_id def test_create_recurring_profile response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + @gateway.recurring(1000, @credit_card, periodicity: :monthly) end assert_success response assert !response.params['profile_id'].blank? @@ -232,7 +346,7 @@ def test_create_recurring_profile def test_create_recurring_profile_with_invalid_date response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now) + @gateway.recurring(1000, @credit_card, periodicity: :monthly, starting_at: Time.now) end assert_failure response assert_equal 'Field format error: Start or next payment date must be a valid future date', response.message @@ -242,7 +356,7 @@ def test_create_recurring_profile_with_invalid_date def test_create_and_cancel_recurring_profile response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + @gateway.recurring(1000, @credit_card, periodicity: :monthly) end assert_success response assert !response.params['profile_id'].blank? @@ -258,10 +372,10 @@ def test_create_and_cancel_recurring_profile def test_full_feature_set_for_recurring_profiles # Test add @options.update( - :periodicity => :weekly, - :payments => '12', - :starting_at => Time.now + 1.day, - :comment => 'Test Profile' + periodicity: :weekly, + payments: '12', + starting_at: Time.now + 1.day, + comment: 'Test Profile' ) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do @gateway.recurring(100, @credit_card, @options) @@ -275,10 +389,10 @@ def test_full_feature_set_for_recurring_profiles # Test modify @options.update( - :periodicity => :monthly, - :starting_at => Time.now + 1.day, - :payments => '4', - :profile_id => @recurring_profile_id + periodicity: :monthly, + starting_at: Time.now + 1.day, + payments: '4', + profile_id: @recurring_profile_id ) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do @gateway.recurring(400, @credit_card, @options) @@ -298,7 +412,7 @@ def test_full_feature_set_for_recurring_profiles # Test payment history inquiry response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring_inquiry(@recurring_profile_id, :history => true) + @gateway.recurring_inquiry(@recurring_profile_id, history: true) end assert_equal '0', response.params['result'] assert_success response @@ -331,11 +445,13 @@ def test_reference_purchase def test_recurring_with_initial_authorization response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(1000, @credit_card, - :periodicity => :monthly, - :initial_transaction => { - :type => :purchase, - :amount => 500 + @gateway.recurring( + 1000, + @credit_card, + periodicity: :monthly, + initial_transaction: { + type: :purchase, + amount: 500 } ) end @@ -404,13 +520,15 @@ def test_high_verbosity def three_d_secure_option { - :three_d_secure => { - :status => 'Y', - :authentication_id => 'QvDbSAxSiaQs241899E0', - :eci => '02', - :cavv => 'jGvQIvG/5UhjAREALGYa6Vu/hto=', - :xid => 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' - } + three_d_secure: { + authentication_id: 'QvDbSAxSiaQs241899E0', + authentication_response_status: 'Y', + eci: '02', + cavv: 'jGvQIvG/5UhjAREALGYa6Vu/hto=', + xid: 'UXZEYlNBeFNpYVFzMjQxODk5RTA=', + version: '2.2.0', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC' + } } end diff --git a/test/remote/gateways/remote_payflow_uk_test.rb b/test/remote/gateways/remote_payflow_uk_test.rb index afe6791cfc8..799380eab47 100644 --- a/test/remote/gateways/remote_payflow_uk_test.rb +++ b/test/remote/gateways/remote_payflow_uk_test.rb @@ -8,26 +8,26 @@ def setup @gateway = PayflowUkGateway.new(fixtures(:payflow_uk)) @creditcard = CreditCard.new( - :number => '5105105105105100', - :month => 11, - :year => 2009, - :first_name => 'Cody', - :last_name => 'Fauser', - :verification_value => '000', - :brand => 'master' + number: '5105105105105100', + month: 11, + year: 2009, + first_name: 'Cody', + last_name: 'Fauser', + verification_value: '000', + brand: 'master' ) @options = { - :billing_address => { - :name => 'Cody Fauser', - :address1 => '1234 Shady Brook Lane', - :city => 'Ottawa', - :state => 'ON', - :country => 'CA', - :zip => '90210', - :phone => '555-555-5555' + billing_address: { + name: 'Cody Fauser', + address1: '1234 Shady Brook Lane', + city: 'Ottawa', + state: 'ON', + country: 'CA', + zip: '90210', + phone: '555-555-5555' }, - :email => 'cody@example.com' + email: 'cody@example.com' } end @@ -128,8 +128,8 @@ def test_authorize_and_void def test_invalid_login gateway = PayflowGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(100, @creditcard, @options) assert_equal 'Invalid vendor account', response.message @@ -138,8 +138,8 @@ def test_invalid_login def test_duplicate_request_id gateway = PayflowUkGateway.new( - :login => @login, - :password => @password + login: @login, + password: @password ) request_id = Digest::SHA1.hexdigest(rand.to_s).slice(0, 32) diff --git a/test/remote/gateways/remote_payment_express_test.rb b/test/remote/gateways/remote_payment_express_test.rb index a2d2a3b0135..f1493090a98 100644 --- a/test/remote/gateways/remote_payment_express_test.rb +++ b/test/remote/gateways/remote_payment_express_test.rb @@ -1,17 +1,16 @@ require 'test_helper' class RemotePaymentExpressTest < Test::Unit::TestCase - def setup @gateway = PaymentExpressGateway.new(fixtures(:payment_express)) @credit_card = credit_card('4111111111111111') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :email => 'cody@example.com', - :description => 'Store purchase' + order_id: generate_unique_id, + billing_address: address, + email: 'cody@example.com', + description: 'Store purchase' } @amount = 100 @@ -32,7 +31,19 @@ def test_successful_purchase_with_reference_id end def test_successful_purchase_with_ip - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '192.168.0.1')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ip: '192.168.0.1')) + assert_success response + assert_equal 'The Transaction was approved', response.message + assert_not_nil response.authorization + end + + def test_successful_purchase_with_avs_fields + options = { + enable_avs_data: 0, + avs_action: 3 + } + + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'The Transaction was approved', response.message assert_not_nil response.authorization @@ -66,26 +77,34 @@ def test_purchase_and_refund assert_success purchase assert_equal 'The Transaction was approved', purchase.message assert !purchase.authorization.blank? - assert refund = @gateway.refund(amount, purchase.authorization, :description => 'Giving a refund') + assert refund = @gateway.refund(amount, purchase.authorization, description: 'Giving a refund') assert_success refund end def test_failed_capture assert response = @gateway.capture(@amount, '999') assert_failure response - assert_equal 'DpsTxnRef Invalid', response.message + assert_equal 'The transaction has not been processed.', response.message end def test_invalid_login gateway = PaymentExpressGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_match %r{Invalid Credentials}i, response.message assert_failure response end + def test_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'The Transaction was approved', response.message + assert_not_nil token = response.authorization + assert_equal token, response.token + end + def test_store_credit_card assert response = @gateway.store(@credit_card) assert_success response @@ -96,7 +115,7 @@ def test_store_credit_card def test_store_with_custom_token token = Time.now.to_i.to_s - assert response = @gateway.store(@credit_card, :billing_id => token) + assert response = @gateway.store(@credit_card, billing_id: token) assert_success response assert_equal 'The Transaction was approved', response.message assert_not_nil response.authorization @@ -146,5 +165,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) assert_scrubbed(@gateway.options[:password], clean_transcript) end - end diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb index 831b61b07ca..910b16c262d 100644 --- a/test/remote/gateways/remote_paymentez_test.rb +++ b/test/remote/gateways/remote_paymentez_test.rb @@ -3,16 +3,19 @@ class RemotePaymentezTest < Test::Unit::TestCase def setup @gateway = PaymentezGateway.new(fixtures(:paymentez)) + @ecuador_gateway = PaymentezGateway.new(fixtures(:paymentez_ecuador)) @amount = 100 @credit_card = credit_card('4111111111111111', verification_value: '666') - @elo_credit_card = credit_card('6362970000457013', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @otp_card = credit_card('36417002140808', verification_value: '666') + @elo_credit_card = credit_card( + '6362970000457013', + month: 10, + year: 2022, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'elo' ) @declined_card = credit_card('4242424242424242', verification_value: '666') @options = { @@ -23,6 +26,29 @@ def setup vat: 0, dev_reference: 'Testing' } + + @cavv = 'example-cavv-value' + @xid = 'three-ds-v1-trans-id' + @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' + + @three_ds_v1_mpi = { + cavv: @cavv, + eci: @eci, + version: @three_ds_v1_version, + xid: @xid + } + + @three_ds_v2_mpi = { + 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 + } end def test_successful_purchase @@ -78,7 +104,8 @@ def test_successful_purchase_with_extra_params configuration1: 'value1', configuration2: 'value2', configuration3: 'value3' - }} + } + } response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) assert_success response @@ -92,6 +119,18 @@ def test_successful_purchase_with_token assert_success purchase_response end + def test_successful_purchase_with_3ds1_mpi_fields + @options[:three_d_secure] = @three_ds_v1_mpi + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_3ds2_mpi_fields + @options[:three_d_secure] = @three_ds_v2_mpi + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -104,6 +143,18 @@ def test_successful_refund assert refund = @gateway.refund(@amount, auth.authorization, @options) assert_success refund + assert_equal 'Completed', refund.message + end + + def test_successful_refund_with_more_info + auth = @gateway.purchase(@amount, @credit_card, @options) + assert_success auth + + assert refund = @gateway.refund(@amount, auth.authorization, @options.merge(more_info: true)) + assert_success refund + assert_equal 'Completed', refund.message + assert_equal '00', refund.params['transaction']['carrier_code'] + assert_equal 'Reverse by mock', refund.params['transaction']['message'] end def test_successful_refund_with_elo @@ -112,6 +163,7 @@ def test_successful_refund_with_elo assert refund = @gateway.refund(@amount, auth.authorization, @options) assert_success refund + assert_equal 'Completed', refund.message end def test_successful_void @@ -120,6 +172,7 @@ def test_successful_void assert void = @gateway.void(auth.authorization) assert_success void + assert_equal 'Completed', void.message end def test_successful_void_with_elo @@ -128,12 +181,13 @@ def test_successful_void_with_elo assert void = @gateway.void(auth.authorization) assert_success void + assert_equal 'Completed', void.message end def test_failed_void response = @gateway.void('') assert_failure response - assert_equal 'Carrier not supported', response.message + assert_equal 'ValidationError', response.message assert_equal Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code end @@ -145,6 +199,14 @@ def test_successful_authorize_and_capture assert_equal 'Response by mock', capture.message end + def test_successful_authorize_and_capture_with_nil_amount + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + assert_equal 'Response by mock', capture.message + end + def test_successful_authorize_and_capture_with_elo auth = @gateway.authorize(@amount, @elo_credit_card, @options) assert_success auth @@ -167,11 +229,24 @@ def test_successful_authorize_and_capture_with_token def test_successful_authorize_and_capture_with_different_amount auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount + 100, auth.authorization) + amount = 99.0 + assert capture = @gateway.capture(amount, auth.authorization) assert_success capture assert_equal 'Response by mock', capture.message end + def test_successful_authorize_with_3ds1_mpi_fields + @options[:three_d_secure] = @three_ds_v1_mpi + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_authorize_with_3ds2_mpi_fields + @options[:three_d_secure] = @three_ds_v2_mpi + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response @@ -182,7 +257,8 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert capture = @gateway.capture(@amount - 1, auth.authorization) - assert_failure capture # Paymentez explicitly does not support partial capture; only GREATER than auth capture + assert_success capture + assert_equal 'Response by mock', capture.message end def test_failed_capture @@ -191,6 +267,18 @@ def test_failed_capture assert_equal 'The modification of the amount is not supported by carrier', response.message end + def test_successful_capture_with_otp + @options[:vat] = 0.1 + response = @ecuador_gateway.authorize(@amount, @otp_card, @options.merge({ otp_flow: true })) + assert_success response + assert_equal 'pending', response.params['transaction']['status'] + + transaction_id = response.params['transaction']['id'] + options = @options.merge({ type: 'BY_OTP', value: '012345' }) + response = @ecuador_gateway.capture(nil, transaction_id, options) + assert_success response + end + def test_store response = @gateway.store(@credit_card, @options) assert_success response @@ -217,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_paymill_test.rb b/test/remote/gateways/remote_paymill_test.rb index 880f346a47d..800605d1c99 100644 --- a/test/remote/gateways/remote_paymill_test.rb +++ b/test/remote/gateways/remote_paymill_test.rb @@ -7,7 +7,7 @@ def setup @amount = 100 @credit_card = credit_card('5500000000000004') @options = { - :email => 'Longbob.Longse@example.com' + email: 'Longbob.Longse@example.com' } @declined_card = credit_card('5105105105105100', month: 5, year: 2020) @@ -171,5 +171,4 @@ def test_verify_credentials gateway = PaymillGateway.new(public_key: 'unknown_key', private_key: 'unknown_key') assert !gateway.verify_credentials end - end diff --git a/test/remote/gateways/remote_paypal_express_test.rb b/test/remote/gateways/remote_paypal_express_test.rb index c599649a814..76857c338ea 100644 --- a/test/remote/gateways/remote_paypal_express_test.rb +++ b/test/remote/gateways/remote_paypal_express_test.rb @@ -7,29 +7,28 @@ def setup @gateway = PaypalExpressGateway.new(fixtures(:paypal_certificate)) @options = { - :order_id => '230000', - :email => 'buyer@jadedpallet.com', - :billing_address => - { - :name => 'Fred Brooks', - :address1 => '1234 Penny Lane', - :city => 'Jonsetown', - :state => 'NC', - :country => 'US', - :zip => '23456' - }, - :description => 'Stuff that you purchased, yo!', - :ip => '10.0.0.1', - :return_url => 'http://example.com/return', - :cancel_return_url => 'http://example.com/cancel' + order_id: '230000', + email: 'buyer@jadedpallet.com', + billing_address: { + name: 'Fred Brooks', + address1: '1234 Penny Lane', + city: 'Jonsetown', + state: 'NC', + country: 'US', + zip: '23456' + }, + description: 'Stuff that you purchased, yo!', + ip: '10.0.0.1', + return_url: 'http://example.com/return', + cancel_return_url: 'http://example.com/cancel' } end def test_set_express_authorization @options.update( - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com', - :email => 'Buyer1@paypal.com' + return_url: 'http://example.com', + cancel_return_url: 'http://example.com', + email: 'Buyer1@paypal.com' ) response = @gateway.setup_authorization(500, @options) assert response.success? @@ -39,9 +38,9 @@ def test_set_express_authorization def test_set_express_purchase @options.update( - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com', - :email => 'Buyer1@paypal.com' + return_url: 'http://example.com', + cancel_return_url: 'http://example.com', + email: 'Buyer1@paypal.com' ) response = @gateway.setup_purchase(500, @options) assert response.success? diff --git a/test/remote/gateways/remote_paypal_test.rb b/test/remote/gateways/remote_paypal_test.rb index 7465a369da7..eee5d7aba1a 100644 --- a/test/remote/gateways/remote_paypal_test.rb +++ b/test/remote/gateways/remote_paypal_test.rb @@ -8,19 +8,18 @@ def setup @declined_card = credit_card('234234234234') @params = { - :order_id => generate_unique_id, - :email => 'buyer@jadedpallet.com', - :billing_address => - { - :name => 'Longbob Longsen', - :address1 => '4321 Penny Lane', - :city => 'Jonsetown', - :state => 'NC', - :country => 'US', - :zip => '23456' - }, - :description => 'Stuff that you purchased, yo!', - :ip => '10.0.0.1' + order_id: generate_unique_id, + email: 'buyer@jadedpallet.com', + billing_address: { + name: 'Longbob Longsen', + address1: '4321 Penny Lane', + city: 'Jonsetown', + state: 'NC', + country: 'US', + zip: '23456' + }, + description: 'Stuff that you purchased, yo!', + ip: '10.0.0.1' } @amount = 100 @@ -65,6 +64,32 @@ def test_successful_purchase_with_descriptors assert response.params['transaction_id'] end + def test_successful_purchase_with_order_total_elements + order_total_elements = { + subtotal: @amount / 4, + shipping: @amount / 4, + handling: @amount / 4, + tax: @amount / 4 + } + + response = @gateway.purchase(@amount, @credit_card, @params.merge(order_total_elements)) + assert_success response + assert response.params['transaction_id'] + end + + def test_successful_purchase_with_non_fractional_currency_when_any_order_total_element_is_nil + order_total_elements = { + subtotal: @amount / 4, + shipping: @amount / 4, + handling: nil, + tax: @amount / 4 + } + + response = @gateway.purchase(@amount, @credit_card, @params.merge(order_total_elements).merge(currency: 'JPY')) + assert_success response + assert response.params['transaction_id'] + end + def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @params) assert_failure response @@ -87,6 +112,7 @@ def test_failed_authorization def test_successful_reauthorization return if not @three_days_old_auth_id + auth = @gateway.reauthorize(1000, @three_days_old_auth_id) assert_success auth assert auth.authorization @@ -99,7 +125,8 @@ def test_successful_reauthorization end def test_failed_reauthorization - return if not @three_days_old_auth_id2 # was authed for $10, attempt $20 + return if not @three_days_old_auth_id2 # was authed for $10, attempt $20 + auth = @gateway.reauthorize(2000, @three_days_old_auth_id2) assert_false auth? assert !auth.authorization @@ -118,20 +145,20 @@ def test_successful_capture def test_successful_incomplete_captures auth = @gateway.authorize(100, @credit_card, @params) assert_success auth - response = @gateway.capture(60, auth.authorization, {:complete_type => 'NotComplete'}) + response = @gateway.capture(60, auth.authorization, { complete_type: 'NotComplete' }) assert_success response assert response.params['transaction_id'] assert_equal '0.60', response.params['gross_amount'] - response_2 = @gateway.capture(40, auth.authorization) - assert_success response_2 - assert response_2.params['transaction_id'] - assert_equal '0.40', response_2.params['gross_amount'] + response2 = @gateway.capture(40, auth.authorization) + assert_success response2 + assert response2.params['transaction_id'] + assert_equal '0.40', response2.params['gross_amount'] end def test_successful_capture_updating_the_invoice_id auth = @gateway.authorize(@amount, @credit_card, @params) assert_success auth - response = @gateway.capture(@amount, auth.authorization, :order_id => "NEWID#{generate_unique_id}") + response = @gateway.capture(@amount, auth.authorization, order_id: "NEWID#{generate_unique_id}") assert_success response assert response.params['transaction_id'] assert_equal '1.00', response.params['gross_amount'] @@ -149,7 +176,7 @@ def test_purchase_and_full_credit purchase = @gateway.purchase(@amount, @credit_card, @params) assert_success purchase - credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry') + credit = @gateway.refund(@amount, purchase.authorization, note: 'Sorry') assert_success credit assert credit.test? assert_equal 'USD', credit.params['net_refund_amount_currency_id'] @@ -191,7 +218,7 @@ def test_successful_transfer response = @gateway.purchase(@amount, @credit_card, @params) assert_success response - response = @gateway.transfer(@amount, 'joe@example.com', :subject => 'Your money', :note => 'Thanks for taking care of that') + response = @gateway.transfer(@amount, 'joe@example.com', subject: 'Your money', note: 'Thanks for taking care of that') assert_success response end @@ -205,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'], - [600, 'jane@example.com', {:note => 'Thanks for taking care of that'}], - :subject => 'Your money') + response = @gateway.transfer( + [@amount, 'joe@example.com'], + [600, 'jane@example.com', { note: 'Thanks for taking care of that' }], + subject: 'Your money' + ) assert_success response end @@ -225,7 +254,7 @@ def test_successful_email_transfer response = @gateway.purchase(@amount, @credit_card, @params) assert_success response - response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'EmailAddress', :subject => 'Your money', :note => 'Thanks for taking care of that') + response = @gateway.transfer([@amount, 'joe@example.com'], receiver_type: 'EmailAddress', subject: 'Your money', note: 'Thanks for taking care of that') assert_success response end @@ -233,7 +262,7 @@ def test_successful_userid_transfer response = @gateway.purchase(@amount, @credit_card, @params) assert_success response - response = @gateway.transfer([@amount, '4ET96X3PQEN8H'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that') + response = @gateway.transfer([@amount, '4ET96X3PQEN8H'], receiver_type: 'UserID', subject: 'Your money', note: 'Thanks for taking care of that') assert_success response end @@ -241,7 +270,7 @@ def test_failed_userid_transfer response = @gateway.purchase(@amount, @credit_card, @params) assert_success response - response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that') + response = @gateway.transfer([@amount, 'joe@example.com'], receiver_type: 'UserID', subject: 'Your money', note: 'Thanks for taking care of that') assert_failure response end @@ -256,4 +285,32 @@ def test_successful_referenced_id_purchase assert_success response2 end + def test_successful_purchase_with_3ds_version_1 + params = @params.merge!({ + three_d_secure: { + trans_status: 'Y', + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=' + } + }) + response = @gateway.purchase(@amount, @credit_card, params) + assert_success response + assert response.params['transaction_id'] + end + + def test_successful_purchase_with_3ds_version_2 + params = @params.merge!({ + three_d_secure: { + authentication_response_status: 'Y', + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + ds_transaction_id: 'bDE9Aa1A-C5Ac-AD3a-4bBC-aC918ab1de3E', + version: '2.1.0' + } + }) + response = @gateway.purchase(@amount, @credit_card, params) + assert_success response + assert response.params['transaction_id'] + end end diff --git a/test/remote/gateways/remote_paysafe_test.rb b/test/remote/gateways/remote_paysafe_test.rb new file mode 100644 index 00000000000..72f5c0a3aae --- /dev/null +++ b/test/remote/gateways/remote_paysafe_test.rb @@ -0,0 +1,423 @@ +require 'test_helper' + +class RemotePaysafeTest < Test::Unit::TestCase + def setup + @gateway = PaysafeGateway.new(fixtures(:paysafe)) + + @amount = 100 + @credit_card = credit_card('4037111111000000') + @mastercard = credit_card('5200400000000009', brand: 'master') + @pm_token = 'Ci3S9DWyOP9CiJ5' + @options = { + billing_address: address, + merchant_descriptor: { + dynamic_descriptor: 'Store Purchase', + phone: '999-8887777' + } + } + @profile_options = { + date_of_birth: { + year: 1979, + month: 1, + day: 1 + }, + email: 'profile@memail.com', + phone: '111-222-3456', + address: address + } + @mc_three_d_secure_2_options = { + currency: 'EUR', + three_d_secure: { + eci: 0, + cavv: 'AAABBhkXYgAAAAACBxdiENhf7A+=', + version: '2.1.0', + ds_transaction_id: 'a3a721f3-b6fa-4cb5-84ea-c7b5c39890a2' + } + } + @visa_three_d_secure_2_options = { + currency: 'EUR', + three_d_secure: { + eci: 5, + cavv: 'AAABBhkXYgAAAAACBxdiENhf7A+=', + version: '2.1.0' + } + } + @mc_three_d_secure_1_options = { + currency: 'EUR', + three_d_secure: { + eci: 0, + cavv: 'AAABBhkXYgAAAAACBxdiENhf7A+=', + xid: 'aWg4N1ZZOE53TkFrazJuMmkyRDA=', + version: '1.0.2', + ds_transaction_id: 'a3a721f3-b6fa-4cb5-84ea-c7b5c39890a2' + } + } + @visa_three_d_secure_1_options = { + currency: 'EUR', + three_d_secure: { + eci: 5, + cavv: 'AAABBhkXYgAAAAACBxdiENhf7A+=', + xid: 'aWg4N1ZZOE53TkFrazJuMmkyRDA=', + version: '1.0.2' + } + } + @airline_details = { + airline_travel_details: { + passenger_name: 'Joe Smith', + departure_date: '2026-11-30', + origin: 'SXF', + computerized_reservation_system: 'DATS', + ticket: { + ticket_number: 9876789, + is_restricted_ticket: false + }, + customer_reference_number: 107854099, + travel_agency: { + name: 'Sally Travel', + code: 'AGENTS' + }, + trip_legs: { + leg1: { + flight: { + carrier_code: 'LH', + flight_number: '344' + }, + service_class: 'F', + is_stop_over_allowed: true, + destination: 'ISL', + fare_basis: 'VMAY', + departure_date: '2026-11-30' + }, + leg2: { + flight: { + carrier_code: 'TK', + flight_number: '999' + }, + service_class: 'F', + is_stop_over_allowed: true, + destination: 'SOF', + fare_basis: 'VMAY', + departure_date: '2026-11-30' + } + } + } + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'COMPLETED', response.message + assert_equal 0, response.params['availableToSettle'] + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_more_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_equal '127.0.0.1', response.params['customerIp'] + end + + # Merchant account must be setup to support split pay transactions using a specific account_id + def test_successful_purchase_with_split_payouts + @split_pay = PaysafeGateway.new(fixtures(:paysafe_split_pay)) + options = { + split_pay: [ + { + linked_account: '1002179730', + percent: 50 + } + ] + } + response = @split_pay.purchase(5000, @credit_card, @options.merge(options)) + assert_success response + assert_equal 2500, response.params['settlements'].first['splitpay'].first['amount'] + end + + def test_successful_purchase_with_airline_details + response = @gateway.purchase(@amount, @credit_card, @options.merge(@airline_details)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_equal 'LH', response.params['airlineTravelDetails']['tripLegs']['leg1']['flight']['carrierCode'] + 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 + assert_equal 'COMPLETED', response.message + assert_equal 0, response.params['availableToSettle'] + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_token_3ds2 + response = @gateway.purchase(200, @pm_token, @options.merge(@visa_three_d_secure_2_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_mastercard_3ds1 + response = @gateway.purchase(@amount, @mastercard, @options.merge(@mc_three_d_secure_1_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_mastercard_3ds2 + response = @gateway.purchase(@amount, @mastercard, @options.merge(@mc_three_d_secure_2_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_visa_3ds1 + response = @gateway.purchase(@amount, @credit_card, @options.merge(@visa_three_d_secure_1_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_visa_3ds2 + response = @gateway.purchase(@amount, @credit_card, @options.merge(@visa_three_d_secure_2_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + + initial_response = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_not_nil initial_response.params['storedCredential'] + network_transaction_id = initial_response.params['id'] + + stored_options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'installment', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, stored_options) + assert_success response + assert_equal 'COMPLETED', response.message + end + + # Merchant account must be setup to support funding transaction, and funding transaction type must be correct for the MCC + def test_successful_purchase_with_correct_funding_transaction_type + response = @gateway.purchase(@amount, @credit_card, @options.merge({ funding_transaction: 'SDW_WALLET_TRANSFER' })) + assert_success response + assert_equal 'COMPLETED', response.message + assert_equal 0, response.params['availableToSettle'] + assert_not_nil response.params['authCode'] + assert_match 'SDW_WALLET_TRANSFER', response.params['fundingTransaction']['type'] + end + + def test_failed_purchase_with_incorrect_funding_transaction_type + response = @gateway.purchase(@amount, @credit_card, @options.merge({ funding_transaction: 'SVDW_FUNDS_TRANSFER' })) + assert_failure response + assert_equal 'Error(s)- code:3068, message:You submitted a funding transaction that is not correct for the merchant account.', response.message + end + + def test_failed_purchase + response = @gateway.purchase(11, @credit_card, @options) + assert_failure response + assert_equal 'Error(s)- code:3022, message:The card has been declined due to insufficient funds.', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'PENDING', capture.message + assert_equal @amount, capture.params['availableToRefund'] + end + + def test_successful_authorize_and_capture_with_token + auth = @gateway.authorize(@amount, @pm_token, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'PENDING', capture.message + assert_equal @amount, capture.params['availableToRefund'] + end + + def test_successful_authorize_with_token + response = @gateway.authorize(250, @pm_token, @options) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_authorize_with_token_3ds1 + response = @gateway.authorize(200, @pm_token, @options.merge(@visa_three_d_secure_1_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_successful_authorize_with_token_3ds2 + response = @gateway.authorize(200, @pm_token, @options.merge(@visa_three_d_secure_2_options)) + assert_success response + assert_equal 'COMPLETED', response.message + assert_not_nil response.params['authCode'] + end + + def test_failed_authorize + response = @gateway.authorize(5, @credit_card, @options) + assert_failure response + assert_equal 'Error(s)- code:3009, message:Your request has been declined by the issuing bank.', 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, 'invalidtransactionid') + assert_failure response + assert_equal 'Error(s)- code:3201, message:The authorization ID included in this settlement request could not be found.', response.message + end + + # Can test refunds by logging into our portal and grabbing transaction IDs from settled transactions + # Refunds will return 'PENDING' status until they are batch processed at EOD + # def test_successful_refund + # auth = '' + + # assert refund = @gateway.refund(@amount, auth) + # assert_success refund + # assert_equal 'PENDING', refund.message + # end + + # def test_partial_refund + # auth = '' + + # assert refund = @gateway.refund(@amount - 1, auth) + # assert_success refund + # assert_equal 'PENDING', refund.message + # end + + def test_failed_refund + response = @gateway.refund(@amount, 'thisisnotavalidtrasactionid') + assert_failure response + assert_equal 'Error(s)- code:3407, message:The settlement referred to by the transaction response ID you provided cannot be found.', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'COMPLETED', void.message + end + + def test_successful_void_with_token_purchase + auth = @gateway.authorize(@amount, @pm_token, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'COMPLETED', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal "Error(s)- code:5023, message:Request method 'POST' not supported", response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'COMPLETED', response.message + end + + def test_successful_verify_with_token + response = @gateway.verify(@pm_token, @options) + assert_success response + assert_match 'COMPLETED', response.message + end + + # Not including a test_failed_verify since the only way to force a failure on this + # gateway is with a specific dollar amount + + def test_successful_store + response = @gateway.store(credit_card('4111111111111111'), @profile_options) + assert_success response + assert_equal 'ACTIVE', response.params['status'] + assert_equal 1979, response.params['dateOfBirth']['year'] + assert_equal '456 My Street', response.params['addresses'].first['street'] + end + + def test_successful_store_and_purchase + response = @gateway.store(credit_card('4111111111111111'), @profile_options) + assert_success response + token = response.authorization + + purchase = @gateway.purchase(300, token, @options) + assert_success purchase + assert_match 'COMPLETED', purchase.message + end + + def test_successful_store_and_unstore + response = @gateway.store(credit_card('4111111111111111'), @profile_options) + assert_success response + id = response.authorization + unstore = @gateway.unstore(id) + assert_success unstore + end + + def test_invalid_login + gateway = PaysafeGateway.new(username: 'badbunny', password: 'carrotsrock', account_id: 'rejectstew') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '5279', response.error_code + assert_match 'invalid', response.params['error']['message'].downcase + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value, clean_transcript) + end +end diff --git a/test/remote/gateways/remote_payscout_test.rb b/test/remote/gateways/remote_payscout_test.rb index ce0a0768a1e..9aadde0d11d 100644 --- a/test/remote/gateways/remote_payscout_test.rb +++ b/test/remote/gateways/remote_payscout_test.rb @@ -9,9 +9,9 @@ def setup @declined_card = credit_card('34343') @options = { - :order_id => '1', - :description => 'Store Purchase', - :billing_address => address + order_id: '1', + description: 'Store Purchase', + billing_address: address } end @@ -148,8 +148,8 @@ def test_not_found_transaction_id_void def test_invalid_credentials gateway = PayscoutGateway.new( - :username => 'xxx', - :password => 'xxx' + username: 'xxx', + password: 'xxx' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_paystation_test.rb b/test/remote/gateways/remote_paystation_test.rb index 84285905003..5144faeb8c6 100644 --- a/test/remote/gateways/remote_paystation_test.rb +++ b/test/remote/gateways/remote_paystation_test.rb @@ -1,11 +1,10 @@ require 'test_helper' class RemotePaystationTest < Test::Unit::TestCase - def setup @gateway = PaystationGateway.new(fixtures(:paystation)) - @credit_card = credit_card('5123456789012346', :month => 5, :year => 13, :verification_value => 123) + @credit_card = credit_card('5123456789012346', month: 5, year: 13, verification_value: 123) @successful_amount = 10000 @insufficient_funds_amount = 10051 @@ -14,8 +13,8 @@ def setup @bank_error_amount = 10091 @options = { - :billing_address => address, - :description => 'Store Purchase' + billing_address: address, + description: 'Store Purchase' } end @@ -27,7 +26,7 @@ def test_successful_purchase end def test_successful_purchase_in_gbp - assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:currency => 'GBP')) + assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(currency: 'GBP')) assert_success response assert_equal 'Transaction successful', response.message @@ -39,7 +38,7 @@ def test_failed_purchases ['invalid_transaction', @invalid_transaction_amount, 'Transaction Type Not Supported'], ['expired_card', @expired_card_amount, 'Expired Card'], ['bank_error', @bank_error_amount, 'Error Communicating with Bank'] - ].each do |name, amount, message| + ].each do |_name, amount, message| assert response = @gateway.purchase(amount, @credit_card, @options) assert_failure response assert_equal message, response.message @@ -48,7 +47,7 @@ def test_failed_purchases def test_storing_token time = Time.now.to_i - assert response = @gateway.store(@credit_card, @options.merge(:token => "justatest#{time}")) + assert response = @gateway.store(@credit_card, @options.merge(token: "justatest#{time}")) assert_success response assert_equal 'Future Payment Saved Ok', response.message @@ -70,7 +69,7 @@ def test_authorize_and_capture assert_success auth assert auth.authorization - assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:credit_card_verification => 123)) + assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(credit_card_verification: 123)) assert_success capture end diff --git a/test/remote/gateways/remote_payu_in_test.rb b/test/remote/gateways/remote_payu_in_test.rb index 182c3a1cc17..c6d8c477159 100644 --- a/test/remote/gateways/remote_payu_in_test.rb +++ b/test/remote/gateways/remote_payu_in_test.rb @@ -68,7 +68,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount-1, purchase.authorization) + refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -82,30 +82,28 @@ def test_3ds_enrolled_card_fails assert_failure response assert_equal '3D-secure enrolled cards are not supported.', response.message -=begin - # This is handy for testing that 3DS is working with PayU - response = response.responses.first - - # You'll probably need a new bin from http://requestb.in - bin = "" - File.open("3ds.html", "w") do |f| - f.puts %( - - -
- - - - -
- - - ) - end - puts "Test 3D-secure via `open 3ds.html`" - puts "View results at http://requestb.in/#{bin}?inspect" - puts "Finalize with: `curl -v -d PaRes='' -d MD='' '#{response.params["form_post_vars"]["TermUrl"]}'`" -=end + # # This is handy for testing that 3DS is working with PayU + # response = response.responses.first + # + # # You'll probably need a new bin from http://requestb.in + # bin = "" + # File.open("3ds.html", "w") do |f| + # f.puts %( + # + # + #
+ # + # + # + # + #
+ # + # + # ) + # end + # puts "Test 3D-secure via `open 3ds.html`" + # puts "View results at http://requestb.in/#{bin}?inspect" + # puts "Finalize with: `curl -v -d PaRes='' -d MD='' '#{response.params["form_post_vars"]["TermUrl"]}'`" end def test_transcript_scrubbing diff --git a/test/remote/gateways/remote_payu_latam_test.rb b/test/remote/gateways/remote_payu_latam_test.rb index 8f6518fc4b1..b84990a4422 100644 --- a/test/remote/gateways/remote_payu_latam_test.rb +++ b/test/remote/gateways/remote_payu_latam_test.rb @@ -3,14 +3,20 @@ class RemotePayuLatamTest < Test::Unit::TestCase def setup @gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(payment_country: 'AR')) + @colombia_gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(payment_country: 'CO', account_id: '512321')) @amount = 4000 - @credit_card = credit_card('4097440000000004', verification_value: '444', first_name: 'APPROVED', last_name: '') - @declined_card = credit_card('4097440000000004', verification_value: '444', first_name: 'REJECTED', last_name: '') - @pending_card = credit_card('4097440000000004', verification_value: '444', first_name: 'PENDING', last_name: '') + @credit_card = credit_card('4097440000000004', month: 6, year: 2035, verification_value: '777', first_name: 'APPROVED', last_name: '') + @declined_card = credit_card('4097440000000004', verification_value: '777', first_name: 'REJECTED', last_name: '') + @pending_card = credit_card('4097440000000004', verification_value: '777', first_name: 'PENDING', last_name: '') + @naranja_credit_card = credit_card('5895620000000002', verification_value: '123', first_name: 'APPROVED', last_name: '', brand: 'naranja') + @cabal_credit_card = credit_card('5896570000000004', verification_value: '123', first_name: 'APPROVED', last_name: '', brand: 'cabal') + @invalid_cabal_card = credit_card('6271700000000000', verification_value: '123', first_name: 'APPROVED', last_name: '', brand: 'cabal') + @condensa_card = credit_card('5907120000000009', month: 6, year: 2035, verification_value: '777', first_name: 'APPROVED', brand: 'condensa') @options = { dni_number: '5415668464654', + merchant_buyer_id: '1', currency: 'ARS', order_id: generate_unique_id, description: 'Active Merchant Transaction', @@ -49,6 +55,27 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_naranja_card + response = @gateway.purchase(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_cabal_card + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_condensa_card + response = @colombia_gateway.purchase(@amount, @condensa_card, @options.merge(currency: 'COP')) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + def test_successful_purchase_with_specified_language response = @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es')) assert_success response @@ -56,8 +83,15 @@ def test_successful_purchase_with_specified_language assert response.test? end - def test_successul_purchase_with_buyer - gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR')) + def test_successful_purchase_with_blank_billing_address_country + response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: { address1: 'Viamonte', country: '', zip: '10001' })) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_buyer + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(account_id: '512327', payment_country: 'BR')) options_buyer = { currency: 'BRL', @@ -83,6 +117,7 @@ def test_successul_purchase_with_buyer name: 'Jorge Borges', dni_number: '5415668464123', dni_type: 'TI', + merchant_buyer_id: '2', cnpj: '32593371000110', email: 'axaxaxas@mlo.org' } @@ -95,7 +130,7 @@ def test_successul_purchase_with_buyer end def test_successful_purchase_brazil - gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR')) + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(account_id: '512327', payment_country: 'BR')) options_brazil = { payment_country: 'BR', @@ -130,7 +165,7 @@ def test_successful_purchase_brazil end def test_successful_purchase_colombia - gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512321', payment_country: 'CO')) + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(account_id: '512321', payment_country: 'CO')) options_colombia = { payment_country: 'CO', @@ -164,7 +199,7 @@ def test_successful_purchase_colombia end def test_successful_purchase_mexico - gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512324', payment_country: 'MX')) + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(account_id: '512324', payment_country: 'MX')) options_mexico = { payment_country: 'MX', @@ -206,11 +241,30 @@ def test_successful_purchase_no_description end def test_failed_purchase - response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@amount, @declined_card) assert_failure response assert_equal 'DECLINED', response.params['transactionResponse']['state'] end + # Published API does not currently provide a way to request a CONTACT_THE_ENTITY + # def test_failed_purchase_correct_message_when_payment_network_response_error_present + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # assert_equal 'CONTACT_THE_ENTITY | Contactar con entidad emisora', response.message + # assert_equal 'Contactar con entidad emisora', response.params['transactionResponse']['paymentNetworkResponseErrorMessage'] + + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # assert_equal 'CONTACT_THE_ENTITY', response.message + # assert_nil response.params['transactionResponse']['paymentNetworkResponseErrorMessage'] + # end + + def test_failed_purchase_with_cabal_card + response = @gateway.purchase(@amount, @invalid_cabal_card, @options) + assert_failure response + assert_equal 'ERROR', response.params['code'] + end + def test_failed_purchase_with_no_options response = @gateway.purchase(@amount, @declined_card, {}) assert_failure response @@ -231,6 +285,20 @@ def test_successful_authorize assert_match %r(^\d+\|(\w|-)+$), response.authorization end + def test_successful_authorize_with_naranja_card + response = @gateway.authorize(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_successful_authorize_with_cabal_card + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + def test_successful_authorize_with_specified_language response = @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es')) assert_success response @@ -239,7 +307,7 @@ def test_successful_authorize_with_specified_language end def test_failed_authorize - response = @gateway.authorize(@amount, @pending_card, @options) + response = @gateway.authorize(@amount, @declined_card) assert_failure response assert_equal 'DECLINED', response.params['transactionResponse']['state'] end @@ -251,40 +319,43 @@ def test_failed_authorize_with_specified_language assert_equal 'Credenciales inválidas', response.message end - # As noted above, capture transactions are currently not supported, but in the hope - # they will one day be, here you go + def test_successful_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization - # def test_successful_capture - # response = @gateway.authorize(@amount, @credit_card, @options) - # assert_success response - # assert_equal 'APPROVED', response.message - # assert_match %r(^\d+\|(\w|-)+$), response.authorization + capture = @gateway.capture(@amount, response.authorization, @options) + assert_success capture + assert_equal 'APPROVED', response.message + assert response.test? + end - # capture = @gateway.capture(@amount, response.authorization, @options) - # assert_success capture - # assert_equal 'APPROVED', response.message - # assert response.test? - # end + def test_successful_partial_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization - # def test_successful_partial_capture - # response = @gateway.authorize(@amount, @credit_card, @options) - # assert_success response - # assert_equal 'APPROVED', response.message - # assert_match %r(^\d+\|(\w|-)+$), response.authorization - - # capture = @gateway.capture(@amount - 1, response.authorization, @options) - # assert_success capture - # assert_equal 'APPROVED', response.message - # assert_equal '39.99', response.params['TX_VALUE']['value'] - # assert response.test? - # end + capture = @gateway.capture(@amount - 1, response.authorization, @options) + assert_success capture + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_match(/may not be null/, response.message) + end - def test_well_formed_refund_fails_as_expected + def test_successful_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase assert refund = @gateway.refund(@amount, purchase.authorization, @options) - assert_equal 'The payment plan id cannot be empty', refund.message + assert_success refund + assert_equal 'APPROVED', refund.message end def test_failed_refund @@ -299,16 +370,6 @@ def test_failed_refund_with_specified_language assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message) end - # If this test fails, support for void may have been added to the sandbox - def test_unsupported_test_void_fails_as_expected - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - - assert void = @gateway.void(auth.authorization) - assert_failure void - assert_equal 'Internal payment provider error. ', void.message - end - def test_failed_void response = @gateway.void('') assert_failure response @@ -321,22 +382,6 @@ def test_failed_void_with_specified_language assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message) end - # If this test fails, support for captures may have been added to the sandbox - def test_unsupported_test_capture_fails_as_expected - auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - - assert capture = @gateway.capture(@amount, auth.authorization, @options) - assert_failure capture - assert_equal 'Internal payment provider error. ', capture.message - end - - def test_failed_capture - response = @gateway.capture(@amount, '') - assert_failure response - assert_match(/must not be null/, response.message) - end - def test_verify_credentials assert @gateway.verify_credentials @@ -366,17 +411,17 @@ def test_successful_verify_with_specified_language end def test_failed_verify_with_specified_amount - verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 1699)) + verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 0)) assert_failure verify - assert_equal 'The order value is less than minimum allowed. Minimum value allowed 17 ARS', verify.message + assert_equal 'INVALID_TRANSACTION | [The given payment value [0] is inferior than minimum configured value [0.01]]', verify.message end def test_failed_verify_with_specified_language - verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 1699, language: 'es')) + verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 0, language: 'es')) assert_failure verify - assert_equal 'The order value is less than minimum allowed. Minimum value allowed 17 ARS', verify.message + assert_equal 'INVALID_TRANSACTION | [El valor recibido [0] es inferior al valor mínimo configurado [0,01]]', verify.message end def test_transcript_scrubbing @@ -389,4 +434,16 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) assert_scrubbed(@gateway.options[:api_key], clean_transcript) end + + def test_successful_store + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'SUCCESS', store.message + end + + def test_successful_purchase_with_extra_fields + response = @gateway.purchase(@amount, @credit_card, @options.merge({ extra_1: '123456', extra_2: 'abcdef', extra_3: 'testing' })) + assert_success response + assert_equal 'APPROVED', response.message + end end diff --git a/test/remote/gateways/remote_payway_dot_com_test.rb b/test/remote/gateways/remote_payway_dot_com_test.rb new file mode 100644 index 00000000000..3bb97f34bf6 --- /dev/null +++ b/test/remote/gateways/remote_payway_dot_com_test.rb @@ -0,0 +1,143 @@ +require 'test_helper' + +class RemotePaywayDotComTest < Test::Unit::TestCase + def setup + @gateway = PaywayDotComGateway.new(fixtures(:payway_dot_com)) + + @amount = 100 + @credit_card = credit_card('4000100011112224', verification_value: '737') + @declined_card = credit_card('4000300011112220') + @invalid_luhn_card = credit_card('4000300011112221') + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '5000', response.message[0, 4] + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + eci_type: '5', + tax: '7', + soft_descriptor: "Dan's Guitar Store" + } + + response = @gateway.purchase(101, @credit_card, options) + assert_success response + assert_equal '5000', response.message[0, 4] + end + + def test_failed_purchase + response = @gateway.purchase(102, @invalid_luhn_card, @options) + assert_failure response + assert_equal PaywayDotComGateway::STANDARD_ERROR_CODE_MAPPING['5035'], response.error_code + assert_equal '5035', response.message[0, 4] + end + + def test_successful_authorize + auth_only = @gateway.authorize(103, @credit_card, @options) + assert_success auth_only + assert_equal '5000', auth_only.message[0, 4] + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(104, @credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal '5000', capture.message[0, 4] + end + + def test_failed_authorize + response = @gateway.authorize(105, @invalid_luhn_card, @options) + assert_failure response + assert_equal '5035', response.message[0, 4] + end + + def test_failed_capture + response = @gateway.capture(106, '') + assert_failure response + assert_equal '5025', response.message[0, 4] + end + + def test_successful_credit + credit = @gateway.credit(107, @credit_card, @options) + assert_success credit + assert_equal '5000', credit.message[0, 4] + end + + def test_failed_credit + response = @gateway.credit(108, @invalid_luhn_card, @options) + assert_failure response + assert_equal '5035', response.message[0, 4] + end + + def test_successful_void + auth = @gateway.authorize(109, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal '5025', response.message[0, 4] + end + + def test_successful_void_of_sale + sale = @gateway.purchase(110, @credit_card, @options) + assert_success sale + + assert void = @gateway.void(sale.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_successful_void_of_credit + credit = @gateway.credit(111, @credit_card, @options) + assert_success credit + + assert void = @gateway.void(credit.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_invalid_login + gateway = PaywayDotComGateway.new(login: '', password: '', company_id: '', source_id: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{5001}, response.message[0, 4] + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_transcript_scrubbing_failed_purchase + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @invalid_luhn_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@invalid_luhn_card.number, transcript) + assert_scrubbed(@invalid_luhn_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_payway_test.rb b/test/remote/gateways/remote_payway_test.rb index 9d900c8528b..643411f520a 100644 --- a/test/remote/gateways/remote_payway_test.rb +++ b/test/remote/gateways/remote_payway_test.rb @@ -4,46 +4,52 @@ class PaywayTest < Test::Unit::TestCase def setup @amount = 1100 - @options = {:order_id => generate_unique_id} + @options = { order_id: generate_unique_id } @gateway = ActiveMerchant::Billing::PaywayGateway.new(fixtures(:payway)) - @visa = credit_card('4564710000000004', - :month => 2, - :year => 2019, - :verification_value => '847' + @visa = credit_card( + '4564710000000004', + month: 2, + year: 2019, + verification_value: '847' ) - @mastercard = credit_card('5163200000000008', - :month => 8, - :year => 2020, - :verification_value => '070', - :brand => 'master' + @mastercard = credit_card( + '5163200000000008', + month: 8, + year: 2020, + verification_value: '070', + brand: 'master' ) - @expired = credit_card('4564710000000012', - :month => 2, - :year => 2005, - :verification_value => '963' + @expired = credit_card( + '4564710000000012', + month: 2, + year: 2005, + verification_value: '963' ) - @low = credit_card('4564710000000020', - :month => 5, - :year => 2020, - :verification_value => '234' + @low = credit_card( + '4564710000000020', + month: 5, + year: 2020, + verification_value: '234' ) - @stolen_mastercard = credit_card('5163200000000016', - :month => 12, - :year => 2019, - :verification_value => '728', - :brand => 'master' + @stolen_mastercard = credit_card( + '5163200000000016', + month: 12, + year: 2019, + verification_value: '728', + brand: 'master' ) - @invalid = credit_card('4564720000000037', - :month => 9, - :year => 2019, - :verification_value => '030' + @invalid = credit_card( + '4564720000000037', + month: 9, + year: 2019, + verification_value: '030' ) end @@ -85,10 +91,10 @@ def test_invalid_visa def test_invalid_login gateway = ActiveMerchant::Billing::PaywayGateway.new( - :username => 'bogus', - :password => 'bogus', - :merchant => 'TEST', - :pem => fixtures(:payway)['pem'] + username: 'bogus', + password: 'bogus', + merchant: 'TEST', + pem: fixtures(:payway)['pem'] ) assert response = gateway.purchase(@amount, @visa, @options) assert_failure response diff --git a/test/remote/gateways/remote_pin_test.rb b/test/remote/gateways/remote_pin_test.rb index 2539bc8b616..003b2729601 100644 --- a/test/remote/gateways/remote_pin_test.rb +++ b/test/remote/gateways/remote_pin_test.rb @@ -5,17 +5,26 @@ def setup @gateway = PinGateway.new(fixtures(:pin)) @amount = 100 - @credit_card = credit_card('5520000000000000', :year => Time.now.year + 2) - @visa_credit_card = credit_card('4200000000000000', :year => Time.now.year + 3) + @credit_card = credit_card('5520000000000000', year: Time.now.year + 2) + @visa_credit_card = credit_card('4200000000000000', year: Time.now.year + 3) @declined_card = credit_card('4100000000000001') @options = { - :email => 'roland@pinpayments.com', - :ip => '203.59.39.62', - :order_id => '1', - :billing_address => address, - :description => "Store Purchase #{DateTime.now.to_i}" + email: 'roland@pinpayments.com', + ip: '203.59.39.62', + order_id: '1', + billing_address: address, + description: "Store Purchase #{DateTime.now.to_i}" } + + @additional_options_3ds = @options.merge( + three_d_secure: { + version: '1.0.2', + eci: '06', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=' + } + ) end def test_successful_purchase @@ -53,6 +62,16 @@ def test_successful_authorize_and_capture assert_equal true, response.params['response']['captured'] end + def test_successful_authorize_and_capture_with_passthrough_3ds + authorization = @gateway.authorize(@amount, @credit_card, @additional_options_3ds) + assert_success authorization + assert_equal false, authorization.params['response']['captured'] + + response = @gateway.capture(@amount, authorization.authorization, @options) + assert_success response + assert_equal true, response.params['response']['captured'] + end + def test_failed_authorize response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response @@ -94,16 +113,16 @@ def test_store_and_charge_with_pinjs_card_token } # Get a token equivalent to what is returned by Pin.js card_attrs = { - :number => @credit_card.number, - :expiry_month => @credit_card.month, - :expiry_year => @credit_card.year, - :cvc => @credit_card.verification_value, - :name => "#{@credit_card.first_name} #{@credit_card.last_name}", - :address_line1 => '42 Sevenoaks St', - :address_city => 'Lathlain', - :address_postcode => '6454', - :address_start => 'WA', - :address_country => 'Australia' + number: @credit_card.number, + expiry_month: @credit_card.month, + expiry_year: @credit_card.year, + cvc: @credit_card.verification_value, + name: "#{@credit_card.first_name} #{@credit_card.last_name}", + address_line1: '42 Sevenoaks St', + address_city: 'Lathlain', + address_postcode: '6454', + address_start: 'WA', + address_country: 'Australia' } url = @gateway.test_url + '/cards' @@ -141,12 +160,21 @@ def test_store_and_update assert_not_nil response.authorization assert_equal @credit_card.year, response.params['response']['card']['expiry_year'] - response = @gateway.update(response.authorization, @visa_credit_card, :address => address) + response = @gateway.update(response.authorization, @visa_credit_card, address: address) assert_success response assert_not_nil response.authorization assert_equal @visa_credit_card.year, response.params['response']['card']['expiry_year'] end + def test_store_and_unstore + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + + response = @gateway.unstore(response.authorization) + assert_success response + end + def test_refund response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -170,8 +198,27 @@ def test_failed_refund assert_failure response end + def test_successful_void + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + + assert void = @gateway.void(authorization.authorization, @options) + assert_success void + end + + def test_failed_void + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + + assert void = @gateway.void(authorization.authorization, @options) + assert_success void + + assert already_voided = @gateway.void(authorization.authorization, @options) + assert_failure already_voided + end + def test_invalid_login - gateway = PinGateway.new(:api_key => '') + gateway = PinGateway.new(api_key: '') response = gateway.purchase(@amount, @credit_card, @options) assert_failure response end diff --git a/test/remote/gateways/remote_plexo_test.rb b/test/remote/gateways/remote_plexo_test.rb new file mode 100644 index 00000000000..433a8a72245 --- /dev/null +++ b/test/remote/gateways/remote_plexo_test.rb @@ -0,0 +1,266 @@ +require 'test_helper' + +class RemotePlexoTest < Test::Unit::TestCase + def setup + @gateway = PlexoGateway.new(fixtures(:plexo)) + + @amount = 100 + @credit_card = credit_card('5555555555554444', month: '12', year: '2024', verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + @declined_card = credit_card('5555555555554445') + @options = { + email: 'snavatta@plexo.com.uy', + ip: '127.0.0.1', + items: [ + { + name: 'prueba', + description: 'prueba desc', + quantity: '1', + price: '100', + discount: '0' + } + ], + amount_details: { + tip_amount: '5' + }, + identification_type: '1', + identification_value: '123456', + billing_address: address + } + + @cancel_options = { + description: 'Test desc', + reason: 'requested by client' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_finger_print + response = @gateway.purchase(@amount, @credit_card, @options.merge({ finger_print: 'USABJHABSFASNJKN123532' })) + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'denied', response.params['status'] + assert_equal '10', response.error_code + end + + def test_successful_authorize_with_metadata + meta = { + custom_one: 'my field 1' + } + auth = @gateway.authorize(@amount, @credit_card, @options.merge({ metadata: meta })) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal '10', response.error_code + assert_equal 'denied', response.params['status'] + 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, '123') + assert_failure response + assert_equal 'An internal error occurred. Contact support.', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @cancel_options) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options.merge({ refund_type: 'partial-refund' })) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @cancel_options.merge({ type: 'partial-refund' })) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '123', @cancel_options) + assert_failure response + assert_equal 'An internal error occurred. Contact support.', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @cancel_options) + assert_success void + end + + def test_failed_void + response = @gateway.void('123', @cancel_options) + assert_failure response + assert_equal 'An internal error occurred. Contact support.', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_custom_amount + response = @gateway.verify(@credit_card, @options.merge({ verify_amount: '400' })) + assert_success response + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_invalid_login + gateway = PlexoGateway.new(client_id: '', api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + + def test_successful_purchase_passcard + credit_card = credit_card('6280260025383009', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + end + + def test_successful_purchase_edenred + credit_card = credit_card('6374830000000823', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + end + + def test_successful_purchase_anda + credit_card = credit_card('6031991248204901', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + end + + # This test is omitted until Plexo confirms that the transaction will indeed + # be declined as indicated in the documentation. + def test_successful_purchase_and_declined_refund_anda + omit + credit_card = credit_card('6031997614492616', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @cancel_options) + assert_failure refund + assert_equal 'An internal error occurred. Contact support.', refund.message + end + + # This test is omitted until Plexo confirms that the transaction will indeed + # be declined as indicated in the documentation. + def test_successful_purchase_and_declined_cancellation_anda + omit + credit_card = credit_card('6031998427187914', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, @cancel_options) + assert_failure void + end + + def test_successful_purchase_tarjetad + credit_card = credit_card('6018287227431046', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + end + + def test_failure_purchase_tarjetad + credit_card = credit_card('6018282227431033', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'denied', response.params['status'] + assert_equal '10', response.error_code + end + + def test_successful_purchase_sodexo + credit_card = credit_card('5058645584812145', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + end + + # This test is omitted until Plexo confirms that the transaction will indeed + # be declined as indicated in the documentation. + def test_successful_purchase_and_declined_refund_sodexo + omit + credit_card = credit_card('5058647731868699', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @cancel_options) + assert_failure refund + assert_equal 'An internal error occurred. Contact support.', refund.message + end + + def test_successful_purchase_and_declined_cancellation_sodexo + credit_card = credit_card('5058646599260130', month: '12', year: '2024', + verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, @cancel_options) + assert_success void + end +end diff --git a/test/remote/gateways/remote_plugnpay_test.rb b/test/remote/gateways/remote_plugnpay_test.rb index f05f4dc7701..2fcf76dbdd6 100644 --- a/test/remote/gateways/remote_plugnpay_test.rb +++ b/test/remote/gateways/remote_plugnpay_test.rb @@ -6,8 +6,8 @@ def setup @good_card = credit_card('4111111111111111', first_name: 'cardtest') @bad_card = credit_card('1234123412341234') @options = { - :billing_address => address, - :description => 'Store purchaes' + billing_address: address, + description: 'Store purchaes' } @amount = 100 end diff --git a/test/remote/gateways/remote_priority_test.rb b/test/remote/gateways/remote_priority_test.rb new file mode 100644 index 00000000000..ef3849fb283 --- /dev/null +++ b/test/remote/gateways/remote_priority_test.rb @@ -0,0 +1,352 @@ +require 'test_helper' + +class RemotePriorityTest < Test::Unit::TestCase + def setup + @gateway = PriorityGateway.new(fixtures(:priority)) + + @amount = 2 + @credit_amount = 2000 + @credit_card = credit_card + @invalid_credit_card = credit_card('123456') + @replay_id = rand(100...99999999) + @options = { billing_address: address } + + @additional_options = { + is_auth: false, + should_get_credit_card_level: true, + should_vault_card: false, + invoice: '123', + tax_exempt: true + } + + @additional_creditoptions = { + is_auth: true, + should_get_credit_card_level: false, + should_vault_card: false, + invoice: '123', + tax_exempt: true, + is_ticket: false, + source_zip: '30022', + auth_code: '', + ach_indicator: '', + bank_account: '', + meta: 'Harry Maguire is the best defender in premier league' + } + + @custom_pos_data = { + 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_data = { + 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 + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @invalid_credit_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @invalid_credit_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + end + + def test_failed_purchase_missing_card_month + card_without_month = credit_card('4242424242424242', month: '') + response = @gateway.purchase(@amount, card_without_month, @options) + + assert_failure response + assert_equal 'ValidationError', response.error_code + assert_equal 'Missing expiration month and / or year', response.message + end + + def test_failed_purchase_missing_card_verification_number + card_without_cvv = credit_card('4242424242424242', verification_value: '') + response = @gateway.purchase(@amount, card_without_cvv, @options) + + assert_failure response + assert_equal 'CVV is required based on merchant fraud settings', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_failed_capture + capture = @gateway.capture(@amount, 'bogus_authorization', @options) + assert_failure capture + assert_equal 'Original Transaction Not Found', capture.message + end + + def test_successful_purchase_with_shipping_data + options_with_shipping = @options.merge({ ship_to_country: 'USA', ship_to_zip: 27703, ship_amount: 0.01 }) + response = @gateway.purchase(@amount, @credit_card, options_with_shipping) + + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_successful_purchase_with_purchases_data + options_with_purchases = @options.merge(@purchases_data) + response = @gateway.purchase(@amount, @credit_card, options_with_purchases) + + assert_success response + assert_equal response.params['purchases'].first['name'], @purchases_data[:purchases].first[:name] + assert_equal response.params['purchases'].last['name'], @purchases_data[:purchases].last[:name] + assert_equal 'Approved or completed successfully', response.message + end + + def test_successful_purchase_with_custom_pos_data + options_with_custom_pos_data = @options.merge(@custom_pos_data) + response = @gateway.purchase(@amount, @credit_card, options_with_custom_pos_data) + + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_successful_purchase_with_additional_options + options = @options.merge(@additional_options) + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_successful_credit + options = @options.merge(@additional_creditoptions) + response = @gateway.credit(@credit_amount, @credit_card, options) + assert_success response + assert_equal 'Approved or completed successfully', response.message + end + + def test_failed_credit + options = @options.merge(@additional_creditoptions) + response = @gateway.credit(@credit_amount, @invalid_credit_card, options) + assert_failure response + assert_equal 'Invalid card number', response.message + end + + def test_failed_credit_missing_card_month + card_without_month = credit_card('4242424242424242', month: '') + options = @options.merge(@additional_creditoptions) + response = @gateway.credit(@credit_amount, card_without_month, options) + assert_failure response + assert_equal 'ValidationError', response.error_code + assert_equal 'Missing expiration month and / or year', response.message + end + + def test_successful_void_with_batch_open + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + # Batch status is by default is set to Open when Sale transaction is created + batch_check = @gateway.get_payment_status(purchase.params['batchId']) + assert_equal 'Open', batch_check.message + + void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_successful_void_after_closing_batch + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + # Manually close open batch; resulting status should be 'Pending' + @gateway.close_batch(purchase.params['batchId']) + payment_status = @gateway.get_payment_status(purchase.params['batchId']) + assert_equal 'Pending', payment_status.message + + void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + bogus_transaction_id = '123456' + assert void = @gateway.void(bogus_transaction_id, @options) + + assert_failure void + assert_equal 'Unauthorized', void.error_code + assert_equal 'Original Payment Not Found Or You Do Not Have Access.', void.message + end + + def test_successful_refund_with_open_batch + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + batch_check = @gateway.get_payment_status(purchase.params['batchId']) + assert_equal 'Open', batch_check.message + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved or completed successfully', refund.message + end + + def test_successful_refund_after_closing_batch + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + # Manually close open batch; resulting status should be 'Pending' + @gateway.close_batch(purchase.params['batchId']) + payment_status = @gateway.get_payment_status(purchase.params['batchId']) + assert_equal 'Pending', payment_status.message + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved or completed successfully', refund.message + end + + def test_successful_get_payment_status + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + batch_check = @gateway.get_payment_status(response.params['batchId']) + + assert_success batch_check + assert_equal 'Open', batch_check.message + end + + def test_failed_get_payment_status + batch_check = @gateway.get_payment_status(123456) + + assert_failure batch_check + assert_equal 'Invalid JSON response', batch_check.params['message'][0..20] + end + + def test_successful_verify + response = @gateway.verify(credit_card('411111111111111')) + assert_success response + assert_match 'JPMORGAN CHASE BANK, N.A.', response.params['bank']['name'] + end + + def test_failed_verify + response = @gateway.verify(@invalid_credit_card) + assert_failure response + assert_match 'No bank information found for bin number', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_successful_purchase_with_duplicate_replay_id + response = @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: @replay_id)) + + assert_success response + assert_equal @replay_id, response.params['replayId'] + + duplicate_response = @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: response.params['replayId'])) + + assert_success duplicate_response + assert_equal response.params['id'], duplicate_response.params['id'] + end + + def test_failed_purchase_with_duplicate_replay_id + response = @gateway.purchase(@amount, @invalid_credit_card, @options.merge(replay_id: @replay_id)) + assert_failure response + + duplicate_response = @gateway.purchase(@amount, @invalid_credit_card, @options.merge(replay_id: response.params['replayId'])) + assert_failure duplicate_response + + assert_equal response.message, duplicate_response.message + assert_equal response.params['status'], duplicate_response.params['status'] + + assert_equal response.params['id'], duplicate_response.params['id'] + end + + def test_successful_purchase_with_unique_replay_id + first_purchase_response = @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: @replay_id)) + + assert_success first_purchase_response + assert_equal @replay_id, first_purchase_response.params['replayId'] + + second_purchase_response = @gateway.purchase(@amount + 1, @credit_card, @options.merge(replay_id: @replay_id + 1)) + + assert_success second_purchase_response + assert_not_equal first_purchase_response.params['id'], second_purchase_response.params['id'] + end + + def test_failed_duplicate_refund + purchase_response = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase_response + + refund_response = @gateway.refund(@amount, purchase_response.authorization) + + assert_success refund_response + assert_equal 'Approved or completed successfully', refund_response.message + + duplicate_refund_response = @gateway.refund(@amount, purchase_response.authorization) + + assert_failure duplicate_refund_response + assert_equal 'Payment already refunded', duplicate_refund_response.message + end + + def test_failed_duplicate_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization) + assert_success void + + duplicate_void = @gateway.void(purchase.authorization) + + assert_failure duplicate_void + assert_equal 'Payment already voided.', duplicate_void.message + end +end diff --git a/test/remote/gateways/remote_pro_pay_test.rb b/test/remote/gateways/remote_pro_pay_test.rb index c447f996862..8ecea99cf19 100644 --- a/test/remote/gateways/remote_pro_pay_test.rb +++ b/test/remote/gateways/remote_pro_pay_test.rb @@ -67,7 +67,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) assert_success capture end @@ -90,7 +90,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options) assert_success refund end diff --git a/test/remote/gateways/remote_psigate_test.rb b/test/remote/gateways/remote_psigate_test.rb index 74d643794fe..d4e50fc8539 100644 --- a/test/remote/gateways/remote_psigate_test.rb +++ b/test/remote/gateways/remote_psigate_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class PsigateRemoteTest < Test::Unit::TestCase - def setup Base.mode = :test @gateway = PsigateGateway.new(fixtures(:psigate)) @@ -10,9 +9,9 @@ def setup @amount = 2400 @creditcard = credit_card('4111111111111111') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :email => 'jack@example.com' + order_id: generate_unique_id, + billing_address: address, + email: 'jack@example.com' } end @@ -45,7 +44,7 @@ def test_successful_purchase_and_refund end def test_failed_purchase - assert response = @gateway.purchase(@amount, @creditcard, @options.update(:test_result => 'D')) + assert response = @gateway.purchase(@amount, @creditcard, @options.update(test_result: 'D')) assert_failure response end diff --git a/test/remote/gateways/remote_psl_card_test.rb b/test/remote/gateways/remote_psl_card_test.rb index 6b1b9e6e215..18e911faea2 100644 --- a/test/remote/gateways/remote_psl_card_test.rb +++ b/test/remote/gateways/remote_psl_card_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemotePslCardTest < Test::Unit::TestCase - def setup @gateway = PslCardGateway.new(fixtures(:psl_card)) @@ -25,93 +24,70 @@ 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 def test_no_login @gateway = PslCardGateway.new( - :login => '' - ) - response = @gateway.authorize(@accept_amount, @uk_maestro, - :billing_address => @uk_maestro_address + login: '' ) + 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_qbms_test.rb b/test/remote/gateways/remote_qbms_test.rb index 017ebd1fc95..5e9de931936 100644 --- a/test/remote/gateways/remote_qbms_test.rb +++ b/test/remote/gateways/remote_qbms_test.rb @@ -11,7 +11,7 @@ def setup @card = credit_card('4111111111111111') @options = { - :billing_address => address, + billing_address: address } end @@ -66,7 +66,7 @@ def test_successful_credit end def test_invalid_ticket - gateway = QbmsGateway.new(@gateway_options.merge(:ticket => 'test123')) + gateway = QbmsGateway.new(@gateway_options.merge(ticket: 'test123')) assert response = gateway.authorize(@amount, @card, @options) assert_instance_of Response, response @@ -102,6 +102,6 @@ def test_transcript_scrubbing private def error_card(config_id) - credit_card('4111111111111111', :first_name => "configid=#{config_id}", :last_name => '') + credit_card('4111111111111111', first_name: "configid=#{config_id}", last_name: '') end end diff --git a/test/remote/gateways/remote_quantum_test.rb b/test/remote/gateways/remote_quantum_test.rb index 370e802d66c..30bbf393c64 100644 --- a/test/remote/gateways/remote_quantum_test.rb +++ b/test/remote/gateways/remote_quantum_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteQuantumTest < Test::Unit::TestCase - def setup @gateway = QuantumGateway.new(fixtures(:quantum)) @@ -54,7 +53,7 @@ def test_void end def test_passing_billing_address - options = {:billing_address => address} + options = { billing_address: address } assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'Transaction is APPROVED', response.message @@ -64,9 +63,9 @@ def test_passing_billing_address # So we check to see if the parse failed and report def test_invalid_login gateway = QuantumGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card) assert_failure response assert_equal 'ERROR: Invalid Gateway Login!!', response.message diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb index c9e95c959c8..f3457706af5 100644 --- a/test/remote/gateways/remote_quickbooks_test.rb +++ b/test/remote/gateways/remote_quickbooks_test.rb @@ -13,8 +13,7 @@ def setup order_id: '1', billing_address: address({ zip: 90210, country: 'US', - state: 'CA' - }), + state: 'CA' }), description: 'Store Purchase' } end @@ -23,12 +22,14 @@ def test_successful_purchase response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'CAPTURED', response.message + assert_equal @gateway.options[:access_token], response.params['access_token'] + assert_equal @gateway.options[:refresh_token], response.params['refresh_token'] end def test_failed_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'cardNumber is invalid.', response.message + assert_equal 'card.number is invalid.', response.message end def test_successful_authorize_and_capture @@ -89,7 +90,7 @@ def test_successful_verify def test_failed_verify response = @gateway.verify(@declined_card, @options) assert_failure response - assert_match %r{cardNumber is invalid.}, response.message + assert_match %r{card.number is invalid.}, response.message assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code end @@ -106,7 +107,62 @@ def test_invalid_login end end - def test_dump_transcript - # See quickbooks_test.rb for an example of a scrubbed transcript + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'ISSUED', void.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:access_token], transcript) + 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) + assert_failure response + assert_equal 'AuthenticationFailed', response.params['code'] + end + + def test_successful_purchase_with_expired_token + @gateway.options[:access_token] = 'not_a_valid_token' + response = @gateway.purchase(@amount, @credit_card, @options.merge(allow_refresh: true)) + assert_success response + end + + def test_successful_purchase_without_state_in_address + options = { + order_id: '1', + billing_address: + { + zip: 90210, + # Submitting a value of an empty string for the `state` field + # results in a `region is invalid` error message from Quickbooks. + # This test ensures that an empty string is not sent from AM. + state: '', + country: '' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'CAPTURED', response.message + end + + def test_refresh + response = @gateway.refresh + assert_success response + assert response.params['access_token'] end end diff --git a/test/remote/gateways/remote_quickpay_test.rb b/test/remote/gateways/remote_quickpay_test.rb index 760c71f096e..e547d74242d 100644 --- a/test/remote/gateways/remote_quickpay_test.rb +++ b/test/remote/gateways/remote_quickpay_test.rb @@ -8,11 +8,11 @@ def setup @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address + order_id: generate_unique_id[0...10], + billing_address: address } - @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa_no_cvv2 = credit_card('4000300011112220', verification_value: nil) @visa = credit_card('4000100011112224') @dankort = credit_card('5019717010103742') @visa_dankort = credit_card('4571100000000000') @@ -26,7 +26,7 @@ def setup @amex = credit_card('3700100000000000') # forbrugsforeningen doesn't use a verification value - @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil) + @forbrugsforeningen = credit_card('6007221000000000', verification_value: nil) end def test_successful_purchase @@ -38,7 +38,7 @@ def test_successful_purchase end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @visa, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -174,22 +174,22 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription')) assert_success store - assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(order_id: generate_unique_id[0...10])) assert_success purchase end def test_failed_store - assert store = @gateway.store(credit_card('4'), @options.merge(:description => 'New subscription')) + assert store = @gateway.store(credit_card('4'), @options.merge(description: 'New subscription')) assert_failure store assert_equal 'Error in field: cardnumber', store.message end def test_invalid_login gateway = QuickpayGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v10_test.rb b/test/remote/gateways/remote_quickpay_v10_test.rb index 8c71fcb1742..6cbb9e1533d 100644 --- a/test/remote/gateways/remote_quickpay_v10_test.rb +++ b/test/remote/gateways/remote_quickpay_v10_test.rb @@ -1,13 +1,12 @@ require 'test_helper' class RemoteQuickPayV10Test < Test::Unit::TestCase - def setup @gateway = QuickpayV10Gateway.new(fixtures(:quickpay_v10_api_key)) @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address(country: 'DNK') + order_id: generate_unique_id[0...10], + billing_address: address(country: 'DNK') } @valid_card = credit_card('1000000000000008') @@ -16,8 +15,8 @@ def setup @capture_rejected_card = credit_card('1000000000000032') @refund_rejected_card = credit_card('1000000000000040') - @valid_address = address(:phone => '4500000001') - @invalid_address = address(:phone => '4500000002') + @valid_address = address(phone: '4500000001') + @invalid_address = address(phone: '4500000002') end def card_brand(response) @@ -25,7 +24,7 @@ def card_brand(response) end def test_successful_purchase_with_short_country - options = @options.merge({billing_address: address(country: 'DK')}) + options = @options.merge({ billing_address: address(country: 'DK') }) assert response = @gateway.purchase(@amount, @valid_card, options) assert_equal 'OK', response.message @@ -35,7 +34,7 @@ def test_successful_purchase_with_short_country end def test_successful_purchase_with_order_id_format - options = @options.merge({order_id: "##{Time.new.to_f}"}) + options = @options.merge({ order_id: "##{Time.new.to_f}" }) assert response = @gateway.purchase(@amount, @valid_card, options) assert_equal 'OK', response.message @@ -60,7 +59,7 @@ def test_unsuccessful_purchase_with_invalid_card end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @valid_card, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @valid_card, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -68,13 +67,13 @@ def test_successful_usd_purchase end def test_successful_purchase_with_acquirers - assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'nets')) + assert response = @gateway.purchase(@amount, @valid_card, @options.update(acquirer: 'nets')) assert_equal 'OK', response.message assert_success response end def test_unsuccessful_purchase_with_invalid_acquirers - assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'invalid')) + assert response = @gateway.purchase(@amount, @valid_card, @options.update(acquirer: 'invalid')) assert_failure response assert_equal 'Validation error', response.message end @@ -268,5 +267,4 @@ def test_transcript_scrubbing assert_scrubbed(@valid_card.verification_value.to_s, clean_transcript) assert_scrubbed(@gateway.options[:api_key], clean_transcript) end - end diff --git a/test/remote/gateways/remote_quickpay_v4_test.rb b/test/remote/gateways/remote_quickpay_v4_test.rb index 9eb4ca69970..a9517f6e09f 100644 --- a/test/remote/gateways/remote_quickpay_v4_test.rb +++ b/test/remote/gateways/remote_quickpay_v4_test.rb @@ -4,15 +4,15 @@ class RemoteQuickpayV4Test < Test::Unit::TestCase # These test assumes that you have not added your development IP in # the Quickpay Manager. def setup - @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(:version => 4)) + @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(version: 4)) @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address + order_id: generate_unique_id[0...10], + billing_address: address } - @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa_no_cvv2 = credit_card('4000300011112220', verification_value: nil) @visa = credit_card('4000100011112224') @dankort = credit_card('5019717010103742') @visa_dankort = credit_card('4571100000000000') @@ -52,7 +52,7 @@ def test_successful_purchase_with_all_fraud_parameters end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @visa, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -189,16 +189,16 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription')) assert_success store - assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(order_id: generate_unique_id[0...10])) assert_success purchase end def test_invalid_login gateway = QuickpayGateway.new( - :login => '999999999', - :password => '' + login: '999999999', + password: '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v5_test.rb b/test/remote/gateways/remote_quickpay_v5_test.rb index 02838bf2bb8..367711d0d0d 100644 --- a/test/remote/gateways/remote_quickpay_v5_test.rb +++ b/test/remote/gateways/remote_quickpay_v5_test.rb @@ -4,15 +4,15 @@ class RemoteQuickpayV5Test < Test::Unit::TestCase # These test assumes that you have not added your development IP in # the Quickpay Manager. def setup - @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(:version => 5)) + @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(version: 5)) @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address + order_id: generate_unique_id[0...10], + billing_address: address } - @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa_no_cvv2 = credit_card('4000300011112220', verification_value: nil) @visa = credit_card('4000100011112224') @dankort = credit_card('5019717010103742') @visa_dankort = credit_card('4571100000000000') @@ -52,7 +52,7 @@ def test_successful_purchase_with_all_fraud_parameters end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @visa, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -189,16 +189,16 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription')) assert_success store - assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(order_id: generate_unique_id[0...10])) assert_success purchase end def test_invalid_login gateway = QuickpayGateway.new( - :login => '999999999', - :password => '' + login: '999999999', + password: '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v6_test.rb b/test/remote/gateways/remote_quickpay_v6_test.rb index 2d113657c0c..39d66fceb2b 100644 --- a/test/remote/gateways/remote_quickpay_v6_test.rb +++ b/test/remote/gateways/remote_quickpay_v6_test.rb @@ -4,15 +4,15 @@ class RemoteQuickpayV6Test < Test::Unit::TestCase # These test assumes that you have not added your development IP in # the Quickpay Manager. def setup - @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(:version => 6)) + @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(version: 6)) @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address + order_id: generate_unique_id[0...10], + billing_address: address } - @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa_no_cvv2 = credit_card('4000300011112220', verification_value: nil) @visa = credit_card('4000100011112224') @dankort = credit_card('5019717010103742') @visa_dankort = credit_card('4571100000000000') @@ -52,7 +52,7 @@ def test_successful_purchase_with_all_fraud_parameters end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @visa, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -189,16 +189,16 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription')) assert_success store - assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(order_id: generate_unique_id[0...10])) assert_success purchase end def test_invalid_login gateway = QuickpayGateway.new( - :login => '999999999', - :password => '' + login: '999999999', + password: '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v7_test.rb b/test/remote/gateways/remote_quickpay_v7_test.rb index 8b8def4f168..185f9369be5 100644 --- a/test/remote/gateways/remote_quickpay_v7_test.rb +++ b/test/remote/gateways/remote_quickpay_v7_test.rb @@ -8,11 +8,11 @@ def setup @amount = 100 @options = { - :order_id => generate_unique_id[0...10], - :billing_address => address + order_id: generate_unique_id[0...10], + billing_address: address } - @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa_no_cvv2 = credit_card('4000300011112220', verification_value: nil) @visa = credit_card('4000100011112224') @dankort = credit_card('5019717010103742') @visa_dankort = credit_card('4571100000000000') @@ -52,7 +52,7 @@ def test_successful_purchase_with_all_fraud_parameters end def test_successful_usd_purchase - assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert response = @gateway.purchase(@amount, @visa, @options.update(currency: 'USD')) assert_equal 'OK', response.message assert_equal 'USD', response.params['currency'] assert_success response @@ -60,13 +60,13 @@ def test_successful_usd_purchase end def test_successful_purchase_with_acquirers - assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'nets')) + assert response = @gateway.purchase(@amount, @visa, @options.update(acquirers: 'nets')) assert_equal 'OK', response.message assert_success response end def test_unsuccessful_purchase_with_invalid_acquirers - assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'invalid')) + assert response = @gateway.purchase(@amount, @visa, @options.update(acquirers: 'invalid')) assert_equal 'Error in field: acquirers', response.message assert_failure response end @@ -201,26 +201,26 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription')) assert_success store - assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(order_id: generate_unique_id[0...10])) assert_success purchase end def test_successful_store_with_acquirers - assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription', :acquirers => 'nets')) + assert store = @gateway.store(@visa, @options.merge(description: 'New subscription', acquirers: 'nets')) assert_success store end def test_successful_store_sans_description - assert store = @gateway.store(@visa, @options.merge(:acquirers => 'nets')) + assert store = @gateway.store(@visa, @options.merge(acquirers: 'nets')) assert_success store end def test_invalid_login gateway = QuickpayGateway.new( - :login => '999999999', - :password => '' + login: '999999999', + password: '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_qvalent_test.rb b/test/remote/gateways/remote_qvalent_test.rb index cc610703d86..79360070c57 100644 --- a/test/remote/gateways/remote_qvalent_test.rb +++ b/test/remote/gateways/remote_qvalent_test.rb @@ -5,14 +5,16 @@ def setup @gateway = QvalentGateway.new(fixtures(:qvalent)) @amount = 100 - @credit_card = credit_card('4000100011112224') + @credit_card = credit_card('4242424242424242') + @mastercard = credit_card('5163200000000008', brand: 'master') @declined_card = credit_card('4000000000000000') @expired_card = credit_card('4111111113444494') @options = { order_id: generate_unique_id, billing_address: address, - description: 'Store Purchase' + description: 'Store Purchase', + customer_reference_number: generate_unique_id } end @@ -59,9 +61,9 @@ def test_successful_purchase_with_3d_secure order_id: generate_unique_id, billing_address: address, description: 'Store Purchase', - xid: '123', - cavv: '456', - eci: '5' + xid: 'sgf7h125tr8gh24abmah', + cavv: 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=', + eci: 'INS' } response = @gateway.purchase(@amount, @credit_card, options) @@ -189,4 +191,52 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, clean_transcript) assert_scrubbed(@gateway.options[:password], clean_transcript) end + + def test_successful_purchase_initial + stored_credential = { + stored_credential: { + initial_transaction: true, + initiator: 'merchant', + reason_type: 'unscheduled' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['response.authTraceId'] + end + + def test_successful_purchase_cardholder + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'cardholder', + reason_type: 'unscheduled', + network_transaction_id: 'qwerty7890' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_mastercard + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'recurring', + network_transaction_id: 'qwerty7890' + } + } + + response = @gateway.purchase(@amount, @mastercard, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + end end diff --git a/test/remote/gateways/remote_rapyd_test.rb b/test/remote/gateways/remote_rapyd_test.rb new file mode 100644 index 00000000000..ee76076e6eb --- /dev/null +++ b/test/remote/gateways/remote_rapyd_test.rb @@ -0,0 +1,416 @@ +require 'test_helper' + +class RemoteRapydTest < Test::Unit::TestCase + def setup + @gateway = RapydGateway.new(fixtures(:rapyd)) + + @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') + @check = check + @options = { + pm_type: 'us_debit_visa_card', + currency: 'USD', + complete_payment_url: 'www.google.com', + error_payment_url: 'www.google.com', + description: 'Describe this transaction', + statement_descriptor: 'Statement Descriptor', + email: 'test@example.com', + 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', + email: 'test@example.com', + billing_address: address(name: 'Jim Reynolds') + } + @metadata = { + 'array_of_objects': [ + { 'name': 'John Doe' }, + { 'type': 'customer' } + ], + 'array_of_strings': %w[ + color + size + ], + 'number': 1234567890, + 'object': { + 'string': 'person' + }, + 'string': 'preferred', + 'Boolean': true + } + @three_d_secure = { + version: '2.1.0', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + 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 + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + 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 + # 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 + + 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 + + def test_successful_purchase_with_address + billing_address = address(name: 'Henry Winkler', address1: '123 Happy Days Lane') + + response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: billing_address)) + assert_success response + 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 + assert_equal 'SUCCESS', response.message + assert_equal 'ACT', response.params['data']['status'] + end + + def test_successful_purchase_with_options + options = @options.merge(metadata: @metadata, ewallet_id: 'ewallet_897aca846f002686e14677541f78a0f4') + response = @gateway.purchase(100000, @credit_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Do Not Honor', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'SUCCESS', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Do Not Honor', 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, 'madeupauth') + assert_failure response + assert_equal 'The request tried to retrieve a payment, but the payment was not found. The request was rejected. Corrective action: Use a valid payment ID.', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_successful_refund_with_options + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options.merge(metadata: @metadata)) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_partial_refund + amount = 5000 + purchase = @gateway.purchase(amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(amount - 1050, purchase.authorization) + assert_success refund + assert_equal 'SUCCESS', refund.message + assert_equal 39.5, refund.params['data']['amount'] + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'The request attempted an operation that requires a payment ID, but the payment was not found. The request was rejected. Corrective action: Use the ID of a valid payment.', response.message + end + + def test_failed_void_with_payment_method_error + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_failure void + assert_equal 'ERROR_PAYMENT_METHOD_TYPE_DOES_NOT_SUPPORT_PAYMENT_CANCELLATION', void.error_code + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'UNAUTHORIZED_API_CALL', 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_verify_with_peso + @options[:pm_type] = 'mx_visa_card' + @options[:currency] = 'MXN' + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Do Not Honor', response.message + end + + def test_successful_store_and_purchase + store = @gateway.store(@credit_card, @options) + assert_success store + assert store.params.dig('data', 'id') + assert store.params.dig('data', 'default_payment_method') + + # 3DS authorization is required on storing a payment method for future transactions + # 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 + store = @gateway.store(@credit_card, @options) + assert_success store + assert customer_id = store.params.dig('data', 'id') + assert store.params.dig('data', 'default_payment_method') + + unstore = @gateway.unstore(store.authorization) + assert_success unstore + assert_equal true, unstore.params.dig('data', 'deleted') + assert_equal customer_id, unstore.params.dig('data', 'id') + end + + def test_failed_store + store = @gateway.store(@declined_card, @options) + assert_failure store + end + + def test_failed_unstore + store = @gateway.store(@credit_card, @options) + assert_success store + assert store.params.dig('data', 'id') + + unstore = @gateway.unstore('') + assert_failure unstore + end + + def test_invalid_login + gateway = RapydGateway.new(secret_key: '', access_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The request did not contain the required headers for authentication. The request was rejected. Corrective action: Add authentication headers.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:secret_key], transcript) + assert_scrubbed(@gateway.options[:access_key], transcript) + end + + def test_transcript_scrubbing_with_ach + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @ach_options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:secret_key], transcript) + assert_scrubbed(@gateway.options[:access_key], transcript) + end + + def test_successful_authorize_with_3ds_v1_options + options = @options.merge(three_d_secure: @three_d_secure) + options[:pm_type] = 'gb_visa_card' + options[:three_d_secure][:version] = '1.0.2' + + 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'] + end + + def test_successful_authorize_with_3ds_v2_options + options = @options.merge(three_d_secure: @three_d_secure) + options[:pm_type] = 'gb_visa_card' + + 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'] + 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 +end diff --git a/test/remote/gateways/remote_reach_test.rb b/test/remote/gateways/remote_reach_test.rb new file mode 100644 index 00000000000..b72af06f159 --- /dev/null +++ b/test/remote/gateways/remote_reach_test.rb @@ -0,0 +1,326 @@ +require 'test_helper' + +class RemoteReachTest < Test::Unit::TestCase + def setup + @gateway = ReachGateway.new(fixtures(:reach)) + @amount = 100 + @credit_card = credit_card('4444333322221111', { + month: 3, + year: 2030, + verification_value: 737 + }) + @not_supported_cc = credit_card('4444333322221111', { + month: 3, + year: 2030, + verification_value: 737, + brand: 'alelo' + }) + @declined_card = credit_card('4000300011112220') + @options = { + email: 'johndoe@reach.com', + order_id: '123', + description: 'Store Purchase', + currency: 'USD', + billing_address: { + address1: '1670', + address2: '1670 NW 82ND AVE', + city: 'Miami', + state: 'FL', + zip: '32191', + country: 'US' + }, + device_fingerprint: fingerprint + } + @non_valid_authorization = SecureRandom.uuid + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_failed_authorize + @options[:currency] = 'PPP' + @options.delete(:email) + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Invalid ConsumerCurrency', response.message + end + + def test_failed_authorize_with_not_supported_payment_method + response = @gateway.authorize(@amount, @not_supported_cc, @options) + + assert_failure response + assert_equal 'PaymentMethodUnsupported', response.error_code + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'NotATestCard', response.message + end + + def test_successful_purchase_with_fingerprint + @options[:device_fingerprint] = '54fd66c2-b5b5-4dbd-ab89-12a8b6177347' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_successful_purchase_with_shipping_data + @options[:price] = '1.01' + @options[:taxes] = '2.01' + @options[:duty] = '1.01' + + @options[:consignee_name] = 'Jane Doe' + @options[:consignee_address] = '1670 NW 82ND STR' + @options[:consignee_city] = 'Houston' + @options[:consignee_country] = 'US' + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_failed_purchase_with_incomplete_shipping_data + @options[:price] = '1.01' + @options[:taxes] = '2.01' + + @options[:consignee_name] = 'Jane Doe' + @options[:consignee_address] = '1670 NW 82ND STR' + @options[:consignee_city] = 'Houston' + @options[:consignee_country] = 'US' + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Invalid shipping values.', response.message + end + + def test_failed_purchase_with_shipping_data_and_no_consignee_info + @options[:price] = '1.01' + @options[:taxes] = '2.01' + @options[:duty] = '1.01' + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Invalid JSON submitted', response.message + end + + def test_successful_purchase_with_items + @options[:items] = [ + { + Sku: SecureRandom.alphanumeric, + ConsumerPrice: '10', + Quantity: 1 + }, + { + Sku: SecureRandom.alphanumeric, + ConsumerPrice: '90', + Quantity: 1 + } + ] + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + # The Complete flag in the response returns false when capture is + # in progress + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + response = @gateway.capture(@amount, response.authorization) + assert_success response + end + + def test_successful_purchase_with_store_credentials + @options[:stored_credential] = { initiator: 'cardholder', initial_transaction: true, reason_type: 'installment' } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_successful_purchase_with_store_credentials_mit + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: false, reason_type: 'recurring' } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_successful_purchase_with_store_credentials_mit_and_network_transaction_id + @options[:stored_credential] = { initiator: 'cardholder', initial_transaction: true, reason_type: 'installment' } + purchase = @gateway.purchase(@amount, @credit_card, @options) + + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: false, reason_type: 'unscheduled', network_transaction_id: purchase.network_transaction_id } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + + assert response.params['response']['Authorized'] + assert response.params['response']['OrderId'] + end + + def test_failed_purchase_with_store_credentials_mit_and_network_transaction_id + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: false, reason_type: 'unscheduled', network_transaction_id: 'uhh123' } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + + assert_equal response.message, 'InvalidPreviousNetworkPaymentReference' + end + + def test_failed_purchase_payment_model_nil + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: false, reason_type: 'installment', network_transaction_id: 'uhh123' } + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Invalid PaymentModel', response.message + end + + def test_failed_capture + response = @gateway.capture(@amount, "#{@gateway.options[:merchant_id]}#123") + + assert_failure response + assert_equal 'Not Found', response.message + end + + def test_successful_refund_with_reference_id + response = @gateway.refund( + @amount, + '7d689cc1-4478-4e92-8cd9-f05528cde2f4', + { reference_id: 'REFUND_TAG' } + ) + + assert_success response + assert response.params['response']['RefundId'].present? + end + + def test_successful_refund_with_order_id + response = @gateway.refund( + @amount, + '7d689cc1-4478-4e92-8cd9-f05528cde2f4', + { order_id: 'REFUND_TAG' } + ) + + assert_success response + assert response.params['response']['RefundId'].present? + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.refund(@amount, purchase.authorization, { reference_id: 'REFUND_TAG' }) + + assert_failure response + assert_equal 'OrderStateInvalid', response.error_code + assert response.params['response']['OrderId'].present? + end + + def test_successful_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.void(authorize.authorization, @options) + + assert_success response + assert response.params['response']['OrderId'].present? + end + + def test_failed_void + response = @gateway.void(@non_valid_authorization, @options) + assert_failure response + + assert_equal 'Not Found', response.message + assert response.params.blank? + end + + def test_successful_partial_void + authorize = @gateway.authorize(@amount / 2, @credit_card, @options) + response = @gateway.void(authorize.authorization, @options) + + assert_success response + assert response.params['response']['OrderId'].present? + end + + def test_successful_void_higher_amount + authorize = @gateway.authorize(@amount * 2, @credit_card, @options) + response = @gateway.void(authorize.authorization, @options) + + assert_success response + assert response.params['response']['OrderId'].present? + end + + def test_successful_double_void_and_idempotent + authorize = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.void(authorize.authorization, @options) + + assert_success response + assert response.params['response']['OrderId'].present? + + second_void_response = @gateway.void(authorize.authorization, @options) + + assert_success second_void_response + assert second_void_response.params['response']['OrderId'].present? + + assert_equal response.params['response']['OrderId'], second_void_response.params['response']['OrderId'] + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + + assert_success response + assert response.params['response']['OrderId'].present? + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + + assert_failure response + assert response.params['response']['OrderId'].present? + assert_equal 'PaymentAuthorizationFailed', response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:merchant_id], transcript) + assert_scrubbed(@gateway.options[:secret], transcript) + end + + 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 + end +end diff --git a/test/remote/gateways/remote_realex_test.rb b/test/remote/gateways/remote_realex_test.rb index b8280a6d8bc..a45904d8e60 100644 --- a/test/remote/gateways/remote_realex_test.rb +++ b/test/remote/gateways/remote_realex_test.rb @@ -1,16 +1,16 @@ require 'test_helper' class RemoteRealexTest < Test::Unit::TestCase - def setup @gateway = RealexGateway.new(fixtures(:realex)) # Replace the card numbers with the test account numbers from Realex - @visa = card_fixtures(:realex_visa) - @visa_declined = card_fixtures(:realex_visa_declined) - @visa_referral_b = card_fixtures(:realex_visa_referral_b) - @visa_referral_a = card_fixtures(:realex_visa_referral_a) - @visa_coms_error = card_fixtures(:realex_visa_coms_error) + @visa = card_fixtures(:realex_visa) + @visa_declined = card_fixtures(:realex_visa_declined) + @visa_referral_b = card_fixtures(:realex_visa_referral_b) + @visa_referral_a = card_fixtures(:realex_visa_referral_a) + @visa_coms_error = card_fixtures(:realex_visa_coms_error) + @visa_3ds_enrolled = card_fixtures(:realex_visa_3ds_enrolled) @mastercard = card_fixtures(:realex_mastercard) @mastercard_declined = card_fixtures(:realex_mastercard_declined) @@ -18,14 +18,16 @@ 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 ) - @declined_apple_pay = network_tokenization_credit_card('4000120000001154', + @declined_apple_pay = network_tokenization_credit_card( + '4000120000001154', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', verification_value: nil, eci: '05', @@ -35,17 +37,19 @@ def setup end def card_fixtures(name) - credit_card(nil, fixtures(name)) + credit_card(nil, fixtures(name).merge({ month: 1, year: Time.now.year + 1 })) end def test_realex_purchase - [ @visa, @mastercard ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + [@visa, @mastercard].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) assert_not_nil response @@ -58,12 +62,14 @@ def test_realex_purchase def test_realex_purchase_with_invalid_login gateway = RealexGateway.new( - :login => 'invalid', - :password => 'invalid' + login: 'invalid', + password: 'invalid' ) - response = gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Invalid login test' + response = gateway.purchase( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Invalid login test' ) assert_not_nil response @@ -74,30 +80,34 @@ def test_realex_purchase_with_invalid_login end def test_realex_purchase_with_invalid_account - response = RealexGateway.new(fixtures(:realex_with_account)).purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Test Realex purchase with invalid acocunt' + 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' ) assert_not_nil response assert_failure response - assert_equal '504', response.params['result'] + assert_equal '506', response.params['result'] assert_match %r{no such}i, response.message end def test_realex_purchase_with_apple_pay - response = @gateway.purchase(1000, @apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay') + response = @gateway.purchase(1000, @apple_pay, order_id: generate_unique_id, description: 'Test Realex with ApplePay') assert_success response assert response.test? assert_equal 'Successful', response.message end def test_realex_purchase_declined - [ @visa_declined, @mastercard_declined ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex purchase declined' + [@visa_declined, @mastercard_declined].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex purchase declined' ) assert_not_nil response assert_failure response @@ -108,18 +118,87 @@ def test_realex_purchase_declined end def test_realex_purchase_with_apple_pay_declined - response = @gateway.purchase(1101, @declined_apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay') + response = @gateway.purchase(1101, @declined_apple_pay, order_id: generate_unique_id, description: 'Test Realex with ApplePay') assert_failure response assert response.test? assert_equal '101', response.params['result'] assert_match %r{DECLINED}i, response.message end + def test_realex_purchase_with_three_d_secure_1 + response = @gateway.purchase( + 1000, + @visa_3ds_enrolled, + three_d_secure: { + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=', + version: '1.0.2' + }, + order_id: generate_unique_id, + description: 'Test Realex with 3DS' + ) + assert_success response + assert response.test? + assert_equal 'Successful', response.message + end + + def test_realex_purchase_with_three_d_secure_2 + response = @gateway.purchase( + 1000, + @visa_3ds_enrolled, + three_d_secure: { + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + ds_transaction_id: 'bDE9Aa1A-C5Ac-AD3a-4bBC-aC918ab1de3E', + version: '2.1.0' + }, + order_id: generate_unique_id, + description: 'Test Realex with 3DS' + ) + assert_success response + assert response.test? + assert_equal 'Successful', response.message + end + + def test_initial_purchase_with_stored_credential + options = { + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + } + response = @gateway.purchase(@amount, @visa, options.merge(order_id: generate_unique_id)) + assert_success response + end + + def test_subsequent_purchase_with_stored_credential + initial_response = @gateway.purchase(@amount, @visa, order_id: generate_unique_id) + assert_success initial_response + network_id = initial_response.params['srd'] + + options = { + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: network_id + } + } + + subsequent_response = @gateway.purchase(@amount, @visa, options.merge(order_id: generate_unique_id)) + assert_success subsequent_response + end + def test_realex_purchase_referral_b - [ @visa_referral_b, @mastercard_referral_b ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex Referral B' + [@visa_referral_b, @mastercard_referral_b].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex Referral B' ) assert_not_nil response assert_failure response @@ -130,10 +209,12 @@ def test_realex_purchase_referral_b end def test_realex_purchase_referral_a - [ @visa_referral_a, @mastercard_referral_a ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex Rqeferral A' + [@visa_referral_a, @mastercard_referral_a].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex Rqeferral A' ) assert_not_nil response assert_failure response @@ -143,12 +224,13 @@ def test_realex_purchase_referral_a end def test_realex_purchase_coms_error - [ @visa_coms_error, @mastercard_coms_error ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex coms error' + [@visa_coms_error, @mastercard_coms_error].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex coms error' ) - assert_not_nil response assert_failure response @@ -160,9 +242,11 @@ def test_realex_purchase_coms_error def test_realex_expiry_month_error @visa.month = 13 - response = @gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Test Realex expiry month error' + response = @gateway.purchase( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Test Realex expiry month error' ) assert_not_nil response assert_failure response @@ -174,9 +258,11 @@ def test_realex_expiry_month_error def test_realex_expiry_year_error @visa.year = 2005 - response = @gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Test Realex expiry year error' + response = @gateway.purchase( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Test Realex expiry year error' ) assert_not_nil response assert_failure response @@ -189,9 +275,11 @@ def test_invalid_credit_card_name @visa.first_name = '' @visa.last_name = '' - response = @gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'test_chname_error' + response = @gateway.purchase( + @amount, + @visa, + order_id: generate_unique_id, + description: 'test_chname_error' ) assert_not_nil response assert_failure response @@ -203,9 +291,11 @@ def test_invalid_credit_card_name def test_cvn @visa_cvn = @visa.clone @visa_cvn.verification_value = '111' - response = @gateway.purchase(@amount, @visa_cvn, - :order_id => generate_unique_id, - :description => 'test_cvn' + response = @gateway.purchase( + @amount, + @visa_cvn, + order_id: generate_unique_id, + description: 'test_cvn' ) assert_not_nil response assert_success response @@ -213,10 +303,12 @@ def test_cvn end def test_customer_number - response = @gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'test_cust_num', - :customer => 'my customer id' + response = @gateway.purchase( + @amount, + @visa, + order_id: generate_unique_id, + description: 'test_cust_num', + customer: 'my customer id' ) assert_not_nil response assert_success response @@ -224,12 +316,14 @@ def test_customer_number end def test_realex_authorize - response = @gateway.authorize(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + response = @gateway.authorize( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) @@ -243,12 +337,14 @@ def test_realex_authorize def test_realex_authorize_then_capture order_id = generate_unique_id - auth_response = @gateway.authorize(@amount, @visa, - :order_id => order_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + auth_response = @gateway.authorize( + @amount, + @visa, + order_id: order_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) assert auth_response.test? @@ -265,12 +361,14 @@ 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, - :order_id => order_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + 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? @@ -287,12 +385,14 @@ 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, - :order_id => order_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + purchase_response = @gateway.purchase( + @amount, + @visa, + order_id: order_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) assert purchase_response.test? @@ -308,14 +408,16 @@ def test_realex_purchase_then_void def test_realex_purchase_then_refund order_id = generate_unique_id - gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:rebate_secret => 'rebate')) + gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(rebate_secret: 'rebate')) - purchase_response = gateway_with_refund_password.purchase(@amount, @visa, - :order_id => order_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + 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? @@ -329,9 +431,10 @@ def test_realex_purchase_then_refund end def test_realex_verify - response = @gateway.verify(@visa, - :order_id => generate_unique_id, - :description => 'Test Realex verify' + response = @gateway.verify( + @visa, + order_id: generate_unique_id, + description: 'Test Realex verify' ) assert_not_nil response @@ -342,9 +445,10 @@ def test_realex_verify end def test_realex_verify_declined - response = @gateway.verify(@visa_declined, - :order_id => generate_unique_id, - :description => 'Test Realex verify declined' + response = @gateway.verify( + @visa_declined, + order_id: generate_unique_id, + description: 'Test Realex verify declined' ) assert_not_nil response @@ -354,14 +458,54 @@ def test_realex_verify_declined assert_match %r{DECLINED}i, response.message end + def test_successful_credit + gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(refund_secret: 'refund')) + + credit_response = gateway_with_refund_password.credit( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Test Realex Credit', + billing_address: { + zip: '90210', + country: 'US' + } + ) + + assert_not_nil credit_response + assert_success credit_response + assert credit_response.authorization.length > 0 + assert_equal 'Successful', credit_response.message + end + + def test_failed_credit + credit_response = @gateway.credit( + @amount, + @visa, + order_id: generate_unique_id, + description: 'Test Realex Credit', + billing_address: { + zip: '90210', + country: 'US' + } + ) + + assert_not_nil credit_response + assert_failure credit_response + assert credit_response.authorization.length > 0 + assert_equal 'Refund Hash not present.', credit_response.message + end + def test_maps_avs_and_cvv_response_codes - [ @visa, @mastercard ].each do |card| - response = @gateway.purchase(@amount, card, - :order_id => generate_unique_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + [@visa, @mastercard].each do |card| + response = @gateway.purchase( + @amount, + card, + order_id: generate_unique_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) assert_not_nil response @@ -373,12 +517,14 @@ def test_maps_avs_and_cvv_response_codes def test_transcript_scrubbing transcript = capture_transcript(@gateway) do - @gateway.purchase(@amount, @visa_declined, - :order_id => generate_unique_id, - :description => 'Test Realex Purchase', - :billing_address => { - :zip => '90210', - :country => 'US' + @gateway.purchase( + @amount, + @visa_declined, + order_id: generate_unique_id, + description: 'Test Realex Purchase', + billing_address: { + zip: '90210', + country: 'US' } ) end diff --git a/test/remote/gateways/remote_redsys_sha256_test.rb b/test/remote/gateways/remote_redsys_sha256_test.rb index 43718a5f67a..334e4cc21ca 100644 --- a/test/remote/gateways/remote_redsys_sha256_test.rb +++ b/test/remote/gateways/remote_redsys_sha256_test.rb @@ -3,27 +3,288 @@ class RemoteRedsysSHA256Test < Test::Unit::TestCase def setup @gateway = RedsysGateway.new(fixtures(:redsys_sha256)) + @amount = 100 @credit_card = credit_card('4548812049400004') @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, + order_id: generate_order_id } end def test_successful_purchase - response = @gateway.purchase(100, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_threeds1_as_mpi_has_no_effect + # 1.0.2 is NOT supported by RedSys External MPI; thus, behaviour should be same as if not present. + xid = '97267598-FAE6-48F2-8083-C23433990FBC' + cavv = '123' + eci = '01' + version = '1.0.2' + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + three_d_secure: { + version: version, + xid: xid, + cavv: cavv, + eci: eci + }, + description: 'description', + store: 'store', + sca_exemption: 'MOTO' + ) + ) + + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_succesful_purchase_threeds1_as_mpi + xid = '97267598-FAE6-48F2-8083-C23433990FBC' + cavv = '123' + eci = '01' + version = '0.0.0' + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + three_d_secure: { + version: version, + xid: xid, + cavv: cavv, + eci: eci + }, + description: 'description', + store: 'store', + sca_exemption: 'MOTO' + ) + ) + + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_threeds2_as_mpi + three_ds_server_trans_id = '97267598-FAE6-48F2-8083-C23433990FBC' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + eci = '01' + version = '2.1.0' + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + three_d_secure: { + version: version, + ds_transaction_id: ds_transaction_id, + three_ds_server_trans_id: three_ds_server_trans_id, + eci: eci + }, + description: 'description', + store: 'store', + sca_exemption: 'MOTO' + ) + ) + + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_authorize_3ds + 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 'NO_3DS_v2', JSON.parse(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, terminal: 12) + response = @gateway.purchase(@amount, @threeds2_credit_card, options) + assert_success response + assert three_ds_data = JSON.parse(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 + + # 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 + + 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 + + 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 + + def test_successful_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Transaction Approved', initial_response.message + assert_not_nil initial_response.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_response.params['ds_merchant_cof_txnid'] + + used_options = @options.merge( + order_id: generate_order_id, + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, used_options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_auth_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Transaction Approved', initial_response.message + assert_not_nil initial_response.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_response.params['ds_merchant_cof_txnid'] + + used_options = @options.merge( + order_id: generate_order_id, + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, used_options) assert_success response assert_equal 'Transaction Approved', response.message end + def test_successful_3ds2_purchase_with_mit_exemption + options = @options.merge(execute_threed: true, terminal: 12) + response = @gateway.purchase(@amount, @threeds2_credit_card, options.merge(sca_exemption: 'MIT', sca_exemption_direct_payment_enabled: true)) + assert_success response + assert response.params['ds_emv3ds'] + assert response.params['ds_card_psd2'] + assert_equal '2.1.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion'] + assert_equal 'Y', response.params['ds_card_psd2'] + assert_equal 'CardConfiguration', response.message + + # ensure MIT exemption is recognized + assert response.params['ds_excep_sca'] + assert_match 'MIT', response.params['ds_excep_sca'] + end + + def test_successful_ntid_generation_with_verify + # This test validates a special use case for generating a network transaction id + # for payment methods that previously relied on the 'magic' fifteen-nines value. + # However, the transaction id will only be returned from a Redsys production + # environment, so this test simply validates that the zero-value request succeeds. + + alternate_gateway = RedsysGateway.new(fixtures(:redsys_sha256_alternate)) + options = @options.merge( + sca_exemption_behavior_override: 'endpoint_and_ntid', + sca_exemption: 'MIT', + terminal: 2, + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: '999999999999999' + } + ) + response = alternate_gateway.verify(@credit_card, options) + assert_success response + end + + def test_failed_3ds2_purchase_with_mit_exemption_but_missing_direct_payment_enabled + options = @options.merge(execute_threed: true, terminal: 12) + response = @gateway.purchase(@amount, @threeds2_credit_card, options.merge(sca_exemption: 'MIT')) + assert_success response + assert response.params['ds_emv3ds'] + assert response.params['ds_card_psd2'] + assert_equal '2.1.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion'] + assert_equal 'Y', response.params['ds_card_psd2'] + assert_equal 'CardConfiguration', response.message + + # ensure MIT exemption is recognized + assert response.params['ds_excep_sca'] + assert_match 'MIT', response.params['ds_excep_sca'] + end + + def test_successful_purchase_with_mit_exemption + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Transaction Approved', initial_response.message + assert_not_nil initial_response.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_response.params['ds_merchant_cof_txnid'] + + used_options = @options.merge( + order_id: generate_order_id, + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: network_transaction_id + }, + sca_exemption: 'MIT', + sca_exemption_direct_payment_enabled: true + ) + + response = @gateway.purchase(@amount, @credit_card, used_options) + assert_success response + assert_equal response.message, 'Transaction Approved' + end + def test_purchase_with_invalid_order_id - response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_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_successful_purchase_using_vault_id - response = @gateway.purchase(100, @credit_card, @options.merge(store: true)) + response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true)) assert_success response assert_equal 'Transaction Approved', response.message @@ -31,44 +292,44 @@ def test_successful_purchase_using_vault_id assert_not_nil credit_card_token @options[:order_id] = generate_order_id - response = @gateway.purchase(100, credit_card_token, @options) + response = @gateway.purchase(@amount, credit_card_token, @options) assert_success response assert_equal 'Transaction Approved', response.message end def test_failed_purchase - response = @gateway.purchase(100, @declined_card, @options) + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_purchase_and_refund - purchase = @gateway.purchase(100, @credit_card, @options) + purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(100, purchase.authorization) + refund = @gateway.refund(@amount, purchase.authorization) assert_success refund end # Multiple currencies are not supported in test, but should at least fail. def test_purchase_and_refund_with_currency - response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN')) + response = @gateway.purchase(600, @credit_card, @options.merge(currency: 'PEN')) assert_failure response assert_equal 'SIS0027 ERROR', response.message end def test_successful_authorise_and_capture - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization - capture = @gateway.capture(100, authorize.authorization) + capture = @gateway.capture(@amount, authorize.authorization) assert_success capture assert_match(/Refund.*approved/, capture.message) end def test_successful_authorise_using_vault_id - authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true)) + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(store: true)) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization @@ -77,20 +338,20 @@ def test_successful_authorise_using_vault_id assert_not_nil credit_card_token @options[:order_id] = generate_order_id - authorize = @gateway.authorize(100, credit_card_token, @options) + authorize = @gateway.authorize(@amount, credit_card_token, @options) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization end def test_failed_authorize - response = @gateway.authorize(100, @declined_card, @options) + response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_successful_void - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize void = @gateway.void(authorize.authorization) @@ -100,7 +361,7 @@ def test_successful_void end def test_failed_void - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize void = @gateway.void(authorize.authorization) @@ -123,7 +384,7 @@ def test_successful_verify def test_unsuccessful_verify assert response = @gateway.verify(@declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_transcript_scrubbing diff --git a/test/remote/gateways/remote_redsys_test.rb b/test/remote/gateways/remote_redsys_test.rb index cfaf52723b2..b8c790d30f9 100644 --- a/test/remote/gateways/remote_redsys_test.rb +++ b/test/remote/gateways/remote_redsys_test.rb @@ -3,29 +3,99 @@ class RemoteRedsysTest < Test::Unit::TestCase def setup @gateway = RedsysGateway.new(fixtures(:redsys)) + @amount = 100 @credit_card = credit_card('4548812049400004') @declined_card = credit_card @options = { order_id: generate_order_id, description: 'Test Description' } - @amount = 100 end def test_successful_purchase - response = @gateway.purchase(100, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_threeds2 + three_ds_server_trans_id = '97267598-FAE6-48F2-8083-C23433990FBC' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + version = '2.1.0' + eci = '02' + + response = @gateway.purchase( + 100, + @credit_card, + @options.merge( + three_d_secure: { + version: version, + ds_transaction_id: ds_transaction_id, + three_ds_server_trans_id: three_ds_server_trans_id, + eci: eci + } + ) + ) + + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_threeds1 + xid = '97267598-FAE6-48F2-8083-C23433990FBC' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + eci = '02' + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + three_d_secure: { + eci: eci, + cavv: cavv, + xid: xid + } + ) + ) + + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_with_stored_credentials + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_response = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success initial_response + assert_equal 'Transaction Approved', initial_response.message + assert_not_nil initial_response.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_response.params['ds_merchant_cof_txnid'] + + used_options = @options.merge( + order_id: generate_order_id, + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + network_transaction_id: network_transaction_id + } + ) + response = @gateway.purchase(@amount, @credit_card, used_options) assert_success response assert_equal 'Transaction Approved', response.message end def test_purchase_with_invalid_order_id - response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_order_id}") + 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_successful_purchase_using_vault_id - response = @gateway.purchase(100, @credit_card, @options.merge(store: true)) + response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true)) assert_success response assert_equal 'Transaction Approved', response.message @@ -33,44 +103,44 @@ def test_successful_purchase_using_vault_id assert_not_nil credit_card_token @options[:order_id] = generate_order_id - response = @gateway.purchase(100, credit_card_token, @options) + response = @gateway.purchase(@amount, credit_card_token, @options) assert_success response assert_equal 'Transaction Approved', response.message end def test_failed_purchase - response = @gateway.purchase(100, @declined_card, @options) + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_purchase_and_refund - purchase = @gateway.purchase(100, @credit_card, @options) + purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(100, purchase.authorization) + refund = @gateway.refund(@amount, purchase.authorization) assert_success refund end # Multiple currencies are not supported in test, but should at least fail. def test_purchase_and_refund_with_currency - response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN')) + response = @gateway.purchase(600, @credit_card, @options.merge(currency: 'PEN')) assert_failure response assert_equal 'SIS0027 ERROR', response.message end def test_successful_authorise_and_capture - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization - capture = @gateway.capture(100, authorize.authorization) + capture = @gateway.capture(@amount, authorize.authorization) assert_success capture assert_match(/Refund.*approved/, capture.message) end def test_successful_authorise_using_vault_id - authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true)) + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(store: true)) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization @@ -79,20 +149,20 @@ def test_successful_authorise_using_vault_id assert_not_nil credit_card_token @options[:order_id] = generate_order_id - authorize = @gateway.authorize(100, credit_card_token, @options) + authorize = @gateway.authorize(@amount, credit_card_token, @options) assert_success authorize assert_equal 'Transaction Approved', authorize.message assert_not_nil authorize.authorization end def test_failed_authorize - response = @gateway.authorize(100, @declined_card, @options) + response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_successful_void - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize void = @gateway.void(authorize.authorization) @@ -102,7 +172,7 @@ def test_successful_void end def test_failed_void - authorize = @gateway.authorize(100, @credit_card, @options) + authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize void = @gateway.void(authorize.authorization) @@ -125,7 +195,7 @@ def test_successful_verify def test_unsuccessful_verify assert response = @gateway.verify(@declined_card, @options) assert_failure response - assert_equal 'SIS0093 ERROR', response.message + assert_equal 'Refusal with no specific reason', response.message end def test_transcript_scrubbing @@ -180,6 +250,17 @@ def test_whitespace_string_cvv_transcript_scrubbing assert_equal clean_transcript.include?('[BLANK]'), true end + def test_encrypt_handles_url_safe_character_in_secret_key_without_error + gateway = RedsysGateway.new({ + login: '091952713', + secret_key: 'yG78qf-PkHyRzRiZGSTCJdO2TvjWgFa8', + terminal: '1', + signature_algorithm: 'sha256' + }) + response = gateway.purchase(@amount, @credit_card, @options) + assert response + end + private def generate_order_id diff --git a/test/remote/gateways/remote_s5_test.rb b/test/remote/gateways/remote_s5_test.rb index 1c9f31bf0ab..b1c831ea98a 100644 --- a/test/remote/gateways/remote_s5_test.rb +++ b/test/remote/gateways/remote_s5_test.rb @@ -90,7 +90,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -111,7 +111,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb index fc64bacb1c1..8e8fe4dd945 100644 --- a/test/remote/gateways/remote_safe_charge_test.rb +++ b/test/remote/gateways/remote_safe_charge_test.rb @@ -16,9 +16,18 @@ def setup @three_ds_options = @options.merge(three_d_secure: true) @three_ds_gateway = SafeChargeGateway.new(fixtures(:safe_charge_three_ds)) - @three_ds_enrolled_card = credit_card('4012 0010 3749 0014') + @three_ds_enrolled_card = credit_card('4407 1064 3967 1112') @three_ds_non_enrolled_card = credit_card('5333 3062 3122 6927') @three_ds_invalid_pa_res_card = credit_card('4012 0010 3749 0006') + + @network_token_credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + brand: 'Visa', + payment_cryptogram: 'UnVBR0RlYm42S2UzYWJKeWJBdWQ=', + number: '4012001037490014', + source: :network_token, + month: '12', + year: 2020 + }) end def test_successful_3ds_purchase @@ -43,9 +52,7 @@ def test_successful_regular_purchase_through_3ds_flow_with_non_enrolled_card def test_successful_regular_purchase_through_3ds_flow_with_invalid_pa_res response = @three_ds_gateway.purchase(@amount, @three_ds_invalid_pa_res_card, @three_ds_options) assert_success response - assert !response.params['acsurl'].blank? - assert !response.params['pareq'].blank? - assert !response.params['xid'].blank? + assert_equal 'Attempted But Card Not Enrolled', response.params['threedreason'] assert response.params['threedflow'] = 1 assert_equal 'Success', response.message end @@ -56,6 +63,89 @@ def test_successful_purchase assert_equal 'Success', response.message end + def test_successful_purchase_with_non_fractional_currency + options = @options.merge(currency: 'CLP') + response = @gateway.purchase(127999, @credit_card, options) + + assert_success response + assert_equal 'Success', response.message + assert_equal '1279', response.params['requestedamount'] + end + + def test_successful_purchase_with_mpi_options_3ds_1 + options = @options.merge({ + three_d_secure: { + xid: '00000000000000000501', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=' + } + }) + + response = @gateway.purchase(@amount, @three_ds_enrolled_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_mpi_options_3ds_2 + options = @options.merge({ + three_d_secure: { + version: '2.1.0', + ds_transaction_id: 'c5b808e7-1de1-4069-a17b-f70d3b3b1645', + eci: '05', + cavv: 'Vk83Y2t0cHRzRFZzRlZlR0JIQXo=', + challenge_preference: 'NoPreference' + } + }) + + response = @gateway.purchase(@amount, @three_ds_enrolled_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_network_tokenization_request + options = @options.merge({ + three_d_secure: { + eci: '05' + } + }) + + response = @gateway.purchase(@amount, @network_token_credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_mpi_options_3ds_2 + options = @options.merge({ + three_d_secure: { + version: '2.1.0', + ds_transaction_id: 'c5b808e7-1de1-4069-a17b-f70d3b3b1645', + eci: '05', + cavv: 'Vk83Y2t0cHRzRFZzRlZlR0JIQXo=', + challenge_preference: 'NoPreference' + } + }) + + response = @gateway.purchase(@amount, @declined_card, options) + assert_failure response + assert_equal 'Decline', response.message + end + + def test_successful_authorize_with_mpi_options_3ds_2 + options = @options.merge({ + three_d_secure: { + version: '2.1.0', + ds_transaction_id: 'c5b808e7-1de1-4069-a17b-f70d3b3b1645', + eci: '05', + cavv: 'Vk83Y2t0cHRzRFZzRlZlR0JIQXo=', + challenge_preference: 'NoPreference' + } + }) + + response = @gateway.authorize(@amount, @three_ds_enrolled_card, options) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_purchase_with_more_options options = { order_id: '1', @@ -67,7 +157,8 @@ def test_successful_purchase_with_more_options merchant_descriptor: 'Test Descriptor', merchant_phone_number: '(555)555-5555', merchant_name: 'Test Merchant', - stored_credential_mode: true + stored_credential_mode: true, + product_id: 'Test Product' } response = @gateway.purchase(@amount, @credit_card, options) @@ -101,11 +192,22 @@ def test_successful_authorize_and_capture_with_more_options merchant_descriptor: 'Test Descriptor', merchant_phone_number: '(555)555-5555', merchant_name: 'Test Merchant', - stored_credential_mode: true + stored_credential_mode: true, + product_id: 'Test Product' } auth = @gateway.authorize(@amount, @credit_card, extra) assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization, extra) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_and_capture_with_not_use_cvv + @credit_card.verification_value = nil + auth = @gateway.authorize(@amount, @credit_card, @options.merge!({ not_use_cvv: true })) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture assert_equal 'Success', capture.message @@ -121,7 +223,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -154,6 +256,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 @@ -171,7 +295,8 @@ def test_successful_credit_with_extra_options merchant_descriptor: 'Test Descriptor', merchant_phone_number: '(555)555-5555', merchant_name: 'Test Merchant', - stored_credential_mode: true + stored_credential_mode: true, + product_id: 'Test Product' } response = @gateway.credit(@amount, credit_card('4444436501403986'), extra) @@ -179,6 +304,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 @@ -230,5 +361,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:client_password], transcript) end - end diff --git a/test/remote/gateways/remote_sage_pay_test.rb b/test/remote/gateways/remote_sage_pay_test.rb index c7647d53969..487332e1097 100644 --- a/test/remote/gateways/remote_sage_pay_test.rb +++ b/test/remote/gateways/remote_sage_pay_test.rb @@ -12,90 +12,89 @@ def setup @gateway = SagePayGateway.new(fixtures(:sage_pay)) @amex = CreditCard.new( - :number => '374200000000004', - :month => 12, - :year => next_year, - :verification_value => 4887, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'american_express' + number: '374200000000004', + month: 12, + year: next_year, + verification_value: 4887, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'american_express' ) @maestro = CreditCard.new( - :number => '5641820000000005', - :month => 12, - :year => next_year, - :issue_number => '01', - :start_month => 12, - :start_year => next_year - 2, - :verification_value => 123, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'maestro' + number: '5641820000000005', + month: 12, + year: next_year, + start_month: 12, + start_year: next_year - 2, + verification_value: 123, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'maestro' ) @visa = CreditCard.new( - :number => '4929000000006', - :month => 6, - :year => next_year, - :verification_value => 123, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'visa' + number: '4929000000006', + month: 6, + year: next_year, + verification_value: 123, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'visa' ) @mastercard = CreditCard.new( - :number => '5404000000000001', - :month => 12, - :year => next_year, - :verification_value => 419, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'master' + number: '5404000000000001', + month: 12, + year: next_year, + verification_value: 419, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'master' ) @electron = CreditCard.new( - :number => '4917300000000008', - :month => 12, - :year => next_year, - :verification_value => 123, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'electron' + number: '4917300000000008', + month: 12, + year: next_year, + verification_value: 123, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'electron' ) @declined_card = CreditCard.new( - :number => '4111111111111111', - :month => 9, - :year => next_year, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'visa' + number: '4111111111111111', + month: 9, + year: next_year, + first_name: 'Tekin', + last_name: 'Suleyman', + brand: 'visa' ) @options = { - :billing_address => { - :name => 'Tekin Suleyman', - :address1 => 'Flat 10 Lapwing Court', - :address2 => 'West Didsbury', - :city => 'Manchester', - :county => 'Greater Manchester', - :country => 'GB', - :zip => 'M20 2PS' + 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' + 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' + order_id: generate_unique_id, + description: 'Store purchase', + ip: '86.150.65.37', + email: 'tekin@tekin.co.uk', + phone: '0161 123 4567' } @amount = 100 @@ -116,6 +115,19 @@ def test_unsuccessful_purchase assert response.test? end + def test_successful_purchase_via_reference + assert initial_response = @gateway.purchase(@amount, @mastercard, @options) + assert_success initial_response + + options = @options.merge(order_id: generate_unique_id) + assert first_reference_response = @gateway.purchase(@amount, initial_response.authorization, options) + assert_success first_reference_response + + options = @options.merge(order_id: generate_unique_id) + assert second_reference_response = @gateway.purchase(@amount, first_reference_response.authorization, options) + assert_success second_reference_response + end + def test_successful_authorization_and_capture assert auth = @gateway.authorize(@amount, @mastercard, @options) assert_success auth @@ -130,11 +142,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 @@ -157,12 +165,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 @@ -236,7 +239,7 @@ def test_successful_purchase_with_apply_avscv2_field @options[:apply_avscv2] = 1 response = @gateway.purchase(@amount, @visa, @options) assert_success response - assert_equal 'Y', response.cvv_result['code'] + assert_equal 'M', response.cvv_result['code'] end def test_successful_purchase_with_pay_pal_callback_url @@ -331,7 +334,7 @@ def test_invalid_login message = SagePayGateway.simulate ? 'VSP Simulator cannot find your vendor name. Ensure you have have supplied a Vendor field with your VSP Vendor name assigned to it.' : '3034 : The Vendor or VendorName value is required.' gateway = SagePayGateway.new( - :login => '' + login: '' ) assert response = gateway.purchase(@amount, @mastercard, @options) assert_equal message, response.message @@ -364,13 +367,13 @@ def test_successful_store_and_authorize end def test_successful_token_creation_from_purchase - assert response = @gateway.purchase(@amount, @visa, @options.merge(:store => true)) + assert response = @gateway.purchase(@amount, @visa, @options.merge(store: true)) assert_success response assert !response.authorization.blank? end def test_successful_token_creation_from_authorize - assert response = @gateway.authorize(@amount, @visa, @options.merge(:store => true)) + assert response = @gateway.authorize(@amount, @visa, @options.merge(store: true)) assert_success response assert !response.authorization.blank? end @@ -414,56 +417,56 @@ def next_year # Based on example from http://www.sagepay.co.uk/support/basket-xml # Only kept required fields to make sense def basket_xml - <<-XML - - - DVD 1 - 2 - 24.50 - 00.50 - 25.00 - 50.00 - - + <<~XML + + + DVD 1 + 2 + 24.50 + 00.50 + 25.00 + 50.00 + + XML end # Example from http://www.sagepay.co.uk/support/customer-xml def customer_xml - <<-XML - - W - 1983-01-01 - 020 1234567 - 0799 1234567 - 0 - 10 - CUST123 - + <<~XML + + W + 1983-01-01 + 020 1234567 + 0799 1234567 + 0 + 10 + CUST123 + XML end # Example from https://www.sagepay.co.uk/support/12/36/protocol-3-00-surcharge-xml def surcharge_xml - <<-XML - - - DELTA - 2.50 - - - VISA - 2.50 - - - AMEX - 1.50 - - - MC - 1.50 - - + <<~XML + + + DELTA + 2.50 + + + VISA + 2.50 + + + AMEX + 1.50 + + + MC + 1.50 + + XML end end diff --git a/test/remote/gateways/remote_sage_test.rb b/test/remote/gateways/remote_sage_test.rb index 5107a12e97d..1bbca7cbad1 100644 --- a/test/remote/gateways/remote_sage_test.rb +++ b/test/remote/gateways/remote_sage_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteSageTest < Test::Unit::TestCase - def setup @gateway = SageGateway.new(fixtures(:sage)) @@ -16,10 +15,10 @@ def setup @declined_card = credit_card('4000') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :email => 'longbob@example.com' + order_id: generate_unique_id, + billing_address: address, + shipping_address: address, + email: 'longbob@example.com' } end @@ -157,7 +156,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @visa, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options) assert_success refund assert_equal 'APPROVED', refund.message end @@ -165,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 @@ -177,24 +175,22 @@ 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 def test_invalid_login gateway = SageGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @visa, @options) assert_failure response assert_equal 'SECURITY VIOLATION', response.message @@ -231,5 +227,4 @@ def test_echeck_scrubbing assert_scrubbed(@check.routing_number, transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/test/remote/gateways/remote_sallie_mae_test.rb b/test/remote/gateways/remote_sallie_mae_test.rb index f2b47acef22..e9f30994baf 100644 --- a/test/remote/gateways/remote_sallie_mae_test.rb +++ b/test/remote/gateways/remote_sallie_mae_test.rb @@ -9,8 +9,8 @@ def setup @declined_card = credit_card('4000300011112220') @options = { - :billing_address => address, - :description => 'Store Purchase' + billing_address: address, + description: 'Store Purchase' } end @@ -43,7 +43,7 @@ def test_failed_capture end def test_invalid_login - gateway = SallieMaeGateway.new(:login => '') + gateway = SallieMaeGateway.new(login: '') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid merchant', response.message diff --git a/test/remote/gateways/remote_secure_net_test.rb b/test/remote/gateways/remote_secure_net_test.rb index fc92a5276e1..a37510fe077 100644 --- a/test/remote/gateways/remote_secure_net_test.rb +++ b/test/remote/gateways/remote_secure_net_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class SecureNetTest < Test::Unit::TestCase - def setup Base.mode = :test @gateway = SecureNetGateway.new(fixtures(:secure_net)) @@ -29,9 +28,9 @@ def test_expired_credit_card def test_invalid_login gateway = SecureNetGateway.new( - :login => '9988776', - :password => 'RabbitEarsPo' - ) + login: '9988776', + password: 'RabbitEarsPo' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'SECURE KEY IS INVALID FOR SECURENET ID PROVIDED', response.message @@ -135,5 +134,4 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:password], transcript) end - end diff --git a/test/remote/gateways/remote_secure_pay_au_test.rb b/test/remote/gateways/remote_secure_pay_au_test.rb index fc99db2ef81..13323f9572b 100644 --- a/test/remote/gateways/remote_secure_pay_au_test.rb +++ b/test/remote/gateways/remote_secure_pay_au_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteSecurePayAuTest < Test::Unit::TestCase - class MyCreditCard include ActiveMerchant::Billing::CreditCardMethods attr_accessor :number, :month, :year, :first_name, :last_name, :verification_value, :brand @@ -15,12 +14,12 @@ def setup @gateway = SecurePayAuGateway.new(fixtures(:secure_pay_au)) @amount = 100 - @credit_card = credit_card('4242424242424242', {:month => 9, :year => 15}) + @credit_card = credit_card('4242424242424242', { month: 9, year: 15 }) @options = { - :order_id => '2', - :billing_address => address, - :description => 'Store Purchase' + order_id: '2', + billing_address: address, + description: 'Store Purchase' } end @@ -32,13 +31,13 @@ def test_successful_purchase def test_successful_purchase_with_custom_credit_card_class options = { - :number => 4242424242424242, - :month => 9, - :year => Time.now.year + 1, - :first_name => 'Longbob', - :last_name => 'Longsen', - :verification_value => '123', - :brand => 'visa' + number: 4242424242424242, + month: 9, + year: Time.now.year + 1, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: '123', + brand: 'visa' } credit_card = MyCreditCard.new(options) assert response = @gateway.purchase(@amount, credit_card, @options) @@ -73,7 +72,7 @@ def test_failed_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount+1, auth.authorization) + assert capture = @gateway.capture(@amount + 1, auth.authorization) assert_failure capture assert_equal 'Preauth was done for smaller amount', capture.message end @@ -93,7 +92,7 @@ def test_failed_refund assert_success response authorization = response.authorization - assert response = @gateway.refund(@amount+1, authorization) + assert response = @gateway.refund(@amount + 1, authorization) assert_failure response assert_equal 'Only $1.0 available for refund', response.message end @@ -115,13 +114,13 @@ def test_failed_void assert_success response authorization = response.authorization - assert response = @gateway.void(authorization+'1') + assert response = @gateway.void(authorization + '1') assert_failure response assert_equal 'Unable to retrieve original FDR txn', response.message end def test_successful_unstore - @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil + @gateway.store(@credit_card, { billing_id: 'test1234', amount: 15000 }) rescue nil assert response = @gateway.unstore('test1234') assert_success response @@ -140,23 +139,23 @@ def test_repeat_unstore def test_successful_store @gateway.unstore('test1234') rescue nil - assert response = @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) + assert response = @gateway.store(@credit_card, { billing_id: 'test1234', amount: 15000 }) assert_success response assert_equal 'Successful', response.message end def test_failed_store - @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists + @gateway.store(@credit_card, { billing_id: 'test1234', amount: 15000 }) rescue nil # Ensure it already exists - assert response = @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) + assert response = @gateway.store(@credit_card, { billing_id: 'test1234', amount: 15000 }) assert_failure response assert_equal 'Duplicate Client ID Found', response.message end def test_successful_triggered_payment - @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists + @gateway.store(@credit_card, { billing_id: 'test1234', amount: 15000 }) rescue nil # Ensure it already exists assert response = @gateway.purchase(12300, 'test1234', @options) assert_success response @@ -176,9 +175,9 @@ def test_failure_triggered_payment def test_invalid_login gateway = SecurePayAuGateway.new( - :login => 'a', - :password => 'a' - ) + login: 'a', + password: 'a' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid merchant ID', response.message diff --git a/test/remote/gateways/remote_secure_pay_tech_test.rb b/test/remote/gateways/remote_secure_pay_tech_test.rb index 3b76bf77eef..30e46ee0e07 100644 --- a/test/remote/gateways/remote_secure_pay_tech_test.rb +++ b/test/remote/gateways/remote_secure_pay_tech_test.rb @@ -9,8 +9,8 @@ def setup @accepted_amount = 10000 @declined_amount = 10075 - @credit_card = credit_card('4987654321098769', :month => '5', :year => '2013') - @options = { :billing_address => address } + @credit_card = credit_card('4987654321098769', month: '5', year: '2013') + @options = { billing_address: address } end def test_successful_purchase @@ -43,9 +43,9 @@ def test_unsuccessful_cvv_check def test_invalid_login gateway = SecurePayTechGateway.new( - :login => 'foo', - :password => 'bar' - ) + login: 'foo', + password: 'bar' + ) assert response = gateway.purchase(@accepted_amount, @credit_card, @options) assert_equal 'Bad or malformed request', response.message assert_failure response diff --git a/test/remote/gateways/remote_secure_pay_test.rb b/test/remote/gateways/remote_secure_pay_test.rb index 63f83f5e40b..77d41451b3b 100644 --- a/test/remote/gateways/remote_secure_pay_test.rb +++ b/test/remote/gateways/remote_secure_pay_test.rb @@ -1,19 +1,15 @@ require 'test_helper' 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, - :description => 'Store purchase', - :billing_address => address + order_id: generate_unique_id, + description: 'Store purchase', + billing_address: address } @amount = 100 diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb index 59b600899f1..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) @@ -66,10 +74,22 @@ def test_successful_purchase assert_match CHARGE_ID_REGEX, response.authorization end + def test_successful_purchase_with_three_ds_data + @options[:three_d_secure] = { + version: '1.0.2', + eci: '05', + cavv: '3q2+78r+ur7erb7vyv66vv////8=', + acs_transaction_id: '6546464645623455665165+qe-jmhabcdefg', + authentication_response_status: 'Y' + } + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + def test_unsuccessful_purchase response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - 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 @@ -87,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 @@ -104,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 @@ -118,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 @@ -131,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 @@ -161,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 @@ -173,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 new file mode 100644 index 00000000000..4403868aa71 --- /dev/null +++ b/test/remote/gateways/remote_shift4_test.rb @@ -0,0 +1,270 @@ +require 'test_helper' + +class RemoteShift4Test < Test::Unit::TestCase + def setup + @gateway = Shift4Gateway.new(fixtures(:shift4)) + access_token = @gateway.setup_access_token + + @gateway = Shift4Gateway.new(fixtures(:shift4).merge(access_token: access_token)) + + @amount = 500 + @credit_card = credit_card('4000100011112224', verification_value: '333', first_name: 'John', last_name: 'Smith') + @declined_card = credit_card('400030001111220', first_name: 'John', last_name: 'Doe') + @unsupported_card = credit_card('4000100011112224', verification_value: '333', first_name: 'John', last_name: '成龙') + @options = {} + @extra_options = { + clerk_id: '1576', + notes: 'test notes', + tax: '2', + customer_reference: 'D019D09309F2', + destination_postal_code: '94719', + product_descriptors: %w(Hamburger Fries Soda Cookie), + order_id: '123456' + } + @customer_address = { + address1: '65 Easy St', + zip: '65144' + } + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal response.message, 'Transaction successful' + end + + def test_successful_authorize_with_extra_options + response = @gateway.authorize(@amount, @credit_card, @options.merge(@extra_options)) + assert_success response + assert_equal response.message, 'Transaction successful' + end + + def test_successful_authorize_with_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_empty response.authorization + assert_not_include response.authorization, '|' + + response = @gateway.authorize(@amount, response.authorization, @options) + assert_success response + assert_include 'Transaction successful', response.message + end + + def test_successful_capture + authorize_res = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize_res + response = @gateway.capture(@amount, authorize_res.authorization, @options) + + assert_success response + assert_equal response.message, 'Transaction successful' + assert response_result(response)['transaction']['invoice'].present? + assert_equal response_result(response)['transaction']['invoice'], response_result(authorize_res)['transaction']['invoice'] + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_include 'Transaction successful', response.message + end + + def test_successful_purchase_with_customer_details + response = @gateway.purchase(@amount, @credit_card, @options.merge({ billing_address: @customer_address })) + assert_success response + assert_include 'Transaction successful', response.message + end + + def test_successful_purchase_with_extra_options + response = @gateway.purchase(@amount, @credit_card, @options.merge(@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, + reason_type: 'recurring' + } + @options[:merchant_time_zone] = 'EST' + first_response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options.merge({ stored_credential: stored_credential_options }))) + assert_success first_response + + ntxid = first_response.params['result'].first['transaction']['cardOnFile']['transactionId'] + stored_credential_options = { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: ntxid + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options.merge({ stored_credential: stored_credential_options }))) + assert_success response + end + + def test_successful_purchase_with_card_on_file_fields + card_on_file_fields = { + usage_indicator: '01', + indicator: '01', + scheduled_indicator: '01' + } + first_response = @gateway.purchase(@amount, @credit_card, @options.merge(card_on_file_fields)) + assert_success first_response + + ntxid = first_response.params['result'].first['transaction']['cardOnFile']['transactionId'] + card_on_file_fields = { + usage_indicator: '02', + indicator: '01', + scheduled_indicator: '02', + transaction_id: ntxid + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options.merge(card_on_file_fields))) + assert_success response + end + + def test_successful_purchase_with_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_empty response.authorization + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_include 'Transaction successful', response.message + end + + def test_successful_purchase_with_store_having_customer_details + response = @gateway.store(@credit_card, @options.merge({ billing_address: @customer_address })) + assert_success response + assert_not_empty response.authorization + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_include 'Transaction successful', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_card_on_file_fields + card_on_file_fields = { + usage_indicator: '01', + indicator: '01', + scheduled_indicator: '01' + } + first_response = @gateway.purchase(@amount, @credit_card, @options.merge(card_on_file_fields)) + assert_success first_response + ntxid = first_response.params['result'].first['transaction']['cardOnFile']['transactionId'] + + card_on_file_fields = { + usage_indicator: '02', + indicator: '01', + scheduled_indicator: '02', + transaction_id: ntxid + } + response = @gateway.verify(@credit_card, @options.merge(card_on_file_fields)) + assert_success response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed("0#{@credit_card.month}#{@credit_card.year.to_s[2..4]}", transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_include response.message, 'Card for Merchant Id 0008628968 not found' + end + + def test_failure_on_referral_transactions + response = @gateway.purchase(67800, @credit_card, @options) + assert_failure response + assert_include 'Transaction declined', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_include response.message, 'Card for Merchant Id 0008628968 not found' + end + + def test_failed_authorize_with_error_message + response = @gateway.authorize(@amount, @unsupported_card, @options) + assert_failure response + assert_equal response.message, 'Format \'UTF8: An unexpected continuatio\' invalid or incompatible with argument' + end + + def test_failed_capture + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_include response.message, 'Card for Merchant Id 0008628968 not found' + end + + def test_failed_refund + response = @gateway.refund(@amount, 'YC', @options) + assert_failure response + assert_include response.message, 'record not posted' + 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(@amount, @declined_card, @options) + assert_failure response + assert_include response.message, 'Card type not recognized' + end + + def test_successful_refund + res = @gateway.purchase(@amount, @credit_card, @options) + assert_success res + response = @gateway.refund(@amount, res.authorization, @options) + assert_success response + end + + def test_successful_refund_with_expiration_date + res = @gateway.purchase(@amount, @credit_card, @options) + assert_success res + response = @gateway.refund(@amount, res.authorization, @options.merge({ expiration_date: '1235' })) + assert_success response + end + + def test_successful_void + authorize_res = @gateway.authorize(@amount, @credit_card, @options) + assert response = @gateway.void(authorize_res.authorization, @options) + + assert_success response + assert_equal @options[:invoice], response_result(response)['transaction']['invoice'] + end + + def test_failed_void + response = @gateway.void('', @options) + assert_failure response + 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) + response.params['result'][0] + end +end 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..357fbfadde4 --- /dev/null +++ b/test/remote/gateways/remote_shift4_v2_test.rb @@ -0,0 +1,88 @@ +require 'test_helper' +require_relative 'remote_securion_pay_test' + +class RemoteShift4V2Test < RemoteSecurionPayTest + def setup + super + @gateway = Shift4V2Gateway.new(fixtures(:shift4_v2)) + 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 +end diff --git a/test/remote/gateways/remote_simetrik_test.rb b/test/remote/gateways/remote_simetrik_test.rb new file mode 100644 index 00000000000..90f66e2f844 --- /dev/null +++ b/test/remote/gateways/remote_simetrik_test.rb @@ -0,0 +1,276 @@ +require 'test_helper' + +class RemoteSimetrikTest < Test::Unit::TestCase + def setup + @gateway = SimetrikGateway.new(fixtures(:simetrik)) + @token_acquirer = 'bc4c0f26-a357-4294-9b9e-a90e6c868c6e' + @credit_card = CreditCard.new( + first_name: 'Joe', + last_name: 'Doe', + number: '4551708161768059', + month: 7, + year: 2022, + verification_value: '111' + ) + @credit_card_invalid = CreditCard.new( + first_name: 'Joe', + last_name: 'Doe', + number: '3551708161768059', + month: 3, + year: 2026, + verification_value: '111' + ) + @amount = 100 + @sub_merchant = { + address: 'None', + extra_params: { + }, + mcc: '5816', + merchant_id: '400000008', + name: '885.519.237', + phone_number: '3434343', + postal_code: 'None', + url: 'string' + } + @psp_info = { + id: '0123', + name: 'mci', + sub_merchant: { + id: 'string', + name: 'string' + } + + } + + @authorize_options_success = { + acquire_extra_options: {}, + trace_id: SecureRandom.uuid, + user: { + id: '123', + email: 's@example.com' + }, + order_id: rand(100000000000..999999999999).to_s, + description: 'apopsicle', + order: { + datetime_local_transaction: Time.new.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'), + installments: 1 + }, + vat: 19, + currency: 'USD', + authentication: { + three_ds_fields: { + version: '2.1.0', + eci: '05', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9', + cavv_algorithm: '1', + xid: '333333333', + directory_response_status: 'Y', + authentication_response_status: 'Y', + enrolled: 'test', + three_ds_server_trans_id: '24f701e3-9a85-4d45-89e9-af67e70d8fg8' + } + }, + sub_merchant: @sub_merchant, + psp_info: @psp_info, + token_acquirer: @token_acquirer + } + 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 + assert_instance_of Response, response + assert_equal response.message, 'successful authorize' + assert_equal response.error_code, nil, 'Should expected error code equal to nil' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_authorize + options = @authorize_options_success.clone() + options.delete(:user) + + response = @gateway.authorize(@amount, @credit_card, options) + assert_failure response + assert_instance_of Response, response + assert_equal response.error_code, 'config_error' + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_authorize_by_invalid_card + response = @gateway.authorize(@amount, @credit_card_invalid, @authorize_options_success) + assert_failure response + assert_instance_of Response, response + assert_equal response.error_code, 'invalid_number' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_success_purchase + response = @gateway.purchase(@amount, @credit_card, @authorize_options_success) + assert_success response + assert_instance_of Response, response + assert_equal response.message, 'successful charge' + assert_equal response.error_code, nil, 'Should expected error code equal to nil' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_purchase + options = @authorize_options_success.clone() + options.delete(:user) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_instance_of Response, response + assert_equal response.error_code, 'config_error' + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_purchase_by_invalid_card + response = @gateway.purchase(@amount, @credit_card_invalid, @authorize_options_success) + assert_failure response + assert_instance_of Response, response + assert_equal response.error_code, 'invalid_number' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @authorize_options_success) + assert_success auth + sleep(3) + option = { + vat: @authorize_options_success[:vat], + currency: @authorize_options_success[:currency], + + transaction_id: auth.authorization, + token_acquirer: @token_acquirer, + trace_id: @authorize_options_success[:trace_id] + } + assert capture = @gateway.capture(@amount, auth.authorization, option) + assert_success capture + assert_equal 'successful capture', capture.message + end + + def test_failed_capture + auth = @gateway.authorize(@amount, @credit_card, @authorize_options_success) + assert_success auth + + option = { + vat: @authorize_options_success[:vat], + currency: @authorize_options_success[:currency], + transaction_id: auth.authorization, + token_acquirer: @token_acquirer, + trace_id: @authorize_options_success[:trace_id] + } + sleep(3) + capture = @gateway.capture(@amount, auth.authorization, option) + assert_success capture + option = { + vat: 19, + currency: 'USD', + transaction_id: auth.authorization, + token_acquirer: @token_acquirer, + trace_id: @authorize_options_success[:trace_id] + } + + assert capture = @gateway.capture(@amount, auth.authorization, option) + assert_failure capture + assert_equal 'CAPTURE_REJECTED', capture.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @authorize_options_success) + assert_success auth + + option = { + token_acquirer: @token_acquirer, + trace_id: @authorize_options_success[:trace_id], + acquire_extra_options: {} + } + sleep(6) + assert void = @gateway.void(auth.authorization, option) + assert_success void + assert_equal 'successful void', void.message + end + + def test_failed_void + # First successful void + auth = @gateway.authorize(@amount, @credit_card, @authorize_options_success) + assert_success auth + + option = { + token_acquirer: @token_acquirer, + trace_id: @authorize_options_success[:trace_id], + acquire_extra_options: {} + } + sleep(3) + assert void = @gateway.void(auth.authorization, option) + assert_success void + assert_equal 'successful void', void.message + + # Second failed void + option = { + token_acquirer: @token_acquirer, + trace_id: '2717a3e0-0db2-4971-b94f-686d3b72c44b' + } + void = @gateway.void(auth.authorization, option) + assert_failure void + assert_equal 'VOID_REJECTED', void.message + end + + def test_failed_refund + response = @gateway.purchase(@amount, @credit_card, @authorize_options_success) + option = { + token_acquirer: @token_acquirer, + trace_id: '2717a3e0-0db2-4971-b94f-686d3b72c44b', + currency: 'USD', + comment: 'This is a comment', + acquire_extra_options: { + ruc: '13431131234' + } + } + assert_success response + sleep(3) + refund = @gateway.refund(@amount, response.authorization, option) + assert_failure refund + assert_equal 'REFUND_REJECTED', refund.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @authorize_options_success) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:client_secret], transcript) + end +end diff --git a/test/remote/gateways/remote_skipjack_test.rb b/test/remote/gateways/remote_skipjack_test.rb index d8ec78f732b..1c53076c8ff 100644 --- a/test/remote/gateways/remote_skipjack_test.rb +++ b/test/remote/gateways/remote_skipjack_test.rb @@ -6,16 +6,14 @@ 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 @options = { - :order_id => generate_unique_id, - :email => 'email@foo.com', - :billing_address => address + order_id: generate_unique_id, + email: 'email@foo.com', + billing_address: address } end @@ -75,7 +73,7 @@ def test_successful_authorization_and_credit authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - capture = @gateway.capture(@amount, authorization.authorization, :force_settlement => true) + capture = @gateway.capture(@amount, authorization.authorization, force_settlement: true) assert_success capture # developer login won't change transaction immediately to settled, so status will have to mismatch @@ -107,9 +105,9 @@ def test_status_unkown_order def test_invalid_login gateway = SkipJackGateway.new( - :login => '555555555555', - :password => '999999999999' - ) + login: '555555555555', + password: '999999999999' + ) response = gateway.authorize(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_so_easy_pay_test.rb b/test/remote/gateways/remote_so_easy_pay_test.rb index e24d837703b..be0ed41b96f 100644 --- a/test/remote/gateways/remote_so_easy_pay_test.rb +++ b/test/remote/gateways/remote_so_easy_pay_test.rb @@ -1,21 +1,20 @@ require 'test_helper' class RemoteSoEasyPayTest < Test::Unit::TestCase - def setup @gateway = SoEasyPayGateway.new(fixtures(:so_easy_pay)) @amount = 100 - @credit_card = credit_card('4111111111111111', {:verification_value => '000', :month => '12', :year => '2015'}) + @credit_card = credit_card('4111111111111111', { verification_value: '000', month: '12', year: '2015' }) @declined_card = credit_card('4000300011112220') @options = { - :currency => 'EUR', - :ip => '192.168.19.123', - :email => 'test@blaha.com', - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store Purchase' + currency: 'EUR', + ip: '192.168.19.123', + email: 'test@blaha.com', + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' } end @@ -54,9 +53,9 @@ def test_successful_void def test_invalid_login gateway = SoEasyPayGateway.new( - :login => 'one', - :password => 'wrong' - ) + login: 'one', + password: 'wrong' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Website verification failed, wrong websiteID or password', response.message diff --git a/test/remote/gateways/remote_spreedly_core_test.rb b/test/remote/gateways/remote_spreedly_core_test.rb index c9ea36a7d2c..5393149a3f7 100644 --- a/test/remote/gateways/remote_spreedly_core_test.rb +++ b/test/remote/gateways/remote_spreedly_core_test.rb @@ -1,13 +1,13 @@ require 'test_helper' class RemoteSpreedlyCoreTest < Test::Unit::TestCase - def setup @gateway = SpreedlyCoreGateway.new(fixtures(:spreedly_core)) @amount = 100 @credit_card = credit_card('5555555555554444') @declined_card = credit_card('4012888888881881') + @check = check({ routing_number: '021000021', account_number: '9876543210' }) @existing_payment_method = '3rEkRlZur2hXKbwwRBidHJAIUTO' @declined_payment_method = 'UPfh3J3JbekLeYC88BP741JWnS5' @existing_transaction = 'PJ5ICgM6h7v9pBNxDCJjRHDDxBC' @@ -60,10 +60,18 @@ def test_successful_purchase_with_credit_card assert_equal 'cached', response.params['payment_method_storage_state'] end + def test_successful_purchase_with_check + assert response = @gateway.purchase(@amount, @check) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + assert_equal 'cached', response.params['payment_method_storage_state'] + end + def test_successful_purchase_with_card_and_address options = { - :email => 'joebob@example.com', - :billing_address => address, + email: 'joebob@example.com', + billing_address: address } assert response = @gateway.purchase(@amount, @credit_card, options) @@ -88,7 +96,8 @@ def test_failed_purchase_with_invalid_credit_card @credit_card.first_name = ' ' assert response = @gateway.purchase(@amount, @credit_card) assert_failure response - assert_equal "First name can't be blank", response.message + assert_equal 'The payment method is invalid.', response.message + assert_equal "First name can't be blank", response.params['payment_method_errors'].strip end def test_successful_purchase_with_store @@ -96,7 +105,7 @@ def test_successful_purchase_with_store assert_success response assert_equal 'Succeeded!', response.message assert_equal 'Purchase', response.params['transaction_type'] - assert_equal 'retained', response.params['payment_method_storage_state'] + assert %w(retained cached).include?(response.params['payment_method_storage_state']) assert !response.params['payment_method_token'].blank? end @@ -114,8 +123,8 @@ def test_successful_authorize_and_capture_with_credit_card def test_successful_authorize_with_card_and_address options = { - :email => 'joebob@example.com', - :billing_address => address, + email: 'joebob@example.com', + billing_address: address } assert response = @gateway.authorize(@amount, @credit_card, options) @@ -141,7 +150,8 @@ def test_failed_authrorize_with_invalid_credit_card @credit_card.first_name = ' ' assert response = @gateway.authorize(@amount, @credit_card) assert_failure response - assert_equal "First name can't be blank", response.message + assert_equal 'The payment method is invalid.', response.message + assert_equal "First name can't be blank", response.params['payment_method_errors'].strip end def test_successful_authorize_with_store @@ -149,7 +159,7 @@ def test_successful_authorize_with_store assert_success response assert_equal 'Succeeded!', response.message assert_equal 'Authorization', response.params['transaction_type'] - assert_equal 'retained', response.params['payment_method_storage_state'] + assert %w(retained cached).include?(response.params['payment_method_storage_state']) assert !response.params['payment_method_token'].blank? end @@ -163,28 +173,28 @@ def test_successful_store end def test_successful_store_simple_data - assert response = @gateway.store(@credit_card, { :data => 'SomeData' }) + assert response = @gateway.store(@credit_card, { data: 'SomeData' }) assert_success response assert_equal 'SomeData', response.params['payment_method_data'] end def test_successful_store_nested_data options = { - :data => { - :first_attribute => { :sub_dude => 'ExcellentSubValue' }, - :second_attribute => 'AnotherValue' + data: { + first_attribute: { sub_dude: 'ExcellentSubValue' }, + second_attribute: 'AnotherValue' } } assert response = @gateway.store(@credit_card, options) assert_success response - expected_data = { 'first_attribute' => { 'sub_dude'=>'ExcellentSubValue' }, 'second_attribute' =>'AnotherValue' } + expected_data = { 'first_attribute' => { 'sub_dude' => 'ExcellentSubValue' }, 'second_attribute' => 'AnotherValue' } assert_equal expected_data, response.params['payment_method_data'] end def test_successful_store_with_address options = { - :email => 'joebob@example.com', - :billing_address => address, + email: 'joebob@example.com', + billing_address: address } assert response = @gateway.store(@credit_card, options) @@ -198,7 +208,7 @@ def test_successful_store_with_address end def test_failed_store - assert response = @gateway.store(credit_card('5555555555554444', :last_name => ' ')) + assert response = @gateway.store(credit_card('5555555555554444', last_name: ' ')) assert_failure response assert_equal "Last name can't be blank", response.message end @@ -281,7 +291,7 @@ def test_failed_find_transaction end def test_invalid_login - gateway = SpreedlyCoreGateway.new(:login => 'Bogus', :password => 'MoreBogus', :gateway_token => 'EvenMoreBogus') + gateway = SpreedlyCoreGateway.new(login: 'Bogus', password: 'MoreBogus', gateway_token: 'EvenMoreBogus') assert response = gateway.purchase(@amount, @existing_payment_method) assert_failure response diff --git a/test/remote/gateways/remote_stripe_3ds_test.rb b/test/remote/gateways/remote_stripe_3ds_test.rb index ec9650a544e..58a2f84d3b4 100644 --- a/test/remote/gateways/remote_stripe_3ds_test.rb +++ b/test/remote/gateways/remote_stripe_3ds_test.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'mechanize' class RemoteStripe3DSTest < Test::Unit::TestCase CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/ @@ -6,57 +7,187 @@ class RemoteStripe3DSTest < Test::Unit::TestCase def setup @gateway = StripeGateway.new(fixtures(:stripe)) @amount = 100 + @billing_details = address() @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com', - :execute_threed => true, - :redirect_url => 'http://www.example.com/redirect', - :callback_url => 'http://www.example.com/callback' + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com', + execute_threed: true, + redirect_url: 'http://www.example.com/redirect', + callback_url: 'http://www.example.com/callback', + billing_address: @billing_details } @credit_card = credit_card('4000000000003063') @non_3ds_card = credit_card('378282246310005') + + @stripe_account = fixtures(:stripe_destination)[:stripe_user_id] end def test_create_3ds_card_source assert response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) - assert_success response - assert_equal 'source', response.params['object'] - assert_equal 'chargeable', response.params['status'] - assert_equal 'required', response.params['card']['three_d_secure'] - assert_equal 'card', response.params['type'] + assert_card_source(response) end def test_create_non3ds_card_source assert response = @gateway.send(:create_source, @amount, @non_3ds_card, 'card', @options) - assert_success response - assert_equal 'source', response.params['object'] - assert_equal 'chargeable', response.params['status'] - assert_equal 'not_supported', response.params['card']['three_d_secure'] - assert_equal 'card', response.params['type'] + assert_card_source(response, 'not_supported') end def test_create_3ds_source - card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) - assert response = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) + card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert response = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) assert_success response - assert_equal 'source', response.params['object'] - assert_equal 'pending', response.params['status'] - assert_equal 'three_d_secure', response.params['type'] - assert_equal false, response.params['three_d_secure']['authenticated'] + assert_three_ds_source(response) + end + + def test_show_3ds_source + card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert three_d_secure_source = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) + assert_success three_d_secure_source + assert_three_ds_source(three_d_secure_source) + + assert response = @gateway.send(:show_source, three_d_secure_source.params['id'], @options) + assert_three_ds_source(response) end def test_create_webhook_endpoint response = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) assert_includes response.params['enabled_events'], 'source.chargeable' assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_create_webhook_endpoint_on_connected_account + response = @gateway.send(:create_webhook_endpoint, @options.merge({ stripe_account: @stripe_account }), ['source.chargeable']) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_not_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: response.params['id'])) + assert_equal true, deleted_response.params['deleted'] end def test_delete_webhook_endpoint webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) - response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: webhook.params['id'])) + assert_equal response.params['id'], webhook.params['id'] + assert_equal true, response.params['deleted'] + end + + def test_delete_webhook_endpoint_on_connected_account + webhook = @gateway.send(:create_webhook_endpoint, @options.merge({ stripe_account: @stripe_account }), ['source.chargeable']) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: webhook.params['id'])) assert_equal response.params['id'], webhook.params['id'] assert_equal true, response.params['deleted'] end + + def test_show_webhook_endpoint + webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + response = @gateway.send(:show_webhook_endpoint, @options.merge(webhook_id: webhook.params['id'])) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_show_webhook_endpoint_on_connected_account + webhook = @gateway.send(:create_webhook_endpoint, @options.merge({ stripe_account: @stripe_account }), ['source.chargeable']) + response = @gateway.send(:show_webhook_endpoint, @options.merge({ webhook_id: webhook.params['id'], stripe_account: @stripe_account })) + + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_not_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_list_webhook_endpoints + webhook1 = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + webhook2 = @gateway.send(:create_webhook_endpoint, @options.merge({ stripe_account: @stripe_account }), ['source.chargeable']) + assert_nil webhook1.params['application'] + assert_not_nil webhook2.params['application'] + + response = @gateway.send(:list_webhook_endpoints, @options.merge({ limit: 100 })) + assert_not_nil response.params + assert_equal 'list', response.params['object'] + assert response.params['data'].size >= 2 + webhook_id_set = Set.new(response.params['data'].map { |webhook| webhook['id'] }.uniq) + assert Set[webhook1.params['id'], webhook2.params['id']].subset?(webhook_id_set) + + deleted_response1 = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: webhook1.params['id'])) + deleted_response2 = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: webhook2.params['id'])) + assert_equal true, deleted_response1.params['deleted'] + assert_equal true, deleted_response2.params['deleted'] + end + + def test_3ds_purchase + card_source_response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert_card_source(card_source_response) + + assert three_ds_source_response = @gateway.send(:create_source, @amount, card_source_response.params['id'], 'three_d_secure', @options) + assert_success three_ds_source_response + assert_three_ds_source(three_ds_source_response) + + # Simulate 3DS 1.0 authentication in the test environment + authentication_url = three_ds_source_response.params['redirect']['url'] + agent = Mechanize.new + page = agent.get(authentication_url) + + form = page.forms.first + form.submit.tap do |result_page| + assert_equal '200', result_page.code + end + + # Test charging of the 3DS source + threeds_params = {} + threeds_params[:source] = three_ds_source_response.params['id'] + threeds_params[:capture] = 'true' + + @gateway.send(:add_charge_details, threeds_params, @amount, @credit_card, @options) + + assert response = @gateway.send(:commit, :post, 'charges', threeds_params, @options) + assert_equal 'charge', response.params['object'] + assert_equal 'succeeded', response.params['status'] + assert_equal true, response.params['captured'] + assert_equal 'three_d_secure', response.params.dig('source', 'type') + assert_equal true, response.params.dig('payment_method_details', 'card', 'three_d_secure', 'authenticated') + + # Check that billing details have been propagated from the card source to the charge + billing_details = response.params['billing_details'] + assert_equal @options[:email], billing_details['email'] + assert_equal @credit_card.name, billing_details['name'] + assert_equal @billing_details[:phone], billing_details['phone'] + assert_equal @billing_details[:address1], billing_details['address']['line1'] + assert_equal @billing_details[:address2], billing_details['address']['line2'] + assert_equal @billing_details[:city], billing_details['address']['city'] + assert_equal @billing_details[:state], billing_details['address']['state'] + assert_equal @billing_details[:zip], billing_details['address']['postal_code'] + assert_equal @billing_details[:country], billing_details['address']['country'] + end + + def assert_card_source(response, three_d_secure_status = 'required') + assert_success response + assert_equal 'source', response.params['object'] + assert_equal 'chargeable', response.params['status'] + assert_equal three_d_secure_status, response.params['card']['three_d_secure'] + assert_equal 'card', response.params['type'] + end + + def assert_three_ds_source(response) + assert_equal 'source', response.params['object'] + assert_equal 'pending', response.params['status'] + assert_equal 'three_d_secure', response.params['type'] + assert_equal false, response.params['three_d_secure']['authenticated'] + end end diff --git a/test/remote/gateways/remote_stripe_android_pay_test.rb b/test/remote/gateways/remote_stripe_android_pay_test.rb index 5abf0974a17..7adda284d40 100644 --- a/test/remote/gateways/remote_stripe_android_pay_test.rb +++ b/test/remote/gateways/remote_stripe_android_pay_test.rb @@ -8,14 +8,15 @@ def setup @amount = 100 @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com' + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com' } end def test_successful_purchase_with_android_pay_raw_cryptogram - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', verification_value: nil, eci: '05', @@ -31,7 +32,8 @@ 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', diff --git a/test/remote/gateways/remote_stripe_apple_pay_test.rb b/test/remote/gateways/remote_stripe_apple_pay_test.rb index c5231114490..b1a2f8c92aa 100644 --- a/test/remote/gateways/remote_stripe_apple_pay_test.rb +++ b/test/remote/gateways/remote_stripe_apple_pay_test.rb @@ -8,9 +8,9 @@ def setup @amount = 100 @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com' + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com' } @apple_pay_payment_token = apple_pay_payment_token end @@ -54,7 +54,7 @@ def test_successful_void_with_apple_pay_payment_token end def test_successful_store_with_apple_pay_payment_token - assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert response = @gateway.store(@apple_pay_payment_token, { description: 'Active Merchant Test Customer', email: 'email@example.com' }) assert_success response assert_equal 'customer', response.params['object'] assert_equal 'Active Merchant Test Customer', response.params['description'] @@ -66,10 +66,10 @@ def test_successful_store_with_apple_pay_payment_token end def test_successful_store_with_existing_customer_and_apple_pay_payment_token - assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer'}) + assert response = @gateway.store(@credit_card, { description: 'Active Merchant Test Customer' }) assert_success response - assert response = @gateway.store(@apple_pay_payment_token, {:customer => response.params['id'], :description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert response = @gateway.store(@apple_pay_payment_token, { customer: response.params['id'], description: 'Active Merchant Test Customer', email: 'email@example.com' }) assert_success response assert_equal 2, response.responses.size @@ -86,9 +86,9 @@ def test_successful_store_with_existing_customer_and_apple_pay_payment_token end def test_successful_recurring_with_apple_pay_payment_token - assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert response = @gateway.store(@apple_pay_payment_token, { description: 'Active Merchant Test Customer', email: 'email@example.com' }) assert_success response - assert recharge_options = @options.merge(:customer => response.params['id']) + assert recharge_options = @options.merge(customer: response.params['id']) assert response = @gateway.purchase(@amount, nil, recharge_options) assert_success response assert_equal 'charge', response.params['object'] @@ -101,7 +101,8 @@ 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', @@ -117,7 +118,8 @@ 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 @@ -132,7 +134,8 @@ 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', @@ -148,7 +151,8 @@ 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 @@ -161,5 +165,4 @@ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci assert_equal 'wow@example.com', response.params['metadata']['email'] assert_match CHARGE_ID_REGEX, response.authorization end - end diff --git a/test/remote/gateways/remote_stripe_connect_test.rb b/test/remote/gateways/remote_stripe_connect_test.rb index 8309d62f98d..f206eece901 100644 --- a/test/remote/gateways/remote_stripe_connect_test.rb +++ b/test/remote/gateways/remote_stripe_connect_test.rb @@ -10,21 +10,21 @@ def setup @new_credit_card = credit_card('5105105105105100') @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com', - :stripe_account => fixtures(:stripe_destination)[:stripe_user_id] + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com', + stripe_account: fixtures(:stripe_destination)[:stripe_user_id] } end def test_application_fee_for_stripe_connect - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 12)) assert_success response end def test_successful_refund_with_application_fee - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) - assert refund = @gateway.refund(@amount, response.authorization, @options.merge(:refund_application_fee => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 12)) + assert refund = @gateway.refund(@amount, response.authorization, @options.merge(refund_application_fee: true)) assert_success refund # Verify the application fee is refunded @@ -35,8 +35,8 @@ def test_successful_refund_with_application_fee end def test_refund_partial_application_fee - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) - assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '10')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 12)) + assert refund = @gateway.refund(@amount - 20, response.authorization, @options.merge(refund_fee_amount: '10')) assert_success refund # Verify the application fee is partially refunded @@ -47,8 +47,8 @@ def test_refund_partial_application_fee end def test_refund_application_fee_amount_zero - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) - assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '0')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 12)) + assert refund = @gateway.refund(@amount - 20, response.authorization, @options.merge(refund_fee_amount: '0')) assert_success refund # Verify the application fee is not refunded diff --git a/test/remote/gateways/remote_stripe_emv_test.rb b/test/remote/gateways/remote_stripe_emv_test.rb index 6d2742d7d35..2e9baee7d8e 100644 --- a/test/remote/gateways/remote_stripe_emv_test.rb +++ b/test/remote/gateways/remote_stripe_emv_test.rb @@ -14,9 +14,9 @@ def setup } @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com' + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com' } # This capture hex says that the payload is a transaction cryptogram (TC) but does not @@ -140,7 +140,7 @@ def test_purchase_and_void_with_emv_contactless_credit_card end def test_authorization_emv_credit_card_in_us_with_metadata - assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'})) + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options.merge({ metadata: { this_is_a_random_key_name: 'with a random value', i_made_up_this_key_too: 'canyoutell' }, order_id: '42', email: 'foo@wonderfullyfakedomain.com' })) assert_success authorization end end diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb new file mode 100644 index 00000000000..14811db6d95 --- /dev/null +++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb @@ -0,0 +1,1490 @@ +require 'test_helper' + +class RemoteStripeIntentsTest < Test::Unit::TestCase + def setup + @gateway = StripePaymentIntentsGateway.new(fixtures(:stripe)) + @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' + @three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession' + @three_ds_off_session_credit_card = credit_card( + '4000002500003155', + verification_value: '737', + month: 10, + 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_not_required_card = credit_card( + '4000000000003055', + verification_value: '737', + month: 10, + year: 2028 + ) + @three_ds_external_data_card = credit_card( + '4000002760003184', + verification_value: '737', + month: 10, + year: 2031 + ) + @visa_card = credit_card( + '4242424242424242', + verification_value: '737', + month: 10, + year: 2028 + ) + + @google_pay = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :google_pay, + brand: 'visa', + eci: '05', + month: '09', + year: '2030', + first_name: 'Longbob', + last_name: 'Longsen' + ) + + @apple_pay = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :apple_pay, + brand: 'visa', + eci: '05', + month: '09', + year: '2030', + first_name: 'Longbob', + 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 + + def test_authorization_and_void + options = { + currency: 'GBP', + customer: @customer + } + assert authorization = @gateway.authorize(@amount, @visa_payment_method, options) + + assert_equal 'requires_capture', authorization.params['status'] + refute authorization.params.dig('charges', 'data')[0]['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_successful_purchase + options = { + currency: 'GBP', + customer: @customer + } + assert purchase = @gateway.purchase(@amount, @visa_payment_method, options) + assert_equal 'succeeded', purchase.params['status'] + + assert purchase.params.dig('charges', 'data')[0]['captured'] + assert purchase.params.dig('charges', 'data')[0]['balance_transaction'] + end + + def test_successful_purchase_with_shipping_address + options = { + currency: 'GBP', + customer: @customer, + shipping_address: { + name: 'John Adam', + phone_number: '+0018313818368', + city: 'San Diego', + country: 'USA', + address1: 'block C', + address2: 'street 48', + zip: '22400', + 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 + options = { + currency: 'USD', + customer: @customer, + merchant_reference: 123, + customer_reference: 456, + shipping_address_zip: 71601, + shipping_from_zip: 71601, + shipping_amount: 10, + line_items: [ + { + 'product_code' => 1234, + 'product_description' => 'An item', + 'unit_cost' => 15, + 'quantity' => 2, + 'tax_amount' => 0 + }, + { + 'product_code' => 999, + 'product_description' => 'A totes different item', + 'tax_amount' => 10, + 'unit_cost' => 50, + 'quantity' => 1 + } + ] + } + + assert response = @gateway.purchase(100, @visa_card, options) + assert_success response + assert_equal 'succeeded', response.params['status'] + assert response.params.dig('charges', 'data')[0]['captured'] + end + + def test_unsuccessful_purchase_google_pay_with_invalid_card_number + options = { + currency: 'GBP' + } + + @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_false purchase.success? + end + + def test_unsuccessful_purchase_google_pay_without_cryptogram + options = { + currency: 'GBP' + } + @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_false purchase.success? + end + + def test_unsuccessful_purchase_google_pay_without_month + options = { + currency: 'GBP' + } + @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_false purchase.success? + end + + def test_successful_authorize_with_google_pay + options = { + currency: 'GBP' + } + + 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']) + end + + def test_successful_purchase_with_google_pay + options = { + currency: 'GBP' + } + + purchase = @gateway.purchase(@amount, @google_pay, options) + + assert_match('android_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method')) + assert purchase.success? + assert_match('google_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type']) + end + + def test_successful_purchase_with_tokenized_visa + options = { + currency: 'USD', + last_4: '4242' + } + + 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_successful_purchase_with_google_pay_when_sending_the_billing_address + options = { + currency: 'GBP', + billing_address: address + } + + purchase = @gateway.purchase(@amount, @google_pay, options) + + assert_match('android_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method')) + billing_address_line1 = purchase.responses.first.params.dig('token', 'card', 'address_line1') + assert_equal '456 My Street', billing_address_line1 + assert purchase.success? + 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' + } + + purchase = @gateway.purchase(@amount, @apple_pay, options) + assert_match('apple_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method')) + 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 + } + + purchase = @gateway.purchase(@amount, @apple_pay, options) + assert_match('apple_pay', purchase.responses.first.params.dig('token', 'card', 'tokenization_method')) + billing_address_line1 = purchase.responses.first.params.dig('token', 'card', 'address_line1') + assert_equal '456 My Street', billing_address_line1 + assert purchase.success? + assert_match('apple_pay', purchase.params.dig('charges', 'data')[0]['payment_method_details']['card']['wallet']['type']) + end + + def test_succesful_purchase_with_connect_for_apple_pay + options = { + stripe_account: @destination_account + } + assert response = @gateway.purchase(@amount, @apple_pay, options) + assert_success response + end + + def test_succesful_application_with_connect_for_google_pay + options = { + stripe_account: @destination_account + } + assert response = @gateway.purchase(@amount, @google_pay, options) + assert_success response + end + + def test_purchases_with_same_idempotency_key + options = { + currency: 'GBP', + customer: @customer, + idempotency_key: SecureRandom.hex + } + assert purchase1 = @gateway.purchase(@amount, @visa_payment_method, options) + assert_equal 'succeeded', purchase1.params['status'] + assert purchase1.params.dig('charges', 'data')[0]['captured'] + + assert purchase2 = @gateway.purchase(@amount, @visa_payment_method, options) + assert purchase2.success? + assert_equal purchase1.authorization, purchase2.authorization + assert_equal purchase1.params['charges']['data'][0]['id'], purchase2.params['charges']['data'][0]['id'] + end + + def test_credit_card_purchases_with_same_idempotency_key + options = { + currency: 'GBP', + customer: @customer, + idempotency_key: SecureRandom.hex + } + assert purchase1 = @gateway.purchase(@amount, @visa_card, options) + assert_equal 'succeeded', purchase1.params['status'] + assert purchase1.params.dig('charges', 'data')[0]['captured'] + + assert purchase2 = @gateway.purchase(@amount, @visa_card, options) + assert purchase2.success? + assert_equal purchase1.authorization, purchase2.authorization + assert_equal purchase1.params['charges']['data'][0]['id'], purchase2.params['charges']['data'][0]['id'] + end + + def test_purchases_with_same_idempotency_key_different_options + options = { + currency: 'GBP', + customer: @customer, + idempotency_key: SecureRandom.hex + } + assert purchase = @gateway.purchase(@amount, @visa_payment_method, options) + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + + options[:currency] = 'USD' + assert purchase = @gateway.purchase(@amount, @visa_payment_method, options) + refute purchase.success? + assert_match(/^Keys for idempotent requests can only be used with the same parameters they were first used with/, purchase.message) + end + + def test_credit_card_purchases_with_same_idempotency_key_different_options + options = { + currency: 'GBP', + customer: @customer, + idempotency_key: SecureRandom.hex + } + assert purchase = @gateway.purchase(@amount, @visa_card, options) + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + + options[:currency] = 'USD' + assert purchase = @gateway.purchase(@amount, @visa_card, options) + refute purchase.success? + assert_match(/^Keys for idempotent requests can only be used with the same parameters they were first used with/, purchase.message) + end + + def test_unsuccessful_purchase + options = { + currency: 'GBP', + customer: @customer + } + assert purchase = @gateway.purchase(@amount, @declined_payment_method, options) + + assert_equal 'Your card was declined.', purchase.message + refute purchase.params.dig('error', 'payment_intent', 'charges', 'data')[0]['captured'] + end + + def test_successful_purchase_with_external_auth_data_3ds_1 + options = { + currency: 'GBP', + three_d_secure: { + eci: '05', + cavv: '4BQwsg4yuKt0S1LI1nDZTcO9vUM=', + xid: 'd+NEBKSpEMauwleRhdrDY06qj4A=' + } + } + + assert purchase = @gateway.purchase(@amount, @three_ds_external_data_card, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_successful_purchase_with_external_auth_data_3ds_2 + options = { + currency: 'GBP', + three_d_secure: { + version: '2.1.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + ds_transaction_id: 'f879ea1c-aa2c-4441-806d-e30406466d79' + } + } + + assert purchase = @gateway.purchase(@amount, @three_ds_external_data_card, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_successful_purchase_with_customer_token_and_external_auth_data_3ds_2 + options = { + currency: 'GBP', + customer: @customer, + three_d_secure: { + version: '2.1.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + ds_transaction_id: 'f879ea1c-aa2c-4441-806d-e30406466d79' + } + } + + assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_successful_purchase_with_radar_session + options = { + radar_session_id: 'rse_1JXSfZAWOtgoysogUpPJa4sm' + } + assert purchase = @gateway.purchase(@amount, @visa_card, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_successful_purchase_with_skip_radar_rules + options = { skip_radar_rules: true } + assert purchase = @gateway.purchase(@amount, @visa_card, options) + + assert_equal 'succeeded', purchase.params['status'] + assert_equal ['all'], purchase.params['charges']['data'][0]['radar_options']['skip_rules'] + end + + def test_successful_authorization_with_external_auth_data_3ds_2 + options = { + currency: 'GBP', + three_d_secure: { + version: '2.1.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + ds_transaction_id: 'f879ea1c-aa2c-4441-806d-e30406466d79' + } + } + + assert authorization = @gateway.authorize(@amount, @three_ds_external_data_card, options) + + assert_equal 'requires_capture', authorization.params['status'] + refute authorization.params.dig('charges', 'data')[0]['captured'] + end + + def test_successful_authorization_with_radar_session + options = { + radar_session_id: 'rse_1JXSfZAWOtgoysogUpPJa4sm' + } + assert authorization = @gateway.authorize(@amount, @visa_card, options) + + assert_equal 'requires_capture', authorization.params['status'] + refute authorization.params.dig('charges', 'data')[0]['captured'] + end + + def test_create_payment_intent_manual_capture_method + options = { + currency: 'USD', + capture_method: 'manual' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal 'manual', response.params['capture_method'] + end + + def test_create_payment_intent_manual_confimation_method + options = { + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + confirmation_method: 'manual' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal 'manual', response.params['confirmation_method'] + end + + def test_create_payment_intent_with_customer + options = { + currency: 'USD', + customer: @customer || 'set customer in fixtures' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal @customer, response.params['customer'] + end + + def test_create_payment_intent_with_credit_card + options = { + currency: 'USD', + customer: @customer + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + end + + def test_create_payment_intent_with_return_url + options = { + currency: 'USD', + customer: @customer, + confirm: true, + return_url: 'https://www.example.com', + execute_threed: true + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + + assert_success response + assert_equal 'https://www.example.com', response.params['next_action']['redirect_to_url']['return_url'] + end + + def test_create_payment_intent_with_metadata + suffix = 'SUFFIX' + + options = { + currency: 'USD', + customer: @customer, + description: 'ActiveMerchant Test Purchase', + receipt_email: 'test@example.com', + statement_descriptor: 'Statement Descriptor', + statement_descriptor_suffix: suffix, + metadata: { key_1: 'value_1', key_2: 'value_2' }, + event_type: 'concert' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'value_1', response.params['metadata']['key_1'] + assert_equal 'concert', response.params['metadata']['event_type'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'test@example.com', response.params['receipt_email'] + assert_equal 'Statement Descriptor', response.params['statement_descriptor'] + assert_equal suffix, response.params['statement_descriptor_suffix'] + end + + def test_create_payment_intent_that_saves_payment_method + options = { + currency: 'USD', + customer: @customer, + save_payment_method: true + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_success response + + assert response = @gateway.create_intent(@amount, nil, options) + assert_failure response + assert_equal 'A payment method must be provided or already '\ + 'attached to the PaymentIntent when `save_payment_method=true`.', response.message + + options.delete(:customer) + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_failure response + assert_equal 'A valid `customer` must be provided when `save_payment_method=true`.', response.message + end + + def test_create_payment_intent_with_setup_future_usage + options = { + currency: 'USD', + customer: @customer, + setup_future_usage: 'on_session' + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_success response + assert_equal 'on_session', response.params['setup_future_usage'] + end + + def test_3ds_unauthenticated_authorize_with_off_session + options = { + currency: 'USD', + customer: @customer, + off_session: true + } + + assert response = @gateway.authorize(@amount, @three_ds_credit_card, options) + assert_failure response + end + + def test_create_setup_intent_with_setup_future_usage + [@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, { + address: { + email: 'test@example.com', + name: 'John Doe', + line1: '1 Test Ln', + city: 'Durham', + tracking_number: '123456789' + }, + currency: 'USD', + confirm: true, + execute_threed: true, + return_url: 'https://example.com' + }) + + assert_equal 'requires_action', authorize_response.params['status'] + assert_match 'https://hooks.stripe.com', authorize_response.params.dig('next_action', 'redirect_to_url', 'url') + + # 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 = authorize_response.params['id'] + + assert si_reponse = @gateway.retrieve_setup_intent(setup_intent_id) + assert_equal 'requires_action', si_reponse.params['status'] + + assert_not_empty si_reponse.params.dig('latest_attempt', 'payment_method_details', 'card') + assert_nil si_reponse.params.dig('latest_attempt', 'payment_method_details', 'card', 'network_transaction_id') + end + 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, { + address: { + email: 'test@example.com', + name: 'John Doe', + line1: '1 Test Ln', + city: 'Durham', + tracking_number: '123456789' + }, + currency: 'USD', + confirm: true, + execute_threed: true, + return_url: 'https://example.com', + stripe_account: @destination_account + }) + + assert_equal 'requires_action', authorize_response.params['status'] + assert_match 'https://hooks.stripe.com', authorize_response.params.dig('next_action', 'redirect_to_url', 'url') + + # 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 = authorize_response.params['id'] + + # If we did not pass the stripe_account header it would return an error + assert si_response = @gateway.retrieve_setup_intent(setup_intent_id, { + stripe_account: @destination_account + }) + assert_equal 'requires_action', si_response.params['status'] + + assert_not_empty si_response.params.dig('latest_attempt', 'payment_method_details', 'card') + assert_nil si_response.params.dig('latest_attempt', 'payment_method_details', 'card', 'network_transaction_id') + end + end + + def test_create_setup_intent_with_request_three_d_secure + [@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, { + address: { + email: 'test@example.com', + name: 'John Doe', + line1: '1 Test Ln', + city: 'Durham', + tracking_number: '123456789' + }, + currency: 'USD', + confirm: true, + execute_threed: true, + return_url: 'https://example.com', + request_three_d_secure: 'any' + + }) + + assert_equal 'requires_action', authorize_response.params['status'] + assert_match 'https://hooks.stripe.com', authorize_response.params.dig('next_action', 'redirect_to_url', 'url') + + assert_equal 'any', authorize_response.params.dig('payment_method_options', 'card', 'request_three_d_secure') + + # 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 = authorize_response.params['id'] + + assert si_reponse = @gateway.retrieve_setup_intent(setup_intent_id) + assert_equal 'requires_action', si_reponse.params['status'] + + assert_not_empty si_reponse.params.dig('latest_attempt', 'payment_method_details', 'card') + assert_nil si_reponse.params.dig('latest_attempt', 'payment_method_details', 'card', 'network_transaction_id') + end + end + + def test_retrieving_error_for_non_existant_setup_intent + assert si_reponse = @gateway.retrieve_setup_intent('seti_does_not_exist') + assert_nil si_reponse.params['status'] + assert_nil si_reponse.params.dig('latest_attempt', 'payment_method_details', 'card', 'network_transaction_id') + + assert_match 'resource_missing', si_reponse.params.dig('error', 'code') + assert_match "No such setupintent: 'seti_does_not_exist'", si_reponse.params.dig('error', 'message') + end + + def test_3ds_unauthenticated_authorize_with_off_session_requires_capture + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + assert authorize_response = @gateway.authorize(@amount, card_to_use, { + address: { + email: 'test@example.com', + name: 'John Doe', + line1: '1 Test Ln', + city: 'Durham', + tracking_number: '123456789' + }, + currency: 'USD', + confirm: true, + setup_future_usage: 'off_session', + execute_threed: true, + three_d_secure: { + version: '2.2.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + ds_transaction_id: 'f879ea1c-aa2c-4441-806d-e30406466d79' + } + }) + + assert_success authorize_response + assert_equal 'requires_capture', authorize_response.params['status'] + assert_not_empty authorize_response.params.dig('charges', 'data')[0]['payment_method_details']['card']['network_transaction_id'] + end + end + + def test_purchase_sends_network_transaction_id_separate_from_stored_creds + [@visa_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + assert purchase = @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + network_transaction_id: '1234567891011' + }) + 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 + end + + def test_purchase_works_with_stored_credentials + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + assert purchase = @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + setup_future_usage: true, + stored_credential: { + network_transaction_id: '1098510912210968', # TEST env seems happy with any value :/ + ds_transaction_id: 'null' # this is not req + } + }) + + assert_success purchase + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + end + + def test_purchase_works_with_stored_credentials_without_optional_ds_transaction_id + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + assert purchase = @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + stored_credential: { + network_transaction_id: '1098510912210968' # TEST env seems happy with any value :/ + } + }) + + assert_success purchase + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + end + + def test_succeeds_with_ntid_in_stored_credentials_and_separately + [@visa_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + assert purchase = @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + network_transaction_id: '1078784111114777', + stored_credential: { + network_transaction_id: '1098510912210968', + ds_transaction_id: 'null' + } + }) + 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 + 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, { + currency: 'USD', + execute_thread: true, + confirm: true, + off_session: true, + claim_without_transaction_id: true + }) + assert_success response + assert_equal 'succeeded', response.params['status'] + assert response.params.dig('charges', 'data')[0]['captured'] + end + end + + def test_successful_off_session_purchase_with_authentication_when_claim_without_transaction_id_is_false + assert response = @gateway.purchase(@amount, @three_ds_authentication_required_setup_for_off_session, { + currency: 'USD', + execute_thread: true, + confirm: true, + off_session: true, + claim_without_transaction_id: false + }) + # Purchase should succeed since other credentials are passed + assert_success response + assert_equal 'succeeded', response.params['status'] + assert response.params.dig('charges', 'data')[0]['captured'] + end + + def test_failed_off_session_purchase_with_card_when_claim_without_transaction_id_is_false + assert response = @gateway.purchase(@amount, @three_ds_off_session_credit_card, { + currency: 'USD', + execute_thread: true, + confirm: true, + off_session: true, + claim_without_transaction_id: false + }) + # Purchase should fail since no other credentials are passed, + # and Stripe will not manage the transaction without a transaction id + assert_failure response + assert_equal 'failed', response.params.dig('error', 'payment_intent', 'charges', 'data')[0]['status'] + assert !response.params.dig('error', 'payment_intent', 'charges', 'data')[0]['captured'] + end + + def test_purchase_fails_on_unexpected_3ds_initiation + options = { + currency: 'USD', + customer: @customer, + confirm: true, + return_url: 'https://www.example.com' + } + + 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 + end + + def test_create_payment_intent_with_shipping_address + options = { + currency: 'USD', + customer: @customer, + shipping_address: { + address1: '1 Test Ln', + city: 'Durham', + name: 'John Doe' + } + } + + assert response = @gateway.create_intent(@amount, nil, options) + assert_success response + assert response.params['shipping']['address'] + assert_equal 'John Doe', response.params['shipping']['name'] + end + + def test_create_payment_intent_with_billing_address + options = { + currency: 'USD', + customer: @customer, + billing_address: address, + email: 'jim@widgets.inc', + confirm: true + } + assert response = @gateway.create_intent(@amount, @visa_card, options) + assert_success response + assert billing_details = response.params.dig('charges', 'data')[0].dig('billing_details') + assert_equal 'Ottawa', billing_details['address']['city'] + assert_equal 'jim@widgets.inc', billing_details['email'] + end + + def test_create_payment_intent_with_name_if_billing_address_absent + options = { + currency: 'USD', + customer: @customer, + confirm: true + } + name_on_card = [@visa_card.first_name, @visa_card.last_name].join(' ') + + assert response = @gateway.create_intent(@amount, @visa_card, options) + assert_success response + assert_equal name_on_card, response.params.dig('charges', 'data')[0].dig('billing_details', 'name') + end + + def test_create_payment_intent_with_connected_account + transfer_group = 'XFERGROUP' + application_fee = 100 + + # You may not provide the application_fee_amount parameter and the transfer_data[amount] parameter + # simultaneously. They are mutually exclusive. + options = { + currency: 'USD', + customer: @customer, + application_fee: application_fee, + transfer_destination: @destination_account, + on_behalf_of: @destination_account, + transfer_group: transfer_group + } + + assert response = @gateway.create_intent(@amount, nil, options) + assert_success response + assert_equal application_fee, response.params['application_fee_amount'] + assert_equal transfer_group, response.params['transfer_group'] + assert_equal @destination_account, response.params['on_behalf_of'] + assert_equal @destination_account, response.params.dig('transfer_data', 'destination') + end + + def test_create_payment_intent_with_fulfillment_date + options = { + currency: 'USD', + customer: @customer, + fulfillment_date: 1636756194 + } + assert response = @gateway.authorize(@amount, @visa_payment_method, options) + assert_success response + end + + def test_create_a_payment_intent_and_confirm + options = { + currency: 'GBP', + customer: @customer, + return_url: 'https://www.example.com', + confirmation_method: 'manual', + capture_method: 'manual' + } + assert create_response = @gateway.create_intent(@amount, @three_ds_payment_method, options) + assert_equal 'requires_confirmation', create_response.params['status'] + intent_id = create_response.params['id'] + + assert get_response = @gateway.show_intent(intent_id, options) + assert_equal 'requires_confirmation', get_response.params['status'] + + assert confirm_response = @gateway.confirm_intent(intent_id, nil, return_url: 'https://example.com/return-to-me') + assert_equal 'redirect_to_url', confirm_response.params.dig('next_action', 'type') + end + + def test_create_a_payment_intent_and_manually_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal 'requires_capture', create_response.params['status'] + + assert capture_response = @gateway.capture(@amount, intent_id, options) + assert_equal 'succeeded', capture_response.params['status'] + assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_create_a_payment_intent_and_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', + customer: @customer, + confirm: true, + error_on_requires_action: true + } + assert create_response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert create_response.message.include?('This payment required an authentication action to complete, but `error_on_requires_action` was set.') + end + + def test_successful_create_a_payment_intent_with_set_error_on_requires_action + options = { + currency: 'GBP', + customer: @customer, + confirm: true, + error_on_requires_action: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + assert_equal 'succeeded', create_response.params['status'] + end + + def test_amount_localization + amount = 200000 + options = { + currency: 'XPF', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal 'requires_capture', create_response.params['status'] + + assert capture_response = @gateway.capture(amount, intent_id, options) + assert_equal 'succeeded', capture_response.params['status'] + assert_equal 2000, capture_response.params['amount'] + end + + def test_auth_and_capture_with_destination_account_and_fee + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + transfer_destination: @destination_account, + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal 'requires_capture', create_response.params['status'] + assert_equal @destination_account, create_response.params['transfer_data']['destination'] + assert_nil create_response.params['application_fee_amount'] + + assert capture_response = @gateway.capture(@amount, intent_id, { application_fee: 100 }) + assert_equal 'succeeded', capture_response.params['status'] + assert_equal @destination_account, capture_response.params['transfer_data']['destination'] + assert_equal 100, capture_response.params['application_fee_amount'] + assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_create_a_payment_intent_and_automatically_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + assert_nil create_response.params['next_action'] + assert_equal 'succeeded', create_response.params['status'] + assert_equal 'Payment complete.', create_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_failed_capture_after_creation + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, 'pm_card_chargeDeclined', options) + assert_equal 'requires_payment_method', create_response.params.dig('error', 'payment_intent', 'status') + assert_equal false, create_response.params.dig('error', 'payment_intent', 'charges', 'data')[0].dig('captured') + end + + def test_create_a_payment_intent_and_update + amount = 200000 + update_amount = 250000 + options = { + currency: 'XPF', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual' + } + assert create_response = @gateway.create_intent(amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal 2000, create_response.params['amount'] + + assert update_response = @gateway.update_intent(update_amount, intent_id, nil, options.merge(payment_method_types: 'card')) + assert_equal 2500, update_response.params['amount'] + assert_equal 'requires_confirmation', update_response.params['status'] + end + + def test_create_a_payment_intent_and_confirm_with_different_payment_method + options = { + currency: 'USD', + payment_method_types: %w[afterpay_clearpay], + metadata: { key_1: 'value_1', key_2: 'value_2' } + } + assert create_response = @gateway.setup_purchase(@amount, options) + assert_equal 'requires_payment_method', create_response.params['status'] + intent_id = create_response.params['id'] + assert_equal 2000, create_response.params['amount'] + assert_equal 'afterpay_clearpay', create_response.params['payment_method_types'][0] + + assert confirm_response = @gateway.confirm_intent(intent_id, @visa_payment_method, payment_method_types: 'card') + assert_equal 'card', confirm_response.params['payment_method_types'][0] + end + + def test_create_a_payment_intent_and_void + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + + assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal @amount, cancel_response.params.dig('charges', 'data')[0].dig('amount_refunded') + assert_equal 'canceled', cancel_response.params['status'] + assert_equal 'requested_by_customer', cancel_response.params['cancellation_reason'] + end + + def test_create_a_payment_intent_and_void_requires_unique_idempotency_key + idempotency_key = SecureRandom.hex + options = { + currency: 'GBP', + customer: @customer, + return_url: 'https://www.example.com', + confirmation_method: 'manual', + capture_method: 'manual', + idempotency_key: idempotency_key + } + assert create_response = @gateway.create_intent(@amount, @three_ds_payment_method, options) + assert_equal 'requires_confirmation', create_response.params['status'] + intent_id = create_response.params['id'] + + assert get_response = @gateway.show_intent(intent_id, options) + assert_equal 'requires_confirmation', get_response.params['status'] + + assert_failure cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer', idempotency_key: idempotency_key) + assert_match(/^Keys for idempotent requests can only be used for the same endpoint they were first used for/, cancel_response.message) + + assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer', idempotency_key: "#{idempotency_key}-auto-void") + assert_equal 'canceled', cancel_response.params['status'] + assert_equal 'requested_by_customer', cancel_response.params['cancellation_reason'] + end + + def test_failed_void_after_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + assert_equal 'succeeded', create_response.params['status'] + intent_id = create_response.params['id'] + + assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal 'You cannot cancel this PaymentIntent because ' \ + 'it has a status of succeeded. Only a PaymentIntent with ' \ + 'one of the following statuses may be canceled: ' \ + 'requires_payment_method, requires_capture, requires_confirmation, requires_action, processing.', cancel_response.message + end + + def test_refund_a_payment_intent + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + + assert @gateway.capture(@amount, intent_id, options) + + assert refund = @gateway.refund(@amount - 20, intent_id) + assert_equal @amount - 20, refund.params['charge']['amount_refunded'] + assert_equal true, refund.params['charge']['captured'] + refund_id = refund.params['id'] + assert_equal refund.authorization, refund_id + end + + def test_refund_when_payment_intent_not_captured + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + + refund = @gateway.refund(@amount - 20, intent_id) + assert_failure refund + assert refund.params['error'] + end + + def test_refund_when_payment_intent_requires_action + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @three_ds_authentication_required, options) + assert_equal 'requires_action', create_response.params['status'] + intent_id = create_response.params['id'] + + refund = @gateway.refund(@amount - 20, intent_id) + assert_failure refund + assert_match(/has a status of requires_action/, refund.message) + end + + def test_successful_store_purchase_and_unstore + options = { + currency: 'GBP' + } + assert store = @gateway.store(@visa_card, options) + assert store.params['customer'].start_with?('cus_') + + assert purchase = @gateway.purchase(@amount, store.authorization, options) + assert 'succeeded', purchase.params['status'] + + assert unstore = @gateway.unstore(store.authorization) + assert_nil unstore.params['customer'] + end + + def test_successful_store_with_idempotency_key + idempotency_key = SecureRandom.hex + + options = { + currency: 'GBP', + idempotency_key: idempotency_key + } + + assert store1 = @gateway.store(@visa_card, options) + assert store1.success? + assert store1.params['customer'].start_with?('cus_') + + assert store2 = @gateway.store(@visa_card, options) + assert store2.success? + assert_equal store1.authorization, store2.authorization + 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', + validate: false + } + assert store = @gateway.store(@visa_card, options) + assert store.params['customer'].start_with?('cus_') + assert_equal 'unchecked', store.params['card']['checks']['cvc_check'] + end + + def test_successful_store_with_true_validate_option + options = { + currency: 'GBP', + validate: true + } + assert store = @gateway.store(@visa_card, options) + assert store.params['customer'].start_with?('cus_') + assert_equal 'pass', store.params['card']['checks']['cvc_check'] + end + + def test_successful_verify + options = { + customer: @customer + } + assert verify = @gateway.verify(@visa_card, options) + assert_equal 'US', verify.responses[0].params.dig('card', 'country') + assert_equal 'succeeded', verify.params['status'] + end + + def test_failed_verify + options = { + customer: @customer + } + assert verify = @gateway.verify(@declined_payment_method, options) + + assert_equal 'Your card was declined.', verify.message + 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 + options = { + currency: 'GBP', + confirm: true + } + assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options) + + assert_equal 'requires_action', purchase.params['status'] + end + + def test_moto_enabled_card_succeeds_when_marked + options = { + currency: 'GBP', + confirm: true, + moto: true + } + assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_certain_cards_require_action_even_when_marked_as_moto + options = { + currency: 'GBP', + confirm: true, + moto: true + } + assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required, options) + + assert_failure purchase + assert_equal 'Your card was declined. This transaction requires authentication.', purchase.message + end + + def test_request_three_d_secure + options = { + currency: 'GBP', + request_three_d_secure: 'any' + } + assert purchase = @gateway.purchase(@amount, @three_ds_not_required_card, options) + assert_equal 'requires_action', purchase.params['status'] + + options = { + currency: 'GBP' + } + assert purchase = @gateway.purchase(@amount, @three_ds_not_required_card, options) + assert_equal 'succeeded', purchase.params['status'] + end + + def test_setup_purchase + options = { + currency: 'USD', + payment_method_types: %w[afterpay_clearpay card], + metadata: { key_1: 'value_1', key_2: 'value_2' } + } + + assert response = @gateway.setup_purchase(@amount, options) + assert_equal 'requires_payment_method', response.params['status'] + assert_equal 'value_1', response.params['metadata']['key_1'] + assert_equal 'value_2', response.params['metadata']['key_2'] + assert response.params['client_secret'].start_with?('pi') + end + + def test_failed_setup_purchase + options = { + currency: 'GBP', + payment_method_types: %w[afterpay_clearpay card] + } + + assert response = @gateway.setup_purchase(@amount, options) + assert_failure response + assert_match 'The currency provided (gbp) is invalid for one or more payment method types on this PaymentIntent.', response.message + end + + def test_transcript_scrubbing + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + return_url: 'https://www.example.com/return', + confirm: true + } + transcript = capture_transcript(@gateway) do + @gateway.create_intent(@amount, @three_ds_credit_card, options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@three_ds_credit_card.number, transcript) + assert_scrubbed(@three_ds_credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + end +end diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index 04a5b144138..85417f3c583 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -13,28 +13,37 @@ def setup @check = check({ bank_name: 'STRIPE TEST BANK', account_number: '000123456789', - routing_number: '110000000', + routing_number: '110000000' }) @verified_bank_account = fixtures(:stripe_verified_bank_account) @options = { - :currency => 'USD', - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com' + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com' } end def test_transcript_scrubbing + credit_card = credit_card('4242424242424242', verification_value: '745') transcript = capture_transcript(@gateway) do - @gateway.purchase(@amount, @credit_card, @options) + @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(credit_card.number, transcript) + assert_scrubbed(credit_card.verification_value, transcript) assert_scrubbed(@gateway.options[:login], transcript) end + def test_check_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.store(@check) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@check.account_number, transcript) + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -46,7 +55,7 @@ def test_successful_purchase end def test_successful_purchase_with_blank_referer - options = @options.merge({referrer: ''}) + options = @options.merge({ referrer: '' }) assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'charge', response.params['object'] @@ -57,7 +66,7 @@ def test_successful_purchase_with_blank_referer end def test_successful_purchase_with_recurring_flag - custom_options = @options.merge(:eci => 'recurring') + custom_options = @options.merge(eci: 'recurring') assert response = @gateway.purchase(@amount, @credit_card, custom_options) assert_success response assert_equal 'charge', response.params['object'] @@ -68,7 +77,7 @@ def test_successful_purchase_with_recurring_flag def test_successful_purchase_with_destination destination = fixtures(:stripe_destination)[:stripe_user_id] - custom_options = @options.merge(:destination => destination) + custom_options = @options.merge(destination: destination) assert response = @gateway.purchase(@amount, @credit_card, custom_options) assert_success response assert_equal 'charge', response.params['object'] @@ -80,7 +89,7 @@ def test_successful_purchase_with_destination def test_successful_purchase_with_destination_and_amount destination = fixtures(:stripe_destination)[:stripe_user_id] - custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20) + custom_options = @options.merge(destination: destination, destination_amount: @amount - 20) assert response = @gateway.purchase(@amount, @credit_card, custom_options) assert_success response assert_equal 'charge', response.params['object'] @@ -109,7 +118,7 @@ def test_successful_purchase_with_level3_data 'product_description' => 'A totes different item', 'tax_amount' => 10, 'unit_cost' => 50, - 'quantity' => 1, + 'quantity' => 1 } ] @@ -122,6 +131,74 @@ def test_successful_purchase_with_level3_data assert_equal 'wow@example.com', response.params['metadata']['email'] end + def test_successful_purchase_with_shipping_address + @options[:shipping_address] = {} + @options[:shipping_address][:name] = 'Jim Doe' + @options[:shipping_address][:phone_number] = '9194041014' + @options[:shipping_address][:address1] = '100 W Main St' + @options[:shipping_address][:address2] = 'Apt 2' + @options[:shipping_address][:city] = 'Baltimore' + @options[:shipping_address][:state] = 'MD' + @options[:shipping_address][:zip] = '21201' + @options[:shipping_address][:country] = 'US' + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert_equal response.authorization, response.params['id'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'Jim Doe', response.params['shipping']['name'] + assert_equal '9194041014', response.params['shipping']['phone'] + assert_equal '100 W Main St', response.params['shipping']['address']['line1'] + assert_equal 'Apt 2', response.params['shipping']['address']['line2'] + assert_equal 'Baltimore', response.params['shipping']['address']['city'] + assert_equal 'MD', response.params['shipping']['address']['state'] + assert_equal '21201', response.params['shipping']['address']['postal_code'] + assert_equal 'US', response.params['shipping']['address']['country'] + end + + def test_purchase_with_connected_account + destination = fixtures(:stripe_destination)[:stripe_user_id] + transfer_group = 'XFERGROUP' + application_fee_amount = 100 + + # You may not provide the application_fee_amount parameter and the transfer_data[amount] parameter + # simultaneously. They are mutually exclusive. + options = @options.merge({ + customer: @customer, + application_fee_amount: application_fee_amount, + transfer_destination: destination, + on_behalf_of: destination, + transfer_group: transfer_group + }) + + assert response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal application_fee_amount, response.params['application_fee_amount'] + assert_equal transfer_group, response.params['transfer_group'] + assert_equal destination, response.params['on_behalf_of'] + assert_equal destination, response.params.dig('transfer_data', 'destination') + end + + def test_successful_purchase_with_radar_session + options = @options.merge(radar_session_id: 'rse_1JXSfZAWOtgoysogUpPJa4sm') + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + end + + def test_successful_purchase_with_skip_radar_rules + options = @options.merge(skip_radar_rules: true) + assert purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + assert_equal ['all'], purchase.params['radar_options']['skip_rules'] + end + def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response @@ -132,7 +209,7 @@ def test_unsuccessful_purchase def test_unsuccessful_purchase_with_destination_and_amount destination = fixtures(:stripe_destination)[:stripe_user_id] - custom_options = @options.merge(:destination => destination, :destination_amount => @amount + 20) + custom_options = @options.merge(destination: destination, destination_amount: @amount + 20) assert response = @gateway.purchase(@amount, @credit_card, custom_options) assert_failure response assert_match %r{must be less than or equal to the charge amount}, response.message @@ -156,6 +233,23 @@ def test_unsuccessful_direct_bank_account_purchase assert_equal 'Direct bank account transactions are not supported. Bank accounts must be stored and verified before use.', response.message end + def test_unsuccessful_echeck_auth_with_verified_account + customer_id = @verified_bank_account[:customer_id] + bank_account_id = @verified_bank_account[:bank_account_id] + + payment = [customer_id, bank_account_id].join('|') + + response = @gateway.authorize(@amount, payment, @options) + assert_failure response + assert_equal 'You cannot pass capture=false for this payment type.', response.message + end + + def test_unsuccessful_direct_bank_account_auth + response = @gateway.authorize(@amount, @check, @options) + assert_failure response + assert_equal 'Direct bank account transactions are not supported for authorize.', response.message + end + def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization @@ -169,7 +263,7 @@ def test_authorization_and_capture def test_authorization_and_capture_with_destination destination = fixtures(:stripe_destination)[:stripe_user_id] - custom_options = @options.merge(:destination => destination) + custom_options = @options.merge(destination: destination) assert authorization = @gateway.authorize(@amount, @credit_card, custom_options) assert_success authorization @@ -184,7 +278,7 @@ def test_authorization_and_capture_with_destination def test_authorization_and_capture_with_destination_and_amount destination = fixtures(:stripe_destination)[:stripe_user_id] - custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20) + custom_options = @options.merge(destination: destination, destination_amount: @amount - 20) assert authorization = @gateway.authorize(@amount, @credit_card, custom_options) assert_success authorization @@ -197,6 +291,18 @@ def test_authorization_and_capture_with_destination_and_amount assert_success capture end + def test_successful_authorization_and_capture_with_radar_session + options = @options.merge(radar_session_id: 'rse_1JXSfZAWOtgoysogUpPJa4sm') + assert authorization = @gateway.authorize(@amount, @credit_card, options) + assert_success authorization + refute authorization.params['captured'] + assert_equal 'ActiveMerchant Test Purchase', authorization.params['description'] + assert_equal 'wow@example.com', authorization.params['metadata']['email'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + def test_authorization_and_void assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization @@ -237,6 +343,19 @@ def test_successful_void_with_reason assert_equal 'fraudulent', void.params['reason'] end + def test_successful_void_with_reverse_transfer + destination = fixtures(:stripe_destination)[:stripe_user_id] + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(destination: destination)) + assert_success response + + @gateway.capture(@amount, response.authorization) + + assert void = @gateway.void(response.authorization, reverse_transfer: true) + assert_match %r{trr_}, void.params['transfer_reversal'] + assert_success void + assert_equal 'Transaction approved', void.message + end + def test_unsuccessful_void assert void = @gateway.void('active_merchant_fake_charge') assert_failure void @@ -437,9 +556,10 @@ def test_successful_store_with_existing_customer def test_successful_store_with_existing_account account = fixtures(:stripe_destination)[:stripe_user_id] - assert response = @gateway.store(@debit_card, account: account) assert_success response + # Delete the stored external account to prevent hitting the limit + @gateway.delete_latest_test_external_account(account) assert_equal 'card', response.params['object'] end @@ -467,20 +587,18 @@ def test_successful_purchase_using_stored_card_on_existing_customer assert_equal '5100', response.params['source']['last4'] end - def test_successful_purchase_using_stored_card_and_deprecated_api + def test_successful_purchase_using_stored_card_with_customer_id assert store = @gateway.store(@credit_card) assert_success store - recharge_options = @options.merge(:customer => store.params['id']) - assert_deprecation_warning do - response = @gateway.purchase(@amount, nil, recharge_options) - assert_success response - assert_equal '4242', response.params['source']['last4'] - end + recharge_options = @options.merge(customer: store.params['id']) + response = @gateway.purchase(@amount, nil, recharge_options) + assert_success response + assert_equal '4242', response.params['source']['last4'] end def test_successful_unstore - creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) + creation = @gateway.store(@credit_card, { description: 'Active Merchant Unstore Customer' }) card_id = creation.params['sources']['data'].first['id'] assert response = @gateway.unstore(creation.authorization) @@ -491,7 +609,7 @@ def test_successful_unstore end def test_successful_unstore_using_deprecated_api - creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) + creation = @gateway.store(@credit_card, { description: 'Active Merchant Unstore Customer' }) card_id = creation.params['sources']['data'].first['id'] customer_id = creation.params['id'] @@ -535,7 +653,7 @@ def test_successful_purchase_from_stored_and_verified_bank_account end def test_invalid_login - gateway = StripeGateway.new(:login => 'active_merchant_test') + gateway = StripeGateway.new(login: 'active_merchant_test') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_match 'Invalid API Key provided', response.message @@ -544,7 +662,7 @@ def test_invalid_login # These "track data present" tests fail with invalid expiration dates. The # test track data probably needs to be updated. def test_card_present_purchase - @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?' + @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2705101130504392?' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'charge', response.params['object'] @@ -552,7 +670,7 @@ def test_card_present_purchase end def test_card_present_authorize_and_capture - @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?' + @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2705101130504392?' assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization refute authorization.params['captured'] @@ -562,25 +680,25 @@ def test_card_present_authorize_and_capture end def test_creditcard_purchase_with_customer - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:customer => '1234')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(customer: '1234')) assert_success response assert_equal 'charge', response.params['object'] assert response.params['paid'] end def test_expanding_objects - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:expand => 'balance_transaction')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(expand: 'balance_transaction')) assert_success response assert response.params['balance_transaction'].is_a?(Hash) assert_equal 'balance_transaction', response.params['balance_transaction']['object'] end def test_successful_update - creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Credit Card'}) + creation = @gateway.store(@credit_card, { description: 'Active Merchant Update Credit Card' }) customer_id = creation.params['id'] card_id = creation.params['sources']['data'].first['id'] - assert response = @gateway.update(customer_id, card_id, { :name => 'John Doe', :address_line1 => '123 Main Street', :address_city => 'Pleasantville', :address_state => 'NY', :address_zip => '12345', :exp_year => Time.now.year + 2, :exp_month => 6 }) + assert response = @gateway.update(customer_id, card_id, { name: 'John Doe', address_line1: '123 Main Street', address_city: 'Pleasantville', address_state: 'NY', address_zip: '12345', exp_year: Time.now.year + 2, exp_month: 6 }) assert_success response assert_equal 'John Doe', response.params['name'] assert_equal '123 Main Street', response.params['address_line1'] @@ -659,11 +777,18 @@ def test_stripe_account_header assert_success response end + def test_statement_descriptor_suffix + suffix = 'SUFFIX' + + assert response = @gateway.purchase(@amount, @credit_card, statement_descriptor_suffix: suffix) + assert_success response + assert_equal suffix, response.params['statement_descriptor_suffix'] + end + def test_verify_credentials assert @gateway.verify_credentials gateway = StripeGateway.new(login: 'an_unknown_api_key') assert !gateway.verify_credentials end - end diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb new file mode 100644 index 00000000000..e9bc8d9582c --- /dev/null +++ b/test/remote/gateways/remote_sum_up_test.rb @@ -0,0 +1,156 @@ +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_credentials_error + gateway = SumUpGateway.new({ access_token: 'sup_sk_xx', pay_to_email: 'example@example.com' }) + response = gateway.purchase(@amount, @visa_card, @options) + + assert_equal('invalid access token', response.message) + 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, @visa_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 'PENDING', 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 'PENDING', response.params['transactions'].first['status'] + end + + def test_successful_purchase_with_existing_checkout + existing_checkout = @gateway.purchase(@amount, @credit_card, @options) + assert_success existing_checkout + refute_empty existing_checkout.params['id'] + @options[:checkout_id] = existing_checkout.params['id'] + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'PENDING', response.message + assert_equal @options[:order_id], response.params['checkout_reference'] + refute_empty response.params['id'] + assert_equal existing_checkout.params['id'], response.params['id'] + refute_empty response.params['transactions'] + assert_equal response.params['transactions'].count, 2 + refute_empty response.params['transactions'].last['id'] + assert_equal 'PENDING', response.params['transactions'].last['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 'PENDING', 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 + + def test_successful_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal 'PENDING', purchase.message + assert_equal @options[:order_id], purchase.params['checkout_reference'] + refute_empty purchase.params['id'] + refute_empty purchase.params['transactions'] + refute_empty purchase.params['transactions'].first['id'] + assert_equal 'PENDING', purchase.params['transactions'].first['status'] + + response = @gateway.void(purchase.params['id']) + assert_success response + refute_empty response.params['id'] + assert_equal purchase.params['id'], response.params['id'] + refute_empty response.params['transactions'] + refute_empty response.params['transactions'].first['id'] + assert_equal 'CANCELLED', response.params['transactions'].first['status'] + end + + def test_failed_void_invalid_checkout_id + response = @gateway.void('90858be3-23bb-4af5-9fba-ce3bc190fe5b22') + assert_failure response + assert_equal 'Resource not found', response.message + end + + def test_failed_refund_for_pending_checkout + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal 'PENDING', purchase.message + assert_equal @options[:order_id], purchase.params['checkout_reference'] + refute_empty purchase.params['id'] + refute_empty purchase.params['transactions'] + + transaction_id = purchase.params['transactions'].first['id'] + + refute_empty transaction_id + assert_equal 'PENDING', purchase.params['transactions'].first['status'] + + response = @gateway.refund(nil, transaction_id) + assert_failure response + assert_equal 'CONFLICT', response.error_code + assert_equal 'The transaction is not refundable in its current state', 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[:pay_to_email], transcript) + end +end diff --git a/test/remote/gateways/remote_swipe_checkout_test.rb b/test/remote/gateways/remote_swipe_checkout_test.rb index 6299a506fd2..db3c144ff3e 100644 --- a/test/remote/gateways/remote_swipe_checkout_test.rb +++ b/test/remote/gateways/remote_swipe_checkout_test.rb @@ -8,7 +8,7 @@ def setup @accepted_card = credit_card('1234123412341234') @declined_card = credit_card('1111111111111111') @invalid_card = credit_card('1000000000000000') - @empty_card = credit_card('') + @empty_card = credit_card('') @options = { order_id: '1', @@ -24,7 +24,7 @@ def test_successful_purchase end def test_region_switching - assert response = @gateway.purchase(@amount, @accepted_card, @options.merge(:region => 'CA')) + assert response = @gateway.purchase(@amount, @accepted_card, @options.merge(region: 'CA')) assert_success response assert_equal 'Transaction approved', response.message end diff --git a/test/remote/gateways/remote_tns_test.rb b/test/remote/gateways/remote_tns_test.rb index cc1aa80efa1..f515c9854ff 100644 --- a/test/remote/gateways/remote_tns_test.rb +++ b/test/remote/gateways/remote_tns_test.rb @@ -1,15 +1,14 @@ require 'test_helper' class RemoteTnsTest < Test::Unit::TestCase - def setup TnsGateway.ssl_strict = false # Sandbox has an improperly installed cert @gateway = TnsGateway.new(fixtures(:tns)) @amount = 100 - @credit_card = credit_card('5123456789012346') - @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2017, verification_value: 222) - @declined_card = credit_card('4000300011112220') + @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 = { order_id: generate_unique_id, @@ -37,7 +36,7 @@ def test_successful_purchase_sans_options def test_successful_purchase_with_more_options more_options = @options.merge({ ip: '127.0.0.1', - email: 'joe@example.com', + email: 'joe@example.com' }) assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) @@ -45,6 +44,18 @@ def test_successful_purchase_with_more_options assert_equal 'Succeeded', response.message end + # This requires a test account flagged for pay/purchase mode. + # The primary test account (TESTSPREEDLY01) is not flagged for this mode. + # This was initially tested with a private account. + def test_successful_purchase_in_pay_mode + gateway = TnsGateway.new(fixtures(:tns_pay_mode).merge(region: 'europe')) + + assert response = gateway.purchase(@amount, @credit_card, @options.merge(currency: 'GBP', pay_mode: true)) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'CAPTURED', response.params['order']['status'] + end + def test_successful_purchase_with_region @gateway = TnsGateway.new(fixtures(:tns_ap).merge(region: 'asia_pacific')) @@ -56,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 @@ -73,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 @@ -104,9 +115,9 @@ def test_successful_verify def test_invalid_login gateway = TnsGateway.new( - :userid => 'nosuch', - :password => 'thing' - ) + userid: 'nosuch', + password: 'thing' + ) response = gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message @@ -123,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_trans_first_test.rb b/test/remote/gateways/remote_trans_first_test.rb index 3423954560e..2202d87b4e8 100644 --- a/test/remote/gateways/remote_trans_first_test.rb +++ b/test/remote/gateways/remote_trans_first_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteTransFirstTest < Test::Unit::TestCase - def setup @gateway = TransFirstGateway.new(fixtures(:trans_first)) @@ -9,9 +8,9 @@ def setup @check = check @amount = 1201 @options = { - :order_id => generate_unique_id, - :invoice => 'ActiveMerchant Sale', - :billing_address => address + order_id: generate_unique_id, + invoice: 'ActiveMerchant Sale', + billing_address: address } end @@ -107,8 +106,8 @@ def test_successful_refund_with_echeck def test_invalid_login gateway = TransFirstGateway.new( - :login => '', - :password => '' + login: '', + password: '' ) assert response = gateway.purchase(1100, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_trans_first_transaction_express_test.rb b/test/remote/gateways/remote_trans_first_transaction_express_test.rb index 9a97eee54f0..5b972657e15 100644 --- a/test/remote/gateways/remote_trans_first_transaction_express_test.rb +++ b/test/remote/gateways/remote_trans_first_transaction_express_test.rb @@ -1,13 +1,12 @@ require 'test_helper' class RemoteTransFirstTransactionExpressTest < Test::Unit::TestCase - def setup @gateway = TransFirstTransactionExpressGateway.new(fixtures(:trans_first_transaction_express)) @amount = 100 @declined_amount = 21 - @credit_card = credit_card('4485896261017708') + @credit_card = credit_card('4485896261017708', verification_value: 999) @check = check billing_address = address({ @@ -16,7 +15,7 @@ def setup city: 'Broomfield', state: 'CO', zip: '85284', - phone: '(333) 444-5555', + phone: '(333) 444-5555' }) @options = { @@ -59,12 +58,12 @@ def test_successful_purchase_with_only_required options = @options.dup options[:shipping_address] = { address1: '450 Main', - zip: '85284', + zip: '85284' } options[:billing_address] = { address1: '450 Main', - zip: '85284', + zip: '85284' } response = @gateway.purchase(@amount, @credit_card, options) @@ -76,14 +75,34 @@ def test_successful_purchase_with_only_required assert_equal 'CVV matches', response.cvv_result['message'] end + def test_successful_purchase_without_address2 + # Test that empty string in `address2` doesn't cause transaction failure + options = @options.dup + options[:shipping_address] = { + address1: '450 Main', + address2: '', + zip: '85284' + } + + options[:billing_address] = { + address1: '450 Main', + address2: '', + zip: '85284' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_successful_purchase_without_cvv credit_card_opts = { - :number => 4485896261017708, - :month => Date.new((Time.now.year + 1), 9, 30).month, - :year => Date.new((Time.now.year + 1), 9, 30).year, - :first_name => 'Longbob', - :last_name => 'Longsen', - :brand => 'visa' + number: 4485896261017708, + month: Date.new((Time.now.year + 1), 9, 30).month, + year: Date.new((Time.now.year + 1), 9, 30).year, + first_name: 'Longbob', + last_name: 'Longsen', + brand: 'visa' } credit_card = CreditCard.new(credit_card_opts) @@ -94,13 +113,13 @@ def test_successful_purchase_without_cvv def test_successful_purchase_with_empty_string_cvv credit_card_opts = { - :number => 4485896261017708, - :month => Date.new((Time.now.year + 1), 9, 30).month, - :year => Date.new((Time.now.year + 1), 9, 30).year, - :first_name => 'Longbob', - :last_name => 'Longsen', - :verification_value => '', - :brand => 'visa' + number: 4485896261017708, + month: Date.new((Time.now.year + 1), 9, 30).month, + year: Date.new((Time.now.year + 1), 9, 30).year, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: '', + brand: 'visa' } credit_card = CreditCard.new(credit_card_opts) @@ -111,11 +130,11 @@ def test_successful_purchase_with_empty_string_cvv def test_successful_purchase_without_name credit_card_opts = { - :number => 4485896261017708, - :month => Date.new((Time.now.year + 1), 9, 30).month, - :year => Date.new((Time.now.year + 1), 9, 30).year, - :first_name => '', - :last_name => '' + number: 4485896261017708, + month: Date.new((Time.now.year + 1), 9, 30).month, + year: Date.new((Time.now.year + 1), 9, 30).year, + first_name: '', + last_name: '' } credit_card = CreditCard.new(credit_card_opts) @@ -124,9 +143,9 @@ def test_successful_purchase_without_name assert_equal 'Succeeded', response.message credit_card_opts = { - :number => 4485896261017708, - :month => Date.new((Time.now.year + 1), 9, 30).month, - :year => Date.new((Time.now.year + 1), 9, 30).year + number: 4485896261017708, + month: Date.new((Time.now.year + 1), 9, 30).month, + year: Date.new((Time.now.year + 1), 9, 30).year } credit_card = CreditCard.new(credit_card_opts) @@ -374,4 +393,12 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:gateway_id], clean_transcript) assert_scrubbed(@gateway.options[:reg_key], clean_transcript) end + + def test_transcript_scrubbing_account_number + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + assert_scrubbed(@check.account_number, clean_transcript) + end end diff --git a/test/remote/gateways/remote_transact_pro_test.rb b/test/remote/gateways/remote_transact_pro_test.rb index 5e5c428ae29..504a5b65d29 100644 --- a/test/remote/gateways/remote_transact_pro_test.rb +++ b/test/remote/gateways/remote_transact_pro_test.rb @@ -57,7 +57,7 @@ def test_partial_capture assert_success auth assert_raise(ArgumentError) do - @gateway.capture(@amount-1, auth.authorization) + @gateway.capture(@amount - 1, auth.authorization) end end @@ -80,7 +80,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount-1, purchase.authorization) + refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund assert_equal 'Refund Success', refund.message end @@ -89,7 +89,7 @@ def test_failed_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount+1, purchase.authorization) + refund = @gateway.refund(@amount + 1, purchase.authorization) assert_failure refund end diff --git a/test/remote/gateways/remote_transax_test.rb b/test/remote/gateways/remote_transax_test.rb index 1e7bbc00399..7685274b61d 100644 --- a/test/remote/gateways/remote_transax_test.rb +++ b/test/remote/gateways/remote_transax_test.rb @@ -5,15 +5,15 @@ def setup @gateway = TransaxGateway.new(fixtures(:transax)) @amount = 100 - @credit_card = credit_card('4111111111111111', :year => 10, :month => 10) + @credit_card = credit_card('4111111111111111', year: 10, month: 10) @declined_card = credit_card(0xDEADBEEF_0000.to_s) @check = check() @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -76,13 +76,13 @@ def test_purchase_and_update assert_success response assert_equal 'This transaction has been approved', response.message assert response.authorization - assert update = @gateway.amend(response.authorization, :shipping_carrier => 'usps') + assert update = @gateway.amend(response.authorization, shipping_carrier: 'usps') assert_equal 'This transaction has been approved', update.message assert_success update end def test_successful_purchase_with_sku - @options['product_sku_#']='123456' + @options['product_sku_#'] = '123456' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'This transaction has been approved', response.message @@ -115,9 +115,9 @@ def test_failed_verify def test_invalid_login gateway = TransaxGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid Username', response.message diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb index 196ffe26c7f..19e1564c649 100644 --- a/test/remote/gateways/remote_trust_commerce_test.rb +++ b/test/remote/gateways/remote_trust_commerce_test.rb @@ -5,7 +5,8 @@ def setup @gateway = TrustCommerceGateway.new(fixtures(:trust_commerce)) @credit_card = credit_card('4111111111111111') - @check = check({account_number: 55544433221, routing_number: 789456124}) + @declined_credit_card = credit_card('4111111111111112') + @check = check({ account_number: 55544433221, routing_number: 789456124 }) @amount = 100 @@ -13,27 +14,36 @@ def setup @invalid_verification_value = '1234' @valid_address = { - :address1 => '123 Test St.', - :address2 => nil, - :city => 'Somewhere', - :state => 'CA', - :zip => '90001' + address1: '123 Test St.', + address2: nil, + city: 'Somewhere', + state: 'CA', + zip: '90001' } @invalid_address = { - :address1 => '187 Apple Tree Lane.', - :address2 => nil, - :city => 'Woodside', - :state => 'CA', - :zip => '94062' + address1: '187 Apple Tree Lane.', + address2: nil, + city: 'Woodside', + state: 'CA', + zip: '94062' + } + + # The Trust Commerce API does not return anything different when custom fields are present. + # To confirm that the field values are being stored with the transactions, add a custom + # field in your account in the Vault UI, then examine the transactions after running the + # test suite. + custom_fields = { + 'customfield1' => 'test1' } @options = { - :ip => '10.10.10.10', - :order_id => '#1000.1', - :email => 'cody@example.com', - :billing_address => @valid_address, - :shipping_address => @valid_address + ip: '10.10.10.10', + order_id: '#1000.1', + email: 'cody@example.com', + billing_address: @valid_address, + shipping_address: @valid_address, + custom_fields: custom_fields } end @@ -42,9 +52,9 @@ def test_bad_login assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal Response, response.class - assert_equal ['error', - 'offenders', - 'status'], response.params.keys.sort + assert_equal %w[error + offenders + status], response.params.keys.sort assert_match %r{A field was improperly formatted, such as non-digit characters in a number field}, response.message @@ -78,14 +88,26 @@ def test_unsuccessful_purchase_with_invalid_cvv end def test_purchase_with_avs_for_invalid_address - assert response = @gateway.purchase(@amount, @credit_card, @options.update(:billing_address => @invalid_address)) + assert response = @gateway.purchase(@amount, @credit_card, @options.update(billing_address: @invalid_address)) assert_equal 'N', response.params['avs'] assert_match %r{The transaction was successful}, response.message assert_success response end + # Requires enabling the setting: 'Allow voids to process or settle on processing node' in the Trust Commerce vault UI + def test_purchase_and_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'The transaction was successful', void.message + assert_equal 'accepted', void.params['status'] + assert void.params['transid'] + end + def test_successful_authorize_with_avs - assert response = @gateway.authorize(@amount, @credit_card, :billing_address => @valid_address) + assert response = @gateway.authorize(@amount, @credit_card, billing_address: @valid_address) assert_equal 'Y', response.avs_result['code'] assert_match %r{The transaction was successful}, response.message @@ -102,7 +124,7 @@ def test_unsuccessful_authorize_with_invalid_cvv end def test_authorization_with_avs_for_invalid_address - assert response = @gateway.authorize(@amount, @credit_card, @options.update(:billing_address => @invalid_address)) + assert response = @gateway.authorize(@amount, @credit_card, @options.update(billing_address: @invalid_address)) assert_equal 'N', response.params['avs'] assert_match %r{The transaction was successful}, response.message assert_success response @@ -146,36 +168,94 @@ def test_successful_check_refund assert_success response end - def test_store_failure + def test_successful_store assert response = @gateway.store(@credit_card) assert_equal Response, response.class - assert_match %r{The merchant can't accept data passed in this field}, response.message - assert_failure response + assert_equal 'approved', response.params['status'] + assert_match %r{The transaction was successful}, response.message + end + + def test_failed_store + assert response = @gateway.store(@declined_credit_card) + + assert_bad_data_response(response) + end + + def test_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('testme') + assert response = @gateway.unstore('does-not-exist') - assert_match %r{The merchant can't accept data passed in this field}, response.message + assert_match %r{A field was longer or shorter than the server allows}, response.message assert_failure response end - def test_recurring_failure - assert response = @gateway.recurring(@amount, @credit_card, :periodicity => :weekly) + 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 - assert_match %r{The merchant can't accept data passed in this field}, response.message + 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) + + assert_match %r{The transaction was successful}, response.message + assert_success response + end + + def test_failed_recurring + assert response = @gateway.recurring(@amount, @declined_credit_card, periodicity: :weekly) + + assert_bad_data_response(response) + end + def test_transcript_scrubbing @credit_card.verification_value = @invalid_verification_value transcript = capture_transcript(@gateway) do - @gateway.purchase(@amount, @credit_card, @options) + @gateway.purchase(@amount, @credit_card, @options) end clean_transcript = @gateway.scrub(transcript) assert_scrubbed(@credit_card.number, clean_transcript) assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end + + def test_transcript_scrubbing_echeck + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + end + + private + + def assert_bad_data_response(response) + assert_equal Response, response.class + assert_equal 'A field was improperly formatted, such as non-digit characters in a number field', response.message + assert_equal 'baddata', response.params['status'] + end end diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 021f73d910a..83fbe0343b8 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -8,89 +8,89 @@ def setup @amount = 2111 @credit_card = ActiveMerchant::Billing::CreditCard.new( - :number => '4000100011112224', - :month => 9, - :year => Time.now.year + 1, - :brand => 'visa', - :verification_value => '123', - :first_name => 'Fred', - :last_name => 'Flintstone' + number: '4000100011112224', + month: 9, + year: Time.now.year + 1, + brand: 'visa', + verification_value: '123', + first_name: 'Fred', + last_name: 'Flintstone' ) @bad_credit_card = ActiveMerchant::Billing::CreditCard.new( - :number => '4000300011112220', - :month => 9, - :year => 14, - :brand => 'visa', - :verification_value => '999', - :first_name => 'Fred', - :last_name => 'Flintstone' + number: '4000300011112220', + month: 9, + year: 14, + brand: 'visa', + verification_value: '999', + first_name: 'Fred', + last_name: 'Flintstone' ) @check = ActiveMerchant::Billing::Check.new( - :account_number => '123456789', - :routing_number => '120450780', - :account_type => 'checking', - :first_name => 'Fred', - :last_name => 'Flintstone' + account_number: '123456789', + routing_number: '120450780', + account_type: 'checking', + first_name: 'Fred', + last_name: 'Flintstone' ) cc_method = [ - {:name => 'My CC', :sort => 5, :method => @credit_card}, - {:name => 'Other CC', :sort => 12, :method => @credit_card} + { name: 'My CC', sort: 5, method: @credit_card }, + { name: 'Other CC', sort: 12, method: @credit_card } ] @options = { - :client_ip => '127.0.0.1', - :billing_address => address, + client_ip: '127.0.0.1', + billing_address: address } @transaction_options = { - :order_id => '1', - :description => 'Store Purchase' + order_id: '1', + description: 'Store Purchase' } @customer_options = { - :id => 123, - :notes => 'Customer note.', - :data => 'complex data', - :url => 'somesite.com', - :payment_methods => cc_method + id: 123, + notes: 'Customer note.', + data: 'complex data', + url: 'somesite.com', + payment_methods: cc_method } @update_customer_options = { - :notes => 'NEW NOTE!' + notes: 'NEW NOTE!' } @add_payment_options = { - :make_default => true, - :payment_method => { - :name => 'My new card.', - :sort => 10, - :method => @credit_card + make_default: true, + payment_method: { + name: 'My new card.', + sort: 10, + method: @credit_card } } @run_transaction_options = { - :payment_method => @credit_card, - :command => 'sale', - :amount => 10000 + payment_method: @credit_card, + command: 'sale', + amount: 10000 } @run_transaction_check_options = { - :payment_method => @check, - :command => 'check', - :amount => 10000 + payment_method: @check, + command: 'check', + amount: 10000 } @run_sale_options = { - :payment_method => @credit_card, - :amount => 5000 + payment_method: @credit_card, + amount: 5000 } @run_check_sale_options = { - :payment_method => @check, - :amount => 2500 + payment_method: @check, + amount: 2500 } end @@ -138,10 +138,10 @@ def test_refund def test_invalid_login gateway = UsaEpayAdvancedGateway.new( - :login => '', - :password => '', - :software_id => '' - ) + login: '', + password: '', + software_id: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid software ID', response.message @@ -158,7 +158,7 @@ def test_update_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - @options.merge!(@update_customer_options.merge!(:customer_number => customer_number)) + @options.merge!(@update_customer_options.merge!(customer_number: customer_number)) response = @gateway.update_customer(@options) assert response.params['update_customer_return'] end @@ -167,7 +167,7 @@ def test_quick_update_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.quick_update_customer({customer_number: customer_number, update_data: @update_customer_options}) + response = @gateway.quick_update_customer({ customer_number: customer_number, update_data: @update_customer_options }) assert response.params['quick_update_customer_return'] end @@ -175,10 +175,10 @@ def test_enable_disable_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.enable_customer(:customer_number => customer_number) + response = @gateway.enable_customer(customer_number: customer_number) assert response.params['enable_customer_return'] - response = @gateway.disable_customer(:customer_number => customer_number) + response = @gateway.disable_customer(customer_number: customer_number) assert response.params['disable_customer_return'] end @@ -186,7 +186,7 @@ def test_add_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) + @options.merge!(customer_number: customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) assert response.params['add_customer_payment_method_return'] end @@ -196,7 +196,7 @@ def test_add_customer_payment_method_verify customer_number = response.params['add_customer_return'] @add_payment_options[:payment_method][:method] = @bad_credit_card - @options.merge!(:customer_number => customer_number, :verify => true).merge!(@add_payment_options) + @options.merge!(customer_number: customer_number, verify: true).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) assert response.params['faultstring'] end @@ -205,7 +205,7 @@ def test_get_customer_payment_methods response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.get_customer_payment_methods(:customer_number => customer_number) + response = @gateway.get_customer_payment_methods(customer_number: customer_number) assert response.params['get_customer_payment_methods_return']['item'] end @@ -213,10 +213,10 @@ def test_get_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.get_customer_payment_methods(:customer_number => customer_number) + response = @gateway.get_customer_payment_methods(customer_number: customer_number) id = response.params['get_customer_payment_methods_return']['item'][0]['method_id'] - response = @gateway.get_customer_payment_method(:customer_number => customer_number, :method_id => id) + response = @gateway.get_customer_payment_method(customer_number: customer_number, method_id: id) assert response.params['get_customer_payment_method_return'] end @@ -224,12 +224,12 @@ def test_update_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) + @options.merge!(customer_number: customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) payment_method_id = response.params['add_customer_payment_method_return'] - update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, - :name => 'Updated Card.') + update_payment_options = @add_payment_options[:payment_method].merge(method_id: payment_method_id, + name: 'Updated Card.') response = @gateway.update_customer_payment_method(update_payment_options) assert response.params['update_customer_payment_method_return'] @@ -239,11 +239,11 @@ def test_delete_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) + @options.merge!(customer_number: customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) id = response.params['add_customer_payment_method_return'] - response = @gateway.delete_customer_payment_method(:customer_number => customer_number, :method_id => id) + response = @gateway.delete_customer_payment_method(customer_number: customer_number, method_id: id) assert response.params['delete_customer_payment_method_return'] end @@ -251,7 +251,7 @@ def test_delete_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.delete_customer(:customer_number => customer_number) + response = @gateway.delete_customer(customer_number: customer_number) assert response.params['delete_customer_return'] end @@ -259,8 +259,8 @@ def test_run_customer_transaction response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.run_customer_transaction(:customer_number => customer_number, # :method_id => 0, # optional - :command => 'Sale', :amount => 3000) + response = @gateway.run_customer_transaction(customer_number: customer_number, # :method_id => 0, # optional + command: 'Sale', amount: 3000) assert response.params['run_customer_transaction_return'] end @@ -322,7 +322,7 @@ def test_capture_transaction response = @gateway.run_auth_only(options) reference_number = response.params['run_auth_only_return']['ref_num'] - options = @options.merge(:reference_number => reference_number) + options = @options.merge(reference_number: reference_number) response = @gateway.capture_transaction(options) assert response.params['capture_transaction_return'] end @@ -332,7 +332,7 @@ def test_void_transaction response = @gateway.run_sale(options) reference_number = response.params['run_sale_return']['ref_num'] - options = @options.merge(:reference_number => reference_number) + options = @options.merge(reference_number: reference_number) response = @gateway.void_transaction(options) assert response.params['void_transaction_return'] end @@ -342,7 +342,7 @@ def test_refund_transaction response = @gateway.run_sale(options) reference_number = response.params['run_sale_return']['ref_num'] - options = @options.merge(:reference_number => reference_number, :amount => 0) + options = @options.merge(reference_number: reference_number, amount: 0) response = @gateway.refund_transaction(options) assert response.params['refund_transaction_return'] end @@ -353,7 +353,7 @@ def test_override_transaction response = @gateway.run_check_sale(options) reference_number = response.params['run_check_sale_return']['ref_num'] - response = @gateway.override_transaction(:reference_number => reference_number, :reason => 'Because I said so') + response = @gateway.override_transaction(reference_number: reference_number, reason: 'Because I said so') assert response.params['faultstring'] end @@ -362,7 +362,7 @@ def test_run_quick_sale response = @gateway.run_sale(@options) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.run_quick_sale(:reference_number => reference_number, :amount => 9900) + response = @gateway.run_quick_sale(reference_number: reference_number, amount: 9900) assert response.params['run_quick_sale_return'] end @@ -371,7 +371,7 @@ def test_run_quick_sale_check response = @gateway.run_check_sale(@options) reference_number = response.params['run_check_sale_return']['ref_num'] - response = @gateway.run_quick_sale(:reference_number => reference_number, :amount => 9900) + response = @gateway.run_quick_sale(reference_number: reference_number, amount: 9900) assert response.params['run_quick_sale_return'] end @@ -380,7 +380,7 @@ def test_run_quick_credit response = @gateway.run_sale(@options) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.run_quick_credit(:reference_number => reference_number, :amount => 0) + response = @gateway.run_quick_credit(reference_number: reference_number, amount: 0) assert response.params['run_quick_credit_return'] end @@ -389,7 +389,7 @@ def test_run_quick_credit_check response = @gateway.run_check_sale(@options) reference_number = response.params['run_check_sale_return']['ref_num'] - response = @gateway.run_quick_credit(:reference_number => reference_number, :amount => 1234) + response = @gateway.run_quick_credit(reference_number: reference_number, amount: 1234) assert response.params['run_quick_credit_return'] end @@ -399,7 +399,7 @@ def test_get_transaction response = @gateway.run_sale(@options.merge(@run_sale_options)) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.get_transaction(:reference_number => reference_number) + response = @gateway.get_transaction(reference_number: reference_number) assert response.params['get_transaction_return'] end @@ -407,7 +407,7 @@ def test_get_transaction_status response = @gateway.run_sale(@options.merge(@run_sale_options)) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.get_transaction_status(:reference_number => reference_number) + response = @gateway.get_transaction_status(reference_number: reference_number) assert response.params['get_transaction_status_return'] end @@ -415,11 +415,11 @@ def test_get_transaction_custom response = @gateway.run_sale(@options.merge(@run_sale_options)) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.get_transaction_custom(:reference_number => reference_number, - :fields => ['Response.StatusCode', 'Response.Status']) + response = @gateway.get_transaction_custom(reference_number: reference_number, + fields: ['Response.StatusCode', 'Response.Status']) assert response.params['get_transaction_custom_return'] - response = @gateway.get_transaction_custom(:reference_number => reference_number, - :fields => ['Response.StatusCode']) + response = @gateway.get_transaction_custom(reference_number: reference_number, + fields: ['Response.StatusCode']) assert response.params['get_transaction_custom_return'] end @@ -427,7 +427,7 @@ def test_get_check_trace response = @gateway.run_check_sale(@options.merge(@run_check_sale_options)) reference_number = response.params['run_check_sale_return']['ref_num'] - response = @gateway.get_check_trace(:reference_number => reference_number) + response = @gateway.get_check_trace(reference_number: reference_number) assert response.params['get_check_trace_return'] end diff --git a/test/remote/gateways/remote_usa_epay_transaction_test.rb b/test/remote/gateways/remote_usa_epay_transaction_test.rb index 52c24f212c9..9370bb23893 100644 --- a/test/remote/gateways/remote_usa_epay_transaction_test.rb +++ b/test/remote/gateways/remote_usa_epay_transaction_test.rb @@ -3,11 +3,13 @@ class RemoteUsaEpayTransactionTest < Test::Unit::TestCase def setup @gateway = UsaEpayTransactionGateway.new(fixtures(:usa_epay)) + @gateway_with_pin = UsaEpayTransactionGateway.new(fixtures(:usa_epay_with_pin)) @credit_card = credit_card('4000100011112224') @declined_card = credit_card('4000300011112220') @credit_card_with_track_data = credit_card_with_track_data('4000100011112224') + @invalid_transaction_card = credit_card('4000300511112225') @check = check - @options = { :billing_address => address(:zip => '27614', :state => 'NC'), :shipping_address => address } + @options = { billing_address: address(zip: '27614', state: 'NC'), shipping_address: address } @amount = 100 end @@ -17,6 +19,26 @@ def test_successful_purchase assert_success response end + def test_successful_purchase_with_store + response = @gateway.store(@credit_card, @options) + assert_success response + + payment_token = response.authorization + assert response = @gateway.purchase(@amount, payment_token, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_authorize_with_store + response = @gateway.store(@credit_card, @options) + assert_success response + + payment_token = response.authorization + assert response = @gateway.authorize(@amount, payment_token, @options) + assert_equal 'Success', response.message + assert_success response + end + def test_successful_purchase_with_track_data assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) assert_equal 'Success', response.message @@ -51,19 +73,19 @@ def test_successful_purchase_with_manual_entry end def test_successful_purchase_with_extra_details - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:order_id => generate_unique_id, :description => 'socool')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(order_id: generate_unique_id, description: 'socool')) assert_equal 'Success', response.message assert_success response end def test_successful_purchase_with_extra_test_mode - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:test_mode => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(test_mode: true)) assert_equal 'Success', response.message assert_success response end def test_successful_purchase_with_email_receipt - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'hank@hill.com', :cust_receipt => 'Yes')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(email: 'hank@hill.com', cust_receipt: 'Yes')) assert_equal 'Success', response.message assert_success response end @@ -99,8 +121,8 @@ def test_successful_purchase_with_custom_fields def test_successful_purchase_with_line_items line_items = [ - {sku: 'abc123', cost: 119, quantity: 1}, - {sku: 'def456', cost: 200, quantity: 2, name: 'an item' } + { sku: 'abc123', cost: 119, quantity: 1 }, + { sku: 'def456', cost: 200, quantity: 2, name: 'an item' } ] assert response = @gateway.purchase(@amount, @credit_card, @options.merge(line_items: line_items)) @@ -108,11 +130,25 @@ def test_successful_purchase_with_line_items assert_success response end + def test_successful_purchase_with_pin + assert response = @gateway_with_pin.purchase(@amount, @credit_card, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_unsuccessful_purchase_with_bad_pin + gateway = UsaEpayTransactionGateway.new(fixtures(:usa_epay_with_pin).merge({ pin: 'bad_pin' })) + + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_equal 'Transaction authentication failed', response.message + assert_failure response + end + def test_unsuccessful_purchase # For some reason this will fail with "You have tried this card too # many times, please contact merchant" unless a unique order id is # passed. - assert response = @gateway.purchase(@amount, @declined_card, @options.merge(:order_id => generate_unique_id)) + assert response = @gateway.purchase(@amount, @declined_card, @options.merge(order_id: generate_unique_id)) assert_failure response assert_match(/declined/i, response.message) assert Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code @@ -208,7 +244,7 @@ def test_unsuccessful_void_release end def test_invalid_key - gateway = UsaEpayTransactionGateway.new(:login => '') + gateway = UsaEpayTransactionGateway.new(login: '') assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Specified source key not found.', response.message assert_failure response @@ -253,4 +289,10 @@ def test_transcript_scrubbing assert_scrubbed(@check.account_number, transcript) assert_scrubbed(@gateway.options[:login], transcript) end + + def test_processing_error + assert response = @gateway.purchase(@amount, @invalid_transaction_card, @options) + assert_equal 'processing_error', response.error_code + assert_failure response + end end diff --git a/test/remote/gateways/remote_vanco_test.rb b/test/remote/gateways/remote_vanco_test.rb index 077b9b1a248..c35cdf02ad9 100644 --- a/test/remote/gateways/remote_vanco_test.rb +++ b/test/remote/gateways/remote_vanco_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class RemoteVancoTest < Test::Unit::TestCase + SECONDS_PER_DAY = 3600 * 24 + def setup @gateway = VancoGateway.new(fixtures(:vanco)) @@ -34,6 +36,42 @@ def test_successful_purchase_with_ip_address assert_equal 'Success', response.message end + def test_successful_purchase_with_existing_session_id + previous_login_response = @gateway.send(:login) + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + session_id: + { + id: previous_login_response.params['response_sessionid'], + created_at: Time.parse(previous_login_response.params['auth_requesttime']) + } + ) + ) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_expired_session_id + two_days_from_now = Time.now - (2 * SECONDS_PER_DAY) + previous_login_response = @gateway.send(:login) + + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge( + session_id: { + id: previous_login_response.params['response_sessionid'], + created_at: two_days_from_now + } + ) + ) + assert_success response + assert_equal 'Success', response.message + end + def test_successful_purchase_sans_minimal_options response = @gateway.purchase(@amount, @credit_card) assert_success response @@ -73,7 +111,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount-1, purchase.authorization) + refund = @gateway.refund(@amount - 1, purchase.authorization) assert_success refund end @@ -81,7 +119,7 @@ def test_failed_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount+500, purchase.authorization) + refund = @gateway.refund(@amount + 500, purchase.authorization) assert_failure refund assert_match(/Amount Cannot Be Greater Than/, refund.message) end @@ -97,6 +135,15 @@ def test_transcript_scrubbing assert_scrubbed(@gateway.options[:password], transcript) end + def test_account_number_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + end + def test_invalid_login gateway = VancoGateway.new( user_id: 'unknown_id', diff --git a/test/remote/gateways/remote_verifi_test.rb b/test/remote/gateways/remote_verifi_test.rb index 2514864a96f..8cb815df077 100644 --- a/test/remote/gateways/remote_verifi_test.rb +++ b/test/remote/gateways/remote_verifi_test.rb @@ -10,9 +10,9 @@ def setup # Replace with your login and password for the Verifi test environment @options = { - :order_id => '37', - :email => 'test@example.com', - :billing_address => address + order_id: '37', + email: 'test@example.com', + billing_address: address } @amount = 100 @@ -85,8 +85,8 @@ def test_purchase_and_credit def test_bad_login gateway = VerifiGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) assert response = gateway.purchase(@amount, @credit_card, @options) diff --git a/test/remote/gateways/remote_viaklix_test.rb b/test/remote/gateways/remote_viaklix_test.rb index c3dc47c1a43..c4d4c63094a 100644 --- a/test/remote/gateways/remote_viaklix_test.rb +++ b/test/remote/gateways/remote_viaklix_test.rb @@ -8,10 +8,10 @@ def setup @bad_credit_card = credit_card('invalid') @options = { - :order_id => '#1000.1', - :email => 'paul@domain.com', - :description => 'Test Transaction', - :billing_address => address + order_id: '#1000.1', + email: 'paul@domain.com', + description: 'Test Transaction', + billing_address: address } @amount = 100 end diff --git a/test/remote/gateways/remote_visanet_peru_test.rb b/test/remote/gateways/remote_visanet_peru_test.rb index d596f4b451e..9309d63a622 100644 --- a/test/remote/gateways/remote_visanet_peru_test.rb +++ b/test/remote/gateways/remote_visanet_peru_test.rb @@ -58,7 +58,7 @@ def test_successful_authorize_and_capture assert_equal @options[:order_id], response.params['externalTransactionId'] assert_equal '1.00', response.params['data']['IMP_AUTORIZADO'] - capture = @gateway.capture(response.authorization, @options) + capture = @gateway.capture(@amount, response.authorization, @options) assert_success capture assert_equal 'OK', capture.message assert capture.authorization @@ -73,21 +73,25 @@ def test_successful_authorize_fractional_amount assert_equal '1.99', response.params['data']['IMP_AUTORIZADO'] end - def test_failed_authorize + def test_failed_authorize_declined_card response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response assert_equal 400, response.error_code assert_equal 'Operacion Denegada.', response.message + end + def test_failed_authorize_bad_email @options[:email] = 'cybersource@reject.com' response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 400, response.error_code - assert_equal 'REJECT', response.message + + # this also exercises message joining for errorMessage and DSC_COD_ACCION when both are present + assert_equal 'REJECT | Operacion denegada', response.message end def test_failed_capture - response = @gateway.capture('900000044') + response = @gateway.capture(@amount, '900000044') assert_failure response assert_match(/NUMORDEN 900000044 no se encuentra registrado/, response.message) assert_equal 400, response.error_code diff --git a/test/remote/gateways/remote_vpos_test.rb b/test/remote/gateways/remote_vpos_test.rb new file mode 100644 index 00000000000..4a75f8d1754 --- /dev/null +++ b/test/remote/gateways/remote_vpos_test.rb @@ -0,0 +1,115 @@ +require 'test_helper' + +class RemoteVposTest < Test::Unit::TestCase + def setup + @gateway = VposGateway.new(fixtures(:vpos)) + + @amount = 100000 + @credit_card = credit_card('5418630110000014', month: 8, year: 2026, verification_value: '277') + @declined_card = credit_card('4000300011112220') + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaccion aprobada', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'IMPORTE DE LA TRN INFERIOR AL M¿NIMO PERMITIDO', response.message + end + + def test_successful_refund_using_auth + shop_process_id = SecureRandom.random_number(10**15) + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + authorization = purchase.authorization + + assert refund = @gateway.refund(@amount, authorization, @options.merge(shop_process_id: shop_process_id)) + assert_success refund + assert_equal 'Transaccion aprobada', refund.message + end + + def test_successful_refund_using_shop_process_id + shop_process_id = SecureRandom.random_number(10**15) + + assert purchase = @gateway.purchase(@amount, @credit_card, @options.merge(shop_process_id: shop_process_id)) + assert_success purchase + + assert refund = @gateway.refund(@amount, nil, original_shop_process_id: shop_process_id) # 315300749110268, 21611732218038 + assert_success refund + assert_equal 'Transaccion aprobada', refund.message + end + + def test_successful_credit + assert credit = @gateway.credit(@amount, @credit_card) + assert_success credit + assert_equal 'Transaccion aprobada', credit.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card) + assert_failure response + assert_equal 'RefundsServiceError:TIPO DE TRANSACCION NO PERMITIDA PARA TARJETAS EXTRANJERAS', response.message + end + + def test_successful_void + shop_process_id = SecureRandom.random_number(10**15) + options = @options.merge({ shop_process_id: shop_process_id }) + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, options) + assert_success void + assert_equal 'RollbackSuccessful:Transacción Aprobada', void.message + end + + def test_duplicate_void_fails + shop_process_id = SecureRandom.random_number(10**15) + options = @options.merge({ shop_process_id: shop_process_id }) + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, options) + assert_success void + assert_equal 'RollbackSuccessful:Transacción Aprobada', void.message + + assert duplicate_void = @gateway.void(purchase.authorization, options) + assert_failure duplicate_void + assert_equal 'AlreadyRollbackedError:The payment has already been rollbacked.', duplicate_void.message + end + + def test_failed_void + response = @gateway.void('abc#123') + assert_failure response + assert_equal 'BuyNotFoundError:Business Error', response.message + end + + def test_invalid_login + gateway = VposGateway.new(private_key: '', public_key: '', encryption_key: OpenSSL::PKey::RSA.new(512), commerce: 123, commerce_branch: 45) + + response = gateway.void('') + assert_failure response + assert_match %r{InvalidPublicKeyError}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + 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) + end +end diff --git a/test/remote/gateways/remote_vpos_without_key_test.rb b/test/remote/gateways/remote_vpos_without_key_test.rb new file mode 100644 index 00000000000..a17e98838f2 --- /dev/null +++ b/test/remote/gateways/remote_vpos_without_key_test.rb @@ -0,0 +1,125 @@ +require 'test_helper' + +class RemoteVposWithoutKeyTest < Test::Unit::TestCase + def setup + vpos_fixtures = fixtures(:vpos) + vpos_fixtures.delete(:encryption_key) + @gateway = VposGateway.new(vpos_fixtures) + + @amount = 100000 + @credit_card = credit_card('5418630110000014', month: 8, year: 2026, verification_value: '277') + @declined_card = credit_card('4000300011112220') + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaccion aprobada', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'IMPORTE DE LA TRN INFERIOR AL M¿NIMO PERMITIDO', response.message + end + + def test_successful_refund_using_auth + shop_process_id = SecureRandom.random_number(10**15) + + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + authorization = purchase.authorization + + assert refund = @gateway.refund(@amount, authorization, @options.merge(shop_process_id: shop_process_id)) + assert_success refund + assert_equal 'Transaccion aprobada', refund.message + end + + def test_successful_refund_using_shop_process_id + shop_process_id = SecureRandom.random_number(10**15) + + assert purchase = @gateway.purchase(@amount, @credit_card, @options.merge(shop_process_id: shop_process_id)) + assert_success purchase + + assert refund = @gateway.refund(@amount, nil, original_shop_process_id: shop_process_id) # 315300749110268, 21611732218038 + assert_success refund + assert_equal 'Transaccion aprobada', refund.message + end + + def test_successful_credit + assert credit = @gateway.credit(@amount, @credit_card) + assert_success credit + assert_equal 'Transaccion aprobada', credit.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card) + assert_failure response + assert_equal 'RefundsServiceError:TIPO DE TRANSACCION NO PERMITIDA PARA TARJETAS EXTRANJERAS', response.message + end + + def test_successful_void + shop_process_id = SecureRandom.random_number(10**15) + options = @options.merge({ shop_process_id: shop_process_id }) + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, options) + assert_success void + assert_equal 'RollbackSuccessful:Transacción Aprobada', void.message + end + + def test_duplicate_void_fails + shop_process_id = SecureRandom.random_number(10**15) + options = @options.merge({ shop_process_id: shop_process_id }) + + purchase = @gateway.purchase(@amount, @credit_card, options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, options) + assert_success void + assert_equal 'RollbackSuccessful:Transacción Aprobada', void.message + + assert duplicate_void = @gateway.void(purchase.authorization, options) + assert_failure duplicate_void + assert_equal 'AlreadyRollbackedError:The payment has already been rollbacked.', duplicate_void.message + end + + def test_failed_void + response = @gateway.void('abc#123') + assert_failure response + assert_equal 'BuyNotFoundError:Business Error', response.message + end + + def test_invalid_login + gateway = VposGateway.new(private_key: '', public_key: '', encryption_key: OpenSSL::PKey::RSA.new(512), commerce: 123, commerce_branch: 45) + + response = gateway.void('') + assert_failure response + assert_match %r{InvalidPublicKeyError}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + 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) + end + + def test_regenerate_encryption_key + puts 'Regenerating encryption key.' + puts 'Before running the standard vpos remote test suite, run this test individually:' + puts '$ ruby -Ilib:test test/remote/gateways/remote_vpos_without_key_test.rb -n test_regenerate_encryption_key' + puts 'Then copy this key into your fixtures file.' + p @gateway.one_time_public_key + end +end diff --git a/test/remote/gateways/remote_webpay_test.rb b/test/remote/gateways/remote_webpay_test.rb index 3b0e099ea33..42662e25e8e 100644 --- a/test/remote/gateways/remote_webpay_test.rb +++ b/test/remote/gateways/remote_webpay_test.rb @@ -3,7 +3,6 @@ require 'test_helper' class RemoteWebpayTest < Test::Unit::TestCase - def setup @gateway = WebpayGateway.new(fixtures(:webpay)) @@ -14,8 +13,8 @@ def setup @new_credit_card = credit_card('5105105105105100') @options = { - :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com' + description: 'ActiveMerchant Test Purchase', + email: 'wow@example.com' } end @@ -33,13 +32,13 @@ def test_appropriate_purchase_amount end def test_purchase_description - assert response = @gateway.purchase(@amount, @credit_card, { :description => 'TheDescription', :email => 'email@example.com' }) + assert response = @gateway.purchase(@amount, @credit_card, { description: 'TheDescription', email: 'email@example.com' }) assert_equal 'TheDescription', response.params['description'], "Use the description if it's specified." - assert response = @gateway.purchase(@amount, @credit_card, { :email => 'email@example.com' }) + assert response = @gateway.purchase(@amount, @credit_card, { email: 'email@example.com' }) assert_equal 'email@example.com', response.params['description'], 'Use the email if no description is specified.' - assert response = @gateway.purchase(@amount, @credit_card, { }) + assert response = @gateway.purchase(@amount, @credit_card, {}) assert_nil response.params['description'], 'No description or email specified.' end @@ -105,7 +104,7 @@ def test_unsuccessful_refund end def test_successful_store - assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert response = @gateway.store(@credit_card, { description: 'Active Merchant Test Customer', email: 'email@example.com' }) assert_success response assert_equal 'customer', response.params['object'] assert_equal 'Active Merchant Test Customer', response.params['description'] @@ -114,7 +113,7 @@ def test_successful_store end def test_successful_update - creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Customer'}) + creation = @gateway.store(@credit_card, { description: 'Active Merchant Update Customer' }) assert response = @gateway.update(creation.params['id'], @new_credit_card) assert_success response assert_equal 'Active Merchant Update Customer', response.params['description'] @@ -122,17 +121,16 @@ def test_successful_update end def test_successful_unstore - creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) + creation = @gateway.store(@credit_card, { description: 'Active Merchant Unstore Customer' }) assert response = @gateway.unstore(creation.params['id']) assert_success response assert_equal true, response.params['deleted'] end def test_invalid_login - gateway = WebpayGateway.new(:login => 'active_merchant_test') + gateway = WebpayGateway.new(login: 'active_merchant_test') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid API key provided. Check your API key is correct.', response.message end - end diff --git a/test/remote/gateways/remote_wepay_test.rb b/test/remote/gateways/remote_wepay_test.rb index 4a1b01f061d..a227956ddec 100644 --- a/test/remote/gateways/remote_wepay_test.rb +++ b/test/remote/gateways/remote_wepay_test.rb @@ -128,7 +128,7 @@ def test_unsuccessful_store_via_create_with_cvv # end def test_successful_store_with_defaulted_email - response = @gateway.store(@credit_card, {billing_address: address}) + response = @gateway.store(@credit_card, { billing_address: address }) assert_success response end @@ -169,7 +169,7 @@ def test_authorize_and_capture authorize = @gateway.authorize(@amount, @credit_card, @options) assert_success authorize - sleep 30 # Wait for authorization to clear. Doesn't always work. + sleep 30 # Wait for authorization to clear. Doesn't always work. assert capture = @gateway.capture(nil, authorize.authorization) assert_success capture end diff --git a/test/remote/gateways/remote_wirecard_test.rb b/test/remote/gateways/remote_wirecard_test.rb index 586e1567351..213d037d231 100644 --- a/test/remote/gateways/remote_wirecard_test.rb +++ b/test/remote/gateways/remote_wirecard_test.rb @@ -119,14 +119,14 @@ def test_successful_purchase_with_german_address_german_state_and_german_phone end def test_successful_purchase_with_german_address_no_state_and_invalid_phone - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({state: nil, phone: '1234'}))) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({ state: nil, phone: '1234' }))) assert_success response assert response.message[/THIS IS A DEMO/] end def test_successful_purchase_with_german_address_and_valid_phone - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({phone: '+049-261-1234-123'}))) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({ phone: '+049-261-1234-123' }))) assert_success response assert response.message[/THIS IS A DEMO/] @@ -190,13 +190,13 @@ def test_successful_store_then_purchase_by_reference end def test_successful_authorization_as_recurring_transaction_type_initial - assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: 'Initial')) assert_success response assert response.authorization end def test_successful_purchase_as_recurring_transaction_type_initial - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(recurring: 'Initial')) assert_success response assert response.authorization end @@ -258,7 +258,7 @@ def test_invalid_login def test_transcript_scrubbing transcript = capture_transcript(@gateway) do - @gateway.purchase(@amount, @credit_card, @options) + @gateway.purchase(@amount, @credit_card, @options) end clean_transcript = @gateway.scrub(transcript) diff --git a/test/remote/gateways/remote_wompi_test.rb b/test/remote/gateways/remote_wompi_test.rb new file mode 100644 index 00000000000..65c312a1153 --- /dev/null +++ b/test/remote/gateways/remote_wompi_test.rb @@ -0,0 +1,125 @@ +require 'test_helper' + +class RemoteWompiTest < Test::Unit::TestCase + def setup + @gateway = WompiGateway.new(fixtures(:wompi)) + + @amount = 150000 + @credit_card = credit_card('4242424242424242') + @credit_card_without_cvv = credit_card('4242424242424242', verification_value: nil) + @declined_card = credit_card('4111111111111111') + @options = { + billing_address: address, + description: 'Store Purchase', + currency: 'COP' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_more_options + reference = SecureRandom.alphanumeric(12) + response = @gateway.purchase(@amount, @credit_card, @options.merge(reference: reference, installments: 3)) + assert_success response + response_data = response.params['data'] + assert_equal response_data.dig('reference'), reference + assert_equal response_data.dig('payment_method', 'installments'), 3 + end + + def test_successful_purchase_without_cvv + response = @gateway.purchase(@amount, @credit_card_without_cvv, @options) + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'La transacción fue rechazada (Sandbox)', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + end + + def test_successful_auth_capture_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert void = @gateway.void(capture.authorization) + assert_success void + end + + def test_failed_capture + response = @gateway.authorize(@amount, @declined_card, @options) + assert_success response + + assert capture = @gateway.capture(@amount, response.authorization) + assert_failure capture + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 50000, purchase.authorization) + assert_success refund + end + + # def test_failed_refund + # response = @gateway.refund(@amount, '') + # assert_failure response + # message = JSON.parse(response.message) + # assert_equal 'transaction_id Debe ser completado', message['transaction_id'].first + # end + + def test_successful_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('bad_auth') + assert_failure response + assert_equal 'La entidad solicitada no existe', response.message + end + + def test_invalid_login + gateway = WompiGateway.new(test_public_key: 'weet', test_private_key: 'woo') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{La llave proporcionada no corresponde a este ambiente}, 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[:test_private_key], transcript) + end +end diff --git a/test/remote/gateways/remote_world_net_test.rb b/test/remote/gateways/remote_world_net_test.rb index 67d198f1fbf..d7db06354fd 100644 --- a/test/remote/gateways/remote_world_net_test.rb +++ b/test/remote/gateways/remote_world_net_test.rb @@ -8,7 +8,7 @@ def setup @declined_amount = 101 @credit_card = credit_card('3779810000000005') @options = { - order_id: generate_order_id, + order_id: generate_order_id } @refund_options = { operator: 'mr.nobody', @@ -28,7 +28,7 @@ def test_successful_purchase_with_more_options email: 'joe@example.com', billing_address: address, description: 'Store Purchase', - ip: '127.0.0.1', + ip: '127.0.0.1' } response = @gateway.purchase(@amount, @credit_card, options) @@ -72,7 +72,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -95,7 +95,7 @@ def test_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert refund = @gateway.refund(@amount-1, purchase.authorization, @refund_options) + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @refund_options) assert_success refund end diff --git a/test/remote/gateways/remote_worldpay_online_payments_test.rb b/test/remote/gateways/remote_worldpay_online_payments_test.rb index 4f0b2ff05c2..bcb99f603fa 100644 --- a/test/remote/gateways/remote_worldpay_online_payments_test.rb +++ b/test/remote/gateways/remote_worldpay_online_payments_test.rb @@ -67,7 +67,7 @@ def test_partial_capture auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert capture = @gateway.capture(@amount-1, auth.authorization) + assert capture = @gateway.capture(@amount - 1, auth.authorization) assert_success capture end @@ -104,7 +104,7 @@ def test_failed_partial_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - refund = @gateway.refund(@amount+1, purchase.authorization) + refund = @gateway.refund(@amount + 1, purchase.authorization) assert_failure refund end diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb index 03f5c0c4fd4..cf90dfe3e28 100644 --- a/test/remote/gateways/remote_worldpay_test.rb +++ b/test/remote/gateways/remote_worldpay_test.rb @@ -1,29 +1,166 @@ require 'test_helper' class RemoteWorldpayTest < Test::Unit::TestCase - def setup @gateway = WorldpayGateway.new(fixtures(:world_pay_gateway)) @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') - @elo_credit_card = credit_card('4514 1600 0000 0008', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @amex_card = credit_card('3714 496353 98431') + @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', + month: 10, + year: @year + ) + @cabal_card = credit_card('6035220000000006') + @naranja_card = credit_card('5895620000000002') + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') + @declined_card = credit_card('4111111111111111', first_name: nil, last_name: 'REFUSED') + @threeDS_card = credit_card('4111111111111111', first_name: nil, last_name: 'doot') + @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', + brand: 'visa', + eci: '07', + source: :network_token, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + @nt_credit_card_without_eci = network_tokenization_credit_card( + '4895370015293175', + source: :network_token, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' ) - @declined_card = credit_card('4111111111111111', :first_name => nil, :last_name => 'REFUSED') - @threeDS_card = credit_card('4111111111111111', :first_name => nil, :last_name => '3D') - @threeDS_card_external_MPI = credit_card('4444333322221111', :first_name => 'AA', :last_name => 'BD') @options = { order_id: generate_unique_id, email: 'wow@example.com' } + + @level_two_data = { + level_2_data: { + invoice_reference_number: 'INV12233565', + customer_reference: 'CUST00000101', + card_acceptor_tax_id: 'VAT1999292', + sales_tax: { + amount: '20', + exponent: '2', + currency: 'USD' + } + } + } + + @level_three_data = { + 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: { + description: 'Laptop 14', + product_code: 'LP00125', + commodity_code: 'COM00125', + quantity: '2', + unit_cost: { + amount: '1500', + exponent: '2', + currency: 'USD' + }, + 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' + } + } + } + } + + @store_options = { + customer: generate_unique_id, + email: 'wow@example.com' + } + + @sub_merchant_options = { + sub_merchant_data: { + pf_id: '12345678901', + sub_name: 'Example Shop', + sub_id: '1234567', + sub_street: '123 Street', + sub_city: 'San Francisco', + sub_state: 'CA', + sub_country_code: '840', + sub_postal_code: '94101', + sub_tax_id: '987-65-4321' + } + } + @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: 'abc1234567890', + eci: '07', + transaction_id: 'abc123', + 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' + ) end def test_successful_purchase @@ -32,14 +169,165 @@ def test_successful_purchase assert_equal 'SUCCESS', response.message end + def test_successful_purchase_with_network_token + assert response = @gateway.purchase(@amount, @nt_credit_card, @options) + assert_success response + 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) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_with_card_holder_name_apple_pay + response = @gateway.authorize(@amount, @apple_pay_network_token, @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_card_holder_name_google_pay + response = @gateway.authorize(@amount, @google_pay_network_token, @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_without_card_holder_name_apple_pay + @apple_pay_network_token.first_name = '' + @apple_pay_network_token.last_name = '' + + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + + assert_success response + assert_equal 'authorize', response.params['action'] + 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_unsucessfull_authorize_without_token_number_apple_pay + @apple_pay_network_token.number = nil + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + + assert_failure response + assert_equal response.error_code, '5' + assert_equal "Element 'tokenNumber' must have valid numeric content.", response.message + end + + def test_unsucessfull_authorize_with_token_number_as_empty_string_apple_pay + @apple_pay_network_token.number = '' + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + + assert_failure response + assert_equal response.error_code, '5' + assert_equal "Element 'tokenNumber' must have valid numeric content.", response.message + end + + def test_unsucessfull_authorize_with_invalid_token_number_apple_pay + @apple_pay_network_token.first_name = 'REFUSED' # Magic value for testing purposes + @apple_pay_network_token.last_name = '' + + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + assert_failure response + assert_equal 'REFUSED', response.message + end + + def test_unsuccessful_authorize_with_overdue_expire_date_apple_pay + @apple_pay_network_token.month = 10 + @apple_pay_network_token.year = 2019 + + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + assert_failure response + assert_equal 'Invalid payment details : Expiry date = 10/2019', response.message + end + + def test_unsuccessful_authorize_without_expire_date_apple_pay + @apple_pay_network_token.month = nil + @apple_pay_network_token.year = nil + + response = @gateway.authorize(@amount, @apple_pay_network_token, @options) + assert_failure response + assert_match(/of type NMTOKEN must be a name token/, response.message) + end + + def test_purchase_with_apple_pay_card_apple_pay + assert auth = @gateway.purchase(@amount, @apple_pay_network_token, @options) + assert_success auth + assert_equal 'SUCCESS', auth.message + assert auth.authorization + end + + def test_successful_authorize_with_void_apple_pay + assert auth = @gateway.authorize(@amount, @apple_pay_network_token, @options) + assert_success auth + assert_equal 'authorize', auth.params['action'] + assert_equal @amount, auth.params['amount_value'].to_i + assert_equal 'GBP', auth.params['amount_currency_code'] + 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_purchase_with_refund_apple_pay + assert auth = @gateway.purchase(@amount, @apple_pay_network_token, @options) + assert_success auth + assert_equal 'capture', auth.params['action'] + assert_equal @amount, auth.params['amount_value'].to_i + assert_equal 'GBP', auth.params['amount_currency_code'] + assert auth.authorization + assert refund = @gateway.refund(@amount, auth.authorization, @options.merge(authorization_validated: true)) + assert_success refund + end + + def test_successful_store_apple_pay + assert response = @gateway.store(@apple_pay_network_token, @store_options) + assert_success response + assert_equal 'SUCCESS', response.message + assert_match response.params['payment_token_id'], response.authorization + assert_match 'shopper', response.authorization + assert_match @store_options[:customer], response.authorization + end + def test_successful_purchase_with_elo assert response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL')) assert_success response assert_equal 'SUCCESS', response.message end + def test_successful_purchase_with_two_digits_expiration_year + assert response = @gateway.purchase(@amount, @credit_card_with_two_digits_year, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_cabal + response = @gateway.purchase(@amount, @cabal_card, @options.merge(currency: 'ARS')) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_naranja + response = @gateway.purchase(@amount, @naranja_card, @options.merge(currency: 'ARS')) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_skipping_capture + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_capture: true)) + assert_success response + assert response.responses.length == 1 + assert_equal 'SUCCESS', response.message + end + def test_successful_authorize_avs_and_cvv - card = credit_card('4111111111111111', :verification_value => 555) + card = credit_card('4111111111111111', verification_value: 555) assert response = @gateway.authorize(@amount, card, @options.merge(billing_address: address.update(zip: 'CCCC'))) assert_success response assert_equal 'SUCCESS', response.message @@ -47,6 +335,41 @@ def test_successful_authorize_avs_and_cvv assert_match %r{CVV matches}, response.cvv_result['message'] end + def test_successful_authorize_with_sub_merchant_data + options = @options.merge(@sub_merchant_options) + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_3ds2_authorize + options = @options.merge({ execute_threed: true, three_ds_version: '2.0' }) + assert response = @gateway.authorize(@amount, @threeDS2_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_3ds2_authorize_with_browser_size + options = @options.merge({ execute_threed: true, three_ds_version: '2.0', browser_size: '390x400' }) + assert response = @gateway.authorize(@amount, @threeDS2_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_with_risk_data + options = @options.merge({ execute_threed: true, three_ds_version: '2.0', risk_data: risk_data }) + assert response = @gateway.authorize(@amount, @threeDS2_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_sub_merchant_data + options = @options.merge(@sub_merchant_options) + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'SUCCESS', response.message + end + def test_successful_purchase_with_hcg_additional_data @options[:hcg_additional_data] = { key1: 'value1', @@ -121,24 +444,49 @@ def test_authorize_and_purchase_with_instalments def test_successful_authorize_with_3ds session_id = generate_unique_id options = @options.merge( - { - execute_threed: true, - accept_header: 'text/html', - user_agent: 'Mozilla/5.0', - session_id: session_id, - ip: '127.0.0.1', - cookie: 'machine=32423423' - }) + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423' + } + ) assert first_message = @gateway.authorize(@amount, @threeDS_card, options) - assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message assert first_message.test? + 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 + # Ensure the account is configured to use this feature to proceed successfully + def test_marking_3ds_purchase_as_moto + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(metadata: { manual_entry: true })) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_with_3ds2_challenge + session_id = generate_unique_id + options = @options.merge( + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1' + } + ) + assert response = @gateway.authorize(@amount, @threeDS2_challenge_card, options) + assert response.test? + refute response.authorization.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, @@ -147,7 +495,7 @@ def test_successful_auth_and_capture_with_normalized_stored_credential network_transaction_id: nil } - assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({stored_credential: stored_credential_params})) + 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'] @@ -207,21 +555,20 @@ def test_successful_authorize_with_3ds_with_normalized_stored_credentials network_transaction_id: nil } options = @options.merge( - { - execute_threed: true, - accept_header: 'text/html', - user_agent: 'Mozilla/5.0', - session_id: session_id, - ip: '127.0.0.1', - cookie: 'machine=32423423', - stored_credential: stored_credential_params - }) + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423', + stored_credential: stored_credential_params + } + ) assert first_message = @gateway.authorize(@amount, @threeDS_card, options) - assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message assert first_message.test? refute first_message.authorization.blank? - refute first_message.params['issuer_url'].blank? - refute first_message.params['pa_request'].blank? + assert first_message.success? refute first_message.params['cookie'].blank? refute first_message.params['session_id'].blank? end @@ -229,25 +576,62 @@ def test_successful_authorize_with_3ds_with_normalized_stored_credentials def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials session_id = generate_unique_id options = @options.merge( - { - execute_threed: true, - accept_header: 'text/html', - user_agent: 'Mozilla/5.0', - session_id: session_id, - ip: '127.0.0.1', - cookie: 'machine=32423423', - stored_credential_usage: 'FIRST' - }) + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423', + stored_credential_usage: 'FIRST' + } + ) assert first_message = @gateway.authorize(@amount, @threeDS_card, options) - assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message assert first_message.test? refute first_message.authorization.blank? - refute first_message.params['issuer_url'].blank? - refute first_message.params['pa_request'].blank? + assert first_message.success? refute first_message.params['cookie'].blank? refute first_message.params['session_id'].blank? end + def test_successful_purchase_with_level_two_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_two_data)) + assert_success response + assert_equal true, response.params['ok'] + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_level_two_fields_and_sales_tax_zero + @level_two_data[:level_2_data][:sales_tax][:amount] = 0 + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_two_data)) + assert_success response + assert_equal true, response.params['ok'] + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_level_three_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_three_data)) + assert_success response + assert_equal true, response.params['ok'] + assert_equal 'SUCCESS', response.message + end + + def test_unsuccessful_purchase_level_three_data_without_item_mastercard + @level_three_data[:level_3_data][:item] = {} + @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?).' + end + + def test_successful_purchase_with_level_two_and_three_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_two_data, @level_three_data)) + assert_success response + assert_equal true, response.params['ok'] + assert_equal 'SUCCESS', response.message + end + # Fails currently because the sandbox doesn't actually validate the stored_credential options # def test_failed_authorize_with_bad_stored_cred_options # assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST')) @@ -272,13 +656,14 @@ def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials def test_failed_authorize_with_3ds session_id = generate_unique_id options = @options.merge( - { - execute_threed: true, - accept_header: 'text/html', - session_id: session_id, - ip: '127.0.0.1', - cookie: 'machine=32423423' - }) + { + execute_threed: true, + accept_header: 'text/html', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423' + } + ) assert first_message = @gateway.authorize(@amount, @threeDS_card, options) assert_match %r{missing info for 3D-secure transaction}i, first_message.message assert first_message.test? @@ -291,7 +676,7 @@ def test_3ds_version_1_parameters_pass_thru { three_d_secure: { version: '1.0.2', - xid: '', + xid: 'z9UKb06xLziZMOXBEmWSVA1kwG0=', cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=', eci: '05' } @@ -309,7 +694,7 @@ def test_3ds_version_2_parameters_pass_thru { three_d_secure: { version: '2.1.0', - xid: 'A' * 40, + ds_transaction_id: 'c5b808e7-1de1-4069-a17b-f70d3b3b1645', cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=', eci: '05' } @@ -329,7 +714,7 @@ def test_failed_capture end def test_billing_address - assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => address)) + assert_success @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: address)) end def test_partial_address @@ -337,7 +722,13 @@ def test_partial_address billing_address.delete(:address1) billing_address.delete(:zip) billing_address.delete(:country) - assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => billing_address)) + assert_success @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: billing_address)) + end + + def test_state_omitted + billing_address = address + billing_address.delete(:state) + assert_success @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: billing_address)) end def test_ip_address @@ -364,21 +755,21 @@ def test_void_nonexistent_transaction end def test_authorize_fractional_currency - assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'USD'))) + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(currency: 'USD'))) assert_equal 'USD', result.params['amount_currency_code'] assert_equal '1234', result.params['amount_value'] assert_equal '2', result.params['amount_exponent'] end def test_authorize_nonfractional_currency - assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'IDR'))) + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(currency: 'IDR'))) assert_equal 'IDR', result.params['amount_currency_code'] assert_equal '12', result.params['amount_value'] assert_equal '0', result.params['amount_exponent'] end def test_authorize_three_decimal_currency - assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR'))) + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(currency: 'OMR'))) assert_equal 'OMR', result.params['amount_currency_code'] assert_equal '1234', result.params['amount_value'] assert_equal '3', result.params['amount_exponent'] @@ -386,11 +777,11 @@ def test_authorize_three_decimal_currency def test_reference_transaction assert_success(original = @gateway.authorize(100, @credit_card, @options)) - assert_success(@gateway.authorize(200, original.authorization, :order_id => generate_unique_id)) + assert_success(@gateway.authorize(200, original.authorization, order_id: generate_unique_id)) end def test_invalid_login - gateway = WorldpayGateway.new(:login => '', :password => '') + gateway = WorldpayGateway.new(login: '', password: '') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid credentials', response.message @@ -416,6 +807,20 @@ def test_successful_verify assert_match %r{SUCCESS}, response.message end + def test_successful_verify_with_0_auth + options = @options.merge(zero_dollar_auth: true) + response = @gateway.verify(@credit_card, options) + assert_success response + assert_match %r{SUCCESS}, response.message + end + + def test_successful_verify_with_0_auth_and_ineligible_card + options = @options.merge(zero_dollar_auth: true) + response = @gateway.verify(@amex_card, options) + assert_success response + assert_match %r{SUCCESS}, response.message + end + def test_successful_verify_with_elo response = @gateway.verify(@elo_credit_card, @options.merge(currency: 'BRL')) assert_success response @@ -441,9 +846,36 @@ 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 }) + + credit = @cftgateway.credit(@amount, @credit_card, options) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_successful_fast_fund_credit_with_token_on_cft_gateway + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + options = @options.merge({ fast_fund_credit: true }) + assert credit = @cftgateway.credit(@amount, store.authorization, options) + assert_success credit + 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 - @gateway.purchase(@amount, @credit_card, @options) + @gateway.purchase(@amount, @credit_card, @options) end clean_transcript = @gateway.scrub(transcript) @@ -451,6 +883,27 @@ def test_transcript_scrubbing assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end + def test_failed_authorize_with_unknown_card + assert auth = @gateway.authorize(@amount, @sodexo_voucher, @options) + assert_failure auth + assert_equal '5', auth.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, auth.message + end + + def test_failed_purchase_with_unknown_card + assert response = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_failed_verify_with_unknown_card + response = @gateway.verify(@sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + # Worldpay has a delay between asking for a transaction to be captured and actually marking it as captured # These 2 tests work if you get authorizations from a purchase, wait some time and then perform the refund/void operation. # @@ -460,16 +913,365 @@ def test_transcript_scrubbing # puts 'auth: ' + response.authorization # end # - # def test_refund - # refund = @gateway.refund(@amount, '39270fd70be13aab55f84e28be45cad3') - # assert_success refund - # assert_equal 'SUCCESS', refund.message - # end - # + def test_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + refund = @gateway.refund(@amount, response.authorization, authorization_validated: true) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_cancel_or_refund_non_captured_purchase + response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_capture: true)) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + refund = @gateway.refund(@amount, response.authorization, authorization_validated: true, cancel_or_refund: true) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_cancel_or_refund_captured_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + refund = @gateway.refund(@amount, response.authorization, authorization_validated: true, cancel_or_refund: true) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_cancel_or_refund_non_captured_purchase_with_void + response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_capture: true)) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + refund = @gateway.void(response.authorization, authorization_validated: true, cancel_or_refund: true) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_cancel_or_refund_captured_purchase_with_void + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + refund = @gateway.void(response.authorization, authorization_validated: true, cancel_or_refund: true) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_multiple_refunds + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal 'SUCCESS', purchase.message + + partial_amount = @amount - 1 + assert_success refund1 = @gateway.refund(partial_amount, purchase.authorization, authorization_validated: true) + assert_equal 'SUCCESS', refund1.message + + assert_success refund2 = @gateway.refund(@amount - partial_amount, purchase.authorization, authorization_validated: true) + assert_equal 'SUCCESS', refund2.message + end + # def test_void_fails_unless_status_is_authorised # response = @gateway.void('replace_with_authorization') # existing transaction in CAPTURED state # assert_failure response # assert_equal 'A transaction status of 'AUTHORISED' is required.', response.message # end + def test_successful_store + assert response = @gateway.store(@credit_card, @store_options) + assert_success response + assert_equal 'SUCCESS', response.message + assert_match response.params['payment_token_id'], response.authorization + assert_match 'shopper', response.authorization + assert_match @store_options[:customer], response.authorization + end + + def test_successful_store_with_transaction_identifier_using_gateway_specific_field + transaction_identifier = 'ABC123' + options_with_transaction_id = @store_options.merge(stored_credential_transaction_id: transaction_identifier) + assert response = @gateway.store(@credit_card, options_with_transaction_id) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_match transaction_identifier, response.params['transaction_identifier'] + end + + def test_successful_store_with_transaction_identifier_using_normalized_fields + transaction_identifier = 'CDE456' + options_with_transaction_id = @store_options.merge(stored_credential: { network_transaction_id: transaction_identifier }) + assert response = @gateway.store(@credit_card, options_with_transaction_id) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_match transaction_identifier, response.params['transaction_identifier'] + end + + def test_successful_purchase_with_statement_narrative + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(statement_narrative: 'Merchant Statement Narrative')) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_using_token_and_minimum_options + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, order_id: generate_unique_id) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_verify_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + response = @gateway.verify(store.authorization, @options) + assert_success response + assert_match %r{SUCCESS}, response.message + end + + def test_successful_credit_using_token + assert store = @cftgateway.store(@credit_card, @store_options) + assert_success store + + credit = @cftgateway.credit(@amount, store.authorization, @options) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_failed_store + assert response = @gateway.store(@credit_card, @store_options.merge(customer: '_invalidId')) + assert_failure response + assert_equal '2', response.error_code + assert_equal 'authenticatedShopperID cannot start with an underscore', response.message + end + + def test_failed_authorize_using_token + assert store = @gateway.store(@declined_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_failure response + assert_equal '5', response.error_code + assert_equal 'REFUSED', response.message + end + + def test_failed_authorize_using_bogus_token + assert response = @gateway.authorize(@amount, '|this|is|bogus', @options) + assert_failure response + assert_equal '2', response.error_code + assert_match 'tokenScope', response.message + end + + def test_failed_verify_using_token + assert store = @gateway.store(@declined_card, @store_options) + assert_success store + + response = @gateway.verify(store.authorization, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{REFUSED}, response.message + end + + def test_authorize_and_capture_synchronous_response + card = credit_card('4111111111111111', verification_value: 555) + assert auth = @cftgateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @cftgateway.capture(@amount, auth.authorization, @options.merge(authorization_validated: true)) + assert_success capture + + assert duplicate_capture = @cftgateway.capture(@amount, auth.authorization, @options.merge(authorization_validated: true)) + assert_failure duplicate_capture + end + + def test_capture_wrong_amount_synchronous_response + card = credit_card('4111111111111111', verification_value: 555) + assert auth = @cftgateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @cftgateway.capture(@amount + 1, auth.authorization, @options.merge(authorization_validated: true)) + assert_failure capture + assert_equal '5', capture.error_code + assert_equal 'Requested capture amount (GBP 1.01) exceeds the authorised balance for this payment (GBP 1.00)', capture.message + end + + def test_successful_refund_synchronous_response + response = @cftgateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + assert response.authorization + + assert @cftgateway.refund(@amount, response.authorization, authorization_validated: true) + end + + def test_failed_refund_synchronous_response + auth = @cftgateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'SUCCESS', auth.message + assert auth.authorization + + refund = @cftgateway.refund(@amount, auth.authorization, authorization_validated: true) + assert_failure refund + assert_equal 'This order is not refundable', refund.message + + assert capture = @cftgateway.capture(@amount, auth.authorization, @options.merge(authorization_validated: true)) + assert_success capture + + refund = @cftgateway.refund(@amount * 2, auth.authorization, authorization_validated: true) + assert_failure refund + assert_equal 'Refund amount too high', refund.message + end + + 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 + } + options.merge(stored_credential: stored_credential_params) + + assert purchase = @cftgateway.purchase(@amount, @credit_card, options.merge(instalments: 3, skip_capture: true, authorization_validated: true)) + assert_success purchase + end + + # There is a delay of up to 5 minutes for a transaction to be recorded by Worldpay. Inquiring + # too soon will result in an error "Order not ready". Leaving commented out due to included sleeps. + # def test_successful_inquire_with_order_id + # order_id = @options[:order_id] + # assert auth = @gateway.authorize(@amount, @credit_card, @options) + # assert_success auth + # assert auth.authorization + # sleep 60 + + # assert inquire = @gateway.inquire(nil, { order_id: order_id }) + # assert_success inquire + # assert auth.authorization == inquire.authorization + # end + + # def test_successful_inquire_with_authorization + # assert auth = @gateway.authorize(@amount, @credit_card, @options) + # assert_success auth + # assert auth.authorization + # sleep 60 + + # assert inquire = @gateway.inquire(auth.authorization, {}) + # assert_success inquire + # assert auth.authorization == inquire.authorization + # end + + private + + def risk_data + return @risk_data if @risk_data + + authentication_time = Time.now + shopper_account_creation_date = Date.today + shopper_account_modification_date = Date.today - 1.day + shopper_account_password_change_date = Date.today - 2.days + shopper_account_shipping_address_first_use_date = Date.today - 3.day + shopper_account_payment_account_first_use_date = Date.today - 4.day + transaction_risk_data_pre_order_date = Date.today + 1.day + + @risk_data = { + authentication_risk_data: { + authentication_method: 'localAccount', + authentication_date: { + day_of_month: authentication_time.strftime('%d'), + month: authentication_time.strftime('%m'), + year: authentication_time.strftime('%Y'), + hour: authentication_time.strftime('%H'), + minute: authentication_time.strftime('%M'), + second: authentication_time.strftime('%S') + } + }, + shopper_account_risk_data: { + transactions_attempted_last_day: '1', + transactions_attempted_last_year: '2', + purchases_completed_last_six_months: '3', + add_card_attempts_last_day: '4', + previous_suspicious_activity: 'false', # Boolean (true or false) + shipping_name_matches_account_name: 'true', # Boolean (true or false) + shopper_account_age_indicator: 'lessThanThirtyDays', # Possible Values: noAccount, createdDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_change_indicator: 'thirtyToSixtyDays', # Possible values: changedDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_password_change_indicator: 'noChange', # Possible Values: noChange, changedDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_shipping_address_usage_indicator: 'moreThanSixtyDays', # Possible Values: thisTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_payment_account_indicator: 'thirtyToSixtyDays', # Possible Values: noAccount, duringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_creation_date: { + day_of_month: shopper_account_creation_date.strftime('%d'), + month: shopper_account_creation_date.strftime('%m'), + year: shopper_account_creation_date.strftime('%Y') + }, + shopper_account_modification_date: { + day_of_month: shopper_account_modification_date.strftime('%d'), + month: shopper_account_modification_date.strftime('%m'), + year: shopper_account_modification_date.strftime('%Y') + }, + shopper_account_password_change_date: { + day_of_month: shopper_account_password_change_date.strftime('%d'), + month: shopper_account_password_change_date.strftime('%m'), + year: shopper_account_password_change_date.strftime('%Y') + }, + shopper_account_shipping_address_first_use_date: { + day_of_month: shopper_account_shipping_address_first_use_date.strftime('%d'), + month: shopper_account_shipping_address_first_use_date.strftime('%m'), + year: shopper_account_shipping_address_first_use_date.strftime('%Y') + }, + shopper_account_payment_account_first_use_date: { + day_of_month: shopper_account_payment_account_first_use_date.strftime('%d'), + month: shopper_account_payment_account_first_use_date.strftime('%m'), + year: shopper_account_payment_account_first_use_date.strftime('%Y') + } + }, + transaction_risk_data: { + shipping_method: 'digital', + delivery_timeframe: 'electronicDelivery', + delivery_email_address: 'abe@lincoln.gov', + reordering_previous_purchases: 'false', + pre_order_purchase: 'false', + gift_card_count: '0', + transaction_risk_data_gift_card_amount: { + value: '123', + currency: 'EUR', + exponent: '2', + debit_credit_indicator: 'credit' + }, + transaction_risk_data_pre_order_date: { + day_of_month: transaction_risk_data_pre_order_date.strftime('%d'), + month: transaction_risk_data_pre_order_date.strftime('%m'), + year: transaction_risk_data_pre_order_date.strftime('%Y') + } + } + } + end end diff --git a/test/remote/gateways/remote_worldpay_us_test.rb b/test/remote/gateways/remote_worldpay_us_test.rb index a0e50945fa6..29cce46d447 100644 --- a/test/remote/gateways/remote_worldpay_us_test.rb +++ b/test/remote/gateways/remote_worldpay_us_test.rb @@ -5,9 +5,9 @@ def setup @gateway = WorldpayUsGateway.new(fixtures(:worldpay_us)) @amount = 100 - @credit_card = credit_card('4446661234567892', :verification_value => '987') + @credit_card = credit_card('4446661234567892', verification_value: '987') @declined_card = credit_card('4000300011112220') - @check = check(:number => '12345654321') + @check = check(number: '12345654321') @options = { order_id: generate_unique_id, @@ -23,7 +23,7 @@ def test_successful_purchase end def test_successful_purchase_on_backup_url - gateway = WorldpayUsGateway.new(fixtures(:worldpay_us).merge({ use_backup_url: true})) + gateway = WorldpayUsGateway.new(fixtures(:worldpay_us).merge({ use_backup_url: true })) response = gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'Succeeded', response.message @@ -101,16 +101,16 @@ def test_failed_verify end def test_passing_billing_address - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => address)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address)) assert_success response end def test_invalid_login gateway = WorldpayUsGateway.new( - :acctid => '', - :subid => '', - :merchantpin => '' - ) + acctid: '', + subid: '', + merchantpin: '' + ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.message =~ /DECLINED/ diff --git a/test/remote/gateways/remote_xpay_test.rb b/test/remote/gateways/remote_xpay_test.rb new file mode 100644 index 00000000000..92ec7c33ab5 --- /dev/null +++ b/test/remote/gateways/remote_xpay_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +class RemoteRapydTest < Test::Unit::TestCase + def setup + @gateway = XpayGateway.new(fixtures(:x_pay)) + @amount = 200 + @credit_card = credit_card('4111111111111111') + @options = {} + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert response + end +end diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd new file mode 100644 index 00000000000..1bd9f4a1b04 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd @@ -0,0 +1,4770 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd new file mode 100644 index 00000000000..577ae9fc8a6 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd @@ -0,0 +1,4857 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd new file mode 100644 index 00000000000..74dc2e7b7a5 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd @@ -0,0 +1,4894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.164.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.164.xsd new file mode 100644 index 00000000000..8333412b1a0 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.164.xsd @@ -0,0 +1,5111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.181.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.181.xsd new file mode 100644 index 00000000000..3e0c1658642 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.181.xsd @@ -0,0 +1,5290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.198.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.198.xsd new file mode 100644 index 00000000000..aed30c01e3c --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.198.xsd @@ -0,0 +1,5349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.201.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.201.xsd new file mode 100644 index 00000000000..ec16a9d7dcd --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.201.xsd @@ -0,0 +1,5106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/schema/orbital/Request_PTI83.xsd b/test/schema/orbital/Request_PTI83.xsd new file mode 100644 index 00000000000..e2d7e825180 --- /dev/null +++ b/test/schema/orbital/Request_PTI83.xsd @@ -0,0 +1,1142 @@ + + + + + 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 + + + + + + + + + + + + + + + + + + + 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/support/mercury_helper.rb b/test/support/mercury_helper.rb index a8afafa25e5..6641b95633a 100644 --- a/test/support/mercury_helper.rb +++ b/test/support/mercury_helper.rb @@ -46,7 +46,7 @@ def hashify_xml!(xml, response) private - def close_batch(gateway=@gateway) + def close_batch(gateway = @gateway) gateway = ActiveMerchant::Billing::MercuryGateway.new(gateway.options) gateway.extend(BatchClosing) gateway.close_batch diff --git a/test/test_helper.rb b/test/test_helper.rb index 9666234fea9..8c562336cea 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,4 @@ -$:.unshift File.expand_path('../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'bundler/setup' @@ -31,7 +31,7 @@ class SubclassGateway < SimpleTestGateway module ActiveMerchant module Assertions - AssertionClass = defined?(Minitest) ? MiniTest::Assertion : Test::Unit::AssertionFailedError + ASSERTION_CLASS = defined?(Minitest) ? MiniTest::Assertion : Test::Unit::AssertionFailedError def assert_field(field, value) clean_backtrace do @@ -68,20 +68,20 @@ def assert_false(boolean, message = nil) # # A message will automatically show the inspection of the response # object if things go afoul. - def assert_success(response, message=nil) + def assert_success(response, message = nil) clean_backtrace do assert response.success?, build_message(nil, "#{message + "\n" if message}Response expected to succeed: ", response) end end # The negative of +assert_success+ - def assert_failure(response, message=nil) + def assert_failure(response, message = nil) clean_backtrace do assert !response.success?, build_message(nil, "#{message + "\n" if message}Response expected to fail: ", response) end end - def assert_valid(model, message=nil) + def assert_valid(model, message = nil) errors = model.validate clean_backtrace do @@ -101,7 +101,7 @@ def assert_not_valid(model) errors end - def assert_deprecation_warning(message=nil) + def assert_deprecation_warning(message = nil) ActiveMerchant.expects(:deprecated).with(message || anything) yield end @@ -129,14 +129,14 @@ def assert_scrubbed(unexpected_value, transcript) def clean_backtrace(&block) yield - rescue AssertionClass => e + rescue ASSERTION_CLASS => e path = File.expand_path(__FILE__) - raise AssertionClass, e.message, (e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }) + raise ASSERTION_CLASS, e.message, (e.backtrace.reject { |line| File.expand_path(line).match?(/#{path}/) }) end end module Fixtures - HOME_DIR = RUBY_PLATFORM =~ /mswin32/ ? ENV['HOMEPATH'] : ENV['HOME'] unless defined?(HOME_DIR) + HOME_DIR = RUBY_PLATFORM.match?('mswin32') ? ENV['HOMEPATH'] : ENV['HOME'] unless defined?(HOME_DIR) LOCAL_CREDENTIALS = File.join(HOME_DIR.to_s, '.active_merchant/fixtures.yml') unless defined?(LOCAL_CREDENTIALS) DEFAULT_CREDENTIALS = File.join(File.dirname(__FILE__), 'fixtures.yml') unless defined?(DEFAULT_CREDENTIALS) @@ -151,14 +151,15 @@ 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, - :year => default_expiration_date.year, - :first_name => 'Longbob', - :last_name => 'Longsen', - :verification_value => options[:verification_value] || '123', - :brand => 'visa' + number: number, + month: default_expiration_date.month, + year: default_expiration_date.year, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: options[:verification_value] || '123', + brand: 'visa' }.update(options) Billing::CreditCard.new(defaults) @@ -168,7 +169,7 @@ def credit_card_with_track_data(number = '4242424242424242', options = {}) exp_date = default_expiration_date.strftime('%y%m') defaults = { - :track_data => "%B#{number}^LONGSEN/L. ^#{exp_date}1200000000000000**123******?", + track_data: "%B#{number}^LONGSEN/L. ^#{exp_date}1200000000000000**123******?" }.update(options) Billing::CreditCard.new(defaults) @@ -176,13 +177,13 @@ def credit_card_with_track_data(number = '4242424242424242', options = {}) def network_tokenization_credit_card(number = '4242424242424242', options = {}) defaults = { - :number => number, - :month => default_expiration_date.month, - :year => default_expiration_date.year, - :first_name => 'Longbob', - :last_name => 'Longsen', - :verification_value => '123', - :brand => 'visa' + number: number, + month: default_expiration_date.month, + year: default_expiration_date.year, + first_name: 'Longbob', + last_name: 'Longsen', + verification_value: '123', + brand: 'visa' }.update(options) Billing::NetworkTokenizationCreditCard.new(defaults) @@ -190,13 +191,13 @@ def network_tokenization_credit_card(number = '4242424242424242', options = {}) def check(options = {}) defaults = { - :name => 'Jim Smith', - :bank_name => 'Bank of Elbonia', - :routing_number => '244183602', - :account_number => '15378535', - :account_holder_type => 'personal', - :account_type => 'checking', - :number => '1' + name: 'Jim Smith', + bank_name: 'Bank of Elbonia', + routing_number: '244183602', + account_number: '15378535', + account_holder_type: 'personal', + account_type: 'checking', + number: '1' }.update(options) Billing::Check.new(defaults) @@ -213,7 +214,8 @@ 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] @@ -235,6 +237,19 @@ def address(options = {}) }.update(options) end + def shipping_address(options = {}) + { + name: 'Jon Smith', + address1: '123 Your Street', + address2: 'Apt 2', + city: 'Toronto', + state: 'ON', + zip: 'K2C3N7', + country: 'CA', + phone_number: '(123)456-7890' + }.update(options) + end + def statement_address(options = {}) { address1: '456 My Street', @@ -245,6 +260,27 @@ def statement_address(options = {}) }.update(options) end + def stored_credential(*args, **options) + id = options.delete(:id) || options.delete(:network_transaction_id) + + stored_credential = { + network_transaction_id: id, + initial_transaction: false + } + + stored_credential[:initial_transaction] = true if args.include?(:initial) + + stored_credential[:reason_type] = 'recurring' if args.include?(:recurring) + stored_credential[:reason_type] = 'unscheduled' if args.include?(:unscheduled) + stored_credential[:reason_type] = 'installment' if args.include?(:installment) + stored_credential[:reason_type] = 'internet' if args.include?(:internet) + + stored_credential[:initiator] = 'cardholder' if args.include?(:cardholder) + stored_credential[:initiator] = 'merchant' if args.include?(:merchant) + + stored_credential + end + def generate_unique_id SecureRandom.hex(16) end @@ -262,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 @@ -273,7 +309,7 @@ def symbolize_keys(hash) return unless hash.is_a?(Hash) hash.symbolize_keys! - hash.each { |k, v| symbolize_keys(v) } + hash.each { |_k, v| symbolize_keys(v) } end end end @@ -302,49 +338,19 @@ def dump_transcript_and_fail(gateway, amount, credit_card, params) end end -module ActionViewHelperTestHelper - def self.included(base) - base.send(:include, ActiveMerchant::Billing::Integrations::ActionViewHelper) - base.send(:include, ActionView::Helpers::FormHelper) - base.send(:include, ActionView::Helpers::FormTagHelper) - base.send(:include, ActionView::Helpers::UrlHelper) - base.send(:include, ActionView::Helpers::TagHelper) - base.send(:include, ActionView::Helpers::CaptureHelper) - base.send(:include, ActionView::Helpers::TextHelper) - base.send(:attr_accessor, :output_buffer) - end - - def setup - @controller = Class.new do - attr_reader :url_for_options - def url_for(options, *parameters_for_method_reference) - @url_for_options = options - end - end - @controller = @controller.new - @output_buffer = '' - end - - protected - - def protect_against_forgery? - false - end -end - class MockResponse attr_reader :code, :body, :message attr_accessor :headers - def self.succeeded(body, message='') + def self.succeeded(body, message = '') MockResponse.new(200, body, message) end - def self.failed(body, http_status_code=422, message='') + def self.failed(body, http_status_code = 422, message = '') MockResponse.new(http_status_code, body, message) end - def initialize(code, body, message='', headers={}) + def initialize(code, body, message = '', headers = {}) @code, @body, @message, @headers = code, body, message, headers end diff --git a/test/unit/avs_result_test.rb b/test/unit/avs_result_test.rb index f3b960647b7..7e069c6f1ac 100644 --- a/test/unit/avs_result_test.rb +++ b/test/unit/avs_result_test.rb @@ -6,7 +6,7 @@ def test_nil end def test_no_match - result = AVSResult.new(:code => 'N') + result = AVSResult.new(code: 'N') assert_equal 'N', result.code assert_equal 'N', result.street_match assert_equal 'N', result.postal_match @@ -14,7 +14,7 @@ def test_no_match end def test_only_street_match - result = AVSResult.new(:code => 'A') + result = AVSResult.new(code: 'A') assert_equal 'A', result.code assert_equal 'Y', result.street_match assert_equal 'N', result.postal_match @@ -22,7 +22,7 @@ def test_only_street_match end def test_only_postal_match - result = AVSResult.new(:code => 'W') + result = AVSResult.new(code: 'W') assert_equal 'W', result.code assert_equal 'N', result.street_match assert_equal 'Y', result.postal_match @@ -30,30 +30,30 @@ def test_only_postal_match end def test_nil_data - result = AVSResult.new(:code => nil) + result = AVSResult.new(code: nil) assert_nil result.code assert_nil result.message end def test_empty_data - result = AVSResult.new(:code => '') + result = AVSResult.new(code: '') assert_nil result.code assert_nil result.message end def test_to_hash - avs_data = AVSResult.new(:code => 'X').to_hash + avs_data = AVSResult.new(code: 'X').to_hash assert_equal 'X', avs_data['code'] assert_equal AVSResult.messages['X'], avs_data['message'] end def test_street_match - avs_data = AVSResult.new(:street_match => 'Y') + avs_data = AVSResult.new(street_match: 'Y') assert_equal 'Y', avs_data.street_match end def test_postal_match - avs_data = AVSResult.new(:postal_match => 'Y') + avs_data = AVSResult.new(postal_match: 'Y') assert_equal 'Y', avs_data.postal_match end end diff --git a/test/unit/base_test.rb b/test/unit/base_test.rb index 6e5ac461ab7..82e13503378 100644 --- a/test/unit/base_test.rb +++ b/test/unit/base_test.rb @@ -12,7 +12,6 @@ def teardown def test_should_return_a_new_gateway_specified_by_symbol_name assert_equal BogusGateway, Base.gateway(:bogus) assert_equal MonerisGateway, Base.gateway(:moneris) - assert_equal MonerisUsGateway, Base.gateway(:moneris_us) assert_equal AuthorizeNetGateway, Base.gateway(:authorize_net) assert_equal UsaEpayGateway, Base.gateway(:usa_epay) assert_equal LinkpointGateway, Base.gateway(:linkpoint) diff --git a/test/unit/check_test.rb b/test/unit/check_test.rb index e5908553ab9..bdd03346d1d 100644 --- a/test/unit/check_test.rb +++ b/test/unit/check_test.rb @@ -4,35 +4,55 @@ class CheckTest < Test::Unit::TestCase VALID_ABA = '111000025' INVALID_ABA = '999999999' MALFORMED_ABA = 'I like fish' + VALID_ELECTRONIC_CBA = '000194611' + VALID_MICR_CBA = '94611001' + INVALID_NINE_DIGIT_CBA = '012345678' + INVALID_SEVEN_DIGIT_ROUTING_NUMBER = '0123456' ACCOUNT_NUMBER = '123456789012' + CHECK_US = Check.new( + name: 'Fred Bloggs', + routing_number: VALID_ABA, + account_number: ACCOUNT_NUMBER, + account_holder_type: 'personal', + account_type: 'checking' + ) + CHECK_CAN_E_FORMAT = Check.new( + name: 'Tim Horton', + routing_number: VALID_ELECTRONIC_CBA, + account_number: ACCOUNT_NUMBER, + account_holder_type: 'personal', + account_type: 'checking' + ) + CHECK_CAN_MICR_FORMAT = Check.new( + name: 'Tim Horton', + routing_number: VALID_MICR_CBA, + account_number: ACCOUNT_NUMBER, + account_holder_type: 'personal', + account_type: 'checking' + ) + def test_validation assert_not_valid Check.new end def test_first_name_last_name - check = Check.new(:name => 'Fred Bloggs') + check = Check.new(name: 'Fred Bloggs') assert_equal 'Fred', check.first_name assert_equal 'Bloggs', check.last_name assert_equal 'Fred Bloggs', check.name end def test_nil_name - check = Check.new(:name => nil) + check = Check.new(name: nil) assert_nil check.first_name assert_nil check.last_name assert_equal '', check.name end def test_valid - assert_valid Check.new( - :name => 'Fred Bloggs', - :routing_number => VALID_ABA, - :account_number => ACCOUNT_NUMBER, - :account_holder_type => 'personal', - :account_type => 'checking' - ) + assert_valid CHECK_US end def test_credit_card? @@ -40,12 +60,12 @@ def test_credit_card? end def test_invalid_routing_number - errors = assert_not_valid Check.new(:routing_number => INVALID_ABA) + errors = assert_not_valid Check.new(routing_number: INVALID_ABA) assert_equal ['is invalid'], errors[:routing_number] end def test_malformed_routing_number - errors = assert_not_valid Check.new(:routing_number => MALFORMED_ABA) + errors = assert_not_valid Check.new(routing_number: MALFORMED_ABA) assert_equal ['is invalid'], errors[:routing_number] end @@ -78,4 +98,46 @@ def test_account_type c.account_type = nil assert !c.validate[:account_type] end + + def test_valid_canada_routing_number_electronic_format + assert_valid CHECK_CAN_E_FORMAT + end + + def test_valid_canada_routing_number_micr_format + assert_valid CHECK_CAN_MICR_FORMAT + end + + def test_invalid_canada_routing_number + errors = assert_not_valid Check.new( + name: 'Tim Horton', + routing_number: INVALID_NINE_DIGIT_CBA, + account_number: ACCOUNT_NUMBER, + account_holder_type: 'personal', + account_type: 'checking' + ) + + assert_equal ['is invalid'], errors[:routing_number] + end + + def test_invalid_routing_number_length + errors = assert_not_valid Check.new( + name: 'Routing Shortlength', + routing_number: INVALID_SEVEN_DIGIT_ROUTING_NUMBER, + account_number: ACCOUNT_NUMBER, + account_holder_type: 'personal', + account_type: 'checking' + ) + + assert_equal ['is invalid'], errors[:routing_number] + end + + def test_format_routing_number + assert CHECK_CAN_E_FORMAT.micr_format_routing_number == '94611001' + assert CHECK_CAN_MICR_FORMAT.micr_format_routing_number == '94611001' + assert CHECK_US.micr_format_routing_number == '111000025' + + assert CHECK_CAN_E_FORMAT.electronic_format_routing_number == '000194611' + assert CHECK_CAN_MICR_FORMAT.electronic_format_routing_number == '000194611' + assert CHECK_US.electronic_format_routing_number == '111000025' + end end diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb index ac8c29a4f1d..338718a99b4 100644 --- a/test/unit/connection_test.rb +++ b/test/unit/connection_test.rb @@ -1,13 +1,12 @@ require 'test_helper' class ConnectionTest < Test::Unit::TestCase - def setup - @ok = stub(:code => 200, :message => 'OK', :body => 'success') + @ok = stub(code: 200, message: 'OK', body: 'success') @endpoint = 'https://example.com/tx.php' @connection = ActiveMerchant::Connection.new(@endpoint) - @connection.logger = stub(:info => nil, :debug => nil, :error => nil) + @connection.logger = stub(info: nil, debug: nil, error: nil) end def test_connection_endpoint_parses_string_to_uri @@ -30,7 +29,7 @@ def test_connection_passes_env_proxy_by_default spy = Net::HTTP.new('example.com', 443) Net::HTTP.expects(:new).with('example.com', 443, :ENV, nil).returns(spy) spy.expects(:start).returns(true) - spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + spy.expects(:get).with('/tx.php', { 'connection' => 'close' }).returns(@ok) @connection.request(:get, nil, {}) end @@ -40,13 +39,13 @@ def test_connection_does_pass_requested_proxy spy = Net::HTTP.new('example.com', 443) Net::HTTP.expects(:new).with('example.com', 443, 'proxy.example.com', 8080).returns(spy) spy.expects(:start).returns(true) - spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + spy.expects(:get).with('/tx.php', { 'connection' => 'close' }).returns(@ok) @connection.request(:get, nil, {}) end def test_connection_does_not_mutate_headers_argument headers = { 'Content-Type' => 'text/xml' }.freeze - Net::HTTP.any_instance.expects(:get).with('/tx.php', headers.merge({'connection' => 'close'})).returns(@ok) + Net::HTTP.any_instance.expects(:get).with('/tx.php', headers.merge({ 'connection' => 'close' })).returns(@ok) Net::HTTP.any_instance.expects(:start).returns(true) @connection.request(:get, nil, headers) assert_equal({ 'Content-Type' => 'text/xml' }, headers) @@ -54,28 +53,28 @@ def test_connection_does_not_mutate_headers_argument def test_successful_get_request @connection.logger.expects(:info).twice - Net::HTTP.any_instance.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:get).with('/tx.php', { 'connection' => 'close' }).returns(@ok) Net::HTTP.any_instance.expects(:start).returns(true) response = @connection.request(:get, nil, {}) assert_equal 'success', response.body end def test_successful_post_request - Net::HTTP.any_instance.expects(:post).with('/tx.php', 'data', ActiveMerchant::Connection::RUBY_184_POST_HEADERS.merge({'connection' => 'close'})).returns(@ok) + Net::HTTP.any_instance.expects(:post).with('/tx.php', 'data', ActiveMerchant::Connection::RUBY_184_POST_HEADERS.merge({ 'connection' => 'close' })).returns(@ok) Net::HTTP.any_instance.expects(:start).returns(true) response = @connection.request(:post, 'data', {}) assert_equal 'success', response.body end def test_successful_put_request - Net::HTTP.any_instance.expects(:put).with('/tx.php', 'data', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:put).with('/tx.php', 'data', { 'connection' => 'close' }).returns(@ok) Net::HTTP.any_instance.expects(:start).returns(true) response = @connection.request(:put, 'data', {}) assert_equal 'success', response.body end def test_successful_delete_request - Net::HTTP.any_instance.expects(:delete).with('/tx.php', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:delete).with('/tx.php', { 'connection' => 'close' }).returns(@ok) Net::HTTP.any_instance.expects(:start).returns(true) response = @connection.request(:delete, nil, {}) assert_equal 'success', response.body @@ -88,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) @@ -244,5 +236,4 @@ def test_wiredump_service_raises_on_frozen_object @connection.wiredump_device = transcript end end - end diff --git a/test/unit/country_test.rb b/test/unit/country_test.rb index 6251e6fa095..8f8669ce7e1 100644 --- a/test/unit/country_test.rb +++ b/test/unit/country_test.rb @@ -2,7 +2,7 @@ class CountryTest < Test::Unit::TestCase def test_country_from_hash - country = ActiveMerchant::Country.new(:name => 'Canada', :alpha2 => 'CA', :alpha3 => 'CAN', :numeric => '124') + country = ActiveMerchant::Country.new(name: 'Canada', alpha2: 'CA', alpha3: 'CAN', numeric: '124') assert_equal 'CA', country.code(:alpha2).value assert_equal 'CAN', country.code(:alpha3).value assert_equal '124', country.code(:numeric).value diff --git a/test/unit/credit_card_formatting_test.rb b/test/unit/credit_card_formatting_test.rb index 73e70c9e151..90915f241b3 100644 --- a/test/unit/credit_card_formatting_test.rb +++ b/test/unit/credit_card_formatting_test.rb @@ -6,6 +6,11 @@ class CreditCardFormattingTest < Test::Unit::TestCase def test_should_format_number_by_rule assert_equal 2005, format(2005, :steven_colbert) + assert_equal '2022', format(22, :four_digits_year) + assert_equal '2022', format(2022, :four_digits_year) + assert_equal '2022', format('22', :four_digits_year) + assert_equal '2022', format('2022', :four_digits_year) + assert_equal '0005', format(05, :four_digits) assert_equal '2005', format(2005, :four_digits) diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb index 24e643e8eb4..e4c1240131c 100644 --- a/test/unit/credit_card_methods_test.rb +++ b/test/unit/credit_card_methods_test.rb @@ -9,18 +9,43 @@ class CreditCard def maestro_card_numbers %w[ + 5612590000000000 5817500000000000 5818000000000000 6390000000000000 6390700000000000 6390990000000000 6761999999999999 6763000000000000 6799999999999999 + 5000330000000000 5811499999999999 5010410000000000 + 5010630000000000 5892440000000000 5016230000000000 ] end def non_maestro_card_numbers %w[ 4999999999999999 5100000000000000 5599999999999999 - 5900000000000000 5999999999999999 7000000000000000 + 5612709999999999 5817520000000000 5818019999999999 + 5912600000000000 6000009999999999 7000000000000000 ] end + 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 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 + 507001 507002 507004 507082 507090 560014 560565 561033 572402 572610 572626 576904 578614 + 585274 585697 586509 588729 588792 589244 589300 589407 589471 589605 589633 589647 589671 + 590043 590206 590263 590265 + 590278 590361 590362 590379 590393 590590 591235 591420 591481 591620 591770 591948 591994 592024 + 592161 592184 592186 592201 592384 592393 592528 592566 592704 592735 592879 592884 593074 593264 + 593272 593355 593496 593556 593589 593666 593709 593825 593963 593994 594184 594409 594468 594475 + 594581 594665 594691 594710 594874 594968 595355 595364 595532 595547 595561 595568 595743 595929 + 596245 596289 596399 596405 596590 596608 596645 596646 596791 596808 596815 596846 597077 597094 + 597143 597370 597410 597765 597855 597862 598053 598054 598395 598585 598793 598794 598815 598835 + 598838 598880 598889 599000 599069 599089 599148 599191 599310 599741 599742 599867 + 601070 604983 601638 606126 + 630400 636380 636422 636502 636639 637046 637756 639130 639229 690032] + end + def test_should_be_able_to_identify_valid_expiry_months assert_false valid_month?(-1) assert_false valid_month?(13) @@ -98,12 +123,28 @@ def test_should_detect_electron_dk_as_visa def test_should_detect_diners_club assert_equal 'diners_club', CreditCard.brand?('36148010000000') + assert_equal 'diners_club', CreditCard.brand?('3000000000000004') end def test_should_detect_diners_club_dk assert_equal 'diners_club', CreditCard.brand?('30401000000000') end + def test_should_detect_jcb_cards + assert_equal 'jcb', CreditCard.brand?('3528000000000000') + assert_equal 'jcb', CreditCard.brand?('3580000000000000') + assert_equal 'jcb', CreditCard.brand?('3088000000000017') + assert_equal 'jcb', CreditCard.brand?('3094000000000017') + assert_equal 'jcb', CreditCard.brand?('3096000000000000') + assert_equal 'jcb', CreditCard.brand?('3102000000000017') + assert_equal 'jcb', CreditCard.brand?('3112000000000000') + assert_equal 'jcb', CreditCard.brand?('3120000000000017') + assert_equal 'jcb', CreditCard.brand?('3158000000000000') + assert_equal 'jcb', CreditCard.brand?('3159000000000017') + assert_equal 'jcb', CreditCard.brand?('3337000000000000') + assert_equal 'jcb', CreditCard.brand?('3349000000000017') + end + def test_should_detect_maestro_dk_as_maestro assert_equal 'maestro', CreditCard.brand?('6769271000000000') end @@ -112,12 +153,19 @@ def test_should_detect_maestro_cards assert_equal 'maestro', CreditCard.brand?('675675000000000') maestro_card_numbers.each { |number| assert_equal 'maestro', CreditCard.brand?(number) } + maestro_bins.each { |bin| assert_equal 'maestro', CreditCard.brand?("#{bin}0000000000") } non_maestro_card_numbers.each { |number| assert_not_equal 'maestro', CreditCard.brand?(number) } end def test_should_detect_mastercard assert_equal 'master', CreditCard.brand?('2720890000000000') assert_equal 'master', CreditCard.brand?('5413031000000000') + assert_equal 'master', CreditCard.brand?('6052721000000000') + assert_equal 'master', CreditCard.brand?('6062821000000000') + assert_equal 'master', CreditCard.brand?('6370951000000000') + assert_equal 'master', CreditCard.brand?('6375681000000000') + assert_equal 'master', CreditCard.brand?('6375991000000000') + assert_equal 'master', CreditCard.brand?('6376091000000000') end def test_should_detect_forbrugsforeningen @@ -125,17 +173,219 @@ def test_should_detect_forbrugsforeningen end def test_should_detect_sodexo_card - assert_equal 'sodexo', CreditCard.brand?('60606944957644') + assert_equal 'sodexo', CreditCard.brand?('6060694495764400') + end + + def test_should_detect_alia_card + assert_equal 'alia', CreditCard.brand?('5049970000000000') + assert_equal 'alia', CreditCard.brand?('5058780000000000') + assert_equal 'alia', CreditCard.brand?('6010300000000000') + assert_equal 'alia', CreditCard.brand?('6010730000000000') + assert_equal 'alia', CreditCard.brand?('5058740000000000') + end + + def test_should_detect_mada_card + assert_equal 'mada', CreditCard.brand?('5043000000000000') + assert_equal 'mada', CreditCard.brand?('5852650000000000') + assert_equal 'mada', CreditCard.brand?('5888500000000000') + assert_equal 'mada', CreditCard.brand?('6361200000000000') + assert_equal 'mada', CreditCard.brand?('9682040000000000') + end + + def test_alia_number_not_validated + 10.times do + number = rand(5058740000000001..5058749999999999).to_s + assert_equal 'alia', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + end + + def test_should_detect_confiable_card + assert_equal 'confiable', CreditCard.brand?('5607180000000000') + end + + def test_should_detect_bp_plus_card + assert_equal 'bp_plus', CreditCard.brand?('70501 501021600 378') + assert_equal 'bp_plus', CreditCard.brand?('70502 111111111 111') + assert_equal 'bp_plus', CreditCard.brand?('7050 15605297 00114') + assert_equal 'bp_plus', CreditCard.brand?('7050 15546992 00062') + end + + def test_should_validate_bp_plus_card + assert_true CreditCard.valid_number?('70501 501021600 378') + assert_true CreditCard.valid_number?('7050 15605297 00114') + assert_true CreditCard.valid_number?('7050 15546992 00062') + assert_true CreditCard.valid_number?('7050 16150146 00110') + assert_true CreditCard.valid_number?('7050 16364764 00070') + + # numbers with invalid formats + assert_false CreditCard.valid_number?('7050_15546992_00062') + assert_false CreditCard.valid_number?('70501 55469920 0062') + assert_false CreditCard.valid_number?('70 501554699 200062') + + # numbers that are luhn-invalid + assert_false CreditCard.valid_number?('70502 111111111 111') + assert_false CreditCard.valid_number?('7050 16364764 00071') + assert_false CreditCard.valid_number?('7050 16364764 00072') + end + + def test_confiable_number_not_validated + 10.times do + number = rand(5607180000000001..5607189999999999).to_s + assert_equal 'confiable', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + end + + def test_should_detect_maestro_no_luhn_card + assert_equal 'maestro_no_luhn', CreditCard.brand?('5010800000000000') + assert_equal 'maestro_no_luhn', CreditCard.brand?('5010810000000000') + assert_equal 'maestro_no_luhn', CreditCard.brand?('5010820000000000') + assert_equal 'maestro_no_luhn', CreditCard.brand?('501082000000') + assert_equal 'maestro_no_luhn', CreditCard.brand?('5010820000000000000') + end + + def test_maestro_no_luhn_number_not_validated + 10.times do + number = rand(5010800000000001..5010829999999999).to_s + assert_equal 'maestro_no_luhn', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + end + + def test_should_detect_olimpica_card + assert_equal 'olimpica', CreditCard.brand?('6368530000000000') + end + + def test_should_detect_sodexo_no_luhn_card + number1 = '5058645584812145' + number2 = '5058655584812145' + assert_equal 'sodexo', CreditCard.brand?(number1) + assert CreditCard.valid_number?(number1) + assert_equal 'sodexo', CreditCard.brand?(number2) + assert CreditCard.valid_number?(number2) + end + + def test_should_validate_sodexo_no_luhn_card + assert_true CreditCard.valid_number?('5058645584812145') + assert_false CreditCard.valid_number?('5058665584812110') + end + + def test_should_detect_passcard_card + assert_equal 'passcard', CreditCard.brand?('6280260025383009') + assert_equal 'passcard', CreditCard.brand?('6280260025383280') + assert_equal 'passcard', CreditCard.brand?('6280260025383298') + assert_equal 'passcard', CreditCard.brand?('6280260025383306') + assert_equal 'passcard', CreditCard.brand?('6280260025383314') + end + + def test_should_validate_passcard_card + assert_true CreditCard.valid_number?('6280260025383009') + # numbers with invalid formats + assert_false CreditCard.valid_number?('6280_26002538_0005') + # numbers that are luhn-invalid + assert_false CreditCard.valid_number?('6280260025380991') + end + + def test_should_detect_edenred_card + assert_equal 'edenred', CreditCard.brand?('6374830000000823') + assert_equal 'edenred', CreditCard.brand?('6374830000000799') + assert_equal 'edenred', CreditCard.brand?('6374830000000807') + assert_equal 'edenred', CreditCard.brand?('6374830000000815') + assert_equal 'edenred', CreditCard.brand?('6374830000000823') + end + + def test_should_validate_edenred_card + assert_true CreditCard.valid_number?('6374830000000369') + # numbers with invalid formats + assert_false CreditCard.valid_number?('6374 8300000 00369') + # numbers that are luhn-invalid + assert_false CreditCard.valid_number?('6374830000000111') + end + + def test_should_detect_anda_card + assert_equal 'anda', CreditCard.brand?('6031998427187914') + end + + # Creditos directos a.k.a tarjeta d + def test_should_detect_tarjetad_card + assert_equal 'tarjeta-d', CreditCard.brand?('6018282227431033') + end + + def test_should_detect_creditel_card + assert_equal 'creditel', CreditCard.brand?('6019330047539016') end def test_should_detect_vr_card - assert_equal 'vr', CreditCard.brand?('63703644957644') + assert_equal 'vr', CreditCard.brand?('6370364495764400') + assert_equal 'vr', CreditCard.brand?('6274160000000001') end def test_should_detect_elo_card assert_equal 'elo', CreditCard.brand?('5090510000000000') assert_equal 'elo', CreditCard.brand?('5067530000000000') + assert_equal 'elo', CreditCard.brand?('6277800000000000') assert_equal 'elo', CreditCard.brand?('6509550000000000') + assert_equal 'elo', CreditCard.brand?('5090890000000000') + assert_equal 'elo', CreditCard.brand?('5092570000000000') + assert_equal 'elo', CreditCard.brand?('5094100000000000') + end + + def test_should_detect_alelo_card + assert_equal 'alelo', CreditCard.brand?('5067490000000010') + assert_equal 'alelo', CreditCard.brand?('5067700000000028') + assert_equal 'alelo', CreditCard.brand?('5067600000000036') + assert_equal 'alelo', CreditCard.brand?('5067600000000044') + assert_equal 'alelo', CreditCard.brand?('5099920000000000') + assert_equal 'alelo', CreditCard.brand?('5067630000000000') + assert_equal 'alelo', CreditCard.brand?('5098870000000000') + end + + def test_should_detect_naranja_card + assert_equal 'naranja', CreditCard.brand?('5895627823453005') + assert_equal 'naranja', CreditCard.brand?('5895620000000002') + assert_equal 'naranja', CreditCard.brand?('5895626746595650') + end + + # Alelo BINs beginning with the digit 4 overlap with Visa's range of valid card numbers. + # We intentionally misidentify these cards as Visa, which works because transactions with + # such cards will run on Visa rails. + def test_should_detect_alelo_number_beginning_with_4_as_visa + assert_equal 'visa', CreditCard.brand?('4025880000000010') + assert_equal 'visa', CreditCard.brand?('4025880000000028') + assert_equal 'visa', CreditCard.brand?('4025880000000036') + assert_equal 'visa', CreditCard.brand?('4025880000000044') + end + + def test_should_detect_cabal_card + assert_equal 'cabal', CreditCard.brand?('6044009000000000') + assert_equal 'cabal', CreditCard.brand?('5896575500000000') + assert_equal 'cabal', CreditCard.brand?('6035224400000000') + 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 + assert_equal 'unionpay', CreditCard.brand?('6221260000000000') + assert_equal 'unionpay', CreditCard.brand?('6250941006528599') + assert_equal 'unionpay', CreditCard.brand?('6282000000000000') + assert_equal 'unionpay', CreditCard.brand?('8100000000000000') + assert_equal 'unionpay', CreditCard.brand?('814400000000000000') + assert_equal 'unionpay', CreditCard.brand?('8171999927660000') + assert_equal 'unionpay', CreditCard.brand?('8171999900000000021') + assert_equal 'unionpay', CreditCard.brand?('6200000000000005') + end + + def test_should_detect_synchrony_card + assert_equal 'synchrony', CreditCard.brand?('7006000000000000') + end + + def test_should_detect_routex_card + number = '7006760000000000000' + assert_equal 'routex', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + assert_equal 'routex', CreditCard.brand?('7006789224703725591') end def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand @@ -163,7 +413,6 @@ def test_detecting_full_range_of_maestro_card_numbers def test_matching_discover_card assert_equal 'discover', CreditCard.brand?('6011000000000000') assert_equal 'discover', CreditCard.brand?('6500000000000000') - assert_equal 'discover', CreditCard.brand?('6221260000000000') assert_equal 'discover', CreditCard.brand?('6450000000000000') assert_not_equal 'discover', CreditCard.brand?('6010000000000000') @@ -176,6 +425,18 @@ def test_matching_invalid_card assert_false CreditCard.valid_number?(nil) end + def test_matching_valid_naranja + number = '5895627823453005' + assert_equal 'naranja', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + + def test_matching_valid_creditel + number = '6019330047539016' + assert_equal 'creditel', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + def test_16_digit_maestro_uk number = '6759000000000000' assert_equal 16, number.length @@ -195,11 +456,12 @@ def test_19_digit_maestro_uk end def test_carnet_cards - numbers = [ - '5062280000000000', - '6046220312312312', - '6393889871239871', - '5022751231231231' + numbers = %w[ + 5062280000000000 + 6046220312312312 + 6393889871239871 + 5022751231231231 + 6275350000000001 ] numbers.each do |num| assert_equal 16, num.length @@ -207,6 +469,13 @@ def test_carnet_cards end end + def test_should_detect_cartes_bancaires_cards + assert_equal 'cartes_bancaires', CreditCard.brand?('5855010000000000') + assert_equal 'cartes_bancaires', CreditCard.brand?('5075935000000000') + assert_equal 'cartes_bancaires', CreditCard.brand?('5075901100000000') + assert_equal 'cartes_bancaires', CreditCard.brand?('5075890130000000') + end + def test_electron_cards # return the card number so assert failures are easy to isolate electron_test = Proc.new do |card_number| @@ -234,6 +503,43 @@ 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_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 d31748f17bb..595b3698bfa 100644 --- a/test/unit/credit_card_test.rb +++ b/test/unit/credit_card_test.rb @@ -5,6 +5,7 @@ def setup CreditCard.require_verification_value = false @visa = credit_card('4779139500118580', brand: 'visa') @maestro = credit_card('676700000000000000', brand: 'maestro', verification_value: '') + @bp_plus = credit_card('70501 501021600 378', brand: 'bp_plus') end def teardown @@ -49,7 +50,7 @@ def test_cards_with_empty_names_should_not_be_valid end def test_should_be_able_to_liberate_a_bogus_card - c = credit_card('', :brand => 'bogus') + c = credit_card('', brand: 'bogus') assert_valid c c.brand = 'visa' @@ -150,11 +151,11 @@ def test_should_be_invalid_with_empty_year end def test_should_not_be_valid_for_edge_year_cases - @visa.year = Time.now.year - 1 + @visa.year = Time.now.year - 1 errors = assert_not_valid @visa assert errors[:year] - @visa.year = Time.now.year + 21 + @visa.year = Time.now.year + 21 errors = assert_not_valid @visa assert errors[:year] end @@ -173,20 +174,20 @@ 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 def test_should_display_number - assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(:number => '1111222233331234').display_number - assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(:number => '111222233331234').display_number - assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(:number => '1112223331234').display_number + assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(number: '1111222233331234').display_number + assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(number: '111222233331234').display_number + assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(number: '1112223331234').display_number - assert_equal 'XXXX-XXXX-XXXX-', CreditCard.new(:number => nil).display_number - assert_equal 'XXXX-XXXX-XXXX-', CreditCard.new(:number => '').display_number - assert_equal 'XXXX-XXXX-XXXX-123', CreditCard.new(:number => '123').display_number - assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(:number => '1234').display_number - assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(:number => '01234').display_number + assert_equal 'XXXX-XXXX-XXXX-', CreditCard.new(number: nil).display_number + assert_equal 'XXXX-XXXX-XXXX-', CreditCard.new(number: '').display_number + assert_equal 'XXXX-XXXX-XXXX-123', CreditCard.new(number: '123').display_number + assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(number: '1234').display_number + assert_equal 'XXXX-XXXX-XXXX-1234', CreditCard.new(number: '01234').display_number end def test_should_correctly_identify_card_brand @@ -204,7 +205,7 @@ def test_should_be_able_to_require_a_verification_value def test_should_not_be_valid_when_requiring_a_verification_value CreditCard.require_verification_value = true - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) assert_not_valid card card.verification_value = '1234' @@ -214,7 +215,7 @@ def test_should_not_be_valid_when_requiring_a_verification_value card.verification_value = '123' assert_valid card - card = credit_card('341111111111111', :verification_value => '123', :brand => 'american_express') + card = credit_card('341111111111111', verification_value: '123', brand: 'american_express') errors = assert_not_valid card assert_equal errors[:verification_value], ['should be 4 digits'] @@ -224,7 +225,7 @@ def test_should_not_be_valid_when_requiring_a_verification_value def test_should_be_valid_when_not_requiring_a_verification_value CreditCard.require_verification_value = true - card = credit_card('4242424242424242', :verification_value => nil, :require_verification_value => false) + card = credit_card('4242424242424242', verification_value: nil, require_verification_value: false) assert_valid card card.verification_value = '1234' @@ -242,12 +243,12 @@ def test_bogus_cards_are_not_valid_without_verification_value end def test_should_return_last_four_digits_of_card_number - ccn = CreditCard.new(:number => '4779139500118580') + ccn = CreditCard.new(number: '4779139500118580') assert_equal '8580', ccn.last_digits end def test_bogus_last_digits - ccn = CreditCard.new(:number => '1') + ccn = CreditCard.new(number: '1') assert_equal '1', ccn.last_digits end @@ -262,12 +263,12 @@ def test_should_return_empty_string_for_last_digits_of_nil_card_number end def test_should_return_first_four_digits_of_card_number - ccn = CreditCard.new(:number => '4779139500118580') + ccn = CreditCard.new(number: '4779139500118580') assert_equal '477913', ccn.first_digits end def test_should_return_first_bogus_digit_of_card_number - ccn = CreditCard.new(:number => '1') + ccn = CreditCard.new(number: '1') assert_equal '1', ccn.first_digits end @@ -275,7 +276,7 @@ def test_should_be_true_when_credit_card_has_a_first_name c = CreditCard.new assert_false c.first_name? - c = CreditCard.new(:first_name => 'James') + c = CreditCard.new(first_name: 'James') assert c.first_name? end @@ -283,7 +284,7 @@ def test_should_be_true_when_credit_card_has_a_last_name c = CreditCard.new assert_false c.last_name? - c = CreditCard.new(:last_name => 'Herdman') + c = CreditCard.new(last_name: 'Herdman') assert c.last_name? end @@ -291,48 +292,48 @@ def test_should_test_for_a_full_name c = CreditCard.new assert_false c.name? - c = CreditCard.new(:first_name => 'James', :last_name => 'Herdman') + c = CreditCard.new(first_name: 'James', last_name: 'Herdman') assert c.name? end def test_should_handle_full_name_when_first_or_last_is_missing - c = CreditCard.new(:first_name => 'James') + c = CreditCard.new(first_name: 'James') assert c.name? assert_equal 'James', c.name - c = CreditCard.new(:last_name => 'Herdman') + c = CreditCard.new(last_name: 'Herdman') assert c.name? assert_equal 'Herdman', c.name end def test_should_assign_a_full_name - c = CreditCard.new :name => 'James Herdman' + c = CreditCard.new name: 'James Herdman' assert_equal 'James', c.first_name assert_equal 'Herdman', c.last_name - c = CreditCard.new :name => 'Rocket J. Squirrel' + c = CreditCard.new name: 'Rocket J. Squirrel' assert_equal 'Rocket J.', c.first_name assert_equal 'Squirrel', c.last_name - c = CreditCard.new :name => 'Twiggy' + c = CreditCard.new name: 'Twiggy' assert_equal '', c.first_name assert_equal 'Twiggy', c.last_name assert_equal 'Twiggy', c.name end def test_should_remove_trailing_whitespace_on_name - c = CreditCard.new(:last_name => 'Herdman') + c = CreditCard.new(last_name: 'Herdman') assert_equal 'Herdman', c.name - c = CreditCard.new(:last_name => 'Herdman', first_name: '') + c = CreditCard.new(last_name: 'Herdman', first_name: '') assert_equal 'Herdman', c.name end def test_should_remove_leading_whitespace_on_name - c = CreditCard.new(:first_name => 'James') + c = CreditCard.new(first_name: 'James') assert_equal 'James', c.name - c = CreditCard.new(:last_name => '', first_name: 'James') + c = CreditCard.new(last_name: '', first_name: 'James') assert_equal 'James', c.name end @@ -349,29 +350,29 @@ def test_validate_new_card # The following is a regression for a bug where the keys of the # credit card card_companies hash were not duped when detecting the brand def test_create_and_validate_credit_card_from_brand - credit_card = CreditCard.new(:brand => CreditCard.brand?('4242424242424242')) + credit_card = CreditCard.new(brand: CreditCard.brand?('4242424242424242')) assert_nothing_raised do credit_card.validate end end def test_autodetection_of_credit_card_brand - credit_card = CreditCard.new(:number => '4242424242424242') + credit_card = CreditCard.new(number: '4242424242424242') assert_equal 'visa', credit_card.brand end def test_card_brand_should_not_be_autodetected_when_provided - credit_card = CreditCard.new(:number => '4242424242424242', :brand => 'master') + credit_card = CreditCard.new(number: '4242424242424242', brand: 'master') assert_equal 'master', credit_card.brand end def test_detecting_bogus_card - credit_card = CreditCard.new(:number => '1') + credit_card = CreditCard.new(number: '1') assert_equal 'bogus', credit_card.brand end def test_validating_bogus_card - credit_card = credit_card('1', :brand => nil) + credit_card = credit_card('1', brand: nil) assert_valid credit_card end @@ -438,4 +439,11 @@ def test_should_report_as_emv_if_icc_data_present def test_should_not_report_as_emv_if_icc_data_not_present refute CreditCard.new.emv? end + + def test_bp_plus_number_validation + assert_valid @bp_plus + assert_include @bp_plus.number, ' ' + assert_equal @bp_plus.brand, 'bp_plus' + assert @bp_plus.allow_spaces_in_card? + end 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 3df8cff60f3..ff9b0fb657f 100644 --- a/test/unit/gateways/adyen_test.rb +++ b/test/unit/gateways/adyen_test.rb @@ -10,43 +10,111 @@ def setup merchant_account: 'merchantAccount' ) - @credit_card = credit_card('4111111111111111', - :month => 8, - :year => 2018, - :first_name => 'Test', - :last_name => 'Card', - :verification_value => '737', - :brand => 'visa' + @bank_account = check() + + @credit_card = credit_card( + '4111111111111111', + month: 8, + year: 2018, + first_name: 'Test', + last_name: 'Card', + verification_value: '737', + brand: 'visa' ) - @elo_credit_card = credit_card('5066 9911 1111 1118', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @elo_credit_card = credit_card( + '5066 9911 1111 1118', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'elo' + ) + + @cabal_credit_card = credit_card( + '6035 2277 1642 7021', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'cabal' + ) + + @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' ) @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) - @apple_pay_card = network_tokenization_credit_card('4111111111111111', - :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', - :month => '08', - :year => '2018', - :source => :apple_pay, - :verification_value => nil + @apple_pay_card = network_tokenization_credit_card( + '4111111111111111', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '08', + year: '2018', + source: :apple_pay, + verification_value: nil + ) + + @nt_credit_card = network_tokenization_credit_card( + '4895370015293175', + brand: 'visa', + eci: '07', + source: :network_token, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' ) @amount = 100 @options = { billing_address: address(), + shipping_address: address(), shopper_reference: 'John Smith', order_id: '345123', installments: 2, - recurring_processing_model: 'CardOnFile' + stored_credential: { reason_type: 'unscheduled' }, + email: 'john.smith@test.com', + ip: '77.110.174.153' + } + + @options_shopper_data = { + email: 'john.smith@test.com', + ip: '77.110.174.153', + shopper_email: 'john2.smith@test.com', + shopper_ip: '192.168.100.100' } + + @normalized_3ds_2_options = { + reference: '345123', + email: 'john.smith@test.com', + ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } + } + + @long_order_id = 'asdfjkl;asdfjkl;asdfj;aiwyutinvpoaieryutnmv;203987528752098375j3q-p489756ijmfpvbijpq348nmdf;vbjp3845' end # Subdomains are only valid for production gateways, so the test_url check must be manually bypassed for this test to pass. @@ -80,6 +148,18 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_bank_account + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @bank_account, @options) + assert_success response + + assert_equal '#7914775043909934#', response.authorization + assert_equal 'R', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + assert response.test? + end + def test_successful_authorize_with_3ds @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) @@ -93,6 +173,80 @@ def test_successful_authorize_with_3ds refute response.params['paRequest'].blank? end + def test_failed_authorize_with_unexpected_3ds + @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) + response = @gateway.authorize(@amount, @three_ds_enrolled_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 + 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 + + def test_adds_3ds1_standalone_fields + eci = '05' + cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=' + cavv_algorithm = '1' + xid = 'ODUzNTYzOTcwODU5NzY3Qw==' + enrolled = 'Y' + authentication_response_status = 'Y' + options_with_3ds1_standalone = @options.merge( + three_d_secure: { + eci: eci, + cavv: cavv, + cavv_algorithm: cavv_algorithm, + xid: xid, + enrolled: enrolled, + authentication_response_status: authentication_response_status + } + ) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds1_standalone) + end.check_request do |_endpoint, data, _headers| + assert_equal eci, JSON.parse(data)['mpiData']['eci'] + assert_equal cavv, JSON.parse(data)['mpiData']['cavv'] + assert_equal cavv_algorithm, JSON.parse(data)['mpiData']['cavvAlgorithm'] + assert_equal xid, JSON.parse(data)['mpiData']['xid'] + assert_equal enrolled, JSON.parse(data)['mpiData']['directoryResponse'] + assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse'] + end.respond_with(successful_authorize_response) + end + + def test_adds_3ds2_standalone_fields + version = '2.1.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + directory_response_status = 'C' + authentication_response_status = 'Y' + options_with_3ds2_standalone = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds2_standalone) + end.check_request do |_endpoint, data, _headers| + assert_equal version, JSON.parse(data)['mpiData']['threeDSVersion'] + assert_equal eci, JSON.parse(data)['mpiData']['eci'] + assert_equal cavv, JSON.parse(data)['mpiData']['cavv'] + assert_equal ds_transaction_id, JSON.parse(data)['mpiData']['dsTransID'] + assert_equal directory_response_status, JSON.parse(data)['mpiData']['directoryResponse'] + assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse'] + end.respond_with(successful_authorize_response) + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(failed_authorize_response) @@ -101,6 +255,115 @@ def test_failed_authorize assert_failure response end + def test_standard_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_billing_field_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'incorrect_address', response.error_code + end + + def test_unknown_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_invalid_delivery_field_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '702', response.error_code + end + + def test_billing_address_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_billing_address_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:incorrect_address], response.error_code + end + + def test_cvc_length_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_cvc_validation_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code + end + + def test_invalid_card_number_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_invalid_card_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_invalid_amount_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_invalid_amount_response) + + response = @gateway.authorize(nil, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:invalid_amount], response.error_code + end + + def test_invalid_access_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_not_allowed_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:config_error], response.error_code + end + + def test_unknown_reason_error_code_mapping + @gateway.expects(:ssl_post).returns(failed_unknown_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_failed_authorise3d + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.send(:commit, 'authorise3d', {}, {}) + + assert_equal 'Expired Card', response.message + assert_failure response + end + + def test_failed_authorise3ds2 + @gateway.expects(:ssl_post).returns(failed_authorize_3ds2_response) + + response = @gateway.send(:commit, 'authorise3ds2', {}, {}) + + assert_equal '3D Not Authenticated', response.message + 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') @@ -117,7 +380,15 @@ def test_failed_capture assert_failure response end - def test_successful_purchase + 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) end.respond_with(successful_authorize_response, successful_capture_response) @@ -126,10 +397,37 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_bank_account + response = stub_comms do + @gateway.purchase(@amount, @bank_account, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + assert_equal '7914775043909934#8814775564188305#', response.authorization + assert response.test? + end + def test_successful_purchase_with_elo_card response = stub_comms do @gateway.purchase(@amount, @elo_credit_card, @options) - end.respond_with(successful_authorize_with_elo_response, successful_capture_with_elo_repsonse) + end.respond_with(simple_successful_authorize_response, simple_successful_capture_repsonse) + assert_success response + assert_equal '8835511210681145#8835511210689965#', response.authorization + assert response.test? + end + + def test_successful_purchase_with_cabal_card + response = stub_comms do + @gateway.purchase(@amount, @cabal_credit_card, @options) + end.respond_with(simple_successful_authorize_response, simple_successful_capture_repsonse) + assert_success response + assert_equal '8835511210681145#8835511210689965#', response.authorization + assert response.test? + end + + def test_successful_purchase_with_unionpay_card + response = stub_comms do + @gateway.purchase(@amount, @unionpay_credit_card, @options) + end.respond_with(simple_successful_authorize_response, simple_successful_capture_repsonse) assert_success response assert_equal '8835511210681145#8835511210689965#', response.authorization assert response.test? @@ -137,9 +435,9 @@ def test_successful_purchase_with_elo_card def test_successful_maestro_purchase response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge({selected_brand: 'maestro', overwrite_brand: 'true'})) - end.check_request do |endpoint, data, headers| - if endpoint =~ /authorise/ + @gateway.purchase(@amount, @credit_card, @options.merge({ selected_brand: 'maestro', overwrite_brand: 'true' })) + end.check_request do |endpoint, data, _headers| + if /authorise/.match?(endpoint) assert_match(/"overwriteBrand":true/, data) assert_match(/"selectedBrand":"maestro"/, data) end @@ -149,26 +447,149 @@ def test_successful_maestro_purchase assert response.test? end + def test_3ds_2_fields_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options) + end.check_request do |_endpoint, data, _headers| + data = JSON.parse(data) + assert_equal 'browser', data['threeDS2RequestData']['deviceChannel'] + assert_equal 'unknown', data['browserInfo']['acceptHeader'] + assert_equal 100, data['browserInfo']['colorDepth'] + assert_equal false, data['browserInfo']['javaEnabled'] + assert_equal 'US', data['browserInfo']['language'] + assert_equal 1000, data['browserInfo']['screenHeight'] + assert_equal 500, data['browserInfo']['screenWidth'] + assert_equal '-120', data['browserInfo']['timeZoneOffset'] + assert_equal 'unknown', data['browserInfo']['userAgent'] + end.respond_with(successful_authorize_response) + end + def test_installments_sent stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_equal 2, JSON.parse(data)['installments']['value'] end.respond_with(successful_authorize_response) end + def test_capture_delay_hours_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ capture_delay_hours: 4 })) + end.check_request do |_endpoint, data, _headers| + assert_equal 4, JSON.parse(data)['captureDelayHours'] + end.respond_with(successful_authorize_response) + end + def test_custom_routing_sent stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge({custom_routing_flag: 'abcdefg'})) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge({ custom_routing_flag: 'abcdefg' })) + end.check_request do |_endpoint, data, _headers| assert_equal 'abcdefg', JSON.parse(data)['additionalData']['customRoutingFlag'] end.respond_with(successful_authorize_response) end + def test_splits_sent + split_data = [{ + 'amount' => { + 'currency' => 'USD', + 'value' => 50 + }, + 'type' => 'MarketPlace', + 'account' => '163298747', + 'reference' => 'QXhlbFN0b2x0ZW5iZXJnCg' + }, { + 'amount' => { + 'currency' => 'USD', + 'value' => 50 + }, + 'type' => 'Commission', + 'reference' => 'THVjYXNCbGVkc29lCg' + }] + + options = @options.merge({ splits: split_data }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal split_data, JSON.parse(data)['splits'] + end.respond_with(successful_authorize_response) + end + + def test_execute_threed_false_with_additional_data + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ execute_threed: false, overwrite_brand: true, selected_brand: 'maestro' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/"additionalData":{"overwriteBrand":true,"executeThreeD":false}/, data) + assert_match(/"selectedBrand":"maestro"/, data) + end.respond_with(successful_authorize_response) + end + + def test_execute_threed_false_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, '123', @normalized_3ds_2_options.merge({ execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + refute JSON.parse(data)['additionalData']['scaExemption'] + assert_false JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_if_execute_threed_missing_3ds2 + stub_comms do + @gateway.authorize(@amount, '123', @normalized_3ds_2_options.merge({ scaExemption: 'lowValue' })) + end.check_request do |_endpoint, data, _headers| + refute JSON.parse(data)['additionalData']['scaExemption'] + refute JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_and_execute_threed_false_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, '123', @normalized_3ds_2_options.merge({ sca_exemption: 'lowValue', execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + assert_equal 'lowValue', JSON.parse(data)['additionalData']['scaExemption'] + assert_false JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_and_execute_threed_true_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, '123', @normalized_3ds_2_options.merge({ sca_exemption: 'lowValue', execute_threed: true })) + end.check_request do |_endpoint, data, _headers| + assert_equal 'lowValue', JSON.parse(data)['additionalData']['scaExemption'] + assert JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_when_execute_threed_true_3ds1 + stub_comms do + @gateway.authorize(@amount, '123', @options.merge({ sca_exemption: 'lowValue', execute_threed: true })) + end.check_request do |_endpoint, data, _headers| + refute JSON.parse(data)['additionalData']['scaExemption'] + assert JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_when_execute_threed_false_3ds1 + stub_comms do + @gateway.authorize(@amount, '123', @options.merge({ sca_exemption: 'lowValue', execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + refute JSON.parse(data)['additionalData']['scaExemption'] + refute JSON.parse(data)['additionalData']['executeThreeD'] + end.respond_with(successful_authorize_response) + end + + def test_update_shopper_statement_and_industry_usage_sent + stub_comms do + @gateway.adjust(@amount, '123', @options.merge({ update_shopper_statement: 'statement note', industry_usage: 'DelayedCharge' })) + end.check_request do |_endpoint, data, _headers| + assert_equal 'statement note', JSON.parse(data)['additionalData']['updateShopperStatement'] + assert_equal 'DelayedCharge', JSON.parse(data)['additionalData']['industryUsage'] + end.respond_with(successful_adjust_response) + end + def test_risk_data_sent stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: {'operatingSystem' => 'HAL9000'}})) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge({ risk_data: { 'operatingSystem' => 'HAL9000' } })) + end.check_request do |_endpoint, data, _headers| assert_equal 'HAL9000', JSON.parse(data)['additionalData']['riskdata.operatingSystem'] end.respond_with(successful_authorize_response) end @@ -180,8 +601,8 @@ def test_risk_data_complex_data 'basket.item.productTitle' => 'Blue T Shirt', 'promotions.promotion.promotionName' => 'Big Sale promotion' } - @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: risk_data})) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge({ risk_data: risk_data })) + end.check_request do |_endpoint, data, _headers| parsed = JSON.parse(data) assert_equal 'express', parsed['additionalData']['riskdata.deliveryMethod'] assert_equal 'Blue T Shirt', parsed['additionalData']['riskdata.basket.item.productTitle'] @@ -189,6 +610,144 @@ def test_risk_data_complex_data end.respond_with(successful_authorize_response) end + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"Ecommerce"/, data) + assert_match(/"recurringProcessingModel":"Subscription"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_cit_used + @credit_card.verification_value = nil + options = stored_credential_options(:cardholder, :recurring, ntid: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"Subscription"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_initial + options = stored_credential_options(:merchant, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"Subscription"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_used + @credit_card.verification_value = nil + options = stored_credential_options(:merchant, :recurring, ntid: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"Subscription"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_initial + options = stored_credential_options(:cardholder, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"Ecommerce"/, data) + assert_match(/"recurringProcessingModel":"CardOnFile"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_used + @credit_card.verification_value = nil + options = stored_credential_options(:cardholder, :unscheduled, ntid: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"CardOnFile"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_initial + options = stored_credential_options(:merchant, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"UnscheduledCardOnFile"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_used + @credit_card.verification_value = nil + options = stored_credential_options(:merchant, :unscheduled, ntid: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"UnscheduledCardOnFile"/, data) + end.respond_with(successful_authorize_response) + + 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')) + end.check_request do |_endpoint, data, _headers| + assert_match(/"amount\":{\"value\":\"2\",\"currency\":\"JPY\"}/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(200, @credit_card, @options.merge(currency: 'CLP')) + end.check_request do |_endpoint, data, _headers| + assert_match(/"amount\":{\"value\":\"200\",\"currency\":\"CLP\"}/, data) + end.respond_with(successful_authorize_response) + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -222,6 +781,45 @@ def test_failed_refund assert_failure response end + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + response = @gateway.refund(@amount, '') + assert_nil response.authorization + assert_equal "Required field 'reference' is not provided.", response.message + assert_failure response + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + response = @gateway.credit(@amount, '883614109029400G') + assert_equal '#883614109029400G#', response.authorization + assert_equal 'Received', response.message + 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') @@ -230,6 +828,14 @@ def test_successful_void assert response.test? end + def test_successful_cancel_or_refund + @gateway.expects(:ssl_post).returns(successful_cancel_or_refund_response) + response = @gateway.void('7914775043909934') + assert_equal '7914775043909934#8614775821628806#', response.authorization + assert_equal '[cancelOrRefund-received]', response.message + assert response.test? + end + def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) response = @gateway.void('') @@ -237,16 +843,87 @@ def test_failed_void assert_failure response end + def test_successful_adjust + @gateway.expects(:ssl_post).returns(successful_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal '8835544088660594#8835544088660594#', response.authorization + assert_equal '[adjustAuthorisation-received]', response.message + end + + def test_failed_adjust + @gateway.expects(:ssl_post).returns(failed_adjust_response) + response = @gateway.adjust(200, '') + assert_equal 'Original pspReference required for this operation', response.message + assert_failure response + end + + def test_successful_synchronous_adjust + @gateway.expects(:ssl_post).returns(successful_synchronous_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal '8835544088660594#8835574118820108#', response.authorization + assert_equal 'Authorised', response.message + end + + def test_failed_synchronous_adjust + @gateway.expects(:ssl_post).returns(failed_synchronous_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal 'Refused', response.message + assert_failure response + end + + def test_successful_tokenize_only_store + response = stub_comms do + @gateway.store(@credit_card, @options.merge({ tokenize_only: true })) + end.check_request do |_endpoint, data, _headers| + assert_equal 'CardOnFile', JSON.parse(data)['recurringProcessingModel'] + end.respond_with(successful_store_response) + assert_equal '#8835205392522157#', response.authorization + end + + def test_successful_tokenize_only_store_with_ntid + stub_comms do + @gateway.store(@credit_card, @options.merge({ tokenize_only: true, network_transaction_id: '858435661128555' })) + end.check_request do |_endpoint, data, _headers| + assert_equal '858435661128555', JSON.parse(data)['additionalData']['networkTxReference'] + end.respond_with(successful_store_response) + end + def test_successful_store response = stub_comms do @gateway.store(@credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_equal 'CardOnFile', JSON.parse(data)['recurringProcessingModel'] end.respond_with(successful_store_response) assert_success response assert_equal '#8835205392522157#8315202663743702', response.authorization end + def test_successful_store_with_bank_account + response = stub_comms do + @gateway.store(@bank_account, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'CardOnFile', JSON.parse(data)['recurringProcessingModel'] + end.respond_with(successful_store_response) + assert_success response + assert_equal '#8835205392522157#8315202663743702', response.authorization + end + + def test_successful_store_with_recurring_contract_type + stub_comms do + @gateway.store(@credit_card, @options.merge({ recurring_contract_type: 'ONECLICK' })) + end.check_request do |_endpoint, data, _headers| + assert_equal 'ONECLICK', JSON.parse(data)['recurring']['contract'] + end.respond_with(successful_store_response) + end + + def test_recurring_contract_type_set_for_reference_purchase + stub_comms do + @gateway.store('123', @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'RECURRING', JSON.parse(data)['recurring']['contract'] + end.respond_with(successful_store_response) + end + def test_failed_store @gateway.expects(:ssl_post).returns(failed_store_response) response = @gateway.store(@credit_card, @options) @@ -254,77 +931,552 @@ def test_failed_store assert_equal 'Refused', response.message end - def test_successful_verify + def test_successful_unstore + response = stub_comms do + @gateway.unstore(shopper_reference: 'shopper_reference', + recurring_detail_reference: 'detail_reference') + end.respond_with(successful_unstore_response) + assert_success response + assert_equal '[detail-successfully-disabled]', response.message + end + + def test_failed_unstore + @gateway.expects(:ssl_post).returns(failed_unstore_response) + response = @gateway.unstore(shopper_reference: 'random_reference', + recurring_detail_reference: 'detail_reference') + assert_failure response + assert_equal 'Contract not found', response.message + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.check_request do |endpoint, data, _headers| + assert_equal '0', JSON.parse(data)['amount']['value'] if endpoint.include?('authorise') + end.respond_with(successful_verify_response) + assert_success response + assert_equal '#7914776426645103#', response.authorization + assert_equal 'Authorised', response.message + assert response.test? + end + + def test_successful_verify_with_custom_amount + response = stub_comms do + @gateway.verify(@credit_card, @options.merge({ verify_amount: '500' })) + end.check_request do |endpoint, data, _headers| + assert_equal '500', JSON.parse(data)['amount']['value'] if endpoint.include?('authorise') + end.respond_with(successful_verify_response) + assert_success response + end + + def test_successful_verify_with_bank_account + response = stub_comms do + @gateway.verify(@bank_account, @options) + end.respond_with(successful_verify_response) + assert_success response + assert_equal '#7914776426645103#', response.authorization + assert_equal 'Authorised', response.message + assert response.test? + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_verify_response) + assert_failure response + assert_equal '#7914776433387947#', response.authorization + assert_equal 'Refused', response.message + assert response.test? + end + + def test_failed_verify_with_bank_account + response = stub_comms do + @gateway.verify(@bank_account, @options) + end.respond_with(failed_verify_response) + assert_failure response + assert_equal '#7914776433387947#', response.authorization + assert_equal 'Refused', response.message + assert response.test? + end + + def test_failed_avs_check_returns_refusal_reason_raw + @gateway.expects(:ssl_post).returns(failed_authorize_avs_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Refused | 05 : Do not honor', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrub_bank_account + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_bank_account), post_scrubbed_bank_account + end + + def test_scrub_network_tokenization_card + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_network_tokenization_card), post_scrubbed_network_tokenization_card + end + + def test_shopper_data + post = { card: { billingAddress: {} } } + @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, @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 + + def test_add_address + post = { card: { billingAddress: {} } } + @options[:billing_address].delete(:address1) + @options[:billing_address].delete(:address2) + @options[:billing_address].delete(:state) + @options[:shipping_address].delete(:state) + @gateway.send(:add_address, post, @options) + # Billing Address + assert_equal 'NA', post[:billingAddress][:street] + assert_equal 'NA', post[:billingAddress][:houseNumberOrName] + assert_equal 'NA', post[:billingAddress][:stateOrProvince] + assert_equal @options[:billing_address][:zip], post[:billingAddress][:postalCode] + assert_equal @options[:billing_address][:city], post[:billingAddress][:city] + assert_equal @options[:billing_address][:country], post[:billingAddress][:country] + # Shipping Address + assert_equal 'NA', post[:deliveryAddress][:stateOrProvince] + assert_equal @options[:shipping_address][:address1], post[:deliveryAddress][:street] + assert_equal @options[:shipping_address][:address2], post[:deliveryAddress][:houseNumberOrName] + assert_equal @options[:shipping_address][:zip], post[:deliveryAddress][:postalCode] + assert_equal @options[:shipping_address][:city], post[:deliveryAddress][:city] + assert_equal @options[:shipping_address][:country], post[:deliveryAddress][:country] + 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 + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 1234567890, JSON.parse(data)['telephoneNumber'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_auth_phone_number + options = @options.merge(billing_address: { phone_number: 987654321, phone: 1234567890 }) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 987654321, JSON.parse(data)['telephoneNumber'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_auth_application_info + ActiveMerchant::Billing::AdyenGateway.application_id = { name: 'Acme', version: '1.0' } + + options = @options.merge!( + merchantApplication: { + name: 'Acme Inc.', + version: '2' + } + ) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'Acme', JSON.parse(data)['applicationInfo']['externalPlatform']['name'] + assert_equal '1.0', JSON.parse(data)['applicationInfo']['externalPlatform']['version'] + assert_equal 'Acme Inc.', JSON.parse(data)['applicationInfo']['merchantApplication']['name'] + assert_equal '2', JSON.parse(data)['applicationInfo']['merchantApplication']['version'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_purchase_with_long_order_id + options = @options.merge({ order_id: @long_order_id }) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal @long_order_id[0..79], JSON.parse(data)['reference'] + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_authorize_with_credit_card_no_name + credit_card_no_name = ActiveMerchant::Billing::CreditCard.new({ + number: '4111111111111111', + month: 3, + year: 2030, + verification_value: '737', + brand: 'visa' + }) + + response = stub_comms do + @gateway.authorize(@amount, credit_card_no_name, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'Not Provided', JSON.parse(data)['card']['holderName'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_authorize_with_network_tokenization_credit_card_no_name + @apple_pay_card.first_name = nil + @apple_pay_card.last_name = nil + response = stub_comms do + @gateway.authorize(@amount, @apple_pay_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'Not Provided', JSON.parse(data)['card']['holderName'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_authorize_with_network_tokenization_credit_card + response = stub_comms do + @gateway.authorize(@amount, @apple_pay_card, @options) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data) + assert_equal 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', parsed['mpiData']['cavv'] + assert_equal '07', parsed['mpiData']['eci'] + assert_equal 'applepay', parsed['additionalData']['paymentdatasource.type'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_authorize_and_capture_with_network_transaction_id + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response_with_network_tx_ref) + assert_equal auth.network_transaction_id, '858435661128555' + + response = stub_comms do + @gateway.capture(@amount, auth.authorization, @options.merge(network_transaction_id: auth.network_transaction_id)) + end.check_request do |_, data, _| + assert_match(/"networkTxReference":"#{auth.network_transaction_id}"/, data) + end.respond_with(successful_capture_response) + assert_success response + end + + def test_authorize_and_capture_with_network_transaction_id_from_stored_cred_hash + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response_with_network_tx_ref) + assert_equal auth.network_transaction_id, '858435661128555' + + response = stub_comms do + @gateway.capture(@amount, auth.authorization, @options.merge(stored_credential: { network_transaction_id: auth.network_transaction_id })) + end.check_request do |_, data, _| + assert_match(/"networkTxReference":"#{auth.network_transaction_id}"/, data) + end.respond_with(successful_capture_response) + assert_success response + end + + def test_authorize_with_network_token + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @nt_credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_network_token + response = stub_comms do + @gateway.purchase(@amount, @nt_credit_card, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + def test_authorize_with_sub_merchant_id + sub_merchant_data = { + sub_merchant_id: '123451234512345', + sub_merchant_name: 'Wildsea', + sub_merchant_street: '1234 Street St', + sub_merchant_city: 'Night City', + sub_merchant_state: 'East Block', + sub_merchant_postal_code: '112233', + sub_merchant_country: 'EUR', + sub_merchant_tax_id: '12345abcde67', + sub_merchant_mcc: '1234' + } response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(successful_verify_response) + @gateway.authorize(@amount, @credit_card, @options.merge(sub_merchant_data)) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data) + assert parsed['additionalData']['subMerchantID'] + assert parsed['additionalData']['subMerchantName'] + assert parsed['additionalData']['subMerchantStreet'] + assert parsed['additionalData']['subMerchantCity'] + assert parsed['additionalData']['subMerchantState'] + assert parsed['additionalData']['subMerchantPostalCode'] + assert parsed['additionalData']['subMerchantCountry'] + assert parsed['additionalData']['subMerchantTaxId'] + end.respond_with(successful_authorize_response) assert_success response - assert_equal '#7914776426645103#', response.authorization - assert_equal 'Authorised', response.message - assert response.test? end - def test_failed_verify + def test_authorize_with_sub_sellers + 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' + } response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(failed_verify_response) - assert_failure response - assert_equal '#7914776433387947#', response.authorization - assert_equal 'Refused', response.message - assert response.test? + @gateway.authorize(@amount, @credit_card, @options.merge(sub_merchant_data: sub_seller_options)) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data) + additional_data = parsed['additionalData'] + assert additional_data['subMerchant.numberOfSubSellers'] + assert additional_data['subMerchant.subSeller1.id'] + assert additional_data['subMerchant.subSeller1.name'] + assert additional_data['subMerchant.subSeller1.street'] + assert additional_data['subMerchant.subSeller1.city'] + assert additional_data['subMerchant.subSeller1.state'] + assert additional_data['subMerchant.subSeller1.postalCode'] + assert additional_data['subMerchant.subSeller1.country'] + assert additional_data['subMerchant.subSeller1.taxId'] + assert additional_data['subMerchant.subSeller1.debitSettlementBank'] + assert additional_data['subMerchant.subSeller1.debitSettlementAgency'] + assert additional_data['subMerchant.subSeller1.debitSettlementAccountType'] + assert additional_data['subMerchant.subSeller1.debitSettlementAccount'] + assert additional_data['subMerchant.subSeller1.creditSettlementBank'] + assert additional_data['subMerchant.subSeller1.creditSettlementAgency'] + assert additional_data['subMerchant.subSeller1.creditSettlementAccountType'] + assert additional_data['subMerchant.subSeller1.creditSettlementAccount'] + assert additional_data['subMerchant.subSeller2.id'] + assert additional_data['subMerchant.subSeller2.name'] + assert additional_data['subMerchant.subSeller2.street'] + assert additional_data['subMerchant.subSeller2.city'] + assert additional_data['subMerchant.subSeller2.state'] + assert additional_data['subMerchant.subSeller2.postalCode'] + assert additional_data['subMerchant.subSeller2.country'] + assert additional_data['subMerchant.subSeller2.taxId'] + assert additional_data['subMerchant.subSeller2.debitSettlementBank'] + assert additional_data['subMerchant.subSeller2.debitSettlementAgency'] + assert additional_data['subMerchant.subSeller2.debitSettlementAccountType'] + assert additional_data['subMerchant.subSeller2.debitSettlementAccount'] + assert additional_data['subMerchant.subSeller2.creditSettlementBank'] + assert additional_data['subMerchant.subSeller2.creditSettlementAgency'] + assert additional_data['subMerchant.subSeller2.creditSettlementAccountType'] + assert additional_data['subMerchant.subSeller2.creditSettlementAccount'] + end.respond_with(successful_authorize_response) + assert_success response end - def test_failed_avs_check_returns_refusal_reason_raw - @gateway.expects(:ssl_post).returns(failed_authorize_avs_response) + def test_level_2_data + level_2_options = { + total_tax_amount: '160', + customer_reference: '101' + } - response = @gateway.authorize(@amount, @credit_card, @options) - assert_failure response - assert_equal 'Refused | 05 : Do not honor', response.message - end + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: level_2_options)) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data) + additional_data = parsed['additionalData'] + assert_equal additional_data['enhancedSchemeData.totalTaxAmount'], level_2_options[:total_tax_amount] + assert_equal additional_data['enhancedSchemeData.customerReference'], level_2_options[:customer_reference] + end.respond_with(successful_authorize_response) - def test_scrub - assert @gateway.supports_scrubbing? - assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_success response end - def test_scrub_network_tokenization_card - assert @gateway.supports_scrubbing? - assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed - end + def test_level_3_data + level_3_options = { + total_tax_amount: '12800', + customer_reference: '101', + freight_amount: '300', + destination_state_province_code: 'NYC', + ship_from_postal_code: '1082GM', + order_date: '101216', + destination_postal_code: '1082GM', + destination_country_code: 'NLD', + duty_amount: '500', + items: [ + { + description: 'T16 Test products 1', + product_code: 'TEST120', + commodity_code: 'COMMCODE1', + quantity: '5', + unit_of_measure: 'm', + unit_price: '1000', + discount_amount: '60', + total_amount: '4940' + } + ] + } - def test_add_address - post = {:card => {:billingAddress => {}}} - @options[:billing_address].delete(:address1) - @options[:billing_address].delete(:address2) - @options[:billing_address].delete(:state) - @gateway.send(:add_address, post, @options) - assert_equal 'N/A', post[:card][:billingAddress][:street] - assert_equal 'N/A', post[:card][:billingAddress][:houseNumberOrName] - assert_equal 'N/A', post[:card][:billingAddress][:stateOrProvince] - assert_equal @options[:billing_address][:zip], post[:card][:billingAddress][:postalCode] - assert_equal @options[:billing_address][:city], post[:card][:billingAddress][:city] - assert_equal @options[:billing_address][:country], post[:card][:billingAddress][:country] + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(level_3_data: level_3_options)) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data) + additional_data = parsed['additionalData'] + leve_3_keys = ['enhancedSchemeData.freightAmount', 'enhancedSchemeData.destinationStateProvinceCode', + 'enhancedSchemeData.shipFromPostalCode', 'enhancedSchemeData.orderDate', 'enhancedSchemeData.destinationPostalCode', + 'enhancedSchemeData.destinationCountryCode', 'enhancedSchemeData.dutyAmount', + 'enhancedSchemeData.itemDetailLine1.description', 'enhancedSchemeData.itemDetailLine1.productCode', + 'enhancedSchemeData.itemDetailLine1.commodityCode', 'enhancedSchemeData.itemDetailLine1.quantity', + 'enhancedSchemeData.itemDetailLine1.unitOfMeasure', 'enhancedSchemeData.itemDetailLine1.unitPrice', + 'enhancedSchemeData.itemDetailLine1.discountAmount', 'enhancedSchemeData.itemDetailLine1.totalAmount'] + + additional_data_keys = additional_data.keys + assert_all(leve_3_keys) { |item| additional_data_keys.include?(item) } + + mapper = { "enhancedSchemeData.freightAmount": 'freight_amount', + "enhancedSchemeData.destinationStateProvinceCode": 'destination_state_province_code', + "enhancedSchemeData.shipFromPostalCode": 'ship_from_postal_code', + "enhancedSchemeData.orderDate": 'order_date', + "enhancedSchemeData.destinationPostalCode": 'destination_postal_code', + "enhancedSchemeData.destinationCountryCode": 'destination_country_code', + "enhancedSchemeData.dutyAmount": 'duty_amount' } + + mapper.each do |item| + assert_equal additional_data[item[0]], level_3_options[item[1]] + end + end.respond_with(successful_authorize_response) + assert_success response end - def test_authorize_with_network_tokenization_credit_card_no_name - @apple_pay_card.first_name = nil - @apple_pay_card.last_name = nil + 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, @apple_pay_card, @options) - end.check_request do |endpoint, data, headers| - assert_equal 'Not Provided', JSON.parse(data)['card']['holderName'] + @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_authorize_with_network_tokenization_credit_card + 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, @apple_pay_card, @options) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(additional_data_lodging: lodging_data)) + end.check_request do |_endpoint, data, _headers| parsed = JSON.parse(data) - assert_equal 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', parsed['mpiData']['cavv'] - assert_equal '07', parsed['mpiData']['eci'] - assert_equal 'applepay', parsed['additionalData']['paymentdatasource.type'] + 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 @@ -337,17 +1489,35 @@ def test_extended_avs_response end def test_optional_idempotency_key_header - options = @options.merge(:idempotency_key => 'test123') + options = @options.merge(idempotency_key: 'test123') response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, _data, headers| assert headers['Idempotency-Key'] end.respond_with(successful_authorize_response) assert_success response end + def test_three_decimal_places_currency_handling + stub_comms do + @gateway.authorize(1000, @credit_card, @options.merge(currency: 'JOD')) + end.check_request(skip_response: true) do |_endpoint, data| + assert_match(/"amount\":{\"value\":\"1000\",\"currency\":\"JOD\"}/, data) + end + end + private + def stored_credential_options(*args, ntid: nil) + { + order_id: '#1001', + description: 'AM test', + currency: 'GBP', + customer: '123', + stored_credential: stored_credential(*args, ntid: ntid) + } + end + def pre_scrubbed <<-PRE_SCRUBBED opening connection to pal-test.adyen.com:443... @@ -412,6 +1582,70 @@ def post_scrubbed POST_SCRUBBED end + def pre_scrubbed_bank_account + <<-PRE_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic d3NfMTYzMjQ1QENvbXBhbnkuRGFuaWVsYmFra2Vybmw6eXU0aD50ZlxIVEdydSU1PDhxYTVMTkxVUw==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n" + <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"bankAccount\":{\"bankAccountNumber\":\"15378535\",\"bankLocationId\":\"244183602\",\"ownerName\":\"Jim Smith\",\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8514775682339577\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed_bank_account + <<-POST_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n" + <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"bankAccount\":{\"bankAccountNumber\":\"[FILTERED]\",\"bankLocationId\":\"[FILTERED]\",\"ownerName\":\"Jim Smith\",\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8514775682339577\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + def pre_scrubbed_network_tokenization_card <<-PRE_SCRUBBED opening connection to pal-test.adyen.com:443... @@ -492,7 +1726,7 @@ def failed_purchase_response RESPONSE end - def successful_authorize_with_elo_response + def simple_successful_authorize_response <<-RESPONSE { "pspReference":"8835511210681145", @@ -502,7 +1736,7 @@ def successful_authorize_with_elo_response RESPONSE end - def successful_capture_with_elo_repsonse + def simple_successful_capture_repsonse <<-RESPONSE { "pspReference":"8835511210689965", @@ -526,6 +1760,34 @@ def successful_authorize_response RESPONSE end + def successful_authorize_response_with_network_tx_ref + <<~RESPONSE + { + "additionalData": { + "liabilityShift": "false", + "authCode": "034788", + "avsResult": "2 Neither postal code nor address match", + "adjustAuthorisationData": "BQABAQAd37r69soYRcrrGlBumyPHvhurCKvze1aPCT2fztlUyUZZ0+5YZgh/rlmBjM9FNCm3Emv4awkiFXyaMJ4x+Jc7eGJpCaB9oq1QTkeMIw4yjvblij8nBmj8OIloKN/sKVF1WD4tSSC6ybgz0/ZxVZpn+l4TDcHJfGIYfELax7sMFfjGR6HEGw1Ac0we4FcLltxLL8x/aRRGOaadBO74wpvl8aatVYvgVKh42f09ovChJlDvcoIifAopkp5RxuzN1wqcad+ScHZsriVJVySuXgguAaLmEBpF6y/LQfej1pRW+zEEjYgFzrnbP+giWomBQcyY2mCnf6cBwVaeddavLSv6EMcmuplIfUPGDSr7NygJ2wkAAAEZmz6JwmlAmPoKMsuJPnnRNSBdG2EKTRBU139U2ytJuK8hVXNJc98A7bylLQqRc9zjSxJAOdX+KdaEY4KNASUqovgZ1ylPnRt/FYOqfraZcyQtl9otJjTl9oQkgSdfFeQEKg6OD9VVMzObShBEjuVFuT6HAAujEl79i1eS7QhD0w4/c8zW6tsSF29gbr7CPi/CHudeUuFHBPWGQ/NoIQXYKD+TfU+mKyPq0w8NYRdQyIiTHXHppDfrBJFbyCfE3+Dm80KKt3Kf94jvIs4xawFPURiB73GEELHufROqBQwPThWETrnTC0MwzdGB5r1KwKCtSPcV0V1zKd6pVEbjJjUvuE/9z5KaaSK8CwlHmMQcAlkYEpEmaY5bZ21gghsub9ukn/xcIhoERPi39ahnDya5thX+/+IyihGpRCIq3zMPkGKCqTokDRTv8tOK+6CMUlNbnnF95G4Kkar7lbbhxsHtElCsuVziBuoYt8n/l562uSx669+lkJ0X1w6yDPrsU9gWXkZQ8uozxKVdLIB2n0apQp8syqJ7I5atgyLnFYFnuIxW58D4evPdD5pO1d3DlCTA9DT8Df8kPRdIXNol4+skrTrP8YwMjvm3HZGusffseF0nNhOormhWdBSYIX89mu4uUus=", + "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount", + "threeDOffered": "false", + "retry.attempt1.avsResultRaw": "2", + "retry.attempt1.acquirer": "TestPmmAcquirer", + "networkTxReference": "858435661128555", + "authorisationMid": "1000", + "acquirerAccountCode": "TestPmmAcquirerAccount", + "cvcResult": "1 Matches", + "retry.attempt1.responseCode": "Approved", + "recurringProcessingModel": "Subscription", + "threeDAuthenticated": "false", + "retry.attempt1.rawResponse": "AUTHORISED" + }, + "pspReference": "853623109930081E", + "resultCode": "Authorised", + "authCode": "034788" + } + RESPONSE + end + def successful_authorize_with_3ds_response '{"pspReference":"8835440446784145","resultCode":"RedirectShopper","issuerUrl":"https:\\/\\/test.adyen.com\\/hpp\\/3d\\/validate.shtml","md":"djIhcWk3MUhlVFlyQ1h2UC9NWmhpVm10Zz09IfIxi5eDMZgG72AUXy7PEU86esY68wr2cunaFo5VRyNPuWg3ZSvEIFuielSuoYol5WhjCH+R6EJTjVqY8eCTt+0wiqHd5btd82NstIc8idJuvg5OCu2j8dYo0Pg7nYxW\\/2vXV9Wy\\/RYvwR8tFfyZVC\\/U2028JuWtP2WxrBTqJ6nV2mDoX2chqMRSmX8xrL6VgiLoEfzCC\\/c+14r77+whHP0Mz96IGFf4BIA2Qo8wi2vrTlccH\\/zkLb5hevvV6QH3s9h0\\/JibcUrpoXH6M903ulGuikTr8oqVjEB9w8\\/WlUuxukHmqqXqAeOPA6gScehs6SpRm45PLpLysCfUricEIDhpPN1QCjjgw8+qVf3Ja1SzwfjCVocU","paRequest":"eNpVUctuwjAQ\\/BXaD2Dt4JCHFkspqVQOBChwriJnBanIAyepoF9fG5LS+jQz612PZ3F31ETxllSnSeKSmiY90CjPZs+h709cIZgQU88XXLjPEtfRO50lfpFu8qqUfMzGDsJATbtWx7RsJabq\\/LJIJHcmwp0i9BQL0otY7qhp10URqXOXa9IIdxnLtCC5jz6i+VO4rY2v7HSdr5ZOIBBuNVRVV7b6Kn3BEAaCnT7JY9vWIUDTt41VVSDYAsLD1bqzqDGDLnkmV\\/HhO9lt2DLesORTiSR+ZckmsmeGYG9glrYkHcZ97jB35PCQe6HrI9x0TAvrQO638cgkYRz1Atb2nehOuC38FdBEralUwy8GhnSpq5LMDRPpL0Z4mJ6\\/2WBVa7ISzj1azw+YQZ6N+FawU3ITCg9YcBtjCYJthX570G\\/ZoH\\/b\\/wFlSqpp"}' end @@ -540,6 +1802,55 @@ def failed_authorize_response RESPONSE end + def failed_authorize_3ds2_response + <<-RESPONSE + { + "additionalData": + { + "threeds2.threeDS2Result.dsTransID": "1111-abc-234", + "threeds2.threeDS2Result.eci":"07", + "threeds2.threeDS2Result.threeDSServerTransID":"222-cde-321", + "threeds2.threeDS2Result.transStatusReason":"01", + "threeds2.threeDS2Result.messageVersion":"2.1.0", + "threeds2.threeDS2Result.authenticationValue":"ABCDEFG", + "threeds2.threeDS2Result.transStatus":"N" + }, + "pspReference":"8514775559925128", + "refusalReason":"3D Not Authenticated", + "resultCode":"Refused" + } + 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_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 { @@ -580,6 +1891,51 @@ def failed_refund_response RESPONSE end + def successful_credit_response + <<-RESPONSE + { + "pspReference": "883614109029400G", + "resultCode": "Received" + } + 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 + { + "status":422, + "errorCode":"130", + "message":"Required field 'reference' is not provided.", + "errorType":"validation" + } + RESPONSE + end + def successful_void_response <<-RESPONSE { @@ -589,6 +1945,15 @@ def successful_void_response RESPONSE end + def successful_cancel_or_refund_response + <<-RESPONSE + { + "pspReference":"8614775821628806", + "response":"[cancelOrRefund-received]" + } + RESPONSE + end + def failed_void_response <<-RESPONSE { @@ -600,6 +1965,38 @@ def failed_void_response RESPONSE end + def successful_adjust_response + <<-RESPONSE + { + "pspReference": "8835544088660594", + "response": "[adjustAuthorisation-received]" + } + RESPONSE + end + + def failed_adjust_response + <<-RESPONSE + { + "status":422, + "errorCode":"167", + "message":"Original pspReference required for this operation", + "errorType":"validation" + } + RESPONSE + end + + def successful_synchronous_adjust_response + <<-RESPONSE + {\"additionalData\":{\"authCode\":\"70125\",\"adjustAuthorisationData\":\"BQABAQA9NtGnJAkLXKqW1C+VUeCNMzDf4WwzLFBiuQ8iaA2Yvflz41t0cYxtA7XVzG2pzlJPMnkSK75k3eByNS0\\/m0\\/N2+NnnKv\\/9rYPn8Pjq1jc7CapczdqZNl8P9FwqtIa4Kdeq7ZBNeGalx9oH4reutlFggzWCr+4eYXMRqMgQNI2Bu5XvwkqBbXwbDL05CuNPjjEwO64YrCpVBLrxk4vlW4fvCLFR0u8O68C+Y4swmsPDvGUxWpRgwNVqXsTmvt9z8hlej21BErL8fPEy+fJP4Zab8oyfcLrv9FJkHZq03cyzJpOzqX458Ctn9sIwBawXzNEFN5bCt6eT1rgp0yuHeMGEGwrjNl8rijez7Rd\\/vy1WUYAAMfmZFuJMQ73l1+Hkr0VlHv6crlyP\\/FVTY\\/XIUiGMqa1yM08Zu\\/Gur5N7lU8qnMi2WO9QPyHmmdlfo7+AGsrKrzV4wY\\/wISg0pcv8PypBWVq\\/hYoCqlHsGUuIiyGLIW7A8LtG6\\/JqAA9t\\/0EdnQVz0k06IEEYnBzkQoY8Qv3cVszgPQukGstBraB47gQdVDp9vmuQjMstt8Te56SDRxtfcu0z4nQIURVSkJJNj8RYfwXH9OUbz3Vd2vwoR3lCJFTCKIeW8sidNVB3xAZnddBVQ3P\\/QxPnrrRdCcnoWSGoEOBBIxgF00XwNxJ4P7Xj1bB7oq3M7k99dgPnSdZIjyvG6BWKnCQcGyVRB0yOaYBaOCmN66EgWfXoJR5BA4Jo6gnWnESWV62iUC8OCzmis1VagfaBn0A9vWNcqKFkUr\\/68s3w8ixLJFy+WdpAS\\/flzC3bJbvy9YR9nESKAP40XiNGz9iBROCfPI2bSOvdFf831RdTxWaE+ewAC3w9GsgEKAXxzWsVeSODWRZQA0TEVOfX8SaNVa5w3EXLDsRVnmKgUH8yQnEJQBGhDJXg1sEbowE07CzzdAY5Mc=\",\"refusalReasonRaw\":\"AUTHORISED\"},\"pspReference\":\"8835574118820108\",\"response\":\"Authorised\"} + RESPONSE + end + + def failed_synchronous_adjust_response + <<-RESPONSE + {\"additionalData\":{\"authCode\":\"90745\",\"refusalReasonRaw\":\"2\"},\"pspReference\":\"8835574120337117\",\"response\":\"Refused\"} + RESPONSE + end + def successful_verify_response <<-RESPONSE { @@ -610,6 +2007,94 @@ def successful_verify_response RESPONSE end + def failed_unknown_response + <<~RESPONSE + { + "status": 422, + "errorCode": "0", + "message": "An unknown error occurred", + "errorType": "validation" + } + RESPONSE + end + + def failed_not_allowed_response + <<~RESPONSE + { + "status": 422, + "errorCode": "10", + "message": "You are not allowed to perform this action", + "errorType": "validation" + } + RESPONSE + end + + def failed_invalid_amount_response + <<~RESPONSE + { + "status": 422, + "errorCode": "100", + "message": "There is no amount specified in the request", + "errorType": "validation" + } + RESPONSE + end + + def failed_invalid_card_response + <<~RESPONSE + { + "status": 422, + "errorCode": "101", + "message": "The specified card number is not valid", + "errorType": "validation" + } + RESPONSE + end + + def failed_cvc_validation_response + <<~RESPONSE + { + "status": 422, + "errorCode": "103", + "message": "The length of the CVC code is not correct for the given card number", + "errorType": "validation" + } + RESPONSE + end + + def failed_billing_address_response + <<~RESPONSE + { + "status": 422, + "errorCode": "104", + "message": "There was an error in the specified billing address fields", + "errorType": "validation" + } + RESPONSE + end + + def failed_billing_field_response + <<~RESPONSE + { + "status": 422, + "errorCode": "132", + "message": "Required field 'billingAddress.street' is not provided.", + "errorType": "validation" + } + RESPONSE + end + + def failed_invalid_delivery_field_response + <<~RESPONSE + { + "status": 500, + "errorCode": "702", + "message": "The 'deliveryDate' field is invalid. Invalid date (year)", + "errorType": "validation" + } + RESPONSE + end + def failed_verify_response <<-RESPONSE { @@ -626,6 +2111,12 @@ def failed_authorize_avs_response RESPONSE end + def successful_tokenize_only_store_response + <<-RESPONSE + {"alias":"P481159492341538","aliasType":"Default","pspReference":"881574707964582B","recurringDetailReference":"8415747079647045","result":"Success"} + RESPONSE + end + def successful_store_response <<-RESPONSE {"additionalData":{"recurring.recurringDetailReference":"8315202663743702","recurring.shopperReference":"John Smith"},"pspReference":"8835205392522157","resultCode":"Authorised","authCode":"94571"} @@ -638,6 +2129,18 @@ def failed_store_response RESPONSE end + def successful_unstore_response + <<-RESPONSE + {"response":"[detail-successfully-disabled]"} + RESPONSE + end + + def failed_unstore_response + <<-RESPONSE + {"status":422,"errorCode":"800","message":"Contract not found","errorType":"validation"} + RESPONSE + end + def extended_avs_response <<-RESPONSE {\"additionalData\":{\"cvcResult\":\"1 Matches\",\"cvcResultRaw\":\"Y\",\"avsResult\":\"20 Name, address and zip match\",\"avsResultRaw\":\"M\"}} diff --git a/test/unit/gateways/airwallex_test.rb b/test/unit/gateways/airwallex_test.rb new file mode 100644 index 00000000000..6ad38180082 --- /dev/null +++ b/test/unit/gateways/airwallex_test.rb @@ -0,0 +1,552 @@ +require 'test_helper' + +class AirwallexTest < Test::Unit::TestCase + include CommStub + + def setup + @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 + @declined_amount = 8014 + + @options = { + billing_address: address + } + + @stored_credential_cit_options = { initial_transaction: true, initiator: 'cardholder', reason_type: 'recurring', network_transaction_id: nil } + @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 + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'int_hkdmtp6bpg79nn35u43', response.authorization + assert response.test? + end + + def test_failed_purchase_with_declined_card + @gateway.expects(:ssl_post).times(2).returns(successful_payment_intent_response, failed_purchase_response) + + response = @gateway.purchase(@declined_amount, @declined_card, @options) + assert_failure response + assert_equal '14', response.error_code + assert_equal 'The card issuer declined this transaction. Please refer to the original response code.', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options.merge(auto_capture: false)) + assert_success response + + assert_equal 'int_hkdmtp6bpg79nqimh2z', response.authorization + assert response.test? + end + + def test_failed_authorize_with_declined_card + @gateway.expects(:ssl_post).times(2).returns(successful_payment_intent_response, failed_authorize_response) + + response = @gateway.authorize(@declined_amount, @declined_card, @options.merge(auto_capture: false)) + assert_failure response + assert_equal '14', response.error_code + assert_equal 'The card issuer declined this transaction. Please refer to the original response code.', response.message + end + + def test_successful_authorize_with_return_url + return_url = 'https://example.com' + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(return_url: return_url)) + end.check_request do |endpoint, data, _headers| + assert_match(/\"return_url\":\"https:\/\/example.com\"/, data) unless endpoint == setup_endpoint + end.respond_with(successful_authorize_response) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_authorize_with_3ds_v1_options + @options[:three_d_secure] = { + version: '1', + cavv: 'VGhpcyBpcyBhIHRlc3QgYmFzZTY=', + eci: '02', + xid: 'b2h3aDZrd3BJWXVCWEFMbzJqSGQ=' + } + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/\"version\":\"1.0.0\"/, data) + assert_match(/\"cavv\":\"VGhpcyBpcyBhIHRlc3QgYmFzZTY=\"/, data) + assert_match(/\"eci\":\"02\"/, data) + assert_match(/\"xid\":\"b2h3aDZrd3BJWXVCWEFMbzJqSGQ=\"/, data) + end + end.respond_with(successful_authorize_response) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_authorize_with_3ds_v2_options + @options[:three_d_secure] = { + version: '2.2.0', + cavv: 'MTIzNDU2Nzg5MDA5ODc2NTQzMjE=', + ds_transaction_id: 'f25084f0-5b16-4c0a-ae5d-b24808a95e4b', + eci: '02', + three_ds_server_trans_id: 'df8b9557-e41b-4e17-87e9-2328694a2ea0' + } + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/\"version\":\"2.2.0\"/, data) + assert_match(/\"authentication_value\":\"MTIzNDU2Nzg5MDA5ODc2NTQzMjE=\"/, data) + assert_match(/\"ds_transaction_id\":\"f25084f0-5b16-4c0a-ae5d-b24808a95e4b\"/, data) + assert_match(/\"eci\":\"02\"/, data) + assert_match(/\"three_ds_server_transaction_id\":\"df8b9557-e41b-4e17-87e9-2328694a2ea0\"/, data) + end + end.respond_with(successful_authorize_response) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_purchase_with_3ds_version_formatting + @options[:three_d_secure] = { + version: '2.0', + cavv: 'MTIzNDU2Nzg5MDA5ODc2NTQzMjE=', + ds_transaction_id: 'f25084f0-5b16-4c0a-ae5d-b24808a95e4b', + eci: '02', + three_ds_server_trans_id: 'df8b9557-e41b-4e17-87e9-2328694a2ea0' + } + + formatted_version = format_three_ds_version(@options[:three_d_secure][:version]) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, _headers| + data = JSON.parse(data) + assert_match(data['payment_method_options']['card']['external_three_ds']['version'], formatted_version) unless endpoint == setup_endpoint + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + assert_equal 'AUTHORIZED', response.message + end + + def test_successful_skip_3ds_in_payment_intent + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ skip_3ds: true })) + end.check_request do |endpoint, data, _headers| + data = JSON.parse(data) + assert_match(data['payment_method_options']['card']['risk_control']['three_ds_action'], 'SKIP_3DS') if endpoint == setup_endpoint + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ skip_3ds: 'true' })) + end.check_request do |endpoint, data, _headers| + data = JSON.parse(data) + assert_match(data['payment_method_options']['card']['risk_control']['three_ds_action'], 'SKIP_3DS') if endpoint == setup_endpoint + end.respond_with(successful_purchase_response) + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, @credit_card, @options) + assert_success response + + assert_equal 'int_hkdmtp6bpg79nqimh2z', response.authorization + assert response.test? + end + + def test_failed_capture_with_declined_amount + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@declined_amount, '12345', @options) + assert_failure response + assert_equal 'not_found', response.error_code + assert_equal 'The requested endpoint does not exist [/api/v1/pa/payment_intents/12345/capture]', response.message + end + + def test_capture_without_auth_raises_error + assert_raise ArgumentError do + @gateway.capture(@amount, '', @options) + end + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_success response + + assert_equal 'RECEIVED', response.message + assert response.test? + end + + def test_failed_refund_with_declined_amount + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@declined_amount, '12345', @options) + assert_failure response + assert_equal 'resource_not_found', response.error_code + assert_equal 'The PaymentIntent with ID 12345 cannot be found.', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('int_hkdm49cp4g7d5njedty', @options) + assert_success response + + assert_equal 'CANCELLED', response.message + assert response.test? + end + + def test_void_without_auth_raises_error + assert_raise ArgumentError do + @gateway.void('', @options) + end + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void('12345', @options) + + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(3).returns(successful_authorize_response, successful_void_response) + response = @gateway.verify(credit_card('4111111111111111'), @options) + + assert_success response + assert_equal 'CANCELLED', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).times(2).returns(successful_payment_intent_response, failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_refund_passes_both_ids + request_id = "request_#{(Time.now.to_f.round(2) * 100).to_i}" + merchant_order_id = "order_#{(Time.now.to_f.round(2) * 100).to_i}" + stub_comms do + # merchant_order_id is only passed directly on refunds + @gateway.refund(@amount, 'abc123', @options.merge(request_id: request_id, merchant_order_id: merchant_order_id)) + end.check_request do |_endpoint, data, _headers| + assert_match(/request_/, data) + assert_match(/order_/, data) + end.respond_with(successful_purchase_response, successful_refund_response) + end + + def test_purchase_passes_appropriate_request_id_per_call + request_id = SecureRandom.uuid + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(request_id: request_id)) + end.check_request do |_endpoint, data, _headers| + if data.include?('payment_method') + # check for this on the purchase call + assert_match(/\"request_id\":\"#{request_id}\"/, data) + else + # check for this on the create_payment_intent calls + assert_match(/\"request_id\":\"#{request_id}_setup\"/, data) + end + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_appropriate_merchant_order_id + merchant_order_id = SecureRandom.uuid + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(merchant_order_id: merchant_order_id)) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"merchant_order_id\":\"#{merchant_order_id}\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_currency_code + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) + end.check_request do |_endpoint, data, _headers| + # only look for currency code on the create_payment_intent request + assert_match(/USD/, data) if data.include?('_setup') + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_referrer_data + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + # only look for referrer data on the create_payment_intent request + assert_match(/\"referrer_data\":{\"type\":\"spreedly\"}/, data) if data.include?('_setup') + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_descriptor + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(description: 'a simple test')) + end.check_request do |_endpoint, data, _headers| + assert_match(/a simple test/, data) + end.respond_with(successful_purchase_response) + end + + def test_invalid_login + assert_raise ArgumentError do + AirwallexGateway.new(login: '', password: '') + end + end + + def test_successful_cit_with_stored_credential + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end.check_request do |endpoint, data, _headers| + # This conditional runs assertions after the initial setup call is made + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":null,/, data) + assert_match(/"triggered_by\":\"customer\"/, data) + end + end.respond_with(successful_authorize_response) + assert_success auth + end + + def test_successful_mit_with_recurring_stored_credential + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":null,/, data) + assert_match(/"triggered_by\":\"customer\"/, data) + end + end.respond_with(successful_authorize_response) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_mit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":\"123456789012345\"/, data) + assert_match(/"triggered_by\":\"merchant\"/, data) + end + end.respond_with(successful_purchase_response) + assert_success purchase + end + + def test_successful_mit_with_unscheduled_stored_credential + @stored_credential_cit_options[:reason_type] = 'unscheduled' + @stored_credential_mit_options[:reason_type] = 'unscheduled' + + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"unscheduled\"/, data) + assert_match(/"original_transaction_id\":null,/, data) + assert_match(/"triggered_by\":\"customer\"/, data) + end + end.respond_with(successful_authorize_response) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_mit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"unscheduled\"/, data) + assert_match(/"original_transaction_id\":\"123456789012345\"/, data) + assert_match(/"triggered_by\":\"merchant\"/, data) + end + end.respond_with(successful_purchase_response) + assert_success purchase + end + + def test_successful_mit_with_installment_stored_credential + @stored_credential_cit_options[:reason_type] = 'installment' + @stored_credential_mit_options[:reason_type] = 'installment' + + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":null,/, data) + assert_match(/"triggered_by\":\"customer\"/, data) + end + end.respond_with(successful_authorize_response) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_mit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":\"123456789012345\"/, data) + assert_match(/"triggered_by\":\"merchant\"/, data) + end + end.respond_with(successful_purchase_response) + assert_success purchase + end + + def test_successful_network_transaction_id_override_with_mastercard + mastercard = credit_card('2223 0000 1018 1375', { brand: 'master' }) + + auth = stub_comms do + @gateway.authorize(@amount, mastercard, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":null,/, data) + assert_match(/"triggered_by\":\"customer\"/, data) + end + end.respond_with(successful_authorize_response) + assert_success auth + + add_cit_network_transaction_id_to_stored_credential(auth) + + purchase = stub_comms do + @gateway.purchase(@amount, mastercard, @options.merge!({ stored_credential: @stored_credential_mit_options })) + end.check_request do |endpoint, data, _headers| + unless endpoint == setup_endpoint + assert_match(/"external_recurring_data\"/, data) + assert_match(/"merchant_trigger_reason\":\"scheduled\"/, data) + assert_match(/"original_transaction_id\":\"MCC123ABC0101\"/, data) + assert_match(/"triggered_by\":\"merchant\"/, data) + end + end.respond_with(successful_purchase_response) + assert_success purchase + end + + def test_failed_mit_with_unapproved_visa_ntid + @gateway.expects(:ssl_post).returns(failed_ntid_response) + assert_raise ArgumentError do + @gateway.authorize(@amount, @credit_card, @options.merge!({ stored_credential: @stored_credential_cit_options })) + end + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def format_three_ds_version(version) + version = version.split('.') + + version.push('0') until version.length == 3 + version.join('.') + end + + private + + def pre_scrubbed + <<~TRANSCRIPT + opening connection to api-demo.airwallex.com:443...\nopened\nstarting SSL for api-demo.airwallex.com:443...\nSSL established\n<- \"POST /api/v1/pa/payment_intents/create HTTP/1.1\\r\\nContent-Type: application/json\\r\\nAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNWU1OGQzOS02MWIxLTQ3NzgtYjkzMC1iZWNiYmY3NmMxZjIiLCJzdWIiOiIxZDcyZmI4MC1hMThlLTQyNGEtODFjMC01NGEwZThiZDQzYTQiLCJpYXQiOjE2NDYxMTAxMjMsImV4cCI6MTY0NjEyMjEyMywiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwidHlwZSI6ImNsaWVudCIsImRjIjoiSEsiLCJpc3NkYyI6IlVTIn0.peXgGLfxzJcAxzDpej5fgJqFDEMraF0gh--8s4sUmis\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: api-demo.airwallex.com\\r\\nContent-Length: 101\\r\\n\\r\\n\"\n<- \"{\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"AUD\\\",\\\"request_id\\\":\\\"164611012286\\\",\\\"merchant_order_id\\\":\\\"mid_164611012286\\\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Content-Length: 618\\r\\n\"\n-> \"Date: Tue, 01 Mar 2022 04:48:44 GMT\\r\\n\"\n-> \"x-awx-traceid: ac5e1e9927434d751368bda3d37b89fb\\r\\n\"\n-> \"vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers\\r\\n\"\n-> \"x-envoy-upstream-service-time: 82\\r\\n\"\n-> \"x-envoy-decorator-operation: patokeninterceptor.airwallex.svc.cluster.local:80/*\\r\\n\"\n-> \"Server: APISIX\\r\\n\"\n-> \"X-B3-TraceId: ac5e1e9927434d751368bda3d37b89fb\\r\\n\"\n-> \"Via: 1.1 google\\r\\n\"\n-> \"Alt-Svc: h3=\\\":443\\\"; ma=2592000,h3-29=\\\":443\\\"; ma=2592000\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"\\r\\n\"\nreading 618 bytes...\n-> \"{\\\"id\\\":\\\"int_hkdmnnq47g7hwjukwk7\\\",\\\"request_id\\\":\\\"164611012286\\\",\\\"amount\\\":1,\\\"currency\\\":\\\"AUD\\\",\\\"merchant_order_id\\\":\\\"mid_164611012286\\\",\\\"status\\\":\\\"REQUIRES_PAYMENT_METHOD\\\",\\\"captured_amount\\\":0,\\\"created_at\\\":\\\"2022-03-01T04:48:44+0000\\\",\\\"updated_at\\\":\\\"2022-03-01T04:48:44+0000\\\",\\\"available_payment_method_types\\\":[\\\"wechatpay\\\",\\\"card\\\"],\\\"client_secret\\\":\\\"eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NDYxMTAxMjQsImV4cCI6MTY0NjExMzcyNCwiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwiaW50ZW50X2lkIjoiaW50X2hrZG1ubnE0N2c3aHdqdWt3azciLCJwYWRjIjoiSEsifQ.gjWFNQjss2fW0F_afg_Yx0fku-NhzhgERxT0J0he9wU\\\"}\"\nread 618 bytes\nConn close\nopening connection to api-demo.airwallex.com:443...\nopened\nstarting SSL for api-demo.airwallex.com:443...\nSSL established\n<- \"POST /api/v1/pa/payment_intents/int_hkdmnnq47g7hwjukwk7/confirm HTTP/1.1\\r\\nContent-Type: application/json\\r\\nAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNWU1OGQzOS02MWIxLTQ3NzgtYjkzMC1iZWNiYmY3NmMxZjIiLCJzdWIiOiIxZDcyZmI4MC1hMThlLTQyNGEtODFjMC01NGEwZThiZDQzYTQiLCJpYXQiOjE2NDYxMTAxMjMsImV4cCI6MTY0NjEyMjEyMywiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwidHlwZSI6ImNsaWVudCIsImRjIjoiSEsiLCJpc3NkYyI6IlVTIn0.peXgGLfxzJcAxzDpej5fgJqFDEMraF0gh--8s4sUmis\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: api-demo.airwallex.com\\r\\nContent-Length: 278\\r\\n\\r\\n\"\n<- \"{\\\"request_id\\\":\\\"164611012286_purchase\\\",\\\"return_url\\\":\\\"https://example.com\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"card\\\",\\\"card\\\":{\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"2023\\\",\\\"number\\\":\\\"4000100011112224\\\",\\\"name\\\":\\\"Longbob Longsen\\\",\\\"cvc\\\":\\\"123\\\",\\\"billing\\\":{\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\"}}}}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Date: Tue, 01 Mar 2022 04:48:46 GMT\\r\\n\"\n-> \"Vary: Accept-Encoding\\r\\n\"\n-> \"x-awx-traceid: c6bf7d6b1612a8e32c999d3d6ff379a1\\r\\n\"\n-> \"vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers\\r\\n\"\n-> \"x-envoy-upstream-service-time: 1279\\r\\n\"\n-> \"x-envoy-decorator-operation: patokeninterceptor.airwallex.svc.cluster.local:80/*\\r\\n\"\n-> \"Content-Encoding: gzip\\r\\n\"\n-> \"Server: APISIX\\r\\n\"\n-> \"X-B3-TraceId: c6bf7d6b1612a8e32c999d3d6ff379a1\\r\\n\"\n-> \"Via: 1.1 google\\r\\n\"\n-> \"Alt-Svc: h3=\\\":443\\\"; ma=2592000,h3-29=\\\":443\\\"; ma=2592000\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Transfer-Encoding: chunked\\r\\n\"\n-> \"\\r\\n\"\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x1F\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x8B\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\b\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x03\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xAC\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"S\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"]\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"o\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xDA\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"0\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x14\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xFD\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"264\\r\\n\"\nreading 612 bytes...\n-> \"+S^\\xD7jNpB@\\xDAC\\xD5\\xB2\\xAD\\xD2\\xA6U-L\\xDD^\\\"c\\e\\xE2\\x92\\xD8\\xA9?B\\xA1\\xE2\\xBF\\xF7\\xDE\\x04\\x18\\xD5\\xA6i\\x0F}rr?\\x8E\\xCF9\\xF7\\xFA9R\\\"\\x1AGJ\\xFB\\xA2\\\\\\x89Z\\xEBG:\\\\\\x0E\\xCB\\xF5CX\\xADW\\xC3\\xE8,\\xB2\\xF21H\\xE7\\x8B\\xAE,\\xCEh\\x16\\xC7$N\\x92<+\\x9A`y\\xC9\\x9C\\x84\\\"V\\x9B\\xA0}4\\x8E\\xCF\\\"\\x1E\\xAC\\x95\\x9Ao\\xA0\\xFAbv\\x05\\xB9Zb\\x19\\xE0\\e+\\xA4\\xEDqj%\\x8AS,(\\x13\\xD2q\\xAB\\x1Ao,\\xE4\\x85\\\\\\xB0Py\\b;\\xCF|p\\x10\\xBA\\x9B]^N&W\\x13\\x84\\xE4\\xAC\\xF1\\xC1JQ\\x9C\\xDC[1\\x8F4\\e\\xB6\\xA9%\\\\\\xC6\\xBC\\x97u\\x03\\xA9\\xE7^ \\xFCw\\x02\\xE7s\\xAA\\xAB^\\xE0\\xB6\\xCDMq\\xD4y\\x02u\\xC0\\xA8\\xA5/\\x8D8B\\xD4^\\xFC\\x01\\xF1Xm\\xA1\\xD7o\\x1A\\t\\x05\\x9CY\\xD1\\xB1\\xB3]\\x93|j\\x94\\xDD\\x14\\xB5\\xD1\\xBE\\x84,\\x19An\\x1F\\xDBH\\x862\\x13\\x92\\f \\xA8Y\\x8D\\xED_\\x8D^\\xCE\\xCD\\xFC\\x1D\\x9ENjH\\xCC\\x95\\x868%\\x84\\xC4$B\\x89\\xCESlK\\x12\\x8AY\\xCB4\\xF2j\\x95c\\xF0\\xAB\\x9C\\v\\xE0/G\\x19p\\x057\\x02Agw{F\\xC5\\x81$\\xF8\\xA6\\xD0\\xD9\\x85\\xD2Ki\\e\\xABPut}{\\xDF\\x92\\xF6CZ\\xFE\\xA8\\xB2\\xF5\\xE7\\xA7\\xFC\\xD3\\x83\\xBEw7\\xEB{\\xB5Y\\x7FD\\x84\\x96\\x17\\xBC\\x94|\\x05\\xA5\\rs\\xAE#WU\\x00\\x81J\\x17\\xCA\\x82\\xF5\\xAFe\\xEC\\xF9\\x9EFQ\\xD4nw2\\xD3\\xCB\\xDB\\xC9\\xC5\\xB4\\x9F\\xA8\\x950?\\x18\\xA8\\xEFmI\\xCE\\xC9\\xE0\\x9C\\xC4SB\\xC74\\x1FS\\xFA\\x1E<@\\vB#\\xFE\\xA3n\\xF7{\\x86\\xA0\\xAE;\\xFE\\xBD\\xE4GF\\x17\\xB3\\xE9\\x97\\xEF\\xB7\\xD7\\xBF:R\\x8D5\\xAD\\xC2\\x9D\\xF5\\xE0\\xB4c\\xDC+\\xA3{$\\x9A\\xA4\\xA34\\e&C\\n\\xEF \\xA5Y\\x16S\\x92\\xE6y\\x16\\x0FF\\xF9i\\xA3\\xB1j\\xA94\\xAB\\n+]c\\xB4\\x93\\x87\\xB1tbX\\x80\\xFD\\xB2j\\xCB:\\xE0}f0L\\xB3\\xC1\\xE8oKN\\xF01.\\x82\\x16\\xAFcod]z\\xA8s\\xD2\\xFBJ\\x16\\xADb\\xF8l\\x94]\\xB3\\xAA\\x92O{\\xBA\\xE0\\xA5\\xE2=_@c8|\\xE1\\x0E\\x9F`\\xFB\\xC2\\xB2 \\x8E\\xA9\\xDE2\\xB4\\x15\\xDE\\xEE\\xCD\\x14\\xC1\\xB9\\xB1\\xA82\\xC6\\x19\\xB1\\xD6\\xA11\\xF8\\xD0aQ\\xF7[v\\f\\xFC<\\xAC]\\xEF\\xCB\\xB7nu\\xDEV\\xEC\"\n-> \"\\xEE\\x05\\x00\\x00\\xFF\\xFF\\x03\\x00;d\\xC0x\\xFE\\x04\\x00\\x00\"\nread 612 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"0\\r\\n\"\n-> \"\\r\\n\"\nConn close\n + TRANSCRIPT + end + + def post_scrubbed + <<~TRANSCRIPT + opening connection to api-demo.airwallex.com:443...\nopened\nstarting SSL for api-demo.airwallex.com:443...\nSSL established\n<- \"POST /api/v1/pa/payment_intents/create HTTP/1.1\\r\\nContent-Type: application/json\\r\\nAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNWU1OGQzOS02MWIxLTQ3NzgtYjkzMC1iZWNiYmY3NmMxZjIiLCJzdWIiOiIxZDcyZmI4MC1hMThlLTQyNGEtODFjMC01NGEwZThiZDQzYTQiLCJpYXQiOjE2NDYxMTAxMjMsImV4cCI6MTY0NjEyMjEyMywiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwidHlwZSI6ImNsaWVudCIsImRjIjoiSEsiLCJpc3NkYyI6IlVTIn0.peXgGLfxzJcAxzDpej5fgJqFDEMraF0gh--8s4sUmis\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: api-demo.airwallex.com\\r\\nContent-Length: 101\\r\\n\\r\\n\"\n<- \"{\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"AUD\\\",\\\"request_id\\\":\\\"164611012286\\\",\\\"merchant_order_id\\\":\\\"mid_164611012286\\\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Content-Length: 618\\r\\n\"\n-> \"Date: Tue, 01 Mar 2022 04:48:44 GMT\\r\\n\"\n-> \"x-awx-traceid: ac5e1e9927434d751368bda3d37b89fb\\r\\n\"\n-> \"vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers\\r\\n\"\n-> \"x-envoy-upstream-service-time: 82\\r\\n\"\n-> \"x-envoy-decorator-operation: patokeninterceptor.airwallex.svc.cluster.local:80/*\\r\\n\"\n-> \"Server: APISIX\\r\\n\"\n-> \"X-B3-TraceId: ac5e1e9927434d751368bda3d37b89fb\\r\\n\"\n-> \"Via: 1.1 google\\r\\n\"\n-> \"Alt-Svc: h3=\\\":443\\\"; ma=2592000,h3-29=\\\":443\\\"; ma=2592000\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"\\r\\n\"\nreading 618 bytes...\n-> \"{\\\"id\\\":\\\"int_hkdmnnq47g7hwjukwk7\\\",\\\"request_id\\\":\\\"164611012286\\\",\\\"amount\\\":1,\\\"currency\\\":\\\"AUD\\\",\\\"merchant_order_id\\\":\\\"mid_164611012286\\\",\\\"status\\\":\\\"REQUIRES_PAYMENT_METHOD\\\",\\\"captured_amount\\\":0,\\\"created_at\\\":\\\"2022-03-01T04:48:44+0000\\\",\\\"updated_at\\\":\\\"2022-03-01T04:48:44+0000\\\",\\\"available_payment_method_types\\\":[\\\"wechatpay\\\",\\\"card\\\"],\\\"client_secret\\\":\\\"eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NDYxMTAxMjQsImV4cCI6MTY0NjExMzcyNCwiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwiaW50ZW50X2lkIjoiaW50X2hrZG1ubnE0N2c3aHdqdWt3azciLCJwYWRjIjoiSEsifQ.gjWFNQjss2fW0F_afg_Yx0fku-NhzhgERxT0J0he9wU\\\"}\"\nread 618 bytes\nConn close\nopening connection to api-demo.airwallex.com:443...\nopened\nstarting SSL for api-demo.airwallex.com:443...\nSSL established\n<- \"POST /api/v1/pa/payment_intents/int_hkdmnnq47g7hwjukwk7/confirm HTTP/1.1\\r\\nContent-Type: application/json\\r\\nAuthorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNWU1OGQzOS02MWIxLTQ3NzgtYjkzMC1iZWNiYmY3NmMxZjIiLCJzdWIiOiIxZDcyZmI4MC1hMThlLTQyNGEtODFjMC01NGEwZThiZDQzYTQiLCJpYXQiOjE2NDYxMTAxMjMsImV4cCI6MTY0NjEyMjEyMywiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwidHlwZSI6ImNsaWVudCIsImRjIjoiSEsiLCJpc3NkYyI6IlVTIn0.peXgGLfxzJcAxzDpej5fgJqFDEMraF0gh--8s4sUmis\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: api-demo.airwallex.com\\r\\nContent-Length: 278\\r\\n\\r\\n\"\n<- \"{\\\"request_id\\\":\\\"164611012286_purchase\\\",\\\"return_url\\\":\\\"https://example.com\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"card\\\",\\\"card\\\":{\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"2023\\\",\\\"number\\\":\\\"[REDACTED]\\\",\\\"name\\\":\\\"Longbob Longsen\\\",\\\"cvc\\\":\\\"[REDACTED]\\\",\\\"billing\\\":{\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\"}}}}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Date: Tue, 01 Mar 2022 04:48:46 GMT\\r\\n\"\n-> \"Vary: Accept-Encoding\\r\\n\"\n-> \"x-awx-traceid: c6bf7d6b1612a8e32c999d3d6ff379a1\\r\\n\"\n-> \"vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers\\r\\n\"\n-> \"x-envoy-upstream-service-time: 1279\\r\\n\"\n-> \"x-envoy-decorator-operation: patokeninterceptor.airwallex.svc.cluster.local:80/*\\r\\n\"\n-> \"Content-Encoding: gzip\\r\\n\"\n-> \"Server: APISIX\\r\\n\"\n-> \"X-B3-TraceId: c6bf7d6b1612a8e32c999d3d6ff379a1\\r\\n\"\n-> \"Via: 1.1 google\\r\\n\"\n-> \"Alt-Svc: h3=\\\":443\\\"; ma=2592000,h3-29=\\\":443\\\"; ma=2592000\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Transfer-Encoding: chunked\\r\\n\"\n-> \"\\r\\n\"\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x1F\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x8B\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\b\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x00\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x03\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xAC\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"S\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"]\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"o\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xDA\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"0\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"00000001\\r\\n\"\nreading 1 bytes...\n-> \"\\x14\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"000001\\r\\n\"\nreading 1 bytes...\n-> \"\\xFD\"\nread 1 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"264\\r\\n\"\nreading 612 bytes...\n-> \"+S^\\xD7jNpB@\\xDAC\\xD5\\xB2\\xAD\\xD2\\xA6U-L\\xDD^\\\"c\\e\\xE2\\x92\\xD8\\xA9?B\\xA1\\xE2\\xBF\\xF7\\xDE\\x04\\x18\\xD5\\xA6i\\x0F}rr?\\x8E\\xCF9\\xF7\\xFA9R\\\"\\x1AGJ\\xFB\\xA2\\\\\\x89Z\\xEBG:\\\\\\x0E\\xCB\\xF5CX\\xADW\\xC3\\xE8,\\xB2\\xF21H\\xE7\\x8B\\xAE,\\xCEh\\x16\\xC7$N\\x92<+\\x9A`y\\xC9\\x9C\\x84\\\"V\\x9B\\xA0}4\\x8E\\xCF\\\"\\x1E\\xAC\\x95\\x9Ao\\xA0\\xFAbv\\x05\\xB9Zb\\x19\\xE0\\e+\\xA4\\xEDqj%\\x8AS,(\\x13\\xD2q\\xAB\\x1Ao,\\xE4\\x85\\\\\\xB0Py\\b;\\xCF|p\\x10\\xBA\\x9B]^N&W\\x13\\x84\\xE4\\xAC\\xF1\\xC1JQ\\x9C\\xDC[1\\x8F4\\e\\xB6\\xA9%\\\\\\xC6\\xBC\\x97u\\x03\\xA9\\xE7^ \\xFCw\\x02\\xE7s\\xAA\\xAB^\\xE0\\xB6\\xCDMq\\xD4y\\x02u\\xC0\\xA8\\xA5/\\x8D8B\\xD4^\\xFC\\x01\\xF1Xm\\xA1\\xD7o\\x1A\\t\\x05\\x9CY\\xD1\\xB1\\xB3]\\x93|j\\x94\\xDD\\x14\\xB5\\xD1\\xBE\\x84,\\x19An\\x1F\\xDBH\\x862\\x13\\x92\\f \\xA8Y\\x8D\\xED_\\x8D^\\xCE\\xCD\\xFC\\x1D\\x9ENjH\\xCC\\x95\\x868%\\x84\\xC4$B\\x89\\xCESlK\\x12\\x8AY\\xCB4\\xF2j\\x95c\\xF0\\xAB\\x9C\\v\\xE0/G\\x19p\\x057\\x02Agw{F\\xC5\\x81$\\xF8\\xA6\\xD0\\xD9\\x85\\xD2Ki\\e\\xABPut}{\\xDF\\x92\\xF6CZ\\xFE\\xA8\\xB2\\xF5\\xE7\\xA7\\xFC\\xD3\\x83\\xBEw7\\xEB{\\xB5Y\\x7FD\\x84\\x96\\x17\\xBC\\x94|\\x05\\xA5\\rs\\xAE#WU\\x00\\x81J\\x17\\xCA\\x82\\xF5\\xAFe\\xEC\\xF9\\x9EFQ\\xD4nw2\\xD3\\xCB\\xDB\\xC9\\xC5\\xB4\\x9F\\xA8\\x950?\\x18\\xA8\\xEFmI\\xCE\\xC9\\xE0\\x9C\\xC4SB\\xC74\\x1FS\\xFA\\x1E<@\\vB#\\xFE\\xA3n\\xF7{\\x86\\xA0\\xAE;\\xFE\\xBD\\xE4GF\\x17\\xB3\\xE9\\x97\\xEF\\xB7\\xD7\\xBF:R\\x8D5\\xAD\\xC2\\x9D\\xF5\\xE0\\xB4c\\xDC+\\xA3{$\\x9A\\xA4\\xA34\\e&C\\n\\xEF \\xA5Y\\x16S\\x92\\xE6y\\x16\\x0FF\\xF9i\\xA3\\xB1j\\xA94\\xAB\\n+]c\\xB4\\x93\\x87\\xB1tbX\\x80\\xFD\\xB2j\\xCB:\\xE0}f0L\\xB3\\xC1\\xE8oKN\\xF01.\\x82\\x16\\xAFcod]z\\xA8s\\xD2\\xFBJ\\x16\\xADb\\xF8l\\x94]\\xB3\\xAA\\x92O{\\xBA\\xE0\\xA5\\xE2=_@c8|\\xE1\\x0E\\x9F`\\xFB\\xC2\\xB2 \\x8E\\xA9\\xDE2\\xB4\\x15\\xDE\\xEE\\xCD\\x14\\xC1\\xB9\\xB1\\xA82\\xC6\\x19\\xB1\\xD6\\xA11\\xF8\\xD0aQ\\xF7[v\\f\\xFC<\\xAC]\\xEF\\xCB\\xB7nu\\xDEV\\xEC\"\n-> \"\\xEE\\x05\\x00\\x00\\xFF\\xFF\\x03\\x00;d\\xC0x\\xFE\\x04\\x00\\x00\"\nread 612 bytes\nreading 2 bytes...\n-> \"\\r\\n\"\nread 2 bytes\n-> \"0\\r\\n\"\n-> \"\\r\\n\"\nConn close\n + TRANSCRIPT + end + + def successful_payment_intent_response + %( + {"id":"int_hkdmvldq5g8h5qequao","request_id":"164887285684","amount":1,"currency":"AUD","merchant_order_id":"mid_164887285684","status":"REQUIRES_PAYMENT_METHOD","captured_amount":0,"created_at":"2022-04-02T04:14:17+0000","updated_at":"2022-04-02T04:14:17+0000","available_payment_method_types":["wechatpay","card"],"client_secret":"eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NDg4NzI4NTcsImV4cCI6MTY0ODg3NjQ1NywiYWNjb3VudF9pZCI6IjBhMWE4NzQ3LWM4M2YtNGUwNC05MGQyLTNjZmFjNDkzNTNkYSIsImRhdGFfY2VudGVyX3JlZ2lvbiI6IkhLIiwiaW50ZW50X2lkIjoiaW50X2hrZG12bGRxNWc4aDVxZXF1YW8iLCJwYWRjIjoiSEsiLCJidXNpbmVzc19uYW1lIjoiU3ByZWVkbHkgRGVtbyBBY2NvdW50In0.kcaBXnCAsIinOUw6iJ0tyTOa3Mv03JsuoyLZWWbmNnI"} + ) + end + + def successful_purchase_response + %( + {"id":"int_hkdmtp6bpg79nn35u43","request_id":"164546381445_purchase","amount":1,"currency":"AUD","merchant_order_id":"mid_164546381445","descriptor":"default","status":"SUCCEEDED","captured_amount":1,"latest_payment_attempt":{"id":"att_hkdmb6rw6g79nn3k7ld_n35u43","amount":1,"payment_method":{"id":"mtd_hkdmb6rw6g79nn3k7lc","type":"card","card":{"expiry_month":"09","expiry_year":"2023","name":"Longbob Longsen","bin":"400010","last4":"2224","brand":"visa","issuer_country_code":"US","card_type":"credit","fingerprint":"IRXv0v/5hVl6wGx8FjnXsPwXiyw=","cvc_check":"pass","billing":{"first_name":"Longbob","last_name":"Longsen"}},"status":"CREATED","created_at":"2022-02-21T17:16:56+0000","updated_at":"2022-02-21T17:16:56+0000"},"payment_intent_id":"int_hkdmtp6bpg79nn35u43","status":"AUTHORIZED","provider_transaction_id":"184716548151_265868712794696","provider_original_response_code":"00","authorization_code":"803478","captured_amount":0,"refunded_amount":0,"created_at":"2022-02-21T17:16:56+0000","updated_at":"2022-02-21T17:16:57+0000","settle_via":"airwallex","authentication_data":{"ds_data":{},"fraud_data":{"action":"ACCEPT","score":"1"},"avs_result":"U","cvc_result":"Y","cvc_code":"M"}},"created_at":"2022-02-21T17:16:55+0000","updated_at":"2022-02-21T17:16:57+0000"} + ) + end + + def failed_purchase_response + %({"code":"issuer_declined","message":"The card issuer declined this transaction. Please refer to the original response code.","provider_original_response_code":"14"}) + end + + def successful_authorize_response + %({"id":"int_hkdmtp6bpg79nqimh2z","request_id":"164546402207_purchase","amount":1,"currency":"AUD","merchant_order_id":"mid_164546402207","descriptor":"default","status":"REQUIRES_CAPTURE","captured_amount":0,"latest_payment_attempt":{"id":"att_hkdmtp6bpg79nqj30rk_qimh2z","amount":1,"payment_method":{"id":"mtd_hkdmtp6bpg79nqj30rj","type":"card","card":{"expiry_month":"09","expiry_year":"2023","name":"Longbob Longsen","bin":"400010","last4":"2224","brand":"visa","issuer_country_code":"US","card_type":"credit","fingerprint":"IRXv0v/5hVl6wGx8FjnXsPwXiyw=","cvc_check":"pass","billing":{"first_name":"Longbob","last_name":"Longsen"}},"status":"CREATED","created_at":"2022-02-21T17:20:23+0000","updated_at":"2022-02-21T17:20:23+0000"},"payment_intent_id":"int_hkdmtp6bpg79nqimh2z","status":"AUTHORIZED","provider_transaction_id":"648365447295_129943849335300","provider_original_response_code":"00","authorization_code":"676405","captured_amount":0,"refunded_amount":0,"created_at":"2022-02-21T17:20:23+0000","updated_at":"2022-02-21T17:20:24+0000","settle_via":"airwallex","authentication_data":{"ds_data":{},"fraud_data":{"action":"ACCEPT","score":"1"},"avs_result":"U","cvc_result":"Y","cvc_code":"M"}},"created_at":"2022-02-21T17:20:22+0000","updated_at":"2022-02-21T17:20:24+0000"}) + end + + def failed_authorize_response + %({"code":"issuer_declined","message":"The card issuer declined this transaction. Please refer to the original response code.","provider_original_response_code":"14"}) + end + + def successful_capture_response + %({"id":"int_hkdmtp6bpg79nqimh2z","request_id":"164546402207_purchase_capture","amount":1,"currency":"AUD","merchant_order_id":"mid_164546402207","descriptor":"default","status":"SUCCEEDED","captured_amount":1,"latest_payment_attempt":{"id":"att_hkdmtp6bpg79nqj30rk_qimh2z","amount":1,"payment_method":{"id":"mtd_hkdmtp6bpg79nqj30rj","type":"card","card":{"expiry_month":"09","expiry_year":"2023","name":"Longbob Longsen","bin":"400010","last4":"2224","brand":"visa","issuer_country_code":"US","card_type":"credit","fingerprint":"IRXv0v/5hVl6wGx8FjnXsPwXiyw=","cvc_check":"pass","billing":{"first_name":"Longbob","last_name":"Longsen"}},"status":"CREATED","created_at":"2022-02-21T17:20:23+0000","updated_at":"2022-02-21T17:20:23+0000"},"payment_intent_id":"int_hkdmtp6bpg79nqimh2z","status":"CAPTURE_REQUESTED","provider_transaction_id":"648365447295_129943849335300","provider_original_response_code":"00","authorization_code":"676405","captured_amount":1,"refunded_amount":0,"created_at":"2022-02-21T17:20:23+0000","updated_at":"2022-02-21T17:20:25+0000","settle_via":"airwallex","authentication_data":{"ds_data":{},"fraud_data":{"action":"ACCEPT","score":"1"},"avs_result":"U","cvc_result":"Y","cvc_code":"M"}},"created_at":"2022-02-21T17:20:22+0000","updated_at":"2022-02-21T17:20:25+0000"}) + end + + def failed_capture_response + %({"code":"not_found","message":"The requested endpoint does not exist [/api/v1/pa/payment_intents/12345/capture]"}) + end + + def successful_refund_response + %({"id":"rfd_hkdmb6rw6g79o84j2nr_82v60s","request_id":"164546508364_purchase_refund","payment_intent_id":"int_hkdmb6rw6g79o82v60s","payment_attempt_id":"att_hkdmtp6bpg79o839j89_82v60s","amount":1,"currency":"AUD","status":"RECEIVED","created_at":"2022-02-21T17:38:07+0000","updated_at":"2022-02-21T17:38:07+0000"}) + end + + def failed_refund_response + %({"code":"resource_not_found","message":"The PaymentIntent with ID 12345 cannot be found."}) + end + + def successful_void_response + %({"id":"int_hkdm49cp4g7d5njedty","request_id":"164573811628_purchase_void","amount":1,"currency":"AUD","merchant_order_id":"mid_164573811628","descriptor":"default","status":"CANCELLED","captured_amount":0,"latest_payment_attempt":{"id":"att_hkdm8kd2fg7d5njty2v_njedty","amount":1,"payment_method":{"id":"mtd_hkdm8kd2fg7d5njtxb2","type":"card","card":{"expiry_month":"09","expiry_year":"2023","name":"Longbob Longsen","bin":"400010","last4":"2224","brand":"visa","issuer_country_code":"US","card_type":"credit","fingerprint":"IRXv0v/5hVl6wGx8FjnXsPwXiyw=","cvc_check":"pass","billing":{"first_name":"Longbob","last_name":"Longsen"}},"status":"CREATED","created_at":"2022-02-24T21:28:38+0000","updated_at":"2022-02-24T21:28:38+0000"},"payment_intent_id":"int_hkdm49cp4g7d5njedty","status":"CANCELLED","provider_transaction_id":"157857893548_031662842463902","provider_original_response_code":"00","authorization_code":"775572","captured_amount":0,"refunded_amount":0,"created_at":"2022-02-24T21:28:38+0000","updated_at":"2022-02-24T21:28:40+0000","settle_via":"airwallex","authentication_data":{"ds_data":{},"fraud_data":{"action":"ACCEPT","score":"1"},"avs_result":"U","cvc_result":"Y","cvc_code":"M"}},"created_at":"2022-02-24T21:28:37+0000","updated_at":"2022-02-24T21:28:40+0000","cancelled_at":"2022-02-24T21:28:40+0000"}) + end + + def failed_void_response + %({"code":"not_found","message":"The requested endpoint does not exist [/api/v1/pa/payment_intents/12345/cancel]"}) + end + + def failed_ntid_response + %({"code":"validation_error","source":"external_recurring_data.original_transaction_id","message":"external_recurring_data.original_transaction_id should be 13-15 characters long"}) + end + + def add_cit_network_transaction_id_to_stored_credential(auth) + @stored_credential_mit_options[:network_transaction_id] = auth.params['latest_payment_attempt']['provider_transaction_id'] + end + + def setup_endpoint + 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/create' + end +end diff --git a/test/unit/gateways/alelo_test.rb b/test/unit/gateways/alelo_test.rb new file mode 100644 index 00000000000..86e35e917f3 --- /dev/null +++ b/test/unit/gateways/alelo_test.rb @@ -0,0 +1,380 @@ +require 'test_helper' + +class AleloTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = AleloGateway.new(fixtures(:alelo)) + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: 'f63b625e-331e-490a-b15c-50b4087ca64f', + establishment_code: '000002007690360', + sub_merchant_mcc: '5499', + player_identification: '1', + description: 'Store Purchase', + external_trace_number: '123456', + uuid: '49bc3a5c-2e0f-11ed-a261-0242ac120002' + } + 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 + end + + assert_equal 'Missing required parameter: client_id', error.message + end + + def test_supported_card_types + assert_equal AleloGateway.supported_cardtypes, %i[visa master american_express discover] + end + + def test_supported_countries + assert_equal AleloGateway.supported_countries, ['BR'] + end + + def test_support_scrubbing_flag_enabled + assert @gateway.supports_scrubbing? + end + + def test_sucessful_fetch_access_token_with_proper_client_id_client_secret + @gateway = AleloGateway.new(client_id: 'abc123', client_secret: 'def456') + access_token_expectation! @gateway + + resp = @gateway.send(:fetch_access_token) + assert_kind_of Response, resp + assert_equal 'abc123', resp.message + end + + def test_successful_remote_encryption_key + @gateway = AleloGateway.new(client_id: 'abc123', client_secret: 'def456') + encryption_key_expectation! @gateway + + resp = @gateway.send(:remote_encryption_key, 'abc123') + + assert_kind_of Response, resp + assert_equal 'def456', resp.message + assert_equal 'some-uuid', resp.params['uuid'] + end + + def test_successful_purchase_with_provided_credentials + key, secret_key = test_key true + + @gateway.options[:encryption_key] = key + @gateway.options[:access_token] = 'abc123' + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + decrypted = JOSE::JWE.block_decrypt(secret_key, JSON.parse(data)['token']).first + request = JSON.parse(decrypted, symbolize_names: true) + + assert_equal @options[:order_id], request[:requestId] + assert_equal 1.0, request[:amount] + assert_equal @credit_card.number, request[:cardNumber] + assert_equal @credit_card.name, request[:cardholderName] + assert_equal @credit_card.month, request[:expirationMonth] + assert_equal @credit_card.year - 2000, request[:expirationYear] + assert_equal '3', request[:captureType] + assert_equal @credit_card.verification_value, request[:securityCode] + assert_equal @options[:establishment_code], request[:establishmentCode] + assert_equal @options[:player_identification], request[:playerIdentification] + assert_equal @options[:sub_merchant_mcc], request[:subMerchantCode] + assert_equal @options[:external_trace_number], request[:externalTraceNumber] + end.respond_with(successful_capture_response) + + assert_success response + assert_equal 'f63b625e-331e-490a-b15c-50b4087ca64f', response.authorization + + # Check new values for credentials + assert_nil response.params['access_token'] + assert_nil response.params['encryption_key'] + assert_nil response.params['encryption_uuid'] + end + + def test_successful_purchase_with_no_provided_credentials + key = test_key + @gateway.expects(:ssl_post).times(2).returns({ access_token: 'abc123' }.to_json, successful_capture_response) + @gateway.expects(:ssl_get).returns({ publicKey: key, uuid: 'some-uuid' }.to_json) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_kind_of MultiResponse, response + assert_equal 3, response.responses.size + assert_equal 'abc123', response.responses.first.message + assert_equal key, response.responses[1].message + + # Check new values for credentials + assert_equal 'abc123', response.params['access_token'] + assert_equal key, response.params['encryption_key'] + assert_equal 'some-uuid', response.params['encryption_uuid'] + end + + def test_sucessful_retry_with_expired_encryption_key + key = test_key + @gateway.options[:encryption_key] = key + @gateway.options[:access_token] = 'abc123' + + # Expectations + # ssl_post purchace => raises a 401 + # ssl_get => key + # ssl_post => Final purchase success + @gateway.expects(:ssl_post). + times(2). + raises(ActiveMerchant::ResponseError.new(stub('401 Response', code: '401'))). + then.returns(successful_capture_response) + + @gateway.expects(:ssl_get).returns({ publicKey: key, uuid: 'some-uuid' }.to_json) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_kind_of MultiResponse, response + assert_equal 2, response.responses.size + assert_equal key, response.responses.first.message + end + + def test_sucessful_retry_with_expired_access_token_and_encryption_key + key = test_key + @gateway.options[:encryption_key] = key + @gateway.options[:access_token] = 'abc123' + + # Expectations + # ssl_post purchace => raises a 401 + # ssl_get get key => raise a 401 + # ssl_post => access_token + # ssl_get => key + # ssl_post => Final purchase success + @gateway.expects(:ssl_post). + times(3). + raises(ActiveMerchant::ResponseError.new(stub('401 Response', code: '401'))). + then.returns({ access_token: 'abc123' }.to_json, successful_capture_response) + + @gateway.expects(:ssl_get). + times(2). + raises(ActiveMerchant::ResponseError.new(stub('401 Response', code: '401'))). + then.returns({ publicKey: key, uuid: 'some-uuid' }.to_json) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_kind_of MultiResponse, response + assert_equal 3, response.responses.size + assert_equal 'abc123', response.responses.first.message + assert_equal key, response.responses[1].message + end + + def test_sucessful_retry_with_missing_uuid_404 + key = test_key + @gateway.options[:encryption_key] = key + @gateway.options[:access_token] = 'abc123' + + # Expectations + # ssl_post purchace => raises a 401 + # ssl_get get key => raise a 401 + # ssl_post => access_token + # ssl_get => key + # ssl_post => Final purchase success + @gateway.expects(:ssl_post). + times(3). + raises(ActiveMerchant::ResponseError.new(stub('404 Response', code: '404'))). + then.returns({ access_token: 'abc123' }.to_json, successful_capture_response) + + @gateway.expects(:ssl_get). + times(2). + raises(ActiveMerchant::ResponseError.new(stub('404 Response', code: '404'))). + then.returns({ publicKey: key, uuid: 'some-uuid' }.to_json) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_kind_of MultiResponse, response + assert_equal 3, response.responses.size + assert_equal 'abc123', response.responses.first.message + assert_equal key, response.responses[1].message + end + + def test_detecting_successfull_response_from_capture + assert @gateway.send :success_from, 'capture/transaction', { status: 'CONFIRMADA' } + end + + def test_detecting_successfull_response_from_refund + assert @gateway.send :success_from, 'capture/transaction/refund', { status: 'ESTORNADA' } + end + + def test_get_response_message_from_messages_key + message = @gateway.send :message_from, { messages: 'hello', messageUser: 'world' } + assert_equal 'hello', message + end + + def test_get_response_message_from_message_user + message = @gateway.send :message_from, { messages: nil, messageUser: 'world' } + assert_equal 'world', message + end + + def test_url_generation_from_action + action = 'test' + assert_equal @gateway.test_url + action, @gateway.send(:url, action) + end + + def test_request_headers_building + gateway = AleloGateway.new(client_id: 'abc123', client_secret: 'def456') + headers = gateway.send :request_headers, 'access_123' + + assert_equal 'application/json', headers['Accept'] + assert_equal 'abc123', headers['X-IBM-Client-Id'] + assert_equal 'def456', headers['X-IBM-Client-Secret'] + assert_equal 'Bearer access_123', headers['Authorization'] + end + + def test_scrub + assert @gateway.supports_scrubbing? + + pre_scrubbed = File.read('test/unit/transcripts/alelo_purchase') + post_scrubbed = File.read('test/unit/transcripts/alelo_purchase_scrubbed') + + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_success_payload_encryption + @gateway.options[:access_token] = 'abc123' + @gateway.options[:encryption_key] = test_key + @gateway.options[:encryption_uuid] = SecureRandom.uuid + + credentials = @gateway.send(:ensure_credentials) + jwe, = @gateway.send(:encrypt_payload, { hello: 'world' }, credentials, {}) + + refute_nil JSON.parse(jwe)['token'] + refute_nil JSON.parse(jwe)['uuid'] + end + + def test_ensure_encryption_format + key, secret_key = test_key true + body = { hello: 'world' } + @gateway.options[:access_token] = 'abc123' + @gateway.options[:encryption_key] = key + credentials = @gateway.send(:ensure_credentials) + + jwe, _cred = @gateway.send(:encrypt_payload, body, credentials, {}) + parsed_jwe = JSON.parse(jwe, symbolize_names: true) + refute_nil parsed_jwe[:token] + + decrypted = JOSE::JWE.block_decrypt(secret_key, parsed_jwe[:token]).first + assert_equal body.to_json, decrypted + end + + def test_ensure_credentials_with_provided_access_token_and_key + @gateway.options[:access_token] = 'abc123' + @gateway.options[:encryption_key] = 'def456' + + credentials = @gateway.send :ensure_credentials + + assert_equal @gateway.options[:access_token], credentials[:access_token] + assert_equal @gateway.options[:encryption_key], credentials[:key] + assert_nil credentials[:multiresp] + end + + def test_ensure_credentials_with_access_token_and_not_key + encryption_key_expectation! @gateway + + @gateway.options[:access_token] = 'abc123' + credentials = @gateway.send :ensure_credentials + + assert_equal @gateway.options[:access_token], credentials[:access_token] + assert_equal 'def456', credentials[:key] + refute_nil credentials[:multiresp] + assert_equal 1, credentials[:multiresp].responses.size + end + + def test_ensure_credentials_with_key_but_not_access_token + @gateway = AleloGateway.new(client_id: 'abc123', client_secret: 'def456') + @gateway.options[:encryption_key] = 'xx_no_key_xx' + + access_token_expectation! @gateway + encryption_key_expectation! @gateway + + credentials = @gateway.send :ensure_credentials + + assert_equal 'abc123', credentials[:access_token] + assert_equal 'def456', credentials[:key] + refute_nil credentials[:multiresp] + assert_equal 2, credentials[:multiresp].responses.size + end + + def test_credit_card_year_should_be_an_integer + post = {} + @gateway.send :add_payment, post, @credit_card + + assert_kind_of Integer, post[:expirationYear] + assert_equal 2, post[:expirationYear].digits.size + end + + private + + def test_key(with_sk = false) + jwk_rsa_sk = JOSE::JWK.generate_key([:rsa, 4096]) + jwk_rsa_pk = JOSE::JWK.to_public(jwk_rsa_sk) + + pem = jwk_rsa_pk.to_pem.split("\n") + pem.pop + pem.shift + + return pem.join unless with_sk + + return pem.join, jwk_rsa_sk + end + + def access_token_expectation!(gateway, access_token = 'abc123') + url = "#{@gateway.class.test_url}captura-oauth-provider/oauth/token" + params = [ + 'grant_type=client_credentials', + 'client_id=abc123', + 'client_secret=def456', + 'scope=%2Fcapture' + ].join('&') + + headers = { + 'Accept' => 'application/json', + 'Content-Type' => 'application/x-www-form-urlencoded' + } + + gateway.expects(:ssl_post).with(url, params, headers).returns({ access_token: access_token }.to_json) + end + + def encryption_key_expectation!(gateway, public_key = 'def456') + url = "#{@gateway.class.test_url}capture/key" + headers = { + 'Accept' => 'application/json', + 'X-IBM-Client-Id' => gateway.options[:client_id], + 'X-IBM-Client-Secret' => gateway.options[:client_secret], + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer abc123' + } + + @gateway.expects(:ssl_get).with(url, headers).returns({ publicKey: public_key, uuid: 'some-uuid' }.to_json) + end + + def successful_capture_response + { + requestId: 'f63b625e-331e-490a-b15c-50b4087ca64f', + dateTime: '211105181958', + returnCode: '00', + nsu: '00123', + amount: '0.10', + maskedCard: '506758******7013', + authorizationCode: '735977', + messages: 'Transação Confirmada com sucesso.', + status: 'CONFIRMADA', + playerIdentification: '4', + captureType: '3' + }.to_json + end +end diff --git a/test/unit/gateways/allied_wallet_test.rb b/test/unit/gateways/allied_wallet_test.rb index fb291c7c4b4..9da275b48bd 100644 --- a/test/unit/gateways/allied_wallet_test.rb +++ b/test/unit/gateways/allied_wallet_test.rb @@ -78,7 +78,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/123456/, data) end.respond_with(successful_void_response) @@ -88,7 +88,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) end.respond_with(failed_void_response) @@ -105,7 +105,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/123456/, data) end.respond_with(successful_refund_response) diff --git a/test/unit/gateways/authorize_net_arb_test.rb b/test/unit/gateways/authorize_net_arb_test.rb index 5c18ff468ff..dfcc2d4c9ae 100644 --- a/test/unit/gateways/authorize_net_arb_test.rb +++ b/test/unit/gateways/authorize_net_arb_test.rb @@ -6,8 +6,8 @@ class AuthorizeNetArbTest < Test::Unit::TestCase def setup ActiveMerchant.expects(:deprecated).with('ARB functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it.') @gateway = AuthorizeNetArbGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @amount = 100 @credit_card = credit_card @@ -18,15 +18,17 @@ def setup def test_successful_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) - response = @gateway.recurring(@amount, @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :interval => { - :length => 10, - :unit => :days + response = @gateway.recurring( + @amount, + @credit_card, + billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'), + interval: { + length: 10, + unit: :days }, - :duration => { - :start_date => Time.now.strftime('%Y-%m-%d'), - :occurrences => 30 + duration: { + start_date: Time.now.strftime('%Y-%m-%d'), + occurrences: 30 } ) @@ -39,7 +41,7 @@ def test_successful_recurring def test_successful_update_recurring @gateway.expects(:ssl_post).returns(successful_update_recurring_response) - response = @gateway.update_recurring(:subscription_id => @subscription_id, :amount => @amount * 2) + response = @gateway.update_recurring(subscription_id: @subscription_id, amount: @amount * 2) assert_instance_of Response, response assert response.success? @@ -69,73 +71,73 @@ def test_successful_status_recurring end def test_expdate_formatting - assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) - assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013')) + assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', month: '9', year: '2009')) + assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', month: '11', year: '2013')) end private def successful_recurring_response - <<-XML - - Sample - - Ok - - I00001 - Successful. - - - #{@subscription_id} - + <<~XML + + Sample + + Ok + + I00001 + Successful. + + + #{@subscription_id} + XML end def successful_update_recurring_response - <<-XML - - Sample - - Ok - - I00001 - Successful. - - - #{@subscription_id} - + <<~XML + + Sample + + Ok + + I00001 + Successful. + + + #{@subscription_id} + XML end def successful_cancel_recurring_response - <<-XML - - Sample - - Ok - - I00001 - Successful. - - - #{@subscription_id} - + <<~XML + + Sample + + Ok + + I00001 + Successful. + + + #{@subscription_id} + XML end def successful_status_recurring_response - <<-XML - - Sample - - Ok - - I00001 - Successful. - - - #{@subscription_status} - + <<~XML + + Sample + + Ok + + I00001 + Successful. + + + #{@subscription_status} + XML end end diff --git a/test/unit/gateways/authorize_net_cim_test.rb b/test/unit/gateways/authorize_net_cim_test.rb index 1d9bd462192..9ce7d2288d6 100644 --- a/test/unit/gateways/authorize_net_cim_test.rb +++ b/test/unit/gateways/authorize_net_cim_test.rb @@ -5,8 +5,8 @@ class AuthorizeNetCimTest < Test::Unit::TestCase def setup @gateway = AuthorizeNetCimGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @amount = 100 @credit_card = credit_card @@ -15,40 +15,40 @@ def setup @customer_payment_profile_id = '7813' @customer_address_id = '4321' @payment = { - :credit_card => @credit_card + credit_card: @credit_card } @profile = { - :merchant_customer_id => 'Up to 20 chars', # Optional - :description => 'Up to 255 Characters', # Optional - :email => 'Up to 255 Characters', # Optional - :payment_profiles => { # Optional - :customer_type => 'individual or business', # Optional - :bill_to => @address, - :payment => @payment + merchant_customer_id: 'Up to 20 chars', # Optional + description: 'Up to 255 Characters', # Optional + email: 'Up to 255 Characters', # Optional + payment_profiles: { # Optional + customer_type: 'individual or business', # Optional + bill_to: @address, + payment: @payment }, - :ship_to_list => { - :first_name => 'John', - :last_name => 'Doe', - :company => 'Widgets, Inc', - :address1 => '1234 Fake Street', - :city => 'Anytown', - :state => 'MD', - :zip => '12345', - :country => 'USA', - :phone_number => '(123)123-1234', # Optional - Up to 25 digits (no letters) - :fax_number => '(123)123-1234' # Optional - Up to 25 digits (no letters) + ship_to_list: { + first_name: 'John', + last_name: 'Doe', + company: 'Widgets, Inc', + address1: '1234 Fake Street', + city: 'Anytown', + state: 'MD', + zip: '12345', + country: 'USA', + phone_number: '(123)123-1234', # Optional - Up to 25 digits (no letters) + fax_number: '(123)123-1234' # Optional - Up to 25 digits (no letters) } } @options = { - :ref_id => '1234', # Optional - :profile => @profile + ref_id: '1234', # Optional + profile: @profile } end def test_expdate_formatting - assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) - assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013')) - assert_equal 'XXXX', @gateway.send(:expdate, credit_card('XXXX1234', :month => nil, :year => nil)) + assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', month: '9', year: '2009')) + assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', month: '11', year: '2013')) + assert_equal 'XXXX', @gateway.send(:expdate, credit_card('XXXX1234', month: nil, year: nil)) end def test_should_create_customer_profile_request @@ -65,13 +65,13 @@ def test_should_create_customer_payment_profile_request @gateway.expects(:ssl_post).returns(successful_create_customer_payment_profile_response) assert response = @gateway.create_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_type => 'individual', - :bill_to => @address, - :payment => @payment + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_type: 'individual', + bill_to: @address, + payment: @payment }, - :validation_mode => :test + validation_mode: :test ) assert_instance_of Response, response assert_success response @@ -83,17 +83,17 @@ def test_should_create_customer_shipping_address_request @gateway.expects(:ssl_post).returns(successful_create_customer_shipping_address_response) assert response = @gateway.create_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :address => { - :first_name => 'John', - :last_name => 'Doe', - :company => 'Widgets, Inc', - :address1 => '1234 Fake Street', - :city => 'Anytown', - :state => 'MD', - :country => 'USA', - :phone_number => '(123)123-1234', - :fax_number => '(123)123-1234' + customer_profile_id: @customer_profile_id, + address: { + first_name: 'John', + last_name: 'Doe', + company: 'Widgets, Inc', + address1: '1234 Fake Street', + city: 'Anytown', + state: 'MD', + country: 'USA', + phone_number: '(123)123-1234', + fax_number: '(123)123-1234' } ) assert_instance_of Response, response @@ -106,11 +106,11 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_prior_aut @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_only)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :amount => @amount + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_only, + amount: @amount } ) assert_instance_of Response, response @@ -163,12 +163,12 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_prior_aut @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:prior_auth_capture)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :prior_auth_capture, - :amount => @amount, - :trans_id => trans_id + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :prior_auth_capture, + amount: @amount, + trans_id: trans_id } ) assert_instance_of Response, response @@ -187,11 +187,11 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_capture_o @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_only)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :amount => @amount + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_only, + amount: @amount } ) assert_instance_of Response, response @@ -204,12 +204,12 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_capture_o @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:capture_only)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :capture_only, - :amount => @amount, - :approval_code => approval_code + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :capture_only, + amount: @amount, + approval_code: approval_code } ) assert_instance_of Response, response @@ -221,17 +221,17 @@ def test_should_create_customer_profile_transaction_auth_capture_request @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_capture)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => '1234', - :description => 'Test Order Description', - :purchase_order_number => '4321' + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: '1234', + description: 'Test Order Description', + purchase_order_number: '4321' }, - :amount => @amount, - :card_code => '123' + amount: @amount, + card_code: '123' } ) assert_instance_of Response, response @@ -245,16 +245,16 @@ def test_should_create_customer_profile_transaction_auth_capture_request_for_ver @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_capture_version_3_1)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => '1234', - :description => 'Test Order Description', - :purchase_order_number => '4321' + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: '1234', + description: 'Test Order Description', + purchase_order_number: '4321' }, - :amount => @amount + amount: @amount } ) assert_instance_of Response, response @@ -311,7 +311,7 @@ def test_should_create_customer_profile_transaction_auth_capture_request_for_ver def test_should_delete_customer_profile_request @gateway.expects(:ssl_post).returns(successful_delete_customer_profile_response) - assert response = @gateway.delete_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.delete_customer_profile(customer_profile_id: @customer_profile_id) assert_instance_of Response, response assert_success response assert_equal @customer_profile_id, response.authorization @@ -320,7 +320,7 @@ def test_should_delete_customer_profile_request def test_should_delete_customer_payment_profile_request @gateway.expects(:ssl_post).returns(successful_delete_customer_payment_profile_response) - assert response = @gateway.delete_customer_payment_profile(:customer_profile_id => @customer_profile_id, :customer_payment_profile_id => @customer_payment_profile_id) + assert response = @gateway.delete_customer_payment_profile(customer_profile_id: @customer_profile_id, customer_payment_profile_id: @customer_payment_profile_id) assert_instance_of Response, response assert_success response assert_nil response.authorization @@ -329,7 +329,7 @@ def test_should_delete_customer_payment_profile_request def test_should_delete_customer_shipping_address_request @gateway.expects(:ssl_post).returns(successful_delete_customer_shipping_address_response) - assert response = @gateway.delete_customer_shipping_address(:customer_profile_id => @customer_profile_id, :customer_address_id => @customer_address_id) + assert response = @gateway.delete_customer_shipping_address(customer_profile_id: @customer_profile_id, customer_address_id: @customer_address_id) assert_instance_of Response, response assert_success response assert_nil response.authorization @@ -338,7 +338,7 @@ def test_should_delete_customer_shipping_address_request def test_should_get_customer_profile_request @gateway.expects(:ssl_post).returns(successful_get_customer_profile_response) - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_instance_of Response, response assert_success response assert_equal @customer_profile_id, response.authorization @@ -355,7 +355,7 @@ def test_should_get_customer_profile_ids_request def test_should_get_customer_profile_request_with_multiple_payment_profiles @gateway.expects(:ssl_post).returns(successful_get_customer_profile_response_with_multiple_payment_profiles) - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert response = @gateway.get_customer_profile(customer_profile_id: @customer_profile_id) assert_instance_of Response, response assert_success response @@ -367,23 +367,25 @@ def test_should_get_customer_payment_profile_request @gateway.expects(:ssl_post).returns(successful_get_customer_payment_profile_response) assert response = @gateway.get_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :unmask_expiration_date => true + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + unmask_expiration_date: true, + include_issuer_info: true ) assert_instance_of Response, response assert_success response assert_nil response.authorization assert_equal @customer_payment_profile_id, response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert_equal formatted_expiration_date(@credit_card), response.params['profile']['payment_profiles']['payment']['credit_card']['expiration_date'] + assert_equal @credit_card.first_digits, response.params['profile']['payment_profiles']['payment']['credit_card']['issuer_number'] end def test_should_get_customer_shipping_address_request @gateway.expects(:ssl_post).returns(successful_get_customer_shipping_address_response) assert response = @gateway.get_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :customer_address_id => @customer_address_id + customer_profile_id: @customer_profile_id, + customer_address_id: @customer_address_id ) assert_instance_of Response, response assert_success response @@ -394,9 +396,9 @@ def test_should_update_customer_profile_request @gateway.expects(:ssl_post).returns(successful_update_customer_profile_response) assert response = @gateway.update_customer_profile( - :profile => { - :customer_profile_id => @customer_profile_id, - :email => 'new email address' + profile: { + customer_profile_id: @customer_profile_id, + email: 'new email address' } ) assert_instance_of Response, response @@ -408,10 +410,10 @@ def test_should_update_customer_payment_profile_request @gateway.expects(:ssl_post).returns(successful_update_customer_payment_profile_response) assert response = @gateway.update_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_payment_profile_id => @customer_payment_profile_id, - :customer_type => 'business' + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_payment_profile_id: @customer_payment_profile_id, + customer_type: 'business' } ) assert_instance_of Response, response @@ -420,21 +422,21 @@ def test_should_update_customer_payment_profile_request end def test_should_update_customer_payment_profile_request_with_last_four_digits - last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits + last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(number: '4242') # Credit card with only last four digits response = stub_comms do @gateway.update_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :payment_profile => { - :customer_payment_profile_id => @customer_payment_profile_id, - :bill_to => address(:address1 => '345 Avenue B', - :address2 => 'Apt 101'), - :payment => { - :credit_card => last_four_credit_card + customer_profile_id: @customer_profile_id, + payment_profile: { + customer_payment_profile_id: @customer_payment_profile_id, + bill_to: address(address1: '345 Avenue B', + address2: 'Apt 101'), + payment: { + credit_card: last_four_credit_card } } ) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{XXXX4242}, data end.respond_with(successful_update_customer_payment_profile_response) @@ -447,10 +449,10 @@ def test_should_update_customer_shipping_address_request @gateway.expects(:ssl_post).returns(successful_update_customer_shipping_address_response) assert response = @gateway.update_customer_shipping_address( - :customer_profile_id => @customer_profile_id, - :address => { - :customer_address_id => @customer_address_id, - :city => 'New City' + customer_profile_id: @customer_profile_id, + address: { + customer_address_id: @customer_address_id, + city: 'New City' } ) assert_instance_of Response, response @@ -462,10 +464,10 @@ def test_should_validate_customer_payment_profile_request @gateway.expects(:ssl_post).returns(successful_validate_customer_payment_profile_response) assert response = @gateway.validate_customer_payment_profile( - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :customer_address_id => @customer_address_id, - :validation_mode => :live + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + customer_address_id: @customer_address_id, + validation_mode: :live ) assert_instance_of Response, response assert_success response @@ -478,9 +480,9 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_void_r @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:void)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :void, - :trans_id => response.params['direct_response']['transaction_id'] + transaction: { + type: :void, + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -495,12 +497,12 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund @gateway.expects(:ssl_post).returns(unsuccessful_create_customer_profile_transaction_response(:refund)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :trans_id => response.params['direct_response']['transaction_id'] + transaction: { + type: :refund, + amount: 1, + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -519,13 +521,13 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund @gateway.expects(:ssl_post).returns(unsuccessful_create_customer_profile_transaction_response(:refund)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, + transaction: { + type: :refund, + amount: 1, - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :trans_id => response.params['direct_response']['transaction_id'] + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + trans_id: response.params['direct_response']['transaction_id'] } ) assert_instance_of Response, response @@ -559,9 +561,9 @@ def test_should_create_customer_profile_transaction_for_void_request @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:void)) assert response = @gateway.create_customer_profile_transaction_for_void( - :transaction => { - :trans_id => 1 - } + transaction: { + trans_id: 1 + } ) assert_instance_of Response, response assert_success response @@ -573,11 +575,11 @@ def test_should_create_customer_profile_transaction_for_refund_request @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:refund)) assert response = @gateway.create_customer_profile_transaction_for_refund( - :transaction => { - :trans_id => 1, - :amount => '1.00', - :credit_card_number_masked => 'XXXX1234' - } + transaction: { + trans_id: 1, + amount: '1.00', + credit_card_number_masked: 'XXXX1234' + } ) assert_instance_of Response, response assert_success response @@ -588,21 +590,21 @@ def test_should_create_customer_profile_transaction_for_refund_request def test_should_create_customer_profile_transaction_passing_recurring_flag response = stub_comms do @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => '1234', - :description => 'Test Order Description', - :purchase_order_number => '4321' + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: '1234', + description: 'Test Order Description', + purchase_order_number: '4321' }, - :amount => @amount, - :card_code => '123', - :recurring_billing => true + amount: @amount, + card_code: '123', + recurring_billing: true } ) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{true}, data end.respond_with(successful_create_customer_profile_transaction_response(:auth_capture)) @@ -623,13 +625,13 @@ def test_full_or_masked_card_number def test_multiple_errors_when_creating_customer_profile @gateway.expects(:ssl_post).returns(unsuccessful_create_customer_profile_transaction_response_with_multiple_errors(:refund)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :type => :refund, - :amount => 1, + transaction: { + type: :refund, + amount: 1, - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :trans_id => 1 + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + trans_id: 1 } ) assert_equal 'The transaction was unsuccessful.', response.message @@ -642,11 +644,11 @@ def get_auth_only_response @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_only)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :amount => @amount + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_only, + amount: @amount } ) assert_instance_of Response, response @@ -662,16 +664,16 @@ def get_and_validate_auth_capture_response @gateway.expects(:ssl_post).returns(successful_create_customer_profile_transaction_response(:auth_capture)) assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_capture, - :order => { - :invoice_number => '1234', - :description => 'Test Order Description', - :purchase_order_number => '4321' + transaction: { + customer_profile_id: @customer_profile_id, + customer_payment_profile_id: @customer_payment_profile_id, + type: :auth_capture, + order: { + invoice_number: '1234', + description: 'Test Order Description', + purchase_order_number: '4321' }, - :amount => @amount + amount: @amount } ) assert_instance_of Response, response @@ -823,6 +825,7 @@ def successful_get_customer_profile_response #{@credit_card.number} #{@gateway.send(:expdate, @credit_card)} + #{@credit_card.first_digits} @@ -874,6 +877,7 @@ def successful_get_customer_profile_response_with_multiple_payment_profiles #{@credit_card.number} #{@gateway.send(:expdate, @credit_card)} + #{@credit_card.first_digits} @@ -884,6 +888,7 @@ def successful_get_customer_profile_response_with_multiple_payment_profiles XXXX1234 XXXX + 424242 @@ -914,6 +919,7 @@ def successful_get_customer_payment_profile_response #{@credit_card.number} #{@gateway.send(:expdate, @credit_card)} + #{@credit_card.first_digits} @@ -1003,16 +1009,16 @@ def successful_update_customer_shipping_address_response end SUCCESSFUL_DIRECT_RESPONSE = { - :auth_only => '1,1,1,This transaction has been approved.,Gw4NGI,Y,508223659,,,100.00,CC,auth_only,Up to 20 chars,,,,,,,,,,,Up to 255 Characters,,,,,,,,,,,,,,6E5334C13C78EA078173565FD67318E4,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :capture_only => '1,1,1,This transaction has been approved.,,Y,508223660,,,100.00,CC,capture_only,Up to 20 chars,,,,,,,,,,,Up to 255 Characters,,,,,,,,,,,,,,6E5334C13C78EA078173565FD67318E4,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :auth_capture => '1,1,1,This transaction has been approved.,d1GENk,Y,508223661,32968c18334f16525227,Store purchase,1.00,CC,auth_capture,,Longbob,Longsen,,,,,,,,,,,,,,,,,,,,,,,269862C030129C1173727CC10B1935ED,M,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :void => '1,1,1,This transaction has been approved.,nnCMEx,P,2149222068,1245879759,,0.00,CC,void,1245879759,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,F240D65BB27ADCB8C80410B92342B22C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :refund => '1,1,1,This transaction has been approved.,nnCMEx,P,2149222068,1245879759,,0.00,CC,refund,1245879759,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,F240D65BB27ADCB8C80410B92342B22C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :prior_auth_capture => '1,1,1,This transaction has been approved.,VR0lrD,P,2149227870,1245958544,,1.00,CC,prior_auth_capture,1245958544,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,0B8BFE0A0DE6FDB69740ED20F79D04B0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', - :auth_capture_version_3_1 => '1,1,1,This transaction has been approved.,CSYM0K,Y,2163585627,1234,Test Order Description,100.00,CC,auth_capture,Up to 20 chars,,,Widgets Inc,1234 My Street,Ottawa,ON,K1C2N6,CA,,,Up to 255 Characters,,,,,,,,,,,,,4321,02DFBD7934AD862AB16688D44F045D31,,2,,,,,,,,,,,XXXX4242,Visa,,,,,,,,,,,,,,,,' + auth_only: '1,1,1,This transaction has been approved.,Gw4NGI,Y,508223659,,,100.00,CC,auth_only,Up to 20 chars,,,,,,,,,,,Up to 255 Characters,,,,,,,,,,,,,,6E5334C13C78EA078173565FD67318E4,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + capture_only: '1,1,1,This transaction has been approved.,,Y,508223660,,,100.00,CC,capture_only,Up to 20 chars,,,,,,,,,,,Up to 255 Characters,,,,,,,,,,,,,,6E5334C13C78EA078173565FD67318E4,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + auth_capture: '1,1,1,This transaction has been approved.,d1GENk,Y,508223661,32968c18334f16525227,Store purchase,1.00,CC,auth_capture,,Longbob,Longsen,,,,,,,,,,,,,,,,,,,,,,,269862C030129C1173727CC10B1935ED,M,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + void: '1,1,1,This transaction has been approved.,nnCMEx,P,2149222068,1245879759,,0.00,CC,void,1245879759,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,F240D65BB27ADCB8C80410B92342B22C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + refund: '1,1,1,This transaction has been approved.,nnCMEx,P,2149222068,1245879759,,0.00,CC,refund,1245879759,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,F240D65BB27ADCB8C80410B92342B22C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + prior_auth_capture: '1,1,1,This transaction has been approved.,VR0lrD,P,2149227870,1245958544,,1.00,CC,prior_auth_capture,1245958544,,,,,,,K1C2N6,,,,,,,,,,,,,,,,,,0B8BFE0A0DE6FDB69740ED20F79D04B0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,', + auth_capture_version_3_1: '1,1,1,This transaction has been approved.,CSYM0K,Y,2163585627,1234,Test Order Description,100.00,CC,auth_capture,Up to 20 chars,,,Widgets Inc,1234 My Street,Ottawa,ON,K1C2N6,CA,,,Up to 255 Characters,,,,,,,,,,,,,4321,02DFBD7934AD862AB16688D44F045D31,,2,,,,,,,,,,,XXXX4242,Visa,,,,,,,,,,,,,,,,' } UNSUCCESSUL_DIRECT_RESPONSE = { - :refund => '3,2,54,The referenced transaction does not meet the criteria for issuing a credit.,,P,0,,,1.00,CC,credit,1245952682,,,Widgets Inc,1245952682 My Street,Ottawa,ON,K1C2N6,CA,,,bob1245952682@email.com,,,,,,,,,,,,,,207BCBBF78E85CF174C87AE286B472D2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,447250,406104' + refund: '3,2,54,The referenced transaction does not meet the criteria for issuing a credit.,,P,0,,,1.00,CC,credit,1245952682,,,Widgets Inc,1245952682 My Street,Ottawa,ON,K1C2N6,CA,,,bob1245952682@email.com,,,,,,,,,,,,,,207BCBBF78E85CF174C87AE286B472D2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,447250,406104' } def successful_create_customer_profile_transaction_response(transaction_type) diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb index 2cdfde5abcd..deaa457f8ce 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 = { @@ -76,7 +80,7 @@ def test_add_swipe_data_with_bad_data @credit_card.track_data = BAD_TRACK_DATA stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_nil doc.at_xpath('//track1') assert_nil doc.at_xpath('//track2') @@ -89,7 +93,7 @@ def test_add_swipe_data_with_track_1 @credit_card.track_data = TRACK1_DATA stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal '%B378282246310005^LONGSON/LONGBOB^1705101130504392?', doc.at_xpath('//track1').content assert_nil doc.at_xpath('//track2') @@ -102,7 +106,7 @@ def test_add_swipe_data_with_track_2 @credit_card.track_data = TRACK2_DATA stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_nil doc.at_xpath('//track1') assert_equal ';4111111111111111=1803101000020000831?', doc.at_xpath('//track2').content @@ -116,7 +120,7 @@ def test_retail_market_type_device_type_included_in_swipe_transactions_with_vali @credit_card.track_data = track stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_nil doc.at_xpath('//retail') end @@ -127,7 +131,7 @@ def test_retail_market_type_device_type_included_in_swipe_transactions_with_vali @credit_card.track_data = track stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//retail') assert_equal '2', doc.at_xpath('//retail/marketType').content @@ -141,8 +145,8 @@ def test_device_type_used_from_options_if_included_with_valid_track_data [TRACK1_DATA, TRACK2_DATA].each do |track| @credit_card.track_data = track stub_comms do - @gateway.purchase(@amount, @credit_card, {device_type: 1}) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, { device_type: 1 }) + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//retail') assert_equal '2', doc.at_xpath('//retail/marketType').content @@ -153,10 +157,10 @@ 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| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_nil doc.at_xpath('//retail') end @@ -168,7 +172,7 @@ def test_moto_market_type_included_when_card_is_entered_manually @credit_card.manual_entry = true stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//retail') assert_equal '1', doc.at_xpath('//retail/marketType').content @@ -179,7 +183,7 @@ def test_moto_market_type_included_when_card_is_entered_manually def test_market_type_can_be_specified stub_comms do @gateway.purchase(@amount, @credit_card, market_type: 0) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal '0', doc.at_xpath('//retail/marketType').content end @@ -189,7 +193,7 @@ def test_market_type_can_be_specified def test_successful_echeck_authorization response = stub_comms do @gateway.authorize(@amount, @check) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//payment/bankAccount') assert_equal '244183602', doc.at_xpath('//routingNumber').content @@ -207,12 +211,13 @@ def test_successful_echeck_authorization assert_equal '508141794', response.authorization.split('#')[0] end - def test_successful_echeck_purchase + def test_successful_echeck_purchase_with_checking_account_type response = stub_comms do @gateway.purchase(@amount, @check) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//payment/bankAccount') + assert_equal 'checking', doc.at_xpath('//accountType').content assert_equal '244183602', doc.at_xpath('//routingNumber').content assert_equal '15378535', doc.at_xpath('//accountNumber').content assert_equal 'Bank of Elbonia', doc.at_xpath('//bankName').content @@ -228,10 +233,27 @@ def test_successful_echeck_purchase assert_equal '508141795', response.authorization.split('#')[0] end + def test_successful_echeck_purchase_with_savings_account_type + savings_account = check(account_type: 'savings') + response = stub_comms do + @gateway.purchase(@amount, savings_account) + end.check_request do |_endpoint, data, _headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//payment/bankAccount') + assert_equal 'savings', doc.at_xpath('//accountType').content + end + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '508141795', response.authorization.split('#')[0] + end + def test_echeck_passing_recurring_flag response = stub_comms do @gateway.purchase(@amount, @check, recurring: true) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_equal settings_from_doc(parse(data))['recurringBilling'], 'true' end.respond_with(successful_purchase_response) @@ -247,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) - 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 + @gateway.authorize(@amount, @payment_token) + end.check_request do |_endpoint, data, _headers| + assert_no_match(/true<\/isPaymentToken>/, data) + assert_match(//, data) end.respond_with(successful_authorize_response) assert response @@ -263,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) - 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 + @gateway.purchase(@amount, @payment_token, { turn_on_nt_flow: true }) + end.check_request do |_endpoint, data, _headers| + assert_match(/true<\/isPaymentToken>/, data) + assert_no_match(//, data) end.respond_with(successful_purchase_response) assert response @@ -310,7 +328,7 @@ def test_successful_purchase def test_successful_purchase_with_utf_character stub_comms do @gateway.purchase(@amount, credit_card('4000100011112224', last_name: 'Wåhlin')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/Wåhlin/, data) end.respond_with(successful_purchase_response) end @@ -318,7 +336,7 @@ def test_successful_purchase_with_utf_character def test_passes_partial_auth stub_comms do @gateway.purchase(@amount, credit_card, disable_partial_auth: true) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/allowPartialAuth<\/settingName>/, data) assert_match(/false<\/settingValue>/, data) end.respond_with(successful_purchase_response) @@ -327,14 +345,14 @@ def test_passes_partial_auth def test_passes_email_customer stub_comms do @gateway.purchase(@amount, credit_card, email_customer: true) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/emailCustomer<\/settingName>/, data) assert_match(/true<\/settingValue>/, data) end.respond_with(successful_purchase_response) stub_comms do @gateway.purchase(@amount, credit_card, email_customer: false) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/emailCustomer<\/settingName>/, data) assert_match(/false<\/settingValue>/, data) end.respond_with(successful_purchase_response) @@ -343,7 +361,7 @@ def test_passes_email_customer def test_passes_header_email_receipt stub_comms do @gateway.purchase(@amount, credit_card, header_email_receipt: 'yet another field') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/headerEmailReceipt<\/settingName>/, data) assert_match(/yet another field<\/settingValue>/, data) end.respond_with(successful_purchase_response) @@ -352,7 +370,7 @@ def test_passes_header_email_receipt def test_passes_level_3_options stub_comms do @gateway.purchase(@amount, credit_card, @options.merge(@level_3_options)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) assert_match(/#{@level_3_options[:summary_commodity_code]}<\/summaryCommodityCode>/, data) assert_match(/<\/order>/, data) @@ -366,7 +384,7 @@ def test_passes_level_3_options def test_passes_line_items stub_comms do @gateway.purchase(@amount, credit_card, @options.merge(@additional_options)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) assert_match(//, data) assert_match(/#{@additional_options[:line_items][0][:item_id]}<\/itemId>/, data) @@ -387,7 +405,7 @@ def test_passes_line_items def test_passes_level_3_line_items stub_comms do @gateway.purchase(@amount, credit_card, @options.merge(@level_3_line_item_options)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) assert_match(//, data) assert_match(/#{@level_3_line_item_options[:line_items][0][:item_id]}<\/itemId>/, data) @@ -456,7 +474,7 @@ def test_successful_purchase_using_stored_card_and_custom_delimiter @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response_with_pipe_delimiter) - response = @gateway.purchase(@amount, store.authorization, {delimiter: '|', description: 'description, with, commas'}) + response = @gateway.purchase(@amount, store.authorization, { delimiter: '|', description: 'description, with, commas' }) assert_success response assert_equal '2235700270#XXXX2224#cim_purchase', response.authorization @@ -467,6 +485,23 @@ def test_successful_purchase_using_stored_card_and_custom_delimiter assert_equal 'description, with, commas', response.params['order_description'] end + def test_successful_purchase_using_stored_card_and_custom_delimiter_with_quotes + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + assert_success store + + @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response_with_pipe_delimiter_and_quotes) + + response = @gateway.purchase(@amount, store.authorization, { delimiter: '|', description: 'description, with, commas' }) + assert_success response + + assert_equal '12345667#XXXX1111#cim_purchase', response.authorization + assert_equal 'Y', response.avs_result['code'] + assert response.avs_result['street_match'] + assert response.avs_result['postal_match'] + assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message'] + end + def test_failed_purchase_using_stored_card @gateway.expects(:ssl_post).returns(successful_store_response) store = @gateway.store(@credit_card, @options) @@ -504,6 +539,167 @@ def test_successful_authorize_and_capture_using_stored_card assert_equal 'This transaction has been approved.', capture.message end + def test_successful_auth_with_initial_reccuring_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'cardholder', + network_transaction_id: nil + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isFirstRecurringPayment').content + assert_not_match(/isFirstSubsequentAuth/, doc) + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_initial_unscheduled_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isFirstSubsequentAuth').content + assert_not_match(/isFirstRecurringPayment/, doc) + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_initial_installment_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'installment', + initiator: 'cardholder', + network_transaction_id: nil + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isFirstSubsequentAuth').content + assert_not_match(/isFirstRecurringPayment/, doc) + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_installment_stored_credential + stored_credential_params = { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isSubsequentAuth').content + assert_equal '0123', doc.at_xpath('//subsequentAuthInformation/originalNetworkTransId').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_unscheduled_stored_credential + stored_credential_params = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isSubsequentAuth').content + assert_equal '0123', doc.at_xpath('//subsequentAuthInformation/originalNetworkTransId').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_recurring_stored_credential + stored_credential_params = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isSubsequentAuth').content + assert_equal '0123', doc.at_xpath('//subsequentAuthInformation/originalNetworkTransId').content + assert_equal 'recurringBilling', doc.at_xpath('//transactionSettings/setting/settingName').content + assert_equal 'true', doc.at_xpath('//transactionSettings/setting/settingValue').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_installment_stored_credential_and_cardholder_initiator + stored_credential_params = { + initial_transaction: false, + reason_type: 'installment', + initiator: 'cardholder', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isStoredCredentials').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_unscheduled_stored_credential_and_cardholder_initiator + stored_credential_params = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isStoredCredentials').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + + def test_successful_auth_with_subsequent_recurring_stored_credential_and_cardholder_initiator + stored_credential_params = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'cardholder', + network_transaction_id: '0123' + } + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal 'true', doc.at_xpath('//processingOptions/isStoredCredentials').content + end.respond_with(successful_authorize_response) + assert_success auth + assert auth.authorization + end + def test_failed_authorize_using_stored_card @gateway.expects(:ssl_post).returns(successful_store_response) store = @gateway.store(@credit_card, @options) @@ -593,7 +789,10 @@ def test_failed_void_using_stored_card def test_successful_verify response = stub_comms do - @gateway.verify(@credit_card) + @gateway.verify(@credit_card, @options) + end.check_request do |_endpoint, data, _headers| + doc = parse(data) + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content if doc.at_xpath('//transactionRequest/transactionType').content == 'authOnlyTransaction' end.respond_with(successful_authorize_response, successful_void_response) assert_success response end @@ -606,6 +805,20 @@ def test_successful_verify_failed_void assert_match %r{This transaction has been approved}, response.message end + def test_successful_verify_with_0_auth_card + options = { + verify_amount: 0, + billing_address: { + address1: '123 St', + zip: '88888' + } + } + response = stub_comms do + @gateway.verify(@credit_card, options) + end.respond_with(successful_authorize_response) + assert_success response + end + def test_unsuccessful_verify response = stub_comms do @gateway.verify(@credit_card, @options) @@ -675,7 +888,7 @@ def test_failed_store def test_successful_unstore response = stub_comms do @gateway.unstore('35959426#32506918#cim_store') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| doc = parse(data) assert_equal '35959426', doc.at_xpath('//deleteCustomerProfileRequest/customerProfileId').content end.respond_with(successful_unstore_response) @@ -715,8 +928,22 @@ def test_failed_store_new_payment_profile def test_address stub_comms do - @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444' }) + end.check_request do |_endpoint, data, _headers| + parse(data) do |doc| + assert_equal 'CO', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data + assert_equal 'US', doc.at_xpath('//billTo/country').content, data + assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content + assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content + end + end.respond_with(successful_authorize_response) + end + + 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 @@ -730,7 +957,7 @@ def test_address def test_address_with_empty_billing_address stub_comms do @gateway.authorize(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal '', doc.at_xpath('//billTo/address').content, data assert_equal '', doc.at_xpath('//billTo/city').content, data @@ -743,8 +970,8 @@ def test_address_with_empty_billing_address def test_address_with_address2_present stub_comms do - @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', address2: 'Apt 1234', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444' }) + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'CO', doc.at_xpath('//billTo/state').content, data assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data @@ -757,8 +984,8 @@ def test_address_with_address2_present def test_address_north_america_with_defaults stub_comms do - @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', country: 'US' }) + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'NC', doc.at_xpath('//billTo/state').content, data assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data @@ -769,8 +996,8 @@ def test_address_north_america_with_defaults def test_address_outsite_north_america stub_comms do - @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'DE'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', country: 'DE' }) + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data @@ -781,8 +1008,8 @@ def test_address_outsite_north_america def test_address_outsite_north_america_with_address2_present stub_comms do - @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'DE'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { address1: '164 Waverley Street', address2: 'Apt 1234', country: 'DE' }) + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data @@ -794,7 +1021,7 @@ def test_address_outsite_north_america_with_address2_present def test_duplicate_window stub_comms do @gateway.purchase(@amount, @credit_card, duplicate_window: 0) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_equal settings_from_doc(parse(data))['duplicateWindow'], '0' end.respond_with(successful_purchase_response) end @@ -813,7 +1040,7 @@ def test_duplicate_window_class_attribute_deprecated def test_add_cardholder_authentication_value stub_comms do @gateway.purchase(@amount, @credit_card, cardholder_authentication_value: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', authentication_indicator: '2') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content @@ -826,7 +1053,7 @@ def test_alternative_three_d_secure_options three_d_secure_opts = { cavv: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', eci: '2' } stub_comms do @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_opts) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content @@ -845,7 +1072,7 @@ def test_prioritize_authentication_value_params authentication_indicator: '2', three_d_secure: three_d_secure_opts ) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content @@ -854,10 +1081,20 @@ def test_prioritize_authentication_value_params end.respond_with(successful_purchase_response) end + def test_does_not_add_cardholder_authentication_element_without_relevant_values + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + parse(data) do |doc| + assert !doc.at_xpath('//cardholderAuthentication'), data + end + end.respond_with(successful_purchase_response) + end + def test_capture_passing_extra_info response = stub_comms do @gateway.capture(50, '123456789', description: 'Yo', order_id: 'Sweetness') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//order/description'), data assert_equal 'Yo', doc.at_xpath('//order/description').content, data @@ -880,7 +1117,7 @@ def test_successful_refund def test_successful_bank_refund response = stub_comms do @gateway.refund(50, '12345667', account_type: 'checking', routing_number: '123450987', account_number: '12345667', first_name: 'Louise', last_name: 'Belcher') - end.check_request do |endpoint, data, headers| + 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 @@ -891,10 +1128,24 @@ 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') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'Bob', doc.at_xpath('//billTo/firstName').content, data assert_equal 'Smith', doc.at_xpath('//billTo/lastName').content, data @@ -935,11 +1186,11 @@ def test_failed_credit end def test_supported_countries - assert_equal 4, (['US', 'CA', 'AU', 'VA'] & AuthorizeNetGateway.supported_countries).size + assert_equal 3, (%w[US CA AU] & AuthorizeNetGateway.supported_countries).size end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro], AuthorizeNetGateway.supported_cardtypes + assert_equal %i[visa master american_express discover diners_club jcb maestro], AuthorizeNetGateway.supported_cardtypes end def test_failure_without_response_reason_text @@ -990,7 +1241,7 @@ def test_message response = stub_comms do @gateway.purchase(@amount, @credit_card) end.respond_with(no_match_avs_response) - assert_equal 'Street address matches, but 5-digit and 9-digit postal code do not match.', response.message + assert_equal 'Street address matches, but postal code does not match.', response.message response = stub_comms do @gateway.purchase(@amount, @credit_card) @@ -1002,7 +1253,7 @@ def test_solution_id_is_added_to_post_data_parameters @gateway.class.application_id = 'A1000000' stub_comms do @gateway.authorize(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| doc = parse(data) assert_equal 'A1000000', fields_from_doc(doc)['x_solution_id'], data assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content @@ -1025,7 +1276,7 @@ def assert_no_has_customer_id(data) def test_include_cust_id_for_numeric_values stub_comms do @gateway.purchase(@amount, @credit_card, customer: '123') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//customer/id'), data assert_equal '123', doc.at_xpath('//customer/id').content, data @@ -1037,7 +1288,7 @@ def test_include_cust_id_for_numeric_values def test_include_cust_id_for_word_character_values stub_comms do @gateway.purchase(@amount, @credit_card, customer: '4840_TT') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil doc.at_xpath('//customer/id'), data assert_equal '4840_TT', doc.at_xpath('//customer/id').content, data @@ -1049,7 +1300,7 @@ def test_include_cust_id_for_word_character_values def test_dont_include_cust_id_for_email_addresses stub_comms do @gateway.purchase(@amount, @credit_card, customer: 'bob@test.com') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| doc = parse(data) assert !doc.at_xpath('//customer/id'), data assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content @@ -1059,7 +1310,7 @@ def test_dont_include_cust_id_for_email_addresses def test_dont_include_cust_id_for_phone_numbers stub_comms do @gateway.purchase(@amount, @credit_card, customer: '111-123-1231') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| doc = parse(data) assert !doc.at_xpath('//customer/id'), data assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content @@ -1067,9 +1318,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, @@ -1079,7 +1328,7 @@ def test_includes_shipping_name_when_different_from_billing_name stub_comms do @gateway.purchase(@amount, card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'billing', doc.at_xpath('//billTo/firstName').text assert_equal 'name', doc.at_xpath('//billTo/lastName').text @@ -1090,9 +1339,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) @@ -1104,7 +1351,7 @@ def test_includes_shipping_name_when_passed_as_options stub_comms do @gateway.purchase(@amount, card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'billing', doc.at_xpath('//billTo/firstName').text assert_equal 'name', doc.at_xpath('//billTo/lastName').text @@ -1115,10 +1362,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, @@ -1144,7 +1388,7 @@ def test_truncation stub_comms do @gateway.purchase(@amount, card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_truncated(data, 20, '//refId') assert_truncated(data, 255, '//description') assert_address_truncated(data, 50, 'firstName') @@ -1164,7 +1408,7 @@ def test_invalid_cvv card = credit_card(@credit_card.number, { verification_value: cvv }) stub_comms do @gateway.purchase(@amount, card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) { |doc| assert_nil doc.at_xpath('//cardCode') } end.respond_with(successful_purchase_response) end @@ -1174,7 +1418,7 @@ def test_card_number_truncation card = credit_card(@credit_card.number + '0123456789') stub_comms do @gateway.purchase(@amount, card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal @credit_card.number, doc.at_xpath('//cardNumber').text end @@ -1190,13 +1434,11 @@ 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) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content @@ -1210,13 +1452,11 @@ 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) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content @@ -1229,7 +1469,7 @@ def test_failed_apple_pay_authorization_with_network_tokenization_not_supported def test_supports_network_tokenization_true response = stub_comms do @gateway.supports_network_tokenization? - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content assert_equal '0.01', doc.at_xpath('//amount').content @@ -1244,7 +1484,7 @@ def test_supports_network_tokenization_true def test_supports_network_tokenization_false response = stub_comms do @gateway.supports_network_tokenization? - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content assert_equal '0.01', doc.at_xpath('//amount').content @@ -1266,7 +1506,85 @@ def test_verify_bad_credentials assert !@gateway.verify_credentials end - private + def test_0_amount_verify_with_no_zip + @options[:verify_amount] = 0 + @options[:billing_address] = { zip: nil, address1: 'XYZ' } + + response = @gateway.verify(@credit_card, @options) + + assert_failure response + assert_equal 'Billing address including zip code is required for a 0 amount verify', response.message + end + + def test_verify_transcript_with_0_auth + stub_comms do + @options[:verify_amount] = 0 + @gateway.verify(@credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + doc = parse(data) + assert_equal '0.00', doc.at_xpath('//transactionRequest/amount').content if doc.at_xpath('//transactionRequest/transactionType').content == 'authOnlyTransaction' + end + end + + def test_verify_amount_with_bad_string + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: 'dog' + end + + assert_equal 'verify_amount value must be an integer', error.message + end + + def test_verify_amount_with_boolean + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: true + end + + assert_equal 'verify_amount value must be an integer', error.message + end + + def test_verify_amount_with_decimal + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: 0.125 + end + + assert_equal 'verify_amount value must be an integer', error.message + end + + def test_verify_amount_with_negative + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: -100 + end + + assert_equal 'verify_amount value must be an integer', error.message + end + + def test_verify_amount_with_string_as_number + assert_equal 200, @gateway.send(:amount_for_verify, verify_amount: '200') + end + + def test_verify_amount_with_zero_without_zip + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: 0, billing_address: { address1: 'street' } + end + + assert_equal 'Billing address including zip code is required for a 0 amount verify', error.message + end + + def test_verify_amount_with_zero_without_address + error = assert_raises(ArgumentError) do + @gateway.send :amount_for_verify, verify_amount: 0, billing_address: { zip: '051052' } + end + + assert_equal 'Billing address including zip code is required for a 0 amount verify', error.message + end + + def test_verify_amount_without_gsf + assert_equal 100, @gateway.send(:amount_for_verify, {}) + end + + def test_verify_amount_with_nil_value + assert_equal 100, @gateway.send(:amount_for_verify, { verify_amount: nil }) + end def pre_scrubbed <<-PRE_SCRUBBED @@ -1357,7 +1675,7 @@ def assert_address_truncated(data, expected_size, field) end def successful_purchase_response - <<-eos + <<-XML - eos + XML end def fraud_review_response - <<-eos + <<-XML - eos + XML end def address_not_provided_avs_response - <<-eos + <<-XML - eos + XML end def no_match_cvv_response - <<-eos + <<-XML - eos + XML end def no_match_avs_response - <<-eos + <<-XML - eos + XML end def failed_purchase_response - <<-eos + <<-XML @@ -1584,11 +1902,11 @@ def failed_purchase_response - eos + XML end def no_message_response - <<-eos + <<-XML @@ -1620,11 +1938,11 @@ def no_message_response - eos + XML end def successful_purchase_response_test_mode - <<-eos + <<-XML - eos + XML end def successful_authorize_response - <<-eos + <<-XML @@ -1703,11 +2021,11 @@ def successful_authorize_response - eos + XML end def failed_authorize_response - <<-eos + <<-XML @@ -1749,11 +2067,11 @@ def failed_authorize_response - eos + XML end def successful_capture_response - <<-eos + <<-XML @@ -1784,11 +2102,11 @@ def successful_capture_response - eos + XML end def already_actioned_capture_response - <<-eos + <<-XML @@ -1819,11 +2137,11 @@ def already_actioned_capture_response - eos + XML end def failed_capture_response - <<-eos + <<-XML Error @@ -1852,11 +2170,11 @@ def failed_capture_response - eos + XML end def successful_refund_response - <<-eos + <<-XML @@ -1886,11 +2204,11 @@ def successful_refund_response - eos + XML end def failed_refund_response - <<-eos + <<-XML @@ -1920,11 +2238,11 @@ def failed_refund_response - eos + XML end def successful_void_response - <<-eos + <<-XML @@ -1955,11 +2273,11 @@ def successful_void_response - eos + XML end def failed_void_response - <<-eos + <<-XML @@ -1991,11 +2309,11 @@ def failed_void_response - eos + XML end def successful_credit_response - <<-eos + <<-XML 1 @@ -2032,11 +2350,11 @@ def successful_credit_response - eos + XML end def failed_credit_response - <<-eos + <<-XML 1 @@ -2073,11 +2391,11 @@ def failed_credit_response - eos + XML end def successful_store_response - <<-eos + <<-XML @@ -2094,11 +2412,11 @@ def successful_store_response - eos + XML end def failed_store_response - <<-eos + <<-XML @@ -2112,11 +2430,11 @@ def failed_store_response - eos + XML end def successful_unstore_response - <<-eos + <<-XML @@ -2127,11 +2445,11 @@ def successful_unstore_response - eos + XML end def failed_unstore_response - <<-eos + <<-XML @@ -2142,11 +2460,11 @@ def failed_unstore_response - eos + XML end def successful_store_new_payment_profile_response - <<-eos + <<-XML @@ -2159,11 +2477,11 @@ def successful_store_new_payment_profile_response 38392170 34896759 - eos + XML end def failed_store_new_payment_profile_response - <<-eos + <<-XML @@ -2176,11 +2494,11 @@ def failed_store_new_payment_profile_response 38392767 34897359 - eos + XML end def successful_purchase_using_stored_card_response - <<-eos + <<-XML 1 @@ -2193,11 +2511,11 @@ def successful_purchase_using_stored_card_response 1,1,1,This transaction has been approved.,8HUT72,Y,2235700270,1,Store Purchase,1.01,CC,auth_capture,e385c780422f4bd182c4,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,4A20EEAF89018FF075899DDB332E9D35,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,, - eos + XML end def successful_purchase_using_stored_card_response_with_pipe_delimiter - <<-eos + <<-XML 1 @@ -2210,11 +2528,15 @@ def successful_purchase_using_stored_card_response_with_pipe_delimiter 1|1|1|This transaction has been approved.|8HUT72|Y|2235700270|1|description, with, commas|1.01|CC|auth_capture|e385c780422f4bd182c4|Longbob|Longsen||||n/a|||||||||||||||||||4A20EEAF89018FF075899DDB332E9D35||2|||||||||||XXXX2224|Visa|||||||||||||||| - eos + XML + end + + def successful_purchase_using_stored_card_response_with_pipe_delimiter_and_quotes + "\xEF\xBB\xBF12345OkI00001Successful.\"1\"|\"1\"|\"1\"|\"This transaction has been approved.\"|\"001234\"|\"Y\"|\"12345667\"|\"654321\"|\"\"|\"39.95\"|\"CC\"|\"auth_capture\"|\"54321\"|\"Jane\"|\"Doe\"|\"\"|\"1 Main St.\"|\"Durham\"|\"NC\"|\"27707\"|\"US\"|\"\"|\"\"|\"test@example.com\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"XXXX1111\"|\"Visa\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"" end def failed_purchase_using_stored_card_response - <<-eos + <<-XML 1 @@ -2227,11 +2549,11 @@ def failed_purchase_using_stored_card_response 3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_capture,2da01d7b583c706106e2,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,, - eos + XML end def successful_authorize_using_stored_card_response - <<-eos + <<-XML 1 @@ -2244,11 +2566,11 @@ def successful_authorize_using_stored_card_response 1,1,1,This transaction has been approved.,GGHQ5R,Y,2235700640,1,Store Purchase,1.01,CC,auth_only,0bde9d39f8eb9443f2af,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,, - eos + XML end def failed_authorize_using_stored_card_response - <<-eos + <<-XML 1 @@ -2261,11 +2583,11 @@ def failed_authorize_using_stored_card_response 3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_only,f632442ce056f9f82ee9,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,, - eos + XML end def successful_capture_using_stored_card_response - <<-eos + <<-XML @@ -2278,11 +2600,11 @@ def successful_capture_using_stored_card_response 1,1,1,This transaction has been approved.,GGHQ5R,P,2235700640,1,,1.01,CC,prior_auth_capture,0bde9d39f8eb9443f2af,,,,,,,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,, - eos + XML end def failed_capture_using_stored_card_response - <<-eos + <<-XML @@ -2295,11 +2617,11 @@ def failed_capture_using_stored_card_response 3,2,47,The amount requested for settlement cannot be greater than the original amount authorized.,,P,0,,,41.01,CC,prior_auth_capture,,,,,,,,,,,,,,,,,,,,,,,,,,8A556B125A1DA070AF5A84B798B7FBF7,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,, - eos + XML end def failed_refund_using_stored_card_response - <<-eos + <<-XML 1 @@ -2311,11 +2633,11 @@ def failed_refund_using_stored_card_response - eos + XML end def successful_void_using_stored_card_response - <<-eos + <<-XML @@ -2328,11 +2650,11 @@ def successful_void_using_stored_card_response 1,1,1,This transaction has been approved.,3R9YE2,P,2235701141,1,,0.00,CC,void,becdb509b35a32c30e97,,,,,,,,,,,,,,,,,,,,,,,,,C3C4B846B9D5A37D14462C2BF5B924FD,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,, - eos + XML end def failed_void_using_stored_card_response - <<-eos + <<-XML @@ -2345,11 +2667,11 @@ def failed_void_using_stored_card_response 1,1,310,This transaction has already been voided.,,P,0,,,0.00,CC,void,,,,,,,,,,,,,,,,,,,,,,,,,,FD9FAE70BEF461997A6C15D7D597658D,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,, - eos + XML end def network_tokenization_not_supported_response - <<-eos + <<-XML @@ -2391,11 +2713,11 @@ def network_tokenization_not_supported_response - eos + XML end def credentials_are_legit_response - <<-eos + <<-XML @@ -2406,11 +2728,11 @@ def credentials_are_legit_response - eos + XML end def credentials_are_bogus_response - <<-eos + <<-XML @@ -2421,11 +2743,11 @@ def credentials_are_bogus_response - eos + XML end def failed_refund_for_unsettled_payment_response - <<-eos + <<-XML @@ -2457,6 +2779,48 @@ def failed_refund_for_unsettled_payment_response - eos + XML + end + + def unsuccessful_authorize_response_for_jcb_card + <<-XML + + + 1 + + Error + + E00027 + The transaction was unsuccessful. + + + + 3 + + P + + + 0 + + + 0 + XXXX0017 + JCB + + + 289 + This processor does not accept zero dollar authorization for this card type. + + + + + x_currency_code + USD + + + + + + XML end end diff --git a/test/unit/gateways/axcessms_test.rb b/test/unit/gateways/axcessms_test.rb index e87a092498b..c4b0c9bfd12 100644 --- a/test/unit/gateways/axcessms_test.rb +++ b/test/unit/gateways/axcessms_test.rb @@ -21,12 +21,12 @@ def setup ip: '0.0.0.0', mode: @mode, billing_address: { - :address1 => '10 Marklar St', - :address2 => 'Musselburgh', - :city => 'Dunedin', - :zip => '9013', - :state => 'Otago', - :country => 'NZ' + address1: '10 Marklar St', + address2: 'Musselburgh', + city: 'Dunedin', + zip: '9013', + state: 'Otago', + country: 'NZ' } } end @@ -106,13 +106,13 @@ def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) response = @gateway.refund(@amount - 30, 'authorization', @options) assert_failure response - assert_equal 'Reference Error - reversal needs at least one successful transaction of type (CP or DB or RB or PA)', response.message + assert_equal 'Reference Error - reversal needs at least one successful transaction of type (CP or DB or RB or PA)', response.message end def test_authorize_using_reference_sets_proper_elements stub_comms do @gateway.authorize(@amount, 'MY_AUTHORIZE_VALUE', @options) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE') assert_no_match(//, body) end.respond_with(successful_authorize_response) @@ -121,7 +121,7 @@ def test_authorize_using_reference_sets_proper_elements def test_purchase_using_reference_sets_proper_elements stub_comms do @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', @options) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE') assert_no_match(//, body) end.respond_with(successful_authorize_response) @@ -129,8 +129,8 @@ def test_purchase_using_reference_sets_proper_elements def test_setting_mode_sets_proper_element stub_comms do - @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {mode: 'CRAZY_TEST_MODE'}) - end.check_request do |endpoint, body, headers| + @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', { mode: 'CRAZY_TEST_MODE' }) + end.check_request do |_endpoint, body, _headers| assert_xpath_text(body, '//Transaction/@mode', 'CRAZY_TEST_MODE') end.respond_with(successful_authorize_response) end @@ -138,7 +138,7 @@ def test_setting_mode_sets_proper_element def test_defaults_to_integrator_test stub_comms do @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {}) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xpath_text(body, '//Transaction/@mode', 'INTEGRATOR_TEST') end.respond_with(successful_authorize_response) end diff --git a/test/unit/gateways/balanced_test.rb b/test/unit/gateways/balanced_test.rb index 68e98b4f0d4..86d16dfc067 100644 --- a/test/unit/gateways/balanced_test.rb +++ b/test/unit/gateways/balanced_test.rb @@ -33,7 +33,7 @@ def test_successful_purchase def test_successful_purchase_with_outside_token response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, '/cards/CCVOX2d7Ar6Ze5TOxHsebeH', @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_equal('https://api.balancedpayments.com/cards/CCVOX2d7Ar6Ze5TOxHsebeH/debits', endpoint) end.respond_with(debits_response) @@ -211,7 +211,7 @@ def test_store new_email_address = '%d@example.org' % Time.now assert response = @gateway.store(@credit_card, { - email: new_email_address + email: new_email_address }) assert_instance_of String, response.authorization end @@ -221,7 +221,7 @@ def test_successful_purchase_with_legacy_outside_token response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, legacy_outside_token, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_equal('https://api.balancedpayments.com/cards/CC7m1Mtqk6rVJo5tcD1qitAC/debits', endpoint) end.respond_with(debits_response) @@ -237,7 +237,7 @@ def test_capturing_legacy_authorizations [v1_authorization, v11_authorization].each do |authorization| stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits', endpoint) end.respond_with(authorized_debits_response) end @@ -250,7 +250,7 @@ def test_voiding_legacy_authorizations [v1_authorization, v11_authorization].each do |authorization| stub_comms(@gateway, :ssl_request) do @gateway.void(authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |method, endpoint, data, _headers| assert_equal :put, method assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5', endpoint) assert_match %r{\bis_void=true\b}, data @@ -265,7 +265,7 @@ def test_refunding_legacy_purchases [v1_authorization, v11_authorization].each do |authorization| stub_comms(@gateway, :ssl_request) do @gateway.refund(nil, authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_equal('https://api.balancedpayments.com/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds', endpoint) end.respond_with(refunds_response) end @@ -275,8 +275,9 @@ def test_passing_address a = address response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, address: a) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, data, _headers| next if endpoint =~ /debits/ + clean = proc { |s| Regexp.escape(CGI.escape(s)) } assert_match(%r{address\[line1\]=#{clean[a[:address1]]}}, data) assert_match(%r{address\[line2\]=#{clean[a[:address2]]}}, data) @@ -292,8 +293,9 @@ def test_passing_address def test_passing_address_without_zip response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, address: address(zip: nil)) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, data, _headers| next if endpoint =~ /debits/ + assert_no_match(%r{address}, data) end.respond_with(cards_response, debits_response) @@ -303,8 +305,9 @@ def test_passing_address_without_zip def test_passing_address_with_blank_zip response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, address: address(zip: ' ')) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, endpoint, data, _headers| next if endpoint =~ /debits/ + assert_no_match(%r{address}, data) end.respond_with(cards_response, debits_response) @@ -331,539 +334,539 @@ def invalid_login_response end def marketplace_response - <<-RESPONSE -{ - "meta": { - "last": "/marketplaces?limit=10&offset=0", - "next": null, - "href": "/marketplaces?limit=10&offset=0", - "limit": 10, - "offset": 0, - "previous": null, - "total": 1, - "first": "/marketplaces?limit=10&offset=0" - }, - "marketplaces": [ - { - "in_escrow": 47202, - "domain_url": "example.com", - "name": "Test Marketplace", - "links": { - "owner_customer": "AC73SN17anKkjk6Y1sVe2uaq" - }, - "href": "/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO", - "created_at": "2012-07-19T17:33:51.974238Z", - "support_email_address": "support@example.com", - "updated_at": "2012-07-19T17:33:52.848042Z", - "support_phone_number": "+16505551234", - "production": false, - "meta": {}, - "unsettled_fees": 0, - "id": "TEST-MP73SaFdpQePv9dOaG5wXOGO" - } - ], - "links": { - "marketplaces.debits": "/debits", - "marketplaces.reversals": "/reversals", - "marketplaces.customers": "/customers", - "marketplaces.credits": "/credits", - "marketplaces.cards": "/cards", - "marketplaces.card_holds": "/card_holds", - "marketplaces.refunds": "/refunds", - "marketplaces.owner_customer": "/customers/{marketplaces.owner_customer}", - "marketplaces.transactions": "/transactions", - "marketplaces.bank_accounts": "/bank_accounts", - "marketplaces.callbacks": "/callbacks", - "marketplaces.events": "/events" - } -} -RESPONSE + <<~RESPONSE + { + "meta": { + "last": "/marketplaces?limit=10&offset=0", + "next": null, + "href": "/marketplaces?limit=10&offset=0", + "limit": 10, + "offset": 0, + "previous": null, + "total": 1, + "first": "/marketplaces?limit=10&offset=0" + }, + "marketplaces": [ + { + "in_escrow": 47202, + "domain_url": "example.com", + "name": "Test Marketplace", + "links": { + "owner_customer": "AC73SN17anKkjk6Y1sVe2uaq" + }, + "href": "/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO", + "created_at": "2012-07-19T17:33:51.974238Z", + "support_email_address": "support@example.com", + "updated_at": "2012-07-19T17:33:52.848042Z", + "support_phone_number": "+16505551234", + "production": false, + "meta": {}, + "unsettled_fees": 0, + "id": "TEST-MP73SaFdpQePv9dOaG5wXOGO" + } + ], + "links": { + "marketplaces.debits": "/debits", + "marketplaces.reversals": "/reversals", + "marketplaces.customers": "/customers", + "marketplaces.credits": "/credits", + "marketplaces.cards": "/cards", + "marketplaces.card_holds": "/card_holds", + "marketplaces.refunds": "/refunds", + "marketplaces.owner_customer": "/customers/{marketplaces.owner_customer}", + "marketplaces.transactions": "/transactions", + "marketplaces.bank_accounts": "/bank_accounts", + "marketplaces.callbacks": "/callbacks", + "marketplaces.events": "/events" + } + } + RESPONSE end def cards_response - <<-RESPONSE -{ - "cards": [ - { - "cvv_match": null, - "links": { - "customer": null - }, - "name": "Longbob Longsen", - "expiration_year": 2015, - "avs_street_match": null, - "is_verified": true, - "created_at": "2014-02-06T23:19:27.146436Z", - "cvv_result": null, - "brand": "Visa", - "number": "xxxxxxxxxxxx1111", - "updated_at": "2014-02-06T23:19:27.146441Z", - "id": "CCXfdppSxXOGzaMUHp9EQyI", - "expiration_month": 9, - "cvv": null, - "meta": {}, - "href": "/cards/CCXfdppSxXOGzaMUHp9EQyI", - "address": { - "city": null, - "line2": null, - "line1": null, - "state": null, - "postal_code": null, - "country_code": null - }, - "fingerprint": "e0928a7fe2233bf6697413f663b3d94114358e6ac027fcd58ceba0bb37f05039", - "avs_postal_match": null, - "avs_result": null - } - ], - "links": { - "cards.card_holds": "/cards/{cards.id}/card_holds", - "cards.customer": "/customers/{cards.customer}", - "cards.debits": "/cards/{cards.id}/debits" - } -} -RESPONSE + <<~RESPONSE + { + "cards": [ + { + "cvv_match": null, + "links": { + "customer": null + }, + "name": "Longbob Longsen", + "expiration_year": 2015, + "avs_street_match": null, + "is_verified": true, + "created_at": "2014-02-06T23:19:27.146436Z", + "cvv_result": null, + "brand": "Visa", + "number": "xxxxxxxxxxxx1111", + "updated_at": "2014-02-06T23:19:27.146441Z", + "id": "CCXfdppSxXOGzaMUHp9EQyI", + "expiration_month": 9, + "cvv": null, + "meta": {}, + "href": "/cards/CCXfdppSxXOGzaMUHp9EQyI", + "address": { + "city": null, + "line2": null, + "line1": null, + "state": null, + "postal_code": null, + "country_code": null + }, + "fingerprint": "e0928a7fe2233bf6697413f663b3d94114358e6ac027fcd58ceba0bb37f05039", + "avs_postal_match": null, + "avs_result": null + } + ], + "links": { + "cards.card_holds": "/cards/{cards.id}/card_holds", + "cards.customer": "/customers/{cards.customer}", + "cards.debits": "/cards/{cards.id}/debits" + } + } + RESPONSE end def debits_response - <<-RESPONSE -{ - "debits": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "customer": null, - "source": "CCXfdppSxXOGzaMUHp9EQyI", - "order": null, - "dispute": null - }, - "updated_at": "2014-02-06T23:19:29.690815Z", - "created_at": "2014-02-06T23:19:28.709143Z", - "transaction_number": "W250-112-1883", - "failure_reason": null, - "currency": "USD", - "amount": 100, - "failure_reason_code": null, - "meta": {}, - "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", - "appears_on_statement_as": "BAL*example.com", - "id": "WDYZhc3mWCkxvOwIokeUz6M" - } - ], - "links": { - "debits.customer": "/customers/{debits.customer}", - "debits.dispute": "/disputes/{debits.dispute}", - "debits.source": "/resources/{debits.source}", - "debits.order": "/orders/{debits.order}", - "debits.refunds": "/debits/{debits.id}/refunds", - "debits.events": "/debits/{debits.id}/events" - } -} -RESPONSE + <<~RESPONSE + { + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CCXfdppSxXOGzaMUHp9EQyI", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } + } + RESPONSE end def authorized_debits_response - <<-RESPONSE -{ - "debits": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "customer": null, - "source": "CC2uKKcUhaSFRrl2mnGPSbDO", - "order": null, - "dispute": null - }, - "updated_at": "2014-02-06T23:19:29.690815Z", - "created_at": "2014-02-06T23:19:28.709143Z", - "transaction_number": "W250-112-1883", - "failure_reason": null, - "currency": "USD", - "amount": 100, - "failure_reason_code": null, - "meta": {}, - "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", - "appears_on_statement_as": "BAL*example.com", - "id": "WDYZhc3mWCkxvOwIokeUz6M" - } - ], - "links": { - "debits.customer": "/customers/{debits.customer}", - "debits.dispute": "/disputes/{debits.dispute}", - "debits.source": "/resources/{debits.source}", - "debits.order": "/orders/{debits.order}", - "debits.refunds": "/debits/{debits.id}/refunds", - "debits.events": "/debits/{debits.id}/events" - } -} -RESPONSE + <<~RESPONSE + { + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC2uKKcUhaSFRrl2mnGPSbDO", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } + } + RESPONSE end def authorized_partial_debits_response - <<-RESPONSE -{ - "debits": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "customer": null, - "source": "CC2uKKcUhaSFRrl2mnGPSbDO", - "order": null, - "dispute": null - }, - "updated_at": "2014-02-06T23:19:29.690815Z", - "created_at": "2014-02-06T23:19:28.709143Z", - "transaction_number": "W250-112-1883", - "failure_reason": null, - "currency": "USD", - "amount": 50, - "failure_reason_code": null, - "meta": {}, - "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", - "appears_on_statement_as": "BAL*example.com", - "id": "WDYZhc3mWCkxvOwIokeUz6M" - } - ], - "links": { - "debits.customer": "/customers/{debits.customer}", - "debits.dispute": "/disputes/{debits.dispute}", - "debits.source": "/resources/{debits.source}", - "debits.order": "/orders/{debits.order}", - "debits.refunds": "/debits/{debits.id}/refunds", - "debits.events": "/debits/{debits.id}/events" - } -} -RESPONSE + <<~RESPONSE + { + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC2uKKcUhaSFRrl2mnGPSbDO", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 50, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } + } + RESPONSE end def declined_response - <<-RESPONSE -{ - "errors": [ - { - "status": "Payment Required", - "category_code": "card-declined", - "additional": "Customer call bank", - "status_code": 402, - "category_type": "banking", - "extras": {}, - "request_id": "OHMc8d80eb4903011e390c002a1fe53e539", - "description": "R530: Customer call bank. Your request id is OHMc8d80eb4903011e390c002a1fe53e539." - } - ] -} -RESPONSE + <<~RESPONSE + { + "errors": [ + { + "status": "Payment Required", + "category_code": "card-declined", + "additional": "Customer call bank", + "status_code": 402, + "category_type": "banking", + "extras": {}, + "request_id": "OHMc8d80eb4903011e390c002a1fe53e539", + "description": "R530: Customer call bank. Your request id is OHMc8d80eb4903011e390c002a1fe53e539." + } + ] + } + RESPONSE end def bad_email_response - <<-'RESPONSE' -{ - "errors": [ - { - "status": "Bad Request", - "category_code": "request", - "additional": null, - "status_code": 400, - "category_type": "request", - "extras": { - "email": "\"invalid_email\" must be a valid email address as specified by RFC-2822" - }, - "request_id": "OHM9107a4bc903111e390c002a1fe53e539", - "description": "Invalid field [email] - \"invalid_email\" must be a valid email address as specified by RFC-2822 Your request id is OHM9107a4bc903111e390c002a1fe53e539." - } - ] -} -RESPONSE + <<~'RESPONSE' + { + "errors": [ + { + "status": "Bad Request", + "category_code": "request", + "additional": null, + "status_code": 400, + "category_type": "request", + "extras": { + "email": "\"invalid_email\" must be a valid email address as specified by RFC-2822" + }, + "request_id": "OHM9107a4bc903111e390c002a1fe53e539", + "description": "Invalid field [email] - \"invalid_email\" must be a valid email address as specified by RFC-2822 Your request id is OHM9107a4bc903111e390c002a1fe53e539." + } + ] + } + RESPONSE end def account_frozen_response - <<-RESPONSE -{ - "errors": [ - { - "status": "Payment Required", - "category_code": "card-declined", - "additional": "Account Frozen", - "status_code": 402, - "category_type": "banking", - "extras": {}, - "request_id": "OHMec50b6be903c11e387cb026ba7cac9da", - "description": "R758: Account Frozen. Your request id is OHMec50b6be903c11e387cb026ba7cac9da." - } - ], - "links": { - "debits.customer": "/customers/{debits.customer}", - "debits.dispute": "/disputes/{debits.dispute}", - "debits.source": "/resources/{debits.source}", - "debits.order": "/orders/{debits.order}", - "debits.refunds": "/debits/{debits.id}/refunds", - "debits.events": "/debits/{debits.id}/events" - }, - "debits": [ - { - "status": "failed", - "description": "Shopify Purchase", - "links": { - "customer": null, - "source": "CC7a41DYIaSSyGoau6rZ8VcG", - "order": null, - "dispute": null - }, - "updated_at": "2014-02-07T21:15:10.107464Z", - "created_at": "2014-02-07T21:15:09.206335Z", - "transaction_number": "W202-883-1157", - "failure_reason": "R758: Account Frozen.", - "currency": "USD", - "amount": 100, - "failure_reason_code": "card-declined", - "meta": {}, - "href": "/debits/WD7cjQ5gizGWMDWbxDndgm7w", - "appears_on_statement_as": "BAL*example.com", - "id": "WD7cjQ5gizGWMDWbxDndgm7w" - } - ] -} -RESPONSE + <<~RESPONSE + { + "errors": [ + { + "status": "Payment Required", + "category_code": "card-declined", + "additional": "Account Frozen", + "status_code": 402, + "category_type": "banking", + "extras": {}, + "request_id": "OHMec50b6be903c11e387cb026ba7cac9da", + "description": "R758: Account Frozen. Your request id is OHMec50b6be903c11e387cb026ba7cac9da." + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + }, + "debits": [ + { + "status": "failed", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC7a41DYIaSSyGoau6rZ8VcG", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-07T21:15:10.107464Z", + "created_at": "2014-02-07T21:15:09.206335Z", + "transaction_number": "W202-883-1157", + "failure_reason": "R758: Account Frozen.", + "currency": "USD", + "amount": 100, + "failure_reason_code": "card-declined", + "meta": {}, + "href": "/debits/WD7cjQ5gizGWMDWbxDndgm7w", + "appears_on_statement_as": "BAL*example.com", + "id": "WD7cjQ5gizGWMDWbxDndgm7w" + } + ] + } + RESPONSE end def appears_on_response - <<-RESPONSE -{ - "debits": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "customer": null, - "source": "CC4SKo0WY3lhfWc6CgMyPo34", - "order": null, - "dispute": null - }, - "updated_at": "2014-02-07T21:20:13.950392Z", - "created_at": "2014-02-07T21:20:12.737821Z", - "transaction_number": "W337-477-3752", - "failure_reason": null, - "currency": "USD", - "amount": 100, - "failure_reason_code": null, - "meta": {}, - "href": "/debits/WD4UDDm6iqtYMEd21UBaa50H", - "appears_on_statement_as": "BAL*Homer Electric", - "id": "WD4UDDm6iqtYMEd21UBaa50H" - } - ], - "links": { - "debits.customer": "/customers/{debits.customer}", - "debits.dispute": "/disputes/{debits.dispute}", - "debits.source": "/resources/{debits.source}", - "debits.order": "/orders/{debits.order}", - "debits.refunds": "/debits/{debits.id}/refunds", - "debits.events": "/debits/{debits.id}/events" - } -} -RESPONSE + <<~RESPONSE + { + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC4SKo0WY3lhfWc6CgMyPo34", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-07T21:20:13.950392Z", + "created_at": "2014-02-07T21:20:12.737821Z", + "transaction_number": "W337-477-3752", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WD4UDDm6iqtYMEd21UBaa50H", + "appears_on_statement_as": "BAL*Homer Electric", + "id": "WD4UDDm6iqtYMEd21UBaa50H" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } + } + RESPONSE end def holds_response - <<-RESPONSE -{ - "card_holds": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "card": "CC2uKKcUhaSFRrl2mnGPSbDO", - "debit": null - }, - "updated_at": "2014-02-07T21:46:39.678439Z", - "created_at": "2014-02-07T21:46:39.303526Z", - "transaction_number": "HL343-028-3032", - "expires_at": "2014-02-14T21:46:39.532363Z", - "failure_reason": null, - "currency": "USD", - "amount": 100, - "meta": {}, - "href": "/card_holds/HL2wPXf6ByqkLMiWGab7QRsq", - "failure_reason_code": null, - "voided_at": null, - "id": "HL2wPXf6ByqkLMiWGab7QRsq" - } - ], - "links": { - "card_holds.events": "/card_holds/{card_holds.id}/events", - "card_holds.card": "/resources/{card_holds.card}", - "card_holds.debits": "/card_holds/{card_holds.id}/debits", - "card_holds.debit": "/debits/{card_holds.debit}" - } -} -RESPONSE + <<~RESPONSE + { + "card_holds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "card": "CC2uKKcUhaSFRrl2mnGPSbDO", + "debit": null + }, + "updated_at": "2014-02-07T21:46:39.678439Z", + "created_at": "2014-02-07T21:46:39.303526Z", + "transaction_number": "HL343-028-3032", + "expires_at": "2014-02-14T21:46:39.532363Z", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "meta": {}, + "href": "/card_holds/HL2wPXf6ByqkLMiWGab7QRsq", + "failure_reason_code": null, + "voided_at": null, + "id": "HL2wPXf6ByqkLMiWGab7QRsq" + } + ], + "links": { + "card_holds.events": "/card_holds/{card_holds.id}/events", + "card_holds.card": "/resources/{card_holds.card}", + "card_holds.debits": "/card_holds/{card_holds.id}/debits", + "card_holds.debit": "/debits/{card_holds.debit}" + } + } + RESPONSE end def method_not_allowed_response - <<-RESPONSE -{ - "errors": [ - { - "status": "Method Not Allowed", - "category_code": "method-not-allowed", - "description": "Your request id is OHMfaf5570a904211e3bcab026ba7f8ec28.", - "status_code": 405, - "category_type": "request", - "request_id": "OHMfaf5570a904211e3bcab026ba7f8ec28" - } - ] -} -RESPONSE + <<~RESPONSE + { + "errors": [ + { + "status": "Method Not Allowed", + "category_code": "method-not-allowed", + "description": "Your request id is OHMfaf5570a904211e3bcab026ba7f8ec28.", + "status_code": 405, + "category_type": "request", + "request_id": "OHMfaf5570a904211e3bcab026ba7f8ec28" + } + ] + } + RESPONSE end def unauthorized_response - <<-RESPONSE -{ - "errors": [ - { - "status": "Unauthorized", - "category_code": "authentication-required", - "description": "

The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.

In case you are allowed to request the document, please check your user-id and password and try again.

Your request id is OHM56702560904311e3988c026ba7cd33d0.", - "status_code": 401, - "category_type": "permission", - "request_id": "OHM56702560904311e3988c026ba7cd33d0" - } - ] -} -RESPONSE + <<~RESPONSE + { + "errors": [ + { + "status": "Unauthorized", + "category_code": "authentication-required", + "description": "

The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.

In case you are allowed to request the document, please check your user-id and password and try again.

Your request id is OHM56702560904311e3988c026ba7cd33d0.", + "status_code": 401, + "category_type": "permission", + "request_id": "OHM56702560904311e3988c026ba7cd33d0" + } + ] + } + RESPONSE end def voided_hold_response - <<-RESPONSE -{ - "card_holds": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "card": "CC52ACcRnrG5eupOERKK4OAq", - "debit": null - }, - "updated_at": "2014-02-07T22:10:28.923304Z", - "created_at": "2014-02-07T22:10:27.904233Z", - "transaction_number": "HL728-165-8425", - "expires_at": "2014-02-14T22:10:28.045745Z", - "failure_reason": null, - "currency": "USD", - "amount": 100, - "meta": {}, - "href": "/card_holds/HL54qindwhlErSujLo5IcP5J", - "failure_reason_code": null, - "voided_at": "2014-02-07T22:10:28.923308Z", - "id": "HL54qindwhlErSujLo5IcP5J" - } - ], - "links": { - "card_holds.events": "/card_holds/{card_holds.id}/events", - "card_holds.card": "/resources/{card_holds.card}", - "card_holds.debits": "/card_holds/{card_holds.id}/debits", - "card_holds.debit": "/debits/{card_holds.debit}" - } -} -RESPONSE + <<~RESPONSE + { + "card_holds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "card": "CC52ACcRnrG5eupOERKK4OAq", + "debit": null + }, + "updated_at": "2014-02-07T22:10:28.923304Z", + "created_at": "2014-02-07T22:10:27.904233Z", + "transaction_number": "HL728-165-8425", + "expires_at": "2014-02-14T22:10:28.045745Z", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "meta": {}, + "href": "/card_holds/HL54qindwhlErSujLo5IcP5J", + "failure_reason_code": null, + "voided_at": "2014-02-07T22:10:28.923308Z", + "id": "HL54qindwhlErSujLo5IcP5J" + } + ], + "links": { + "card_holds.events": "/card_holds/{card_holds.id}/events", + "card_holds.card": "/resources/{card_holds.card}", + "card_holds.debits": "/card_holds/{card_holds.id}/debits", + "card_holds.debit": "/debits/{card_holds.debit}" + } + } + RESPONSE end def refunds_response - <<-RESPONSE -{ - "links": { - "refunds.dispute": "/disputes/{refunds.dispute}", - "refunds.events": "/refunds/{refunds.id}/events", - "refunds.debit": "/debits/{refunds.debit}", - "refunds.order": "/orders/{refunds.order}" - }, - "refunds": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "debit": "WDAtJcbjh3EJLW0tp7CUxAk", - "order": null, - "dispute": null - }, - "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", - "created_at": "2014-02-07T22:35:06.424855Z", - "transaction_number": "RF424-240-3258", - "updated_at": "2014-02-07T22:35:07.655276Z", - "currency": "USD", - "amount": 100, - "meta": {}, - "id": "RFJ4N00zLaQFrfBkC8cbN68" - } - ] -} -RESPONSE + <<~RESPONSE + { + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" + }, + "refunds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "debit": "WDAtJcbjh3EJLW0tp7CUxAk", + "order": null, + "dispute": null + }, + "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", + "created_at": "2014-02-07T22:35:06.424855Z", + "transaction_number": "RF424-240-3258", + "updated_at": "2014-02-07T22:35:07.655276Z", + "currency": "USD", + "amount": 100, + "meta": {}, + "id": "RFJ4N00zLaQFrfBkC8cbN68" + } + ] + } + RESPONSE end def partial_refunds_response - <<-RESPONSE -{ - "links": { - "refunds.dispute": "/disputes/{refunds.dispute}", - "refunds.events": "/refunds/{refunds.id}/events", - "refunds.debit": "/debits/{refunds.debit}", - "refunds.order": "/orders/{refunds.order}" - }, - "refunds": [ - { - "status": "succeeded", - "description": "Shopify Purchase", - "links": { - "debit": "WDAtJcbjh3EJLW0tp7CUxAk", - "order": null, - "dispute": null - }, - "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", - "created_at": "2014-02-07T22:35:06.424855Z", - "transaction_number": "RF424-240-3258", - "updated_at": "2014-02-07T22:35:07.655276Z", - "currency": "USD", - "amount": 50, - "meta": {}, - "id": "RFJ4N00zLaQFrfBkC8cbN68" - } - ] -} -RESPONSE + <<~RESPONSE + { + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" + }, + "refunds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "debit": "WDAtJcbjh3EJLW0tp7CUxAk", + "order": null, + "dispute": null + }, + "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", + "created_at": "2014-02-07T22:35:06.424855Z", + "transaction_number": "RF424-240-3258", + "updated_at": "2014-02-07T22:35:07.655276Z", + "currency": "USD", + "amount": 50, + "meta": {}, + "id": "RFJ4N00zLaQFrfBkC8cbN68" + } + ] + } + RESPONSE end def refunds_pending_response - <<-RESPONSE -{ - "links": { - "refunds.dispute": "/disputes/{refunds.dispute}", - "refunds.events": "/refunds/{refunds.id}/events", - "refunds.debit": "/debits/{refunds.debit}", - "refunds.order": "/orders/{refunds.order}" - }, - "refunds": [ - { - "status": "pending", - "description": null, - "links": { - "debit": "WD7AT5AGKI0jccoElAEEqiuL", - "order": null, - "dispute": null - }, - "href": "/refunds/RF46a5p6ZVMK4qVIeCJ8u2LE", - "created_at": "2014-05-22T20:20:32.956467Z", - "transaction_number": "RF485-302-2551", - "updated_at": "2014-05-22T20:20:35.991553Z", - "currency": "USD", - "amount": 100, - "meta": {}, - "id": "RF46a5p6ZVMK4qVIeCJ8u2LE" - } - ] -} -RESPONSE + <<~RESPONSE + { + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" + }, + "refunds": [ + { + "status": "pending", + "description": null, + "links": { + "debit": "WD7AT5AGKI0jccoElAEEqiuL", + "order": null, + "dispute": null + }, + "href": "/refunds/RF46a5p6ZVMK4qVIeCJ8u2LE", + "created_at": "2014-05-22T20:20:32.956467Z", + "transaction_number": "RF485-302-2551", + "updated_at": "2014-05-22T20:20:35.991553Z", + "currency": "USD", + "amount": 100, + "meta": {}, + "id": "RF46a5p6ZVMK4qVIeCJ8u2LE" + } + ] + } + RESPONSE end end diff --git a/test/unit/gateways/bambora_apac_test.rb b/test/unit/gateways/bambora_apac_test.rb index 3b4e62c2977..c31335dfdc4 100644 --- a/test/unit/gateways/bambora_apac_test.rb +++ b/test/unit/gateways/bambora_apac_test.rb @@ -16,7 +16,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, order_id: 1) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{username<}, data) assert_match(%r{password<}, data) @@ -48,7 +48,7 @@ def test_failed_purchase def test_successful_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, order_id: 1) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{1<}, data) assert_match(%r{2<}, data) @@ -61,7 +61,7 @@ def test_successful_authorize def test_successful_capture response = stub_comms do @gateway.capture(@amount, 'receipt') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{receipt<}, data) assert_match(%r{100<}, data) @@ -73,7 +73,7 @@ def test_successful_capture def test_successful_refund response = stub_comms do @gateway.refund(@amount, 'receipt') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{receipt<}, data) assert_match(%r{100<}, data) @@ -84,9 +84,10 @@ def test_successful_refund def test_successful_void response = stub_comms do - @gateway.void(@amount, 'receipt') - end.check_request do |endpoint, data, headers| + @gateway.void('receipt', amount: 200) + end.check_request do |_endpoint, data, _headers| assert_match(%r{200<}, data) end.respond_with(successful_void_response) assert_success response @@ -100,133 +101,133 @@ def test_scrub private def pre_scrubbed - <<-'PRE_SCRUBBED' -opening connection to demo.ippayments.com.au:443... -opened -starting SSL for demo.ippayments.com.au:443... -SSL established -<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" -<- "\n\n \n \n \n \n 1\n \n 1\n \n 4005550000000001\n 09\n 2015\n 123\n Longbob Longsen\n \n \n nmi.api\n qwerty123\n \n \n\n]]>\n \n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Server: Microsoft-IIS/6.0\r\n" --> "X-Robots-Tag: noindex\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Cache-Control: private, max-age=0\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Length: 767\r\n" --> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 767 bytes... --> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" -read 767 bytes -Conn close + <<~'PRE_SCRUBBED' + opening connection to demo.ippayments.com.au:443... + opened + starting SSL for demo.ippayments.com.au:443... + SSL established + <- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" + <- "\n\n \n \n \n \n 1\n \n 1\n \n 4005550000000001\n 09\n 2015\n 123\n Longbob Longsen\n \n \n nmi.api\n qwerty123\n \n \n\n]]>\n \n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Microsoft-IIS/6.0\r\n" + -> "X-Robots-Tag: noindex\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Length: 767\r\n" + -> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 767 bytes... + -> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" + read 767 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-'POST_SCRUBBED' -opening connection to demo.ippayments.com.au:443... -opened -starting SSL for demo.ippayments.com.au:443... -SSL established -<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" -<- "\n\n \n \n \n \n 1\n \n 1\n \n [FILTERED]\n 09\n 2015\n [FILTERED]\n Longbob Longsen\n \n \n nmi.api\n [FILTERED]\n \n \n\n]]>\n \n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Server: Microsoft-IIS/6.0\r\n" --> "X-Robots-Tag: noindex\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Cache-Control: private, max-age=0\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Length: 767\r\n" --> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 767 bytes... --> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" -read 767 bytes -Conn close + <<~'POST_SCRUBBED' + opening connection to demo.ippayments.com.au:443... + opened + starting SSL for demo.ippayments.com.au:443... + SSL established + <- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" + <- "\n\n \n \n \n \n 1\n \n 1\n \n [FILTERED]\n 09\n 2015\n [FILTERED]\n Longbob Longsen\n \n \n nmi.api\n [FILTERED]\n \n \n\n]]>\n \n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Microsoft-IIS/6.0\r\n" + -> "X-Robots-Tag: noindex\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Length: 767\r\n" + -> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 767 bytes... + -> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" + read 767 bytes + Conn close POST_SCRUBBED end def successful_purchase_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:07:39</Timestamp> - <Receipt>89435577</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:07:39</Timestamp> + <Receipt>89435577</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def failed_purchase_response - <<-XML -<Response> - <ResponseCode>1</ResponseCode> - <Timestamp>20-Dec-2014 04:14:56</Timestamp> - <Receipt></Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode>05</DeclinedCode> - <DeclinedMessage>Do Not Honour</DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>1</ResponseCode> + <Timestamp>20-Dec-2014 04:14:56</Timestamp> + <Receipt></Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode>05</DeclinedCode> + <DeclinedMessage>Do Not Honour</DeclinedMessage> + </Response> + XML end def successful_authorize_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:18:13</Timestamp> - <Receipt>89435583</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:18:13</Timestamp> + <Receipt>89435583</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def successful_capture_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:18:15</Timestamp> - <Receipt>89435584</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:18:15</Timestamp> + <Receipt>89435584</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def successful_refund_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:24:51</Timestamp> - <Receipt>89435596</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:24:51</Timestamp> + <Receipt>89435596</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def successful_void_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> - </Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end end diff --git a/test/unit/gateways/banwire_test.rb b/test/unit/gateways/banwire_test.rb index faa341c1f97..5bbc177913a 100644 --- a/test/unit/gateways/banwire_test.rb +++ b/test/unit/gateways/banwire_test.rb @@ -5,32 +5,37 @@ class BanwireTest < Test::Unit::TestCase def setup @gateway = BanwireGateway.new( - :login => 'desarrollo', - :currency => 'MXN') - - @credit_card = credit_card('5204164299999999', - :month => 11, - :year => 2012, - :verification_value => '999') + login: 'desarrollo', + currency: 'MXN' + ) + + @credit_card = credit_card( + '5204164299999999', + month: 11, + year: 2012, + verification_value: '999' + ) @amount = 100 @options = { - :order_id => '1', - :email => 'test@email.com', - :billing_address => address, - :description => 'Store purchase' + order_id: '1', + email: 'test@email.com', + billing_address: address, + description: 'Store purchase' } - @amex_credit_card = credit_card('375932134599999', - :month => 3, - :year => 2017, - :first_name => 'Banwire', - :last_name => 'Test Card') + @amex_credit_card = credit_card( + '375932134599999', + month: 3, + year: 2017, + first_name: 'Banwire', + last_name: 'Test Card' + ) @amex_options = { - :order_id => '2', - :email => 'test@email.com', - :billing_address => address(:address1 => 'Horacio', :zip => 11560), - :description => 'Store purchase amex' + order_id: '2', + email: 'test@email.com', + billing_address: address(address1: 'Horacio', zip: 11560), + description: 'Store purchase amex' } end @@ -65,7 +70,7 @@ def test_invalid_json def test_successful_amex_purchase response = stub_comms do @gateway.purchase(@amount, @amex_credit_card, @amex_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/post_code=11560/, data) end.respond_with(successful_purchase_amex_response) diff --git a/test/unit/gateways/barclaycard_smartpay_test.rb b/test/unit/gateways/barclaycard_smartpay_test.rb index 2bb8443b85d..ee0c11c4da3 100644 --- a/test/unit/gateways/barclaycard_smartpay_test.rb +++ b/test/unit/gateways/barclaycard_smartpay_test.rb @@ -46,37 +46,38 @@ def setup } @options_with_shipping_house_number_and_shipping_street = { - order_id: '1', - street: 'Top Level Drive', - house_number: '1000', - billing_address: address, - shipping_house_number: '999', - shipping_street: 'Downtown Loop', - shipping_address: { - name: 'PU JOI SO', - address1: '新北市店溪路3579號139樓', - company: 'Widgets Inc', - city: '新北市', - zip: '231509', - country: 'TW', - phone: '(555)555-5555', - fax: '(555)555-6666' - }, - description: 'Store Purchase' + order_id: '1', + street: 'Top Level Drive', + house_number: '1000', + billing_address: address, + shipping_house_number: '999', + shipping_street: 'Downtown Loop', + shipping_address: { + name: 'PU JOI SO', + address1: '新北市店溪路3579號139樓', + company: 'Widgets Inc', + city: '新北市', + zip: '231509', + country: 'TW', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + description: 'Store Purchase' } @options_with_credit_fields = { order_id: '1', - billing_address: { - name: 'Jim Smith', - address1: '100 Street', - company: 'Widgets Inc', - city: 'Ottawa', - state: 'ON', - zip: 'K1C2N6', - country: 'CA', - phone: '(555)555-5555', - fax: '(555)555-6666'}, + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, email: 'long@bob.com', customer: 'Longbob Longsen', description: 'Store Purchase', @@ -92,14 +93,38 @@ def setup @avs_address = @options.clone @avs_address.update(billing_address: { - name: 'Jim Smith', - street: 'Test AVS result', - houseNumberOrName: '2', - city: 'Cupertino', - state: 'CA', - zip: '95014', - country: 'US' - }) + name: 'Jim Smith', + street: 'Test AVS result', + houseNumberOrName: '2', + city: 'Cupertino', + state: 'CA', + zip: '95014', + country: 'US' + }) + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + }, + notification_url: 'https://example.com/notification' + } + } end def test_successful_purchase @@ -115,7 +140,7 @@ def test_successful_purchase def test_successful_authorize_with_alternate_address response = stub_comms do @gateway.authorize(@amount, @credit_card, @options_with_alternate_address) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/billingAddress.houseNumberOrName=%E6%96%B0%E5%8C%97%E5%B8%82%E5%BA%97%E6%BA%AA%E8%B7%AF3579%E8%99%9F139%E6%A8%93/, data) assert_match(/billingAddress.street=Not\+Provided/, data) end.respond_with(successful_authorize_response) @@ -127,10 +152,8 @@ 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) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options_with_house_number_and_street) + end.check_request do |_endpoint, data, _headers| assert_match(/billingAddress.street=Top\+Level\+Drive/, data) assert_match(/billingAddress.houseNumberOrName=1000/, data) end.respond_with(successful_authorize_response) @@ -142,10 +165,8 @@ 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) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options_with_shipping_house_number_and_shipping_street) + end.check_request do |_endpoint, data, _headers| assert_match(/billingAddress.street=Top\+Level\+Drive/, data) assert_match(/billingAddress.houseNumberOrName=1000/, data) assert_match(/deliveryAddress.street=Downtown\+Loop/, data) @@ -158,11 +179,24 @@ def test_successful_authorize_with_shipping_house_number_and_street end def test_successful_authorize_with_extra_options + shopper_interaction = 'ContAuth' + shopper_statement = 'One-year premium subscription' + device_fingerprint = 'abcde123' + response = stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(shopper_interaction: 'ContAuth', device_fingerprint: 'abcde123')) - end.check_request do |endpoint, data, headers| - assert_match(/shopperInteraction=ContAuth/, data) - assert_match(/deviceFingerprint=abcde123/, data) + @gateway.authorize( + @amount, + @credit_card, + @options.merge( + shopper_interaction: shopper_interaction, + device_fingerprint: device_fingerprint, + shopper_statement: shopper_statement + ) + ) + end.check_request do |_endpoint, data, _headers| + assert_match(/shopperInteraction=#{shopper_interaction}/, data) + assert_match(/shopperStatement=#{Regexp.quote(CGI.escape(shopper_statement))}/, data) + assert_match(/deviceFingerprint=#{device_fingerprint}/, data) end.respond_with(successful_authorize_response) assert_success response @@ -189,6 +223,18 @@ def test_successful_authorize_with_3ds assert response.test? end + def test_successful_authorize_with_3ds2_browser_client_data + @gateway.stubs(:ssl_post).returns(successful_authorize_with_3ds2_response) + + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options) + assert response.test? + assert_equal '8815609737078177', response.authorization + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + def test_failed_authorize @gateway.stubs(:ssl_post).returns(failed_authorize_response) @@ -207,7 +253,7 @@ def test_successful_capture end def test_failed_capture - @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_capture_response))) + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(code: '422', body: failed_capture_response))) response = @gateway.capture(@amount, '0000000000000000', @options) assert_failure response @@ -218,7 +264,7 @@ def test_failed_capture def test_legacy_capture_psp_reference_passed_for_refund response = stub_comms do @gateway.refund(@amount, '8814002632606717', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/originalReference=8814002632606717/, data) end.respond_with(successful_refund_response) @@ -229,7 +275,7 @@ def test_legacy_capture_psp_reference_passed_for_refund def test_successful_refund response = stub_comms do @gateway.refund(@amount, '7914002629995504#8814002632606717', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/originalReference=7914002629995504&/, data) assert_no_match(/8814002632606717/, data) end.respond_with(successful_refund_response) @@ -239,7 +285,7 @@ def test_successful_refund end def test_failed_refund - @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_refund_response))) + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(code: '422', body: failed_refund_response))) response = @gateway.refund(@amount, '0000000000000000', @options) assert_failure response @@ -264,7 +310,7 @@ def test_failed_credit def test_credit_contains_all_fields response = stub_comms do @gateway.credit(@amount, @credit_card, @options_with_credit_fields) - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| assert_match(%r{/refundWithData}, endpoint) assert_match(/dateOfBirth=1990-10-11&/, data) assert_match(/entityType=NaturalPerson&/, data) @@ -278,9 +324,9 @@ def test_credit_contains_all_fields def test_successful_third_party_payout response = stub_comms do - @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({third_party_payout: true})) - end.check_request do |endpoint, data, headers| - if /storeDetailAndSubmitThirdParty/ =~ endpoint + @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({ third_party_payout: true })) + end.check_request do |endpoint, data, _headers| + if /storeDetailAndSubmitThirdParty/.match?(endpoint) assert_match(%r{/storeDetailAndSubmitThirdParty}, endpoint) assert_match(/dateOfBirth=1990-10-11&/, data) assert_match(/entityType=NaturalPerson&/, data) @@ -322,9 +368,9 @@ def test_unsuccessful_verify def test_authorize_nonfractional_currency response = stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'JPY')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/amount.value=1/, data) - assert_match(/amount.currency=JPY/, data) + assert_match(/amount.currency=JPY/, data) end.respond_with(successful_authorize_response) assert_success response @@ -333,9 +379,9 @@ def test_authorize_nonfractional_currency def test_authorize_three_decimal_currency response = stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'OMR')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/amount.value=100/, data) - assert_match(/amount.currency=OMR/, data) + assert_match(/amount.currency=OMR/, data) end.respond_with(successful_authorize_response) assert_success response @@ -349,13 +395,67 @@ def test_successful_store end def test_failed_store - @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_store_response))) + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(code: '422', body: failed_store_response))) response = @gateway.store(@credit_card, @options) assert_failure response assert response.test? end + def test_execute_threed_false_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options.merge({ execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + refute_match(/additionalData.scaExemption/, data) + assert_match(/additionalData.executeThreeD=false/, data) + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_if_execute_threed_missing_3ds2 + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options.merge({ scaExemption: 'lowValue' })) + end.check_request do |_endpoint, data, _headers| + refute_match(/additionalData.scaExemption/, data) + refute_match(/additionalData.executeThreeD=false/, data) + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_and_execute_threed_false_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options.merge({ sca_exemption: 'lowValue', execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + assert_match(/additionalData.scaExemption=lowValue/, data) + assert_match(/additionalData.executeThreeD=false/, data) + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_and_execute_threed_true_sent_3ds2 + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options.merge({ sca_exemption: 'lowValue', execute_threed: true })) + end.check_request do |_endpoint, data, _headers| + assert_match(/additionalData.scaExemption=lowValue/, data) + assert_match(/additionalData.executeThreeD=true/, data) + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_when_execute_threed_true_3ds1 + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ sca_exemption: 'lowValue', execute_threed: true })) + end.check_request do |_endpoint, data, _headers| + refute_match(/additionalData.scaExemption/, data) + assert_match(/additionalData.executeThreeD=true/, data) + end.respond_with(successful_authorize_response) + end + + def test_sca_exemption_not_sent_when_execute_threed_false_3ds1 + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ sca_exemption: 'lowValue', execute_threed: false })) + end.check_request do |_endpoint, data, _headers| + refute_match(/additionalData.scaExemption/, data) + refute_match(/additionalData.executeThreeD/, data) + end.respond_with(successful_authorize_response) + end + def test_avs_result @gateway.expects(:ssl_post).returns(failed_avs_response) @@ -395,6 +495,10 @@ def successful_authorize_with_3ds_response 'pspReference=8815161318854998&resultCode=RedirectShopper&issuerUrl=https%3A%2F%2Ftest.adyen.com%2Fhpp%2F3d%2Fvalidate.shtml&md=WIFa2sF3CuPyN53Txjt3U%2F%2BDuCsddzywiY5NLgEAdUAXPksHUzXL5E%2BsfvdpolkGWR8b1oh%2FNA3jNaUP9UCgfjhXqRslGFy9OGqcZ1ITMz54HHm%2FlsCKN9bTftKnYA4F7GqvOgcIIrinUZjbMvW9doGifwzSqYLo6ASOm6bARL5n7cIFV8IWtA2yPlO%2FztKSTRJt1glN4s8sMcpE57z4soWKMuycbdXdpp6d4ZRSa%2F1TPF0MnJF0zNaSAAkw9JpXqGMOz5sFF2Smpc38HXJzM%2FV%2B1mmoDhhWmXXOb5YQ0QSCS7DXKIcr8ZtuGuGmFp0QOfZiO41%2B2I2N7VhONVx8xSn%2BLu4m6vaDIg5qsnd9saxaWwbJpl9okKm6pB2MJap9ScuBCcvI496BPCrjQ2LHxvDWhk6M3Exemtv942NQIGlsiPaW0KXoC2dQvBsxWh0K&paRequest=eNpVUtuOgjAQ%2FRXj%2B1KKoIWMTVgxWR%2B8RNkPaMpEycrFUlb8%2B20B190%2BnXPm0pnTQnpRiMkJZauQwxabRpxxkmfLacQYDeiczihjgR%2BGbMrhEB%2FxxuEbVZNXJaeO63hAntSUK3kRpeYg5O19s%2BPUm%2FnBHMhIoUC1SXiKjT4URSxvba5QARlkKEWB%2FFSbgbLr41QIpXFVFUB6HWTVllo9OPNMwyeBVl35Reu6iQi53%2B9OM5Y7sipMVqmF1G9tA8QmAnlNeGgtakzjLs%2F4Pjl3u3TtbdNtZzDdJV%2FBPu7PEojNgExo5J5LmUvpfELDyPcjPwDS6yAKOxFffx4nxhXXrDwIUNt74oFQG%2FgrgLFdYSkfPFwws9WTAXZ1VaLJMPb%2BYiCvoVcf1mSpjW%2B%2BN9i8YKFr0MLa3Qdsl9yYREM37NtYAsSWkvElyfjiBv37CT9ySbE1' end + def successful_authorize_with_3ds2_response + 'additionalData.threeds2.threeDS2Token=BQABAQB9sBAzFS%2BrvT1fuY78N4P5BA5DO6s9Y6jCIzvMcH%2Bk5%2B0ms8dRPEZZhO8CYx%2Fa5NCl8r4vyJj0nI0HZ9CBl%2FQLxtGLYfVu6sNxZc9xZry%2Bm24pBGTtHsd4vunorPNPAGlYWHBXtf4h0Sj9Qy0bzlau7a%2Feayi1cpjbfV%2B8Eqw%2FAod1B80heU8sX2DKm5SHlR4o0qTu0WQUSJfKRxjdJ1AntgAxjYo3uFUlU%2FyhNpdRiAxgauLImbllfQTGVTcYBQXsY9FSakfAZRW1kT7bNMraCvRUpp4o1Z5ZezJxPcksfCEzFVPyJYcTvcV4odQK4tT6imRLRvG1OgUVNzNAuDBnEJtFOC%2BE5YwAwfKuloCqB9oAAOzL5ZHXOXPASY2ehJ3RaCZjqj5vmAX8L9GY35FV8q49skYZpzIvlMICWjErI2ayKMCiXHFDE54f2GJEhVRKpY9s506740UGQc0%2FMgbKyLyqtU%2BRG30BwA9bSt3NQKchm9xoOL7U%2Bzm6OIeikmw94TBq%2BmBN7SdQi%2BK2W4yfMkqFsl7hc7HHBa%2BOc6At7wxxdxCLg6wksQmDxElXeQfFkWvoBuR96fIHaXILnVHKjWcTbeulXBhVPA5Y47MLEtZL3G8k%2BzKTFUCW7O0MN2WxUoMBT8foan1%2B9QhZejEqiamreIs56PLQkJvhigyRQmiqwnVjXiFOv%2FEcWn0Z6IM2TnAfw3Kd2KwZ9JaePLtZ2Ck7%2FUEsdt1Kj2HYeE86WM4PESystER5oBT12xWXvbp8CEA7Mulmpd3bkiMl5IVRoSBL5pl4qZd1CrnG%2FeuvtXYTsN%2FdA%2BIcWwiLiXpmSwqaRB8DfChwouuNMAAkfKhQ6b3vLAToc3o%2B3Xa1QetsK8GI1pmjkoZRvLd2xfGhVe%2FmCl23wzQsAicwB9ZXXMgWbaS2OwdwsISQGOmsWrajzp7%2FvR0T4aHqJlrFvKnc9BrWEWbDi8g%2BDFZ2E2ifhFYSYhrHVA7yOIIDdTQnH3CIzaevxUAnbIyFsxrhy8USdP6R6CdJZ%2Bg0rIJ5%2FeZ5P8JjDiYJWi5FDJwy%2BNP9PQIFFim6psbELCtnAaW1m7pU1FeNwjYUGIdVD2f%2BVYJe4cWHPCaWAAsARNXTzjrfUEq%2BpEYDcs%2FLyTB8f69qSrmTSDGsCETsNNy27LY%2BtodGDKsxtW35jIqoV8l2Dra3wucman8nIZp3VTNtNvZDCqWetLXxBbFVZN6ecuoMPwhER5MBFUrkkXCSSFBK%2FNGp%2FXaEDP6A2hmUKvXikL3F9S7MIKQCUYC%2FI7K4DFYFBjTBzN4%3D&additionalData.threeds2.threeDSServerTransID=efbf9d05-5e6b-4659-a64e-f1dfa5d846c4&additionalData.threeds2.threeDSMethodURL=https%3A%2F%2Fpal-test.adyen.com%2Fthreeds2simulator%2Facs%2FstartMethod.shtml&pspReference=8815609737078177&resultCode=IdentifyShopper' + end + def failed_authorize_response 'pspReference=7914002630895750&refusalReason=Refused&resultCode=Refused' end @@ -544,5 +648,4 @@ def scrubbed_transcript Conn close ) end - end diff --git a/test/unit/gateways/barclays_epdq_extra_plus_test.rb b/test/unit/gateways/barclays_epdq_extra_plus_test.rb index 330bdf13d4d..194173a69c9 100644 --- a/test/unit/gateways/barclays_epdq_extra_plus_test.rb +++ b/test/unit/gateways/barclays_epdq_extra_plus_test.rb @@ -2,21 +2,21 @@ class BarclaysEpdqExtraPlusTest < Test::Unit::TestCase def setup - @credentials = { :login => 'pspid', - :user => 'username', - :password => 'password', - :signature => 'mynicesig', - :signature_encryptor => 'sha512' } + @credentials = { login: 'pspid', + user: 'username', + password: 'password', + signature: 'mynicesig', + signature_encryptor: 'sha512' } @gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) @credit_card = credit_card - @mastercard = credit_card('5399999999999999', :brand => 'mastercard') + @mastercard = credit_card('5399999999999999', brand: 'mastercard') @amount = 100 @identification = '3014726' @billing_id = 'myalias' @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @parameters = { 'orderID' => '1', @@ -54,7 +54,7 @@ def test_successful_purchase_with_action_param @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:action => 'SAS')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(action: 'SAS')) assert_success response assert_equal '3014726;SAS', response.authorization assert response.params['HTML_ANSWER'].nil? @@ -74,7 +74,7 @@ def test_successful_purchase_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:eci => 4)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(eci: 4)) assert_success response assert_equal '3014726;SAL', response.authorization assert response.test? @@ -82,7 +82,7 @@ def test_successful_purchase_with_custom_eci def test_successful_purchase_with_3dsecure @gateway.expects(:ssl_post).returns(successful_3dsecure_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:d3d => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(d3d: true)) assert_success response assert_equal '3014726;SAL', response.authorization assert response.params['HTML_ANSWER'] @@ -115,7 +115,7 @@ def test_successful_authorize_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:eci => 4)) + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(eci: 4)) assert_success response assert_equal '3014726;RES', response.authorization assert response.test? @@ -123,7 +123,7 @@ def test_successful_authorize_with_custom_eci def test_successful_authorize_with_3dsecure @gateway.expects(:ssl_post).returns(successful_3dsecure_purchase_response) - assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:d3d => true)) + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(d3d: true)) assert_success response assert_equal '3014726;RES', response.authorization assert response.params['HTML_ANSWER'] @@ -141,7 +141,7 @@ def test_successful_capture def test_successful_capture_with_action_option @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, '3048326', :action => 'SAS') + assert response = @gateway.capture(@amount, '3048326', action: 'SAS') assert_success response assert_equal '3048326;SAS', response.authorization assert response.test? @@ -185,7 +185,7 @@ def test_successful_store @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) - assert response = @gateway.store(@credit_card, :billing_id => @billing_id) + assert response = @gateway.store(@credit_card, billing_id: @billing_id) assert_success response assert_equal '3014726;RES', response.authorization assert_equal '2', response.billing_id @@ -197,7 +197,7 @@ def test_deprecated_store_option @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE) do - assert response = @gateway.store(@credit_card, :store => @billing_id) + assert response = @gateway.store(@credit_card, store: @billing_id) assert_success response assert_equal '3014726;RES', response.authorization assert response.test? @@ -225,7 +225,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro], BarclaysEpdqExtraPlusGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club discover jcb maestro], BarclaysEpdqExtraPlusGateway.supported_cardtypes end def test_default_currency @@ -239,7 +239,7 @@ def test_default_currency end def test_custom_currency_at_gateway_level - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:currency => 'USD')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(currency: 'USD')) gateway.expects(:add_pair).at_least(1) gateway.expects(:add_pair).with(anything, 'currency', 'USD') gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -247,11 +247,11 @@ def test_custom_currency_at_gateway_level end def test_local_custom_currency_overwrite_gateway_level - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:currency => 'USD')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(currency: 'USD')) gateway.expects(:add_pair).at_least(1) gateway.expects(:add_pair).with(anything, 'currency', 'EUR') gateway.expects(:ssl_post).returns(successful_purchase_response) - gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'EUR')) + gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR')) end def test_avs_result @@ -310,13 +310,13 @@ def test_format_error_message_with_no_separator end def test_without_signature - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => nil)) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature: nil, signature_encryptor: nil)) gateway.expects(:ssl_post).returns(successful_purchase_response) assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) do gateway.purchase(@amount, @credit_card, @options) end - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => 'none')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature: nil, signature_encryptor: 'none')) gateway.expects(:ssl_post).returns(successful_purchase_response) assert_no_deprecation_warning do gateway.purchase(@amount, @credit_card, @options) @@ -324,27 +324,27 @@ def test_without_signature end def test_signature_for_accounts_created_before_10_may_20101 - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature_encryptor => nil)) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature_encryptor: nil)) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA1.hexdigest('1100EUR4111111111111111MrPSPIDRES2mynicesig').upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha1 - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature_encryptor => 'sha1')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature_encryptor: 'sha1')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA1.hexdigest(string_to_digest).upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha256 - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature_encryptor => 'sha256')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature_encryptor: 'sha256')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA256.hexdigest(string_to_digest).upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha512 - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature_encryptor => 'sha512')) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(signature_encryptor: 'sha512')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA512.hexdigest(string_to_digest).upcase, signature end @@ -359,13 +359,13 @@ def test_3dsecure_win_3ds_option post = {} gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) - gateway.send(:add_d3d, post, { :win_3ds => :pop_up }) + gateway.send(:add_d3d, post, { win_3ds: :pop_up }) assert 'POPUP', post['WIN3DS'] - gateway.send(:add_d3d, post, { :win_3ds => :pop_ix }) + gateway.send(:add_d3d, post, { win_3ds: :pop_ix }) assert 'POPIX', post['WIN3DS'] - gateway.send(:add_d3d, post, { :win_3ds => :invalid }) + gateway.send(:add_d3d, post, { win_3ds: :invalid }) assert 'MAINW', post['WIN3DS'] end @@ -374,14 +374,14 @@ def test_3dsecure_additional_options gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) gateway.send(:add_d3d, post, { - :http_accept => 'text/html', - :http_user_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', - :accept_url => 'https://accept_url', - :decline_url => 'https://decline_url', - :exception_url => 'https://exception_url', - :paramsplus => 'params_plus', - :complus => 'com_plus', - :language => 'fr_FR' + http_accept: 'text/html', + http_user_agent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', + accept_url: 'https://accept_url', + decline_url: 'https://decline_url', + exception_url: 'https://exception_url', + paramsplus: 'params_plus', + complus: 'com_plus', + language: 'fr_FR' }) assert 'HTTP_ACCEPT', 'text/html' assert 'HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)' @@ -427,7 +427,7 @@ def d3d_string_to_digest end def successful_authorize_response - <<-END + <<-XML - END + XML end def successful_purchase_response - <<-END + <<-XML - END + XML end def successful_3dsecure_purchase_response - <<-END + <<-XML - END + XML end def failed_purchase_response - <<-END + <<-XML - END + XML end def successful_capture_response - <<-END + <<-XML - END + XML end def successful_void_response - <<-END + <<-XML - END + XML end def successful_referenced_credit_response - <<-END + <<-XML - END + XML end def successful_unreferenced_credit_response - <<-END + <<-XML - END + XML end def test_failed_authorization_due_to_unknown_order_number - <<-END + <<-XML - END + XML end def transcript diff --git a/test/unit/gateways/be2bill_test.rb b/test/unit/gateways/be2bill_test.rb index d83f42e42b5..e4ca81f6381 100644 --- a/test/unit/gateways/be2bill_test.rb +++ b/test/unit/gateways/be2bill_test.rb @@ -3,17 +3,17 @@ class Be2billTest < Test::Unit::TestCase def setup @gateway = Be2billGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -41,11 +41,11 @@ def test_unsuccessful_request # Place raw successful response from gateway here def successful_purchase_response - {'OPERATIONTYPE'=>'payment', 'TRANSACTIONID'=>'A189063', 'EXECCODE'=>'0000', 'MESSAGE'=>'The transaction has been accepted.', 'ALIAS'=>'A189063', 'DESCRIPTOR'=>'RENTABILITEST'}.to_json + { 'OPERATIONTYPE' => 'payment', 'TRANSACTIONID' => 'A189063', 'EXECCODE' => '0000', 'MESSAGE' => 'The transaction has been accepted.', 'ALIAS' => 'A189063', 'DESCRIPTOR' => 'RENTABILITEST' }.to_json end # Place raw failed response from gateway here def failed_purchase_response - {'OPERATIONTYPE'=>'payment', 'TRANSACTIONID'=>'A189063', 'EXECCODE'=>'1001', 'MESSAGE'=>"The parameter \"CARDCODE\" is missing.\n", 'DESCRIPTOR'=>'RENTABILITEST'}.to_json + { 'OPERATIONTYPE' => 'payment', 'TRANSACTIONID' => 'A189063', 'EXECCODE' => '1001', 'MESSAGE' => "The parameter \"CARDCODE\" is missing.\n", 'DESCRIPTOR' => 'RENTABILITEST' }.to_json end end diff --git a/test/unit/gateways/beanstream_interac_test.rb b/test/unit/gateways/beanstream_interac_test.rb index a0a7bac3862..71f7d3f9dda 100644 --- a/test/unit/gateways/beanstream_interac_test.rb +++ b/test/unit/gateways/beanstream_interac_test.rb @@ -3,16 +3,16 @@ class BeanstreamInteracTest < Test::Unit::TestCase def setup @gateway = BeanstreamInteracGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end diff --git a/test/unit/gateways/beanstream_test.rb b/test/unit/gateways/beanstream_test.rb index c07adf22c0e..fa9f748c9ff 100644 --- a/test/unit/gateways/beanstream_test.rb +++ b/test/unit/gateways/beanstream_test.rb @@ -7,11 +7,11 @@ def setup Base.mode = :test @gateway = BeanstreamGateway.new( - :login => 'merchant id', - :user => 'username', - :password => 'password', - :api_key => 'api_key' - ) + login: 'merchant id', + user: 'username', + password: 'password', + api_key: 'api_key' + ) @credit_card = credit_card @@ -25,42 +25,43 @@ def setup transaction_id: 'transaction ID' ) - @check = check( - :institution_number => '001', - :transit_number => '26729' - ) + @check = check( + institution_number: '001', + transit_number: '26729' + ) @amount = 1000 @options = { - :order_id => '1234', - :billing_address => { - :name => 'xiaobo zzz', - :phone => '555-555-5555', - :address1 => '1234 Levesque St.', - :address2 => 'Apt B', - :city => 'Montreal', - :state => 'QC', - :country => 'CA', - :zip => 'H2C1X8' + order_id: '1234', + billing_address: { + name: 'xiaobo zzz', + phone: '555-555-5555', + address1: '1234 Levesque St.', + address2: 'Apt B', + city: 'Montreal', + state: 'QC', + country: 'CA', + zip: 'H2C1X8' }, - :email => 'xiaobozzz@example.com', - :subtotal => 800, - :shipping => 100, - :tax1 => 100, - :tax2 => 100, - :custom => 'reference one' + email: 'xiaobozzz@example.com', + subtotal: 800, + shipping: 100, + tax1: 100, + tax2: 100, + custom: 'reference one' } @recurring_options = @options.merge( - :interval => { :unit => :months, :length => 1 }, - :occurrences => 5) + interval: { unit: :months, length: 1 }, + occurrences: 5 + ) end def test_successful_purchase response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| refute_match(/recurringPayment=true/, data) end.respond_with(successful_purchase_response) @@ -71,7 +72,7 @@ def test_successful_purchase def test_successful_purchase_with_recurring response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options.merge(recurring: true)) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/recurringPayment=1/, data) end.respond_with(successful_purchase_response) @@ -81,7 +82,7 @@ def test_successful_purchase_with_recurring def test_successful_authorize_with_recurring response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@amount, @decrypted_credit_card, @options.merge(recurring: true)) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/recurringPayment=1/, data) end.respond_with(successful_purchase_response) @@ -210,7 +211,7 @@ def test_successful_update_recurring @gateway.expects(:ssl_post).returns(successful_update_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.update_recurring(@amount, @credit_card, @recurring_options.merge(:account_id => response.params['rbAccountId'])) + @gateway.update_recurring(@amount, @credit_card, @recurring_options.merge(account_id: response.params['rbAccountId'])) end assert_success response assert_equal 'Request successful', response.message @@ -228,14 +229,14 @@ def test_successful_cancel_recurring @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.cancel_recurring(:account_id => response.params['rbAccountId']) + @gateway.cancel_recurring(account_id: response.params['rbAccountId']) end assert_success response assert_equal 'Request successful', response.message end def test_ip_is_being_sent - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data =~ /customerIp=123\.123\.123\.123/ end.returns(successful_purchase_response) @@ -246,7 +247,7 @@ def test_ip_is_being_sent def test_includes_network_tokenization_fields response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/3DSecureXID/, data) assert_match(/3DSecureECI/, data) assert_match(/3DSecureCAVV/, data) @@ -261,7 +262,7 @@ def test_defaults_state_and_zip_with_country @options[:shipping_address] = address response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ordProvince=--/, data) assert_match(/ordPostalCode=000000/, data) assert_match(/shipProvince=--/, data) @@ -272,12 +273,12 @@ def test_defaults_state_and_zip_with_country end def test_no_state_and_zip_default_with_missing_country - address = { } + address = {} @options[:billing_address] = address @options[:shipping_address] = address response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_no_match(/ordProvince=--/, data) assert_no_match(/ordPostalCode=000000/, data) assert_no_match(/shipProvince=--/, data) @@ -293,7 +294,7 @@ def test_sends_email_without_addresses @options[:shipping_email] = 'ship@mail.com' response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @decrypted_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ordEmailAddress=xiaobozzz%40example.com/, data) assert_match(/shipEmailAddress=ship%40mail.com/, data) end.respond_with(successful_purchase_response) @@ -301,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 @@ -340,11 +354,11 @@ def unsuccessful_void_response end def brazilian_address_params_without_zip_and_state - { :shipProvince => '--', :shipPostalCode => '000000', :ordProvince => '--', :ordPostalCode => '000000', :ordCountry => 'BR', :trnCardOwner => 'Longbob Longsen', :shipCity => 'Rio de Janeiro', :ordAddress1 => '1234 Levesque St.', :ordShippingPrice => '1.00', :deliveryEstimate => nil, :shipName => 'xiaobo zzz', :trnCardNumber => '4242424242424242', :trnAmount => '10.00', :trnType => 'P', :ordAddress2 => 'Apt B', :ordTax1Price => '1.00', :shipEmailAddress => 'xiaobozzz@example.com', :trnExpMonth => '09', :ordCity => 'Rio de Janeiro', :shipPhoneNumber => '555-555-5555', :ordName => 'xiaobo zzz', :trnExpYear => next_year, :trnOrderNumber => '1234', :shipCountry => 'BR', :ordTax2Price => '1.00', :shipAddress1 => '1234 Levesque St.', :ordEmailAddress => 'xiaobozzz@example.com', :trnCardCvd => '123', :trnComments => nil, :shippingMethod => nil, :ref1 => 'reference one', :shipAddress2 => 'Apt B', :ordPhoneNumber => '555-555-5555', :ordItemPrice => '8.00' } + { shipProvince: '--', shipPostalCode: '000000', ordProvince: '--', ordPostalCode: '000000', ordCountry: 'BR', trnCardOwner: 'Longbob Longsen', shipCity: 'Rio de Janeiro', ordAddress1: '1234 Levesque St.', ordShippingPrice: '1.00', deliveryEstimate: nil, shipName: 'xiaobo zzz', trnCardNumber: '4242424242424242', trnAmount: '10.00', trnType: 'P', ordAddress2: 'Apt B', ordTax1Price: '1.00', shipEmailAddress: 'xiaobozzz@example.com', trnExpMonth: '09', ordCity: 'Rio de Janeiro', shipPhoneNumber: '555-555-5555', ordName: 'xiaobo zzz', trnExpYear: next_year, trnOrderNumber: '1234', shipCountry: 'BR', ordTax2Price: '1.00', shipAddress1: '1234 Levesque St.', ordEmailAddress: 'xiaobozzz@example.com', trnCardCvd: '123', trnComments: nil, shippingMethod: nil, ref1: 'reference one', shipAddress2: 'Apt B', ordPhoneNumber: '555-555-5555', ordItemPrice: '8.00' } end def german_address_params_without_state - { :shipProvince => '--', :shipPostalCode => '12345', :ordProvince => '--', :ordPostalCode => '12345', :ordCountry => 'DE', :trnCardOwner => 'Longbob Longsen', :shipCity => 'Berlin', :ordAddress1 => '1234 Levesque St.', :ordShippingPrice => '1.00', :deliveryEstimate => nil, :shipName => 'xiaobo zzz', :trnCardNumber => '4242424242424242', :trnAmount => '10.00', :trnType => 'P', :ordAddress2 => 'Apt B', :ordTax1Price => '1.00', :shipEmailAddress => 'xiaobozzz@example.com', :trnExpMonth => '09', :ordCity => 'Berlin', :shipPhoneNumber => '555-555-5555', :ordName => 'xiaobo zzz', :trnExpYear => next_year, :trnOrderNumber => '1234', :shipCountry => 'DE', :ordTax2Price => '1.00', :shipAddress1 => '1234 Levesque St.', :ordEmailAddress => 'xiaobozzz@example.com', :trnCardCvd => '123', :trnComments => nil, :shippingMethod => nil, :ref1 => 'reference one', :shipAddress2 => 'Apt B', :ordPhoneNumber => '555-555-5555', :ordItemPrice => '8.00' } + { shipProvince: '--', shipPostalCode: '12345', ordProvince: '--', ordPostalCode: '12345', ordCountry: 'DE', trnCardOwner: 'Longbob Longsen', shipCity: 'Berlin', ordAddress1: '1234 Levesque St.', ordShippingPrice: '1.00', deliveryEstimate: nil, shipName: 'xiaobo zzz', trnCardNumber: '4242424242424242', trnAmount: '10.00', trnType: 'P', ordAddress2: 'Apt B', ordTax1Price: '1.00', shipEmailAddress: 'xiaobozzz@example.com', trnExpMonth: '09', ordCity: 'Berlin', shipPhoneNumber: '555-555-5555', ordName: 'xiaobo zzz', trnExpYear: next_year, trnOrderNumber: '1234', shipCountry: 'DE', ordTax2Price: '1.00', shipAddress1: '1234 Levesque St.', ordEmailAddress: 'xiaobozzz@example.com', trnCardCvd: '123', trnComments: nil, shippingMethod: nil, ref1: 'reference one', shipAddress2: 'Apt B', ordPhoneNumber: '555-555-5555', ordItemPrice: '8.00' } end def next_year @@ -370,5 +384,4 @@ def transcript def scrubbed_transcript 'ref1=reference+one&trnCardOwner=Longbob+Longsen&trnCardNumber=[FILTERED]&trnExpMonth=09&trnExpYear=16&trnCardCvd=[FILTERED]&ordName=xiaobo+zzz&ordEmailAddress=xiaobozzz%40example.com&username=awesomesauce&password=[FILTERED]' end - end diff --git a/test/unit/gateways/blue_pay_test.rb b/test/unit/gateways/blue_pay_test.rb index df8f4e62022..d1bc53db5c8 100644 --- a/test/unit/gateways/blue_pay_test.rb +++ b/test/unit/gateways/blue_pay_test.rb @@ -1,11 +1,11 @@ require 'test_helper' RSP = { - :approved_auth => 'AUTH_CODE=XCADZ&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203758&CVV2=_&MESSAGE=Approved%20Auth', - :approved_capture => 'AUTH_CODE=CHTHX&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CAPTURE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203760&CVV2=_&MESSAGE=Approved%20Capture', - :approved_void => 'AUTH_CODE=KTMHB&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=VOID&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203763&CVV2=_&MESSAGE=Approved%20Void', - :declined => 'TRANS_ID=100000000150&STATUS=0&AVS=0&CVV2=7&MESSAGE=Declined&REBID=', - :approved_purchase => 'AUTH_CODE=GYRUY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203767&CVV2=_&MESSAGE=Approved%20Sale' + approved_auth: 'AUTH_CODE=XCADZ&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203758&CVV2=_&MESSAGE=Approved%20Auth', + approved_capture: 'AUTH_CODE=CHTHX&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CAPTURE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203760&CVV2=_&MESSAGE=Approved%20Capture', + approved_void: 'AUTH_CODE=KTMHB&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=VOID&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203763&CVV2=_&MESSAGE=Approved%20Void', + declined: 'TRANS_ID=100000000150&STATUS=0&AVS=0&CVV2=7&MESSAGE=Declined&REBID=', + approved_purchase: 'AUTH_CODE=GYRUY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203767&CVV2=_&MESSAGE=Approved%20Sale' } class BluePayTest < Test::Unit::TestCase @@ -13,20 +13,21 @@ class BluePayTest < Test::Unit::TestCase def setup @gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @amount = 100 @credit_card = credit_card + @check = check @rebill_id = '100096219669' @rebill_status = 'active' - @options = {ip: '192.168.0.1'} + @options = { ip: '192.168.0.1' } end def test_successful_authorization response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192.168.0.1/, data) end.respond_with(RSP[:approved_auth]) @@ -39,7 +40,7 @@ def test_successful_authorization def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192.168.0.1/, data) end.respond_with(RSP[:approved_purchase]) @@ -52,7 +53,7 @@ def test_successful_purchase def test_failed_authorization response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192.168.0.1/, data) end.respond_with(RSP[:declined]) @@ -65,8 +66,8 @@ def test_failed_authorization def test_add_address_outsite_north_america result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'DE', :state => ''}) - assert_equal ['ADDR1', 'ADDR2', 'CITY', 'COMPANY_NAME', 'COUNTRY', 'PHONE', 'STATE', 'ZIP'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, billing_address: { address1: '123 Test St.', address2: '5F', city: 'Testville', company: 'Test Company', country: 'DE', state: '' }) + assert_equal %w[ADDR1 ADDR2 CITY COMPANY_NAME COUNTRY PHONE STATE ZIP], result.stringify_keys.keys.sort assert_equal 'n/a', result[:STATE] assert_equal '123 Test St.', result[:ADDR1] assert_equal 'DE', result[:COUNTRY] @@ -75,9 +76,9 @@ def test_add_address_outsite_north_america def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'}) + @gateway.send(:add_address, result, billing_address: { address1: '123 Test St.', address2: '5F', city: 'Testville', company: 'Test Company', country: 'US', state: 'AK' }) - assert_equal ['ADDR1', 'ADDR2', 'CITY', 'COMPANY_NAME', 'COUNTRY', 'PHONE', 'STATE', 'ZIP'], result.stringify_keys.keys.sort + assert_equal %w[ADDR1 ADDR2 CITY COMPANY_NAME COUNTRY PHONE STATE ZIP], result.stringify_keys.keys.sort assert_equal 'AK', result[:STATE] assert_equal '123 Test St.', result[:ADDR1] assert_equal 'US', result[:COUNTRY] @@ -87,7 +88,7 @@ def test_name_comes_from_payment_method result = {} @gateway.send(:add_creditcard, result, @credit_card) - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'}) + @gateway.send(:add_address, result, billing_address: { address1: '123 Test St.', address2: '5F', city: 'Testville', company: 'Test Company', country: 'US', state: 'AK' }) assert_equal @credit_card.first_name, result[:NAME1] assert_equal @credit_card.last_name, result[:NAME2] @@ -95,19 +96,19 @@ def test_name_comes_from_payment_method def test_add_invoice result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001') + @gateway.send(:add_invoice, result, order_id: '#1001') assert_equal '#1001', result[:invoice_num] end def test_add_description result = {} - @gateway.send(:add_invoice, result, :description => 'My Purchase is great') + @gateway.send(:add_invoice, result, description: 'My Purchase is great') assert_equal 'My Purchase is great', result[:description] end def test_purchase_meets_minimum_requirements params = { - :amount => '1.01', + amount: '1.01' } @gateway.send(:add_creditcard, params, @credit_card) @@ -120,8 +121,8 @@ def test_purchase_meets_minimum_requirements def test_successful_refund response = stub_comms do - @gateway.refund(@amount, '100134230412', @options.merge({:card_number => @credit_card.number})) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, '100134230412', @options.merge({ card_number: @credit_card.number })) + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) end.respond_with(successful_refund_response) @@ -132,12 +133,13 @@ def test_successful_refund def test_refund_passing_extra_info response = stub_comms do - @gateway.refund(50, '123456789', @options.merge({:card_number => @credit_card.number, :first_name => 'Bob', :last_name => 'Smith', :zip => '12345'})) - end.check_request do |endpoint, data, headers| + @gateway.refund(50, '123456789', @options.merge({ card_number: @credit_card.number, first_name: 'Bob', last_name: 'Smith', zip: '12345', doc_type: 'WEB' })) + end.check_request do |_endpoint, data, _headers| assert_match(/NAME1=Bob/, data) assert_match(/NAME2=Smith/, data) assert_match(/ZIP=12345/, data) assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) + assert_match(/DOC_TYPE=WEB/, data) end.respond_with(successful_purchase_response) assert_success response @@ -145,8 +147,8 @@ def test_refund_passing_extra_info def test_failed_refund response = stub_comms do - @gateway.refund(@amount, '123456789', @options.merge({:card_number => @credit_card.number})) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, '123456789', @options.merge({ card_number: @credit_card.number })) + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) end.respond_with(failed_refund_response) @@ -159,8 +161,8 @@ def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_purchase_response) assert_deprecation_warning('credit should only be used to credit a payment method') do response = stub_comms do - @gateway.credit(@amount, '123456789', @options.merge({:card_number => @credit_card.number})) - end.check_request do |endpoint, data, headers| + @gateway.credit(@amount, '123456789', @options.merge({ card_number: @credit_card.number })) + end.check_request do |_endpoint, data, _headers| assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) end.respond_with(failed_refund_response) @@ -170,12 +172,22 @@ def test_deprecated_credit end end + def test_successful_credit_with_check + response = stub_comms do + @gateway.credit(50, @check, @options.merge({ doc_type: 'PPD' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/DOC_TYPE=PPD/, data) + end.respond_with(successful_credit_response) + + assert_success response + end + def test_supported_countries - assert_equal ['US', 'CA'], BluePayGateway.supported_countries + assert_equal %w[US CA], BluePayGateway.supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], BluePayGateway.supported_cardtypes + assert_equal %i[visa master american_express discover diners_club jcb], BluePayGateway.supported_cardtypes end def test_parser_extracts_exactly_the_keys_in_gateway_response @@ -208,8 +220,37 @@ 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 5-digit and 9-digit postal code do 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 + options = @options.merge({ stored_credential: { initiator: 'merchant', reason_type: 'installment' } }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/cof=M/, data) + assert_match(/cofscheduled=Y/, data) + end.respond_with(RSP[:approved_auth]) + end + + def test_passing_stored_credentials_for_cit_transaction + options = @options.merge({ stored_credential: { initiator: 'cardholder', reason_type: 'unscheduled' } }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/cof=C/, data) + assert_match(/cofscheduled=N/, data) + end.respond_with(RSP[:approved_auth]) + end + + def test_passes_nothing_for_unrecognized_stored_credentials_values + options = @options.merge({ stored_credential: { initiator: 'unknown', reason_type: 'something weird' } }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/cof=&/, data) + assert_match(/cofscheduled=&/, data) + end.respond_with(RSP[:approved_auth]) end # Recurring Billing Unit Tests @@ -217,12 +258,14 @@ def test_successful_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :rebill_start_date => '1 MONTH', - :rebill_expression => '14 DAYS', - :rebill_cycles => '24', - :rebill_amount => @amount * 4 + @gateway.recurring( + @amount, + @credit_card, + billing_address: address.merge(first_name: 'Jim', last_name: 'Smith'), + rebill_start_date: '1 MONTH', + rebill_expression: '14 DAYS', + rebill_cycles: '24', + rebill_amount: @amount * 4 ) end @@ -236,7 +279,7 @@ def test_successful_update_recurring @gateway.expects(:ssl_post).returns(successful_update_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.update_recurring(:rebill_id => @rebill_id, :rebill_amount => @amount * 2) + @gateway.update_recurring(rebill_id: @rebill_id, rebill_amount: @amount * 2) end assert_instance_of Response, response @@ -324,6 +367,10 @@ def successful_status_recurring_response 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' end + def successful_credit_response + 'REBID=&AVS=_&TRANS_TYPE=CREDIT&STATUS=1&PAYMENT_ACCOUNT_MASK=C%3A244183602%3Axxxx8535&AUTH_CODE=&CARD_TYPE=ACH&MESSAGE=App%20ACH%20Credit&CVV2=_&TRANS_ID=100786598799' + end + def transcript 'card_num=4111111111111111&exp_date=1212&MASTER_ID=&PAYMENT_TYPE=CREDIT&PAYMENT_ACCOUNT=4242424242424242&CARD_CVV2=123&CARD_EXPIRE=0916&NAME1=Longbob&NAME2=Longsen&ORDER_ID=78c40687dd55dbdc140df777b0e8ece3&INVOICE_ID=&invoice_num=78c40687dd55dbdc140df777b0e8ece3&EMAIL=&CUSTOM_ID=&DUPLICATE_OVERRIDE=&TRANS_TYPE=SALE&AMOUNT=1.00&MODE=TEST&ACCOUNT_ID=100096218902&TAMPER_PROOF_SEAL=55624458ce3e15fa8e33e6f2d784bbcb' end diff --git a/test/unit/gateways/blue_snap_test.rb b/test/unit/gateways/blue_snap_test.rb index c3cb84bea00..8553f4e9aea 100644 --- a/test/unit/gateways/blue_snap_test.rb +++ b/test/unit/gateways/blue_snap_test.rb @@ -1,5 +1,17 @@ +# coding: utf-8 + require 'test_helper' +class BlueSnapCurrencyDocMock + attr_accessor :received_amount + + def currency(currency); end + + def amount(amount) + @received_amount = amount + end +end + class BlueSnapTest < Test::Unit::TestCase include CommStub @@ -8,7 +20,25 @@ def setup @credit_card = credit_card @check = check @amount = 100 + + # BlueSnap may require support contact to activate fraud checking on sandbox accounts. + # Specific merchant-configurable thresholds were set and are reflected in the + # recorded responses: + # Order Total Amount Decline Threshold = 3728 + # Payment Country Decline List = Brazil + @fraudulent_amount = 3729 + @fraudulent_card = credit_card('4007702835532454') + @options = { order_id: '1', personal_identification_number: 'CNPJ' } + @options_3ds2 = @options.merge( + three_d_secure: { + eci: '05', + cavv: 'AAABAWFlmQAAAABjRWWZEEFgFz+A', + xid: 'MGpHWm5ZWVpKclo0aUk0VmltVDA=', + ds_transaction_id: 'jhg34-sdgds87-sdg87-sdfg7', + version: '2.2.0' + } + ) @valid_check_options = { billing_address: { address1: '123 Street', @@ -19,6 +49,11 @@ def setup }, authorized_by_shopper: true } + @option_fraud_info = @options.merge( + transaction_fraud_info: { + fraud_session_id: 'fbcc094208f54c0e974d56875c73af7a' + } + ) end def test_successful_purchase @@ -29,6 +64,193 @@ def test_successful_purchase assert_equal '1012082839', response.authorization end + def test_successful_purchase_with_shipping_contact_info + more_options = @options.merge({ + shipping_address: { + address1: '123 Main St', + adress2: 'Apt B', + city: 'Springfield', + state: 'NC', + country: 'US', + zip: '27701' + } + }) + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, more_options) + end.check_request do |_method, _url, data| + assert_match(/shipping-contact-info/, data) + assert_match(/123 Main St/, data) + assert_match(/Springfield/, data) + assert_match(/NC/, data) + assert_match(/US/, data) + assert_match(/27701/, data) + end.respond_with(successful_purchase_response_with_metadata) + + assert_success response + assert_equal '1012082839', response.authorization + end + + def test_successful_purchase_with_card_holder_info + more_options = @options.merge({ + billing_address: { + address1: '123 Street', + address2: 'Apt 1', + city: 'Happy City', + state: 'CA', + zip: '94901' + }, + phone_number: '555 888 0000' + }) + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, more_options) + end.check_request do |_method, _url, data| + assert_match(/card-holder-info/, data) + assert_match(/
123 Street/, data) + assert_match(/Apt 1/, data) + assert_match(/555 888 0000/, data) + end.respond_with(successful_purchase_response_with_metadata) + + assert_success response + assert_equal '1012082839', response.authorization + end + + def test_successful_purchase_with_metadata + # description option should become meta-data field + + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + transaction_meta_data: [ + { + meta_key: 'stateTaxAmount', + meta_value: '20.00', + meta_description: 'State Tax Amount' + }, + { + meta_key: 'cityTaxAmount', + meta_value: 10.00, + meta_description: 'City Tax Amount' + }, + { + meta_key: 'websiteInfo', + meta_value: 'www.info.com', + meta_description: 'Website' + } + ], + description: 'Legacy Product Desc', + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, more_options) + end.check_request do |_method, _url, data| + assert_match(/transaction-meta-data/, data) + assert_match(/Legacy Product Desc<\/meta-value>/, data) + assert_match(/description<\/meta-key>/, data) + assert_match(/cityTaxAmount<\/meta-key>/, data) + assert_match(/stateTaxAmount<\/meta-key>/, data) + assert_match(/websiteInfo<\/meta-key>/, data) + end.respond_with(successful_purchase_response_with_metadata) + + assert_success response + assert_equal '1012082839', response.authorization + + assert_equal 4, response.params['transaction-meta-data'].length + + response.params['transaction-meta-data'].each { |m| + assert_true m['meta-key'].length > 0 + assert_true m['meta-value'].length > 0 + assert_true m['meta-description'].length > 0 + + case m['meta-key'] + when 'description' + assert_equal 'Product ABC', m['meta-value'] + assert_equal 'Product Description', m['meta-description'] + when 'cityTaxAmount' + assert_equal '10.00', m['meta-value'] + assert_equal 'City Tax Amount', m['meta-description'] + when 'stateTaxAmount' + assert_equal '20.00', m['meta-value'] + assert_equal 'State Tax Amount', m['meta-description'] + end + } + end + + def test_successful_purchase_with_metadata_empty + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + transaction_meta_data: [], + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, more_options) + end.check_request do |_method, _url, data| + assert_not_match(/transaction-meta-data/, data) + assert_not_match(/meta-key/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_nil response.params['transaction-meta-data'] + end + + def test_successful_purchase_with_metadata_nil + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + transaction_meta_data: nil, + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, more_options) + end.check_request do |_method, _url, data| + assert_not_match(/transaction-meta-data/, data) + assert_not_match(/meta-key/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_nil response.params['transaction-meta-data'] + end + + def test_successful_purchase_with_unused_state_code + unrecognized_state_code_options = { + billing_address: { + city: 'Dresden', + state: 'Sachsen', + country: 'DE', + zip: '01069' + } + } + + @gateway.expects(:raw_ssl_request).returns(successful_stateless_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, unrecognized_state_code_options) + assert_success response + assert_equal '1021645629', response.authorization + assert_not_includes(response.params, 'state') + end + + def test_successful_purchase_with_fraud_info + fraud_info = @option_fraud_info.merge({ ip: '123.12.134.1' }) + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, fraud_info) + end.check_request do |_method, _url, data| + assert_match(/fbcc094208f54c0e974d56875c73af7a<\/fraud-session-id>/, data) + assert_match(/123.12.134.1<\/shopper-ip-address>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '1012082839', response.authorization + end + def test_successful_echeck_purchase @gateway.expects(:raw_ssl_request).returns(successful_echeck_purchase_response) @@ -37,6 +259,63 @@ def test_successful_echeck_purchase assert_equal '1019803029', response.authorization end + def test_successful_purchase_with_3ds_auth + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options_3ds2) + end.check_request do |_method, _url, data| + assert_match(//, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:eci])}<\/eci>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:cavv])}<\/cavv>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:xid])}<\/xid>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:version])}<\/three-d-secure-version>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:ds_transaction_id])}<\/ds-transaction-id>/, data) + end.respond_with(successful_purchase_with_3ds_auth_response) + + assert_success response + assert_equal '1024951831', response.authorization + assert_equal '019082915501456', response.params['original-network-transaction-id'] + assert_equal '019082915501456', response.params['network-transaction-id'] + end + + def test_successful_purchase_with_cit_stored_credential_fields + cit_stored_credentials = { + initiator: 'cardholder', + network_transaction_id: 'ABC123' + } + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options_3ds2.merge({ stored_credential: cit_stored_credentials })) + end.check_request do |_method, _url, data| + assert_match 'SHOPPER', data + assert_match 'ABC123', data + end.respond_with(successful_purchase_with_3ds_auth_response) + end + + def test_successful_purchase_with_mit_stored_credential_fields + cit_stored_credentials = { + initiator: 'merchant', + network_transaction_id: 'QER100' + } + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options_3ds2.merge({ stored_credential: cit_stored_credentials })) + end.check_request do |_method, _url, data| + assert_match 'MERCHANT', data + assert_match 'QER100', data + end.respond_with(successful_purchase_with_3ds_auth_response) + end + + def test_does_not_send_3ds_auth_when_empty + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _url, data| + assert_not_match(//, data) + assert_not_match(//, data) + assert_not_match(//, data) + assert_not_match(//, data) + assert_not_match(//, data) + assert_not_match(//, data) + end.respond_with(successful_purchase_response) + end + def test_failed_purchase @gateway.expects(:raw_ssl_request).returns(failed_purchase_response) @@ -56,7 +335,7 @@ def test_failed_echeck_purchase def test_successful_authorize response = stub_comms(@gateway, :raw_ssl_request) do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |type, endpoint, data, headers| + end.check_request do |_type, _endpoint, data, _headers| assert_match 'false', data assert_match 'CNPJ', data end.respond_with(successful_authorize_response) @@ -64,6 +343,37 @@ 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) + end.check_request do |_type, _endpoint, data, _headers| + assert_match(//, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:eci])}<\/eci>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:cavv])}<\/cavv>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:xid])}<\/xid>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:version])}<\/three-d-secure-version>/, data) + assert_match(/#{Regexp.quote(@options_3ds2[:three_d_secure][:ds_transaction_id])}<\/ds-transaction-id>/, data) + end.respond_with(successful_authorize_with_3ds_auth_response) + + assert_success response + assert_equal '1024951833', response.authorization + assert_equal 'MCC8929120829', response.params['original-network-transaction-id'] + assert_equal 'MCC8929120829', response.params['network-transaction-id'] + end + def test_failed_authorize @gateway.expects(:raw_ssl_request).returns(failed_authorize_response) @@ -73,9 +383,25 @@ def test_failed_authorize end def test_successful_capture - @gateway.expects(:raw_ssl_request).returns(successful_capture_response) + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, @credit_card, @options) + end.check_request do |_method, _url, data| + assert_not_match(/1.00<\/amount>/, data) + assert_not_match(/USD<\/currency>/, data) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '1012082881', response.authorization + end + + def test_successful_partial_capture + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, @credit_card, @options.merge(include_capture_amount: true)) + end.check_request do |_method, _url, data| + assert_match(/1.00<\/amount>/, data) + assert_match(/USD<\/currency>/, data) + end.respond_with(successful_capture_response) - response = @gateway.capture(@amount, 'Authorization') assert_success response assert_equal '1012082881', response.authorization end @@ -89,9 +415,58 @@ def test_failed_capture end def test_successful_refund - @gateway.expects(:raw_ssl_request).returns(successful_refund_response) + options = { + reason: 'Refund for order #1992', + cancel_subscription: 'false', + tax_amount: 0.05, + transaction_meta_data: [ + { + meta_key: 'refundedItems', + meta_value: '1552,8832', + meta_description: 'Refunded Items', + meta_is_visible: 'false' + }, + { + meta_key: 'Number2', + meta_value: 'KTD', + meta_description: 'Metadata 2', + meta_is_visible: 'true' + } + ] + } + transaction_id = '1286' + response = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, transaction_id, options) + end.check_request do |_action, endpoint, data, _headers| + doc = REXML::Document.new(data) + + assert_includes endpoint, "/refund/#{transaction_id}" + assert_match(/1.00<\/amount>/, data) + assert_match(/0.05<\/tax-amount>/, data) + assert_match(/false<\/cancel-subscription>/, data) + assert_match(/Refund for order #1992<\/reason>/, data) + assert_match(/refundedItems<\/meta-key>/, data) + assert_match(/KTD<\/meta-value>/, data) + assert_match(/Metadata 2<\/meta-description>/, data) + transaction_meta_data = doc.root.elements['transaction-meta-data'].elements.to_a + transaction_meta_data.each_with_index do |item, index| + assert_match item.elements['meta-key'].text, options[:transaction_meta_data][index][:meta_key] + assert_match item.elements['meta-value'].text, options[:transaction_meta_data][index][:meta_value] + assert_match item.elements['meta-description'].text, options[:transaction_meta_data][index][:meta_description] + assert_match item.elements['is-visible'].text, options[:transaction_meta_data][index][:meta_is_visible] + end + end.respond_with(successful_refund_without_merchant_transaction_id_response) + assert_success response + assert_equal '1061398943', response.authorization + end - response = @gateway.refund(@amount, 'Authorization') + def test_successful_refund_with_merchant_id + merchant_transaction_id = '12678' + response = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, '', @options.merge({ merchant_transaction_id: merchant_transaction_id })) + end.check_request do |_action, endpoint, _data, _headers| + assert_includes endpoint, "/refund/merchant/#{merchant_transaction_id}" + end.respond_with(successful_refund_response) assert_success response assert_equal '1012082907', response.authorization end @@ -171,7 +546,7 @@ def test_failed_echeck_store def test_currency_added_correctly stub_comms(@gateway, :raw_ssl_request) do @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD')) - end.check_request do |method, url, data| + end.check_request do |_method, _url, data| assert_match(/CAD<\/currency>/, data) end.respond_with(successful_purchase_response) end @@ -194,15 +569,41 @@ def test_failed_forbidden_response assert_equal 'You are not authorized to perform this request due to inappropriate role permissions.', response.message end + def test_failed_rate_limit_response + @gateway.expects(:raw_ssl_request).returns(rate_limit_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Client request rate is too high', response.message + end + def test_does_not_send_level_3_when_empty response = stub_comms(@gateway, :raw_ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |type, endpoint, data, headers| + end.check_request do |_type, _endpoint, data, _headers| assert_not_match(/level-3-data/, data) end.respond_with(successful_purchase_response) assert_success response end + def test_fraud_response_handling + @gateway.expects(:raw_ssl_request).returns(fraudulent_purchase_response) + + response = @gateway.purchase(@fraudulent_amount, @credit_card, @options) + assert_failure response + assert_match(/fraud-reference-id/, response.message) + assert_match(/fraud-event/, response.message) + end + + def test_fraud_response_handling_multiple_triggers + @gateway.expects(:raw_ssl_request).returns(fraudulent_purchase_response_multiple_triggers) + + response = @gateway.purchase(@fraudulent_amount, @fraudulent_card, @options) + assert_failure response + assert_match(/orderTotalDecline/, response.message) + assert_match(/blacklistPaymentCountryDecline/, response.message) + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -213,8 +614,41 @@ def test_echeck_scrub assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck end + def test_localizes_currencies + amount = 1234 + + # Check a 2 decimal place currency + assert_equal '12.34', check_amount_registered(amount, 'USD') + + # Check all 0 decimal currencies + ActiveMerchant::Billing::BlueSnapGateway.currencies_without_fractions.each do |currency| + assert_equal '12', check_amount_registered(amount, currency) + end + + # Check all 3 decimal currencies + ActiveMerchant::Billing::BlueSnapGateway.currencies_with_three_decimal_places.each do |currency| + assert_equal '1.234', check_amount_registered(amount, currency) + end + end + + def test_optional_idempotency_key_header + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ idempotency_key: 'test123' })) + end.check_request do |_method, _url, _data, headers| + assert_equal 'test123', headers['Idempotency-Key'] + end.respond_with(successful_authorize_response) + end + private + def check_amount_registered(amount, currency) + doc = BlueSnapCurrencyDocMock.new + options = @options.merge(currency: currency) + @gateway.send(:add_amount, doc, amount, options) + + doc.received_amount + end + def pre_scrubbed %q{ opening connection to sandbox.bluesnap.com:443... @@ -306,6 +740,108 @@ def successful_purchase_response XML end + def successful_purchase_response_with_metadata + MockResponse.succeeded <<-XML + + + AUTH_CAPTURE + 1012082839 + ECOMMERCE + BLS*Spreedly + 1.00 + USD + + Longbob + Longsen + CA + ON + Ottawa + K1C2N6 + CNPJ + + + 9299 + VISA + CREDIT + + + + stateTaxAmount + 20.00 + State Tax Amount + + + cityTaxAmount + 10.00 + City Tax Amount + + + shippingAmount + 150.00 + Shipping Amount + + + websiteInfo + www.info.com + Website + + + + success + ND + U + U + U + + + XML + end + + def successful_purchase_with_3ds_auth_response + MockResponse.succeeded <<-XML + + + AUTH_CAPTURE + 1024951831 + ECOMMERCE + BLS*Spreedly + 1.00 + 1.00 + USD + N + + Longbob + Longsen + CA + ON + Ottawa + K1C2N6 + + 25105083 + + 1091 + VISA + CREDIT + CONSUMER + N + us + + + 019082915501456 + 019082915501456 + + + success + NR + N + N + U + 019082915501456 + + + XML + end + def successful_echeck_purchase_response MockResponse.succeeded <<-XML @@ -333,6 +869,46 @@ def successful_echeck_purchase_response XML end + def successful_stateless_purchase_response + MockResponse.succeeded <<-XML + + + AUTH_CAPTURE + 1021645629 + ECOMMERCE + BLS*Spreedly + 1.00 + 1.00 + USD + + Longbob + Longsen + DE + Dresden + 01069 + + 24449087 + + 9299 + VISA + CREDIT + PLATINUM + CONSUMER + N + ALLIED IRISH BANKS PLC + ie + + + success + ND + U + U + U + + + XML + end + def failed_purchase_response body = <<-XML @@ -398,6 +974,53 @@ def successful_authorize_response XML end + def successful_authorize_with_3ds_auth_response + MockResponse.succeeded <<-XML + + + AUTH_ONLY + 1024951833 + ECOMMERCE + BLS*Spreedly + 1.00 + 1.00 + USD + S + + Longbob + Longsen + CA + ON + Ottawa + K1C2N6 + + 25105085 + + 1096 + MASTERCARD + CREDIT + STANDARD + CONSUMER + N + PUBLIC BANK BERHAD + my + + + MCC8929120829 + MCC8929120829 + + + success + NC + U + U + U + MCC8929120829 + + + XML + end + def failed_authorize_response body = <<-XML @@ -510,6 +1133,32 @@ def failed_refund_response MockResponse.failed(body, 400) end + def successful_refund_without_merchant_transaction_id_response + MockResponse.succeeded <<-XML + + + 1061398943 + 1.00 + 0.05 + + + refundedItems + 1552,8832 + Refunded Items + false + + + Number2 + KTD + Metadata 2 + true + + + Refund for order #1992 + + XML + end + def successful_void_response MockResponse.succeeded <<-XML @@ -712,6 +1361,59 @@ def forbidden_response MockResponse.new(403, 'You are not authorized to perform this request due to inappropriate role permissions.') end + def rate_limit_response + MockResponse.new(429, 'Client request rate is too high') + end + + def fraudulent_purchase_response + body = <<-XML + + + + FRAUD_DETECTED + 15011 + The request cannot be fulfilled for the current shopper. Please contact BlueSnap support for further details. + + 6270209 + + orderTotalDecline + D + 3729 > 3728 + + + + + XML + MockResponse.new(400, body) + end + + def fraudulent_purchase_response_multiple_triggers + body = <<-XML + + + + FRAUD_DETECTED + 15011 + The request cannot be fulfilled for the current shopper. Please contact BlueSnap support for further details. + + 6270189 + + blacklistPaymentCountryDecline + D + BR is in list: [BR] + + + orderTotalDecline + D + 3729 > 3728 + + + + + XML + MockResponse.new(400, body) + end + def credentials_are_legit_response MockResponse.new(400, 'Server Error') end diff --git a/test/unit/gateways/bogus_test.rb b/test/unit/gateways/bogus_test.rb index e6978d828e1..301ed2ddfa6 100644 --- a/test/unit/gateways/bogus_test.rb +++ b/test/unit/gateways/bogus_test.rb @@ -8,13 +8,13 @@ class BogusTest < Test::Unit::TestCase def setup @gateway = BogusGateway.new( - :login => 'bogus', - :password => 'bogus' + login: 'bogus', + password: 'bogus' ) @creditcard = credit_card(CC_SUCCESS_PLACEHOLDER) - @response = ActiveMerchant::Billing::Response.new(true, 'Transaction successful', :transid => BogusGateway::AUTHORIZATION) + @response = ActiveMerchant::Billing::Response.new(true, 'Transaction successful', transid: BogusGateway::AUTHORIZATION) end def test_authorize @@ -47,7 +47,7 @@ def test_purchase def test_capture assert @gateway.capture(1000, '1337').success? assert @gateway.capture(1000, @response.params['transid']).success? - response = @gateway.capture(1000, CC_FAILURE_PLACEHOLDER) + response = @gateway.capture(1000, CC_FAILURE_PLACEHOLDER) refute response.success? assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code assert_raises(ActiveMerchant::Billing::Error) do @@ -57,7 +57,7 @@ def test_capture def test_credit assert @gateway.credit(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? - response = @gateway.credit(1000, credit_card(CC_FAILURE_PLACEHOLDER)) + response = @gateway.credit(1000, credit_card(CC_FAILURE_PLACEHOLDER)) refute response.success? assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code e = assert_raises(ActiveMerchant::Billing::Error) do @@ -78,7 +78,7 @@ def test_refund end def test_credit_uses_refund - options = {:foo => :bar} + options = { foo: :bar } @gateway.expects(:refund).with(1000, '1337', options) assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do @gateway.credit(1000, '1337', options) @@ -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)) @@ -125,71 +136,71 @@ def test_supported_card_types end def test_authorize_with_check - assert @gateway.authorize(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? - assert !@gateway.authorize(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + assert @gateway.authorize(1000, check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: nil)).success? + assert !@gateway.authorize(1000, check(account_number: CHECK_FAILURE_PLACEHOLDER, number: nil)).success? e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.authorize(1000, check(:account_number => '123', :number => nil)) + @gateway.authorize(1000, check(account_number: '123', number: nil)) end assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_purchase_with_check # use account number if number isn't given - assert @gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? - assert !@gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + assert @gateway.purchase(1000, check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: nil)).success? + assert !@gateway.purchase(1000, check(account_number: CHECK_FAILURE_PLACEHOLDER, number: nil)).success? # give priority to number over account_number if given - assert !@gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => CHECK_FAILURE_PLACEHOLDER)).success? - assert @gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => CHECK_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.purchase(1000, check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: CHECK_FAILURE_PLACEHOLDER)).success? + assert @gateway.purchase(1000, check(account_number: CHECK_FAILURE_PLACEHOLDER, number: CHECK_SUCCESS_PLACEHOLDER)).success? e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.purchase(1000, check(:account_number => '123', :number => nil)) + @gateway.purchase(1000, check(account_number: '123', number: nil)) end assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_store_with_check - assert @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? - assert !@gateway.store(check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + assert @gateway.store(check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: nil)).success? + assert !@gateway.store(check(account_number: CHECK_FAILURE_PLACEHOLDER, number: nil)).success? e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.store(check(:account_number => '123', :number => nil)) + @gateway.store(check(account_number: '123', number: nil)) end assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_credit_with_check - assert @gateway.credit(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? - assert !@gateway.credit(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + assert @gateway.credit(1000, check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: nil)).success? + assert !@gateway.credit(1000, check(account_number: CHECK_FAILURE_PLACEHOLDER, number: nil)).success? e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.credit(1000, check(:account_number => '123', :number => nil)) + @gateway.credit(1000, check(account_number: '123', number: nil)) end assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_store_then_purchase_with_check - reference = @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)) + reference = @gateway.store(check(account_number: CHECK_SUCCESS_PLACEHOLDER, number: nil)) assert @gateway.purchase(1000, reference.authorization).success? end def test_authorize_emv - approve_response = @gateway.authorize(1000, credit_card('123', {icc_data: 'DEADBEEF'})) + approve_response = @gateway.authorize(1000, credit_card('123', { icc_data: 'DEADBEEF' })) assert approve_response.success? assert_equal '8A023030', approve_response.emv_authorization - decline_response = @gateway.authorize(1005, credit_card('123', {icc_data: 'DEADBEEF'})) + decline_response = @gateway.authorize(1005, credit_card('123', { icc_data: 'DEADBEEF' })) refute decline_response.success? assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], decline_response.error_code assert_equal '8A023035', decline_response.emv_authorization e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.authorize(1001, credit_card('123', {icc_data: 'DEADBEEF'})) + @gateway.authorize(1001, credit_card('123', { icc_data: 'DEADBEEF' })) end assert_equal('Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception', e.message) end def test_purchase_emv - assert @gateway.purchase(1000, credit_card('123', {icc_data: 'DEADBEEF'})).success? - response = @gateway.purchase(1005, credit_card('123', {icc_data: 'DEADBEEF'})) + assert @gateway.purchase(1000, credit_card('123', { icc_data: 'DEADBEEF' })).success? + response = @gateway.purchase(1005, credit_card('123', { icc_data: 'DEADBEEF' })) refute response.success? assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code e = assert_raises(ActiveMerchant::Billing::Error) do - @gateway.purchase(1001, credit_card('123', {icc_data: 'DEADBEEF'})) + @gateway.purchase(1001, credit_card('123', { icc_data: 'DEADBEEF' })) end assert_equal('Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception', e.message) end diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb index bd38587664a..8550b24eb79 100644 --- a/test/unit/gateways/borgun_test.rb +++ b/test/unit/gateways/borgun_test.rb @@ -49,13 +49,72 @@ def test_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/140601083732/, data) end.respond_with(successful_capture_response) 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({ 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(successful_get_3ds_authentication_response) + + assert_success response + assert !response.params['redirecttoacsform'].blank? + 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 + response = stub_comms do + @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 + end + + def test_authorize_airline_data + # itinerary data abbreviated for brevity + passenger_itinerary_data = { + 'MessageNumber' => '1111111', + 'TrDate' => '20120222', + 'TrTime' => '151515', + 'PassengerName' => 'Jane Doe' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, { passenger_itinerary_data: passenger_itinerary_data }) + end.check_request do |_endpoint, data, _headers| + assert_match('PassengerItineraryData', data) + end.respond_with(successful_authorize_response) + + assert_success response + end + def test_refund response = stub_comms do @gateway.purchase(@amount, @credit_card) @@ -66,7 +125,7 @@ def test_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/140216103700/, data) end.respond_with(successful_refund_response) @@ -83,7 +142,7 @@ def test_void refund = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/140216103700/, data) end.respond_with(successful_void_response) @@ -93,7 +152,7 @@ def test_void def test_passing_cvv stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_purchase_response) end @@ -101,7 +160,7 @@ def test_passing_cvv def test_passing_terminal_id stub_comms do @gateway.purchase(@amount, @credit_card, { terminal_id: '3' }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/TerminalID>3/, data) end.respond_with(successful_purchase_response) end @@ -295,6 +354,55 @@ def successful_void_response ) end + def successful_get_3ds_authentication_response + <<~RESPONSE + + + + + + 0 + + + 1000 + 23 + 23 + 23_20201408041400003 + 9 + ToberedirectedtoACS + Y + 3C21444F43545950452068746D6C2053595354454D202261626F75743A6C65676163792D636F6D706174223E0D0D0A3C68746D6C20636C6173733D226E6F2D6A7322206C616E673D22656E2220786D6C6E733D22687474703A2F2F7777772E77332E6F72672F313939392F7868746D6C223E0D0D0A3C686561643E0D0D0A3C6D65746120687474702D65717569763D22436F6E74656E742D547970652220636F6E74656E743D22746578742F68746D6C3B20636861727365743D7574662D38222F3E0D0D0A3C6D65746120636861727365743D227574662D38222F3E0D0D0A3C7469746C653E3344205365637572652050726F63657373696E673C2F7469746C653E0D0D0A3C6C696E6B20687265663D2268747470733A2F2F6D70692E626F7267756E2E69732F6D647061796D70692F7374617469632F6D70692E637373222072656C3D227374796C6573686565742220747970653D22746578742F637373222F3E0D0D0A3C2F686561643E0D0D0A3C626F64793E0D0D0A3C6469762069643D226D61696E223E0D0D0A3C6469762069643D22636F6E74656E74223E0D0D0A3C6469762069643D226F72646572223E0D0D0A3C68323E3344205365637572652050726F63657373696E673C2F68323E0D0D0A3C696D67207372633D2268747470733A2F2F6D70692E626F7267756E2E69732F6D647061796D70692F7374617469632F7072656C6F616465722E6769662220616C743D22506C6561736520776169742E2E222F3E0D0D0A3C696D67207372633D2268747470733A2F2F6D70692E626F7267756E2E69732F6D647061796D70692F7374617469632F6D635F6964636865636B5F68727A5F6C74645F706F735F31303370782E706E672220616C743D224D61737465724361726420494420436865636B222F3E0D0D0A3C6469762069643D22666F726D646976223E0D0D0A3C73637269707420747970653D22746578742F6A617661736372697074223E0D0D0A66756E6374696F6E2068696465416E645375626D697454696D656428666F726D6964290D0D0A7B0D0D0A7661722074696D65723D73657454696D656F7574282268696465416E645375626D69742827222B666F726D69642B2227293B222C313030293B0D0D0A7D0D0D0A0D0D0A66756E6374696F6E2068696465416E645375626D697428666F726D6964290D0D0A7B0D0D0A76617220666F726D783D646F63756D656E742E676574456C656D656E744279496428666F726D6964293B0D0D0A0969662028666F726D78213D6E756C6C290D0D0A097B0D0D0A09666F726D782E7374796C652E7669736962696C6974793D2268696464656E223B0D0D0A09666F726D782E7375626D697428293B0D0D0A097D0D0D0A7D0D0D0A3C2F7363726970743E0D0D0A3C6469763E0D0D0A3C666F726D2069643D22776562666F726D3022206E616D653D2272656432414353763122206D6574686F643D22504F53542220616374696F6E3D2268747470733A2F2F616373312E3364732E6D6F646972756D2E636F6D2F6D647061796163732F706172657122206163636570745F636861727365743D225554462D38223E0D0D0A3C696E70757420747970653D2268696464656E22206E616D653D225F636861727365745F222076616C75653D225554462D38222F3E0D0D0A3C696E70757420747970653D2268696464656E22206E616D653D225061526571222076616C75653D22654A785655753175676A4155665258692F396D57723443354E7346684D724C676D4F7746574C6C427A437861697445392F56715575663237353979766E6E4D4C487A75466D4A596F426F556363757A37716B476E725A657A5976764F614F4146455976634759636932654B4A77786C5633336153737A6D647530416D614471563246565363366A45615A5674654F417A502F4B4133434563554755706A39306744434D6679413243724137495635317142756B384F586D52505A56765365466F374855724779426A486B5133534B32753341764D79676E416F4C37345475766A6770445063634B383759465946736A6A4F6356676F39354D756251317A2F664A4E552B5449452B627932612F706E6D6166322F53684F587065676E45566B42646165517564536D4E61655377634D4838425456435268367167313350732F4C56595A51616554634D5237736D75594578385A6341343635434B53594645774B384844754A707349302F4D5A51597939346F627036454E6F70555A31626755625A53414E354348702B7357344C6259786B4E4B4978382B4B5157636448796735766A5538756F3279636267455132305475787954336535766F337A2F34415552317277553D222F3E0D0D0A3C696E70757420747970653D2268696464656E22206E616D653D224D44222076616C75653D2232335F3230323031343038303431343030303033222F3E0D0D0A3C696E70757420747970653D2268696464656E22206E616D653D225465726D55726C222076616C75653D22687474703A2F2F6C6F63616C686F73742F696E6465782E68746D6C222F3E0D0D0A3C696E70757420747970653D227375626D697422206E616D653D227375626D697442746E222076616C75653D22506C6561736520636C69636B206865726520746F20636F6E74696E7565222F3E0D0D0A3C2F666F726D3E0D0D0A3C2F6469763E0D0D0A3C2F6469763E0D0D0A3C73637269707420747970653D22746578742F6A617661736372697074223E0D0D0A09090968696465416E645375626D697454696D65642827776562666F726D3027293B0D0D0A09093C2F7363726970743E0D0D0A3C6E6F7363726970743E0D0D0A3C64697620616C69676E3D2263656E746572223E0D0D0A3C623E4A617661736372697074206973207475726E6564206F6666206F72206E6F7420737570706F72746564213C2F623E0D0D0A3C62722F3E0D0D0A3C2F6469763E0D0D0A3C2F6E6F7363726970743E0D0D0A3C2F6469763E0D0D0A3C6469762069643D22636F6E74656E742D666F6F746572223E0D0D0A3C62722F3E0D0D0A3C696D67206865696768743D22323022207372633D2268747470733A2F2F6D70692E626F7267756E2E69732F6D647061796D70692F7374617469632F706F77657265642D62792D6D6F646972756D2E7376672220616C743D22506F7765726564206279204D6F646972756D222F3E0D0D0A3C2F6469763E0D0D0A3C2F6469763E0D0D0A3C2F6469763E0D0D0A3C2F626F64793E0D0D0A3C2F68746D6C3E0D0D0A + + + https://acs1.3ds.modirum.com/mdpayacs/pareq + eJxVUu1ugjAUfRXi/9mWr4C5NsFhMrLgmOwFWLlBzCxaitE9/VqUuf2759yvnnMLHzuFmJYoBoUccuz7qkGnrZezYvvOaOAFEYvcGYci2eKJwxlV33aSszmdu0AmaDqV2FVSc6jEaZVteOAzP/KA3CEcUGUpj90gDCMfyA2CrA7IV51qBuk8OXmRPZVvSeFo7HUrGyBjHkQ3SK2u3AvMygnAoL74TuvjgpDPccK87YFYFsjjOcVgo95MubQ1z/fJNU+TIE+by2a/pnmaf2/ShOXpegnEVkBdaeQudSmNaeSwcMH8BTVCRh6qg13Ps/LVYZQaeTcMR7smuYEx8ZcA465CKSYFEwK8HDuJpsI0/MZQYy94obp6ENopUZ1bgUbZSAN5CHp+sW4LbYxkNKIx8+KQWcdHyg5vjU8uo2ycbgEQ20TuxyT3e5vo3z/4AUR1rwU= + http://localhost/index.html + + + + + 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/bpoint_test.rb b/test/unit/gateways/bpoint_test.rb index 384862ab576..1c156a6212e 100644 --- a/test/unit/gateways/bpoint_test.rb +++ b/test/unit/gateways/bpoint_test.rb @@ -88,13 +88,23 @@ def test_failed_refund def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) - response = @gateway.void(@amount, '') + response = @gateway.void('', amount: 300) assert_success response end + def test_void_passes_correct_transaction_reference + stub_comms do + # transaction number from successful authorize response + @gateway.void('219388558', amount: 300) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(219388558)m, data) + assert_match(%r(300)m, data) + end.respond_with(successful_void_response) + end + def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) - response = @gateway.void(@amount, '') + response = @gateway.void('') assert_failure response end @@ -125,11 +135,20 @@ def test_scrub def test_passing_biller_code stub_comms do @gateway.authorize(@amount, @credit_card, { biller_code: '1234' }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(1234)m, data) end.respond_with(successful_authorize_response) end + def test_passing_reference_and_crn + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ crn1: 'ref' })) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(1)m, data) + assert_match(%r(ref)m, data) + end.respond_with(successful_authorize_response) + end + private def pre_scrubbed @@ -225,7 +244,7 @@ def successful_authorize_response ) end - alias_method :successful_verify_response, :successful_authorize_response + alias successful_verify_response successful_authorize_response def failed_authorize_response %( @@ -252,7 +271,7 @@ def failed_authorize_response ) end - alias_method :failed_verify_response, :failed_authorize_response + alias failed_verify_response failed_authorize_response def successful_capture_response %( diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index ad3a1f1a848..14662a8e1bc 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -5,10 +5,10 @@ def setup @old_verbose, $VERBOSE = $VERBOSE, false @gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :public_key => 'test', - :private_key => 'test', - :test => true + merchant_id: 'test', + public_key: 'test', + private_key: 'test', + test: true ) @internal_gateway = @gateway.instance_variable_get(:@braintree_gateway) @@ -21,22 +21,22 @@ def teardown def test_refund_legacy_method_signature Braintree::TransactionGateway.any_instance.expects(:refund). with('transaction_id', nil). - returns(braintree_result(:id => 'refund_transaction_id')) - response = @gateway.refund('transaction_id', :test => true) + returns(braintree_result(id: 'refund_transaction_id')) + response = @gateway.refund('transaction_id', test: true) assert_equal 'refund_transaction_id', response.authorization end def test_refund_method_signature Braintree::TransactionGateway.any_instance.expects(:refund). with('transaction_id', '10.00'). - returns(braintree_result(:id => 'refund_transaction_id')) - response = @gateway.refund(1000, 'transaction_id', :test => true) + returns(braintree_result(id: 'refund_transaction_id')) + response = @gateway.refund(1000, 'transaction_id', test: true) assert_equal 'refund_transaction_id', response.authorization end def test_transaction_uses_customer_id_by_default Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:customer_id => 'present')). + with(has_entries(customer_id: 'present')). returns(braintree_result) assert response = @gateway.purchase(10, 'present', {}) @@ -46,7 +46,7 @@ def test_transaction_uses_customer_id_by_default def test_transaction_uses_payment_method_token_when_option Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:payment_method_token => 'present')). + with(has_entries(payment_method_token: 'present')). returns(braintree_result) assert response = @gateway.purchase(10, 'present', { payment_method_token: true }) @@ -56,7 +56,7 @@ def test_transaction_uses_payment_method_token_when_option def test_transaction_uses_payment_method_nonce_when_option Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:payment_method_nonce => 'present')). + with(all_of(has_entries(payment_method_nonce: 'present'), has_key(:customer))). returns(braintree_result) assert response = @gateway.purchase(10, 'present', { payment_method_nonce: true }) @@ -86,7 +86,7 @@ def test_purchase_transaction def test_capture_transaction Braintree::TransactionGateway.any_instance.expects(:submit_for_settlement). - returns(braintree_result(:id => 'capture_transaction_id')) + returns(braintree_result(id: 'capture_transaction_id')) response = @gateway.capture(100, 'transaction_id') @@ -94,9 +94,19 @@ def test_capture_transaction assert_equal true, response.test end + def test_partial_capture_transaction + Braintree::TransactionGateway.any_instance.expects(:submit_for_partial_settlement). + returns(braintree_result(id: 'capture_transaction_id')) + + response = @gateway.capture(100, 'transaction_id', { partial_capture: true }) + + assert_equal 'capture_transaction_id', response.authorization + assert_equal true, response.test + end + def test_refund_transaction Braintree::TransactionGateway.any_instance.expects(:refund). - returns(braintree_result(:id => 'refund_transaction_id')) + returns(braintree_result(id: 'refund_transaction_id')) response = @gateway.refund(1000, 'transaction_id') assert_equal 'refund_transaction_id', response.authorization @@ -106,7 +116,7 @@ def test_refund_transaction def test_void_transaction Braintree::TransactionGateway.any_instance.expects(:void). with('transaction_id'). - returns(braintree_result(:id => 'void_transaction_id')) + returns(braintree_result(id: 'void_transaction_id')) response = @gateway.void('transaction_id') assert_equal 'void_transaction_id', response.authorization @@ -127,20 +137,53 @@ def test_verify_bad_credentials assert !@gateway.verify_credentials end + def test_zero_dollar_verification_transaction + Braintree::CreditCardVerificationGateway.any_instance.expects(:create). + returns(braintree_result(cvv_response_code: 'M', avs_error_response_code: 'P')) + + card = credit_card('4111111111111111') + options = { + allow_card_verification: true, + billing_address: { + zip: '10000' + } + } + response = @gateway.verify(card, options) + assert_success response + assert_equal 'transaction_id', response.params['authorization'] + assert_equal 'M', response.cvv_result['code'] + assert_equal 'P', response.avs_result['code'] + end + + def test_failed_verification_transaction + Braintree::CreditCardVerificationGateway.any_instance.expects(:create). + returns(braintree_error_result(message: 'CVV must be 4 digits for American Express and 3 digits for other card types. (81707)')) + + card = credit_card('4111111111111111') + options = { + allow_card_verification: true, + billing_address: { + zip: '10000' + } + } + response = @gateway.verify(card, options) + assert_failure response + end + def test_user_agent_includes_activemerchant_version assert @internal_gateway.config.user_agent.include?("(ActiveMerchant #{ActiveMerchant::VERSION})") end def test_merchant_account_id_present_when_provided_on_gateway_initialization @gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :merchant_account_id => 'present', - :public_key => 'test', - :private_key => 'test' + merchant_id: 'test', + merchant_account_id: 'present', + public_key: 'test', + private_key: 'test' ) Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:merchant_account_id => 'present')). + with(has_entries(merchant_account_id: 'present')). returns(braintree_result) @gateway.authorize(100, credit_card('41111111111111111111')) @@ -148,33 +191,69 @@ def test_merchant_account_id_present_when_provided_on_gateway_initialization def test_merchant_account_id_on_transaction_takes_precedence @gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :merchant_account_id => 'present', - :public_key => 'test', - :private_key => 'test' + merchant_id: 'test', + merchant_account_id: 'present', + public_key: 'test', + private_key: 'test' ) Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:merchant_account_id => 'account_on_transaction')). + with(has_entries(merchant_account_id: 'account_on_transaction')). returns(braintree_result) - @gateway.authorize(100, credit_card('41111111111111111111'), :merchant_account_id => 'account_on_transaction') + @gateway.authorize(100, credit_card('41111111111111111111'), merchant_account_id: 'account_on_transaction') end def test_merchant_account_id_present_when_provided Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:merchant_account_id => 'present')). + with(has_entries(merchant_account_id: 'present')). returns(braintree_result) - @gateway.authorize(100, credit_card('41111111111111111111'), :merchant_account_id => 'present') + @gateway.authorize(100, credit_card('41111111111111111111'), merchant_account_id: 'present') end def test_service_fee_amount_can_be_specified Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:service_fee_amount => '2.31')). + with(has_entries(service_fee_amount: '2.31')). returns(braintree_result) - @gateway.authorize(100, credit_card('41111111111111111111'), :service_fee_amount => '2.31') + @gateway.authorize(100, credit_card('41111111111111111111'), service_fee_amount: '2.31') + end + + def test_venmo_profile_id_can_be_specified + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:venmo][:profile_id] == 'profile_id') + end.returns(braintree_result) + + @gateway.authorize(100, credit_card('41111111111111111111'), venmo_profile_id: 'profile_id') + end + + def test_customer_has_default_payment_method + options = { + payment_method_nonce: 'fake-paypal-future-nonce', + store: true, + device_data: 'device_data', + paypal: { + paypal_flow_type: 'checkout_with_vault' + } + } + + Braintree::TransactionGateway.any_instance.expects(:sale).returns(braintree_result(paypal: { implicitly_vaulted_payment_method_token: 'abc123' })) + + Braintree::CustomerGateway.any_instance.expects(:update).with(nil, { default_payment_method_token: 'abc123' }).returns(nil) + + @gateway.authorize(100, 'fake-paypal-future-nonce', options) + end + + def test_risk_data_can_be_specified + risk_data = { + customer_browser: 'User-Agent Header', + customer_ip: '127.0.0.1' + } + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(risk_data: risk_data)).returns(braintree_result) + + @gateway.authorize(100, credit_card('4111111111111111'), risk_data: risk_data) end def test_hold_in_escrow_can_be_specified @@ -182,7 +261,16 @@ def test_hold_in_escrow_can_be_specified (params[:options][:hold_in_escrow] == true) end.returns(braintree_result) - @gateway.authorize(100, credit_card('41111111111111111111'), :hold_in_escrow => true) + @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 @@ -195,61 +283,61 @@ def test_merchant_account_id_absent_if_not_provided def test_verification_merchant_account_id_exists_when_verify_card_and_merchant_account_id gateway = BraintreeBlueGateway.new( - :merchant_id => 'merchant_id', - :merchant_account_id => 'merchant_account_id', - :public_key => 'public_key', - :private_key => 'private_key' + merchant_id: 'merchant_id', + merchant_account_id: 'merchant_account_id', + public_key: 'public_key', + private_key: 'private_key' ) customer = stub( - :credit_cards => [stub_everything], - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + credit_cards: [stub_everything], + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' ) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options][:verification_merchant_account_id] == 'merchant_account_id' end.returns(result) - gateway.store(credit_card('41111111111111111111'), :verify_card => true) + gateway.store(credit_card('41111111111111111111'), verify_card: true) end def test_merchant_account_id_can_be_set_by_options gateway = BraintreeBlueGateway.new( - :merchant_id => 'merchant_id', - :merchant_account_id => 'merchant_account_id', - :public_key => 'public_key', - :private_key => 'private_key' + merchant_id: 'merchant_id', + merchant_account_id: 'merchant_account_id', + public_key: 'public_key', + private_key: 'private_key' ) customer = stub( - :credit_cards => [stub_everything], - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + credit_cards: [stub_everything], + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' ) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options][:verification_merchant_account_id] == 'value_from_options' end.returns(result) - gateway.store(credit_card('41111111111111111111'), :verify_card => true, :verification_merchant_account_id => 'value_from_options') + gateway.store(credit_card('41111111111111111111'), verify_card: true, verification_merchant_account_id: 'value_from_options') end def test_store_with_verify_card_true customer = stub( - :credit_cards => [stub_everything], - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + credit_cards: [stub_everything], + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' ) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options].has_key?(:verify_card) assert_equal true, params[:credit_card][:options][:verify_card] @@ -257,171 +345,171 @@ def test_store_with_verify_card_true params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :verify_card => true) + response = @gateway.store(credit_card('41111111111111111111'), verify_card: true) assert_equal '123', response.params['customer_vault_id'] assert_equal response.params['customer_vault_id'], response.authorization end def test_passes_email customer = stub( - :credit_cards => [stub_everything], - :email => 'bob@example.com', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith', + credit_cards: [stub_everything], + email: 'bob@example.com', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith', id: '123' ) - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_equal 'bob@example.com', params[:email] params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :email => 'bob@example.com') + response = @gateway.store(credit_card('41111111111111111111'), email: 'bob@example.com') assert_success response end def test_scrubs_invalid_email customer = stub( - :credit_cards => [stub_everything], - :email => nil, - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith', - :id => '123' + credit_cards: [stub_everything], + email: nil, + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith', + id: '123' ) - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_equal nil, params[:email] params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :email => 'bogus') + response = @gateway.store(credit_card('41111111111111111111'), email: 'bogus') assert_success response end def test_store_with_verify_card_false customer = stub( - :credit_cards => [stub_everything], - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + credit_cards: [stub_everything], + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' ) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options].has_key?(:verify_card) assert_equal false, params[:credit_card][:options][:verify_card] params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :verify_card => false) + response = @gateway.store(credit_card('41111111111111111111'), verify_card: false) assert_equal '123', response.params['customer_vault_id'] assert_equal response.params['customer_vault_id'], response.authorization end def test_store_with_billing_address_options customer_attributes = { - :credit_cards => [stub_everything], - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + credit_cards: [stub_everything], + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' } billing_address = { - :address1 => '1 E Main St', - :address2 => 'Suite 403', - :city => 'Chicago', - :state => 'Illinois', - :zip => '60622', - :country_name => 'US' + address1: '1 E Main St', + address2: 'Suite 403', + city: 'Chicago', + state: 'Illinois', + zip: '60622', + country_name: 'US' } customer = stub(customer_attributes) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_not_nil params[:credit_card][:billing_address] - [:street_address, :extended_address, :locality, :region, :postal_code, :country_name].each do |billing_attribute| + %i[street_address extended_address locality region postal_code country_name].each do |billing_attribute| params[:credit_card][:billing_address].has_key?(billing_attribute) if params[:billing_address] end params end.returns(result) - @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + @gateway.store(credit_card('41111111111111111111'), billing_address: billing_address) end def test_store_with_phone_only_billing_address_option customer_attributes = { - :credit_cards => [stub_everything], - :email => 'email', - :first_name => 'John', - :last_name => 'Smith', - :phone => '123-456-7890' + credit_cards: [stub_everything], + email: 'email', + first_name: 'John', + last_name: 'Smith', + phone: '123-456-7890' } billing_address = { - :phone => '123-456-7890' + phone: '123-456-7890' } customer = stub(customer_attributes) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_nil params[:credit_card][:billing_address] params end.returns(result) - @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + @gateway.store(credit_card('41111111111111111111'), billing_address: billing_address) end def test_store_with_nil_billing_address_options customer_attributes = { - :credit_cards => [stub_everything], - :email => 'email', - :first_name => 'John', - :last_name => 'Smith', - :phone => '123-456-7890' + credit_cards: [stub_everything], + email: 'email', + first_name: 'John', + last_name: 'Smith', + phone: '123-456-7890' } billing_address = { - :name => 'John Smith', - :phone => '123-456-7890', - :company => nil, - :address1 => nil, - :address2 => '', - :city => nil, - :state => nil, - :zip => nil, - :country_name => nil + name: 'John Smith', + phone: '123-456-7890', + company: nil, + address1: nil, + address2: '', + city: nil, + state: nil, + zip: nil, + country_name: nil } customer = stub(customer_attributes) customer.stubs(:id).returns('123') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_nil params[:credit_card][:billing_address] params end.returns(result) - @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + @gateway.store(credit_card('41111111111111111111'), billing_address: billing_address) end def test_store_with_credit_card_token customer = stub( - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith' + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith' ) customer.stubs(:id).returns('123') braintree_credit_card = stub_everything(token: 'cctoken') customer.stubs(:credit_cards).returns([braintree_credit_card]) - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_equal 'cctoken', params[:credit_card][:token] params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :credit_card_token => 'cctoken') + response = @gateway.store(credit_card('41111111111111111111'), credit_card_token: 'cctoken') assert_success response assert_equal 'cctoken', response.params['braintree_customer']['credit_cards'][0]['token'] assert_equal 'cctoken', response.params['credit_card_token'] @@ -429,15 +517,15 @@ def test_store_with_credit_card_token def test_store_with_customer_id customer = stub( - :email => 'email', - :phone => '321-654-0987', - :first_name => 'John', - :last_name => 'Smith', - :credit_cards => [stub_everything] + email: 'email', + phone: '321-654-0987', + first_name: 'John', + last_name: 'Smith', + credit_cards: [stub_everything] ) customer.stubs(:id).returns('customerid') - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:find). with('customerid'). raises(Braintree::NotFoundError) @@ -446,7 +534,7 @@ def test_store_with_customer_id params end.returns(result) - response = @gateway.store(credit_card('41111111111111111111'), :customer => 'customerid') + response = @gateway.store(credit_card('41111111111111111111'), customer: 'customerid') assert_success response assert_equal 'customerid', response.params['braintree_customer']['id'] end @@ -479,17 +567,17 @@ def test_store_with_existing_customer_id_and_nil_billing_address_options token: 'cctoken' ) options = { - :customer => 'customerid', - :billing_address => { - :name => 'John Smith', - :phone => '123-456-7890', - :company => nil, - :address1 => nil, - :address2 => nil, - :city => nil, - :state => nil, - :zip => nil, - :country_name => nil + customer: 'customerid', + billing_address: { + name: 'John Smith', + phone: '123-456-7890', + company: nil, + address1: nil, + address2: nil, + city: nil, + state: nil, + zip: nil, + country_name: nil } } @@ -510,88 +598,88 @@ def test_store_with_existing_customer_id_and_nil_billing_address_options end def test_update_with_cvv - stored_credit_card = mock(:token => 'token', :default? => true) - customer = mock(:credit_cards => [stored_credit_card], :id => '123') + stored_credit_card = mock(token: 'token', default?: true) + customer = mock(credit_cards: [stored_credit_card], id: '123') Braintree::CustomerGateway.any_instance.stubs(:find).with('vault_id').returns(customer) BraintreeBlueGateway.any_instance.stubs(:customer_hash) - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:update).with do |vault, params| assert_equal '567', params[:credit_card][:cvv] assert_equal 'Longbob Longsen', params[:credit_card][:cardholder_name] [vault, params] end.returns(result) - @gateway.update('vault_id', credit_card('41111111111111111111', :verification_value => '567')) + @gateway.update('vault_id', credit_card('41111111111111111111', verification_value: '567')) end def test_update_with_verify_card_true - stored_credit_card = stub(:token => 'token', :default? => true) - customer = stub(:credit_cards => [stored_credit_card], :id => '123') + stored_credit_card = stub(token: 'token', default?: true) + customer = stub(credit_cards: [stored_credit_card], id: '123') Braintree::CustomerGateway.any_instance.stubs(:find).with('vault_id').returns(customer) BraintreeBlueGateway.any_instance.stubs(:customer_hash) - result = Braintree::SuccessfulResult.new(:customer => customer) + result = Braintree::SuccessfulResult.new(customer: customer) Braintree::CustomerGateway.any_instance.expects(:update).with do |vault, params| assert_equal true, params[:credit_card][:options][:verify_card] [vault, params] end.returns(result) - @gateway.update('vault_id', credit_card('41111111111111111111'), :verify_card => true) + @gateway.update('vault_id', credit_card('41111111111111111111'), verify_card: true) end def test_merge_credit_card_options_ignores_bad_option - params = {:first_name => 'John', :credit_card => {:cvv => '123'}} - options = {:verify_card => true, :bogus => 'ignore me'} + params = { first_name: 'John', credit_card: { cvv: '123' } } + options = { verify_card: true, bogus: 'ignore me' } merged_params = @gateway.send(:merge_credit_card_options, params, options) - expected_params = {:first_name => 'John', :credit_card => {:cvv => '123', :options => {:verify_card => true}}} + expected_params = { first_name: 'John', credit_card: { cvv: '123', options: { verify_card: true } } } assert_equal expected_params, merged_params end def test_merge_credit_card_options_handles_nil_credit_card - params = {:first_name => 'John'} - options = {:verify_card => true, :bogus => 'ignore me'} + params = { first_name: 'John' } + options = { verify_card: true, bogus: 'ignore me' } merged_params = @gateway.send(:merge_credit_card_options, params, options) - expected_params = {:first_name => 'John', :credit_card => {:options => {:verify_card => true}}} + expected_params = { first_name: 'John', credit_card: { options: { verify_card: true } } } assert_equal expected_params, merged_params end def test_merge_credit_card_options_handles_billing_address billing_address = { - :address1 => '1 E Main St', - :city => 'Chicago', - :state => 'Illinois', - :zip => '60622', - :country => 'US' + address1: '1 E Main St', + city: 'Chicago', + state: 'Illinois', + zip: '60622', + country: 'US' } - params = {:first_name => 'John'} - options = {:billing_address => billing_address} + params = { first_name: 'John' } + options = { billing_address: billing_address } expected_params = { - :first_name => 'John', - :credit_card => { - :billing_address => { - :street_address => '1 E Main St', - :extended_address => nil, - :company => nil, - :locality => 'Chicago', - :region => 'Illinois', - :postal_code => '60622', - :country_code_alpha2 => 'US', - :country_code_alpha3 => 'USA' + first_name: 'John', + credit_card: { + billing_address: { + street_address: '1 E Main St', + extended_address: nil, + company: nil, + locality: 'Chicago', + region: 'Illinois', + postal_code: '60622', + country_code_alpha2: 'US', + country_code_alpha3: 'USA' }, - :options => {} + options: {} } } assert_equal expected_params, @gateway.send(:merge_credit_card_options, params, options) end def test_merge_credit_card_options_only_includes_billing_address_when_present - params = {:first_name => 'John'} + params = { first_name: 'John' } options = {} expected_params = { - :first_name => 'John', - :credit_card => { - :options => {} + first_name: 'John', + credit_card: { + options: {} } } assert_equal expected_params, @gateway.send(:merge_credit_card_options, params, options) @@ -601,78 +689,144 @@ def test_address_country_handling Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_code_alpha2] == 'US') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country => 'US'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { country: 'US' }) Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_code_alpha2] == 'US') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_alpha2 => 'US'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { country_code_alpha2: 'US' }) Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_name] == 'United States of America') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_name => 'United States of America'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { country_name: 'United States of America' }) Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_code_alpha3] == 'USA') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_alpha3 => 'USA'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { country_code_alpha3: 'USA' }) Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_code_numeric] == 840) end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_numeric => 840}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { country_code_numeric: 840 }) end def test_address_zip_handling Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:postal_code] == '12345') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:zip => '12345'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { zip: '12345' }) Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:postal_code] == nil) end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:zip => '1234567890'}) + @gateway.purchase(100, credit_card('41111111111111111111'), billing_address: { zip: '1234567890' }) end def test_cardholder_name_passing_with_card Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:credit_card][:cardholder_name] == 'Longbob Longsen') end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :customer => {:first_name => 'Longbob', :last_name => 'Longsen'}) + @gateway.purchase(100, credit_card('41111111111111111111'), customer: { first_name: 'Longbob', last_name: 'Longsen' }) end - def test_three_d_secure_pass_thru_handling + def test_three_d_secure_pass_thru_handling_version_1 Braintree::TransactionGateway. any_instance. expects(:sale). with(has_entries(three_d_secure_pass_thru: { cavv: 'cavv', eci_flag: 'eci', - xid: 'xid', + xid: 'xid' })). returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: {cavv: 'cavv', eci: 'eci', xid: 'xid'}) + @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: { cavv: 'cavv', eci: 'eci', xid: 'xid' }) + end + + def test_three_d_secure_pass_thru_handling_version_2 + 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: 'eci', + ds_transaction_id: 'trans_id', + cavv_algorithm: 'algorithm', + 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 + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(has_entries(three_d_secure_pass_thru: has_entries( + three_d_secure_version: '2.0', + cavv: 'cavv', + eci_flag: 'eci', + ds_transaction_id: 'trans_id' + ))). + returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: { version: '2.0', cavv: 'cavv', eci: 'eci', ds_transaction_id: 'trans_id' }) + end + + def test_purchase_string_based_payment_method_nonce_removes_customer + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(Not(has_key(:customer))). + returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), payment_method_nonce: '1234') + end + + def test_authorize_string_based_payment_method_nonce_removes_customer + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(Not(has_key(:customer))). + returns(braintree_result) + + @gateway.authorize(100, credit_card('41111111111111111111'), payment_method_nonce: '1234') end def test_passes_recurring_flag @gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :merchant_account_id => 'present', - :public_key => 'test', - :private_key => 'test' + merchant_id: 'test', + merchant_account_id: 'present', + public_key: 'test', + private_key: 'test' ) Braintree::TransactionGateway.any_instance.expects(:sale). - with(has_entries(:recurring => true)). + with(has_entries(transaction_source: 'recurring')). returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :recurring => true) + @gateway.purchase(100, credit_card('41111111111111111111'), recurring: true) Braintree::TransactionGateway.any_instance.expects(:sale). - with(Not(has_entries(:recurring => true))). + with(Not(has_entries(recurring: true))). returns(braintree_result) @gateway.purchase(100, credit_card('41111111111111111111')) @@ -680,10 +834,35 @@ def test_passes_recurring_flag def test_passes_transaction_source Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| - (params[:transaction_source] == 'recurring') - (params[:recurring] == nil) + (params[:transaction_source] == 'recurring') && (params[:recurring] == nil) + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), transaction_source: 'recurring', recurring: true) + end + + def test_passes_skip_avs + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:skip_avs] == true) + end.returns(braintree_result(avs_postal_code_response_code: 'B', avs_street_address_response_code: 'B')) + + response = @gateway.purchase(100, credit_card('41111111111111111111'), skip_avs: true) + assert_equal 'B', response.avs_result['code'] + end + + def test_passes_skip_cvv + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:skip_cvv] == true) + end.returns(braintree_result(cvv_response_code: 'B')) + + response = @gateway.purchase(100, credit_card('41111111111111111111'), skip_cvv: true) + assert_equal 'B', response.cvv_result['code'] + end + + def test_successful_purchase_with_account_type + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + params[:options][:credit_card][:account_type] == 'credit' end.returns(braintree_result) - @gateway.purchase(100, credit_card('41111111111111111111'), :transaction_source => 'recurring', :recurring => true) + + @gateway.purchase(100, credit_card('41111111111111111111'), account_type: 'credit') end def test_configured_logger_has_a_default @@ -704,9 +883,9 @@ def test_default_logger_sets_warn_level_without_overwriting_global # Re-instantiate a gateway to show it doesn't touch the global gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :public_key => 'test', - :private_key => 'test' + merchant_id: 'test', + public_key: 'test', + private_key: 'test' ) internal_gateway = gateway.instance_variable_get(:@braintree_gateway) @@ -723,9 +902,9 @@ def test_that_setting_a_wiredump_device_on_the_gateway_sets_the_braintree_logger assert_not_equal logger, Braintree::Configuration.logger gateway = BraintreeBlueGateway.new( - :merchant_id => 'test', - :public_key => 'test', - :private_key => 'test' + merchant_id: 'test', + public_key: 'test', + private_key: 'test' ) internal_gateway = gateway.instance_variable_get(:@braintree_gateway) @@ -739,7 +918,7 @@ def test_solution_id_is_added_to_create_transaction_parameters ActiveMerchant::Billing::BraintreeBlueGateway.application_id = 'ABC123' assert_equal @gateway.send(:create_transaction_parameters, 100, credit_card('41111111111111111111'), {})[:channel], 'ABC123' - gateway = BraintreeBlueGateway.new(:merchant_id => 'test', :public_key => 'test', :private_key => 'test', channel: 'overidden-channel') + gateway = BraintreeBlueGateway.new(merchant_id: 'test', public_key: 'test', private_key: 'test', channel: 'overidden-channel') assert_equal gateway.send(:create_transaction_parameters, 100, credit_card('41111111111111111111'), {})[:channel], 'overidden-channel' ensure ActiveMerchant::Billing::BraintreeBlueGateway.application_id = nil @@ -748,8 +927,8 @@ def test_solution_id_is_added_to_create_transaction_parameters def test_successful_purchase_with_descriptor Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:descriptor][:name] == 'wow*productname') && - (params[:descriptor][:phone] == '4443331112') && - (params[:descriptor][:url] == 'wow.com') + (params[:descriptor][:phone] == '4443331112') && + (params[:descriptor][:url] == 'wow.com') end.returns(braintree_result) @gateway.purchase(100, credit_card('41111111111111111111'), descriptor_name: 'wow*productname', descriptor_phone: '4443331112', descriptor_url: 'wow.com') end @@ -757,7 +936,7 @@ def test_successful_purchase_with_descriptor def test_successful_purchase_with_device_data Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:device_data] == 'device data string') - end.returns(braintree_result({risk_data: {id: 123456, decision: 'Decline', device_data_captured: true, fraud_service_provider: 'kount'}})) + end.returns(braintree_result({ risk_data: { id: 123456, decision: 'Decline', device_data_captured: true, fraud_service_provider: 'kount' } })) response = @gateway.purchase(100, credit_card('41111111111111111111'), device_data: 'device data string') @@ -769,102 +948,146 @@ def test_successful_purchase_with_device_data assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] end + def test_successful_purchase_with_travel_data + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:industry][:industry_type] == Braintree::Transaction::IndustryType::TravelAndCruise) && + (params[:industry][:data][:travel_package] == 'flight') && + (params[:industry][:data][:departure_date] == '2050-07-22') && + (params[:industry][:data][:lodging_check_in_date] == '2050-07-22') && + (params[:industry][:data][:lodging_check_out_date] == '2050-07-25') && + (params[:industry][:data][:lodging_name] == 'Best Hotel Ever') + end.returns(braintree_result) + + @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 + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:industry][:industry_type] == Braintree::Transaction::IndustryType::Lodging) && + (params[:industry][:data][:folio_number] == 'ABC123') && + (params[:industry][:data][:check_in_date] == '2050-12-22') && + (params[:industry][:data][:check_out_date] == '2050-12-25') && + (params[:industry][:data][:room_rate] == '80.00') + end.returns(braintree_result) + + @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 Braintree::TransactionGateway.any_instance.expects(:sale). with( - :amount => '1.00', - :order_id => '1', - :customer => {:id => nil, :email => nil, :phone => nil, - :first_name => 'Longbob', :last_name => 'Longsen'}, - :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, - :custom_fields => nil, - :apple_pay_card => { - :number => '4111111111111111', - :expiration_month => '09', - :expiration_year => (Time.now.year + 1).to_s, - :cardholder_name => 'Longbob Longsen', - :cryptogram => '111111111100cryptogram', - :eci_indicator => '05' + amount: '1.00', + order_id: '1', + customer: { id: nil, email: nil, phone: nil, + first_name: 'Longbob', last_name: 'Longsen' }, + options: { store_in_vault: false, submit_for_settlement: nil, hold_in_escrow: nil }, + custom_fields: nil, + apple_pay_card: { + number: '4111111111111111', + expiration_month: '09', + expiration_year: (Time.now.year + 1).to_s, + cardholder_name: 'Longbob Longsen', + cryptogram: '111111111100cryptogram', + eci_indicator: '05' } ). - returns(braintree_result(:id => 'transaction_id')) - - credit_card = network_tokenization_credit_card('4111111111111111', - :brand => 'visa', - :transaction_id => '123', - :eci => '05', - :payment_cryptogram => '111111111100cryptogram' + returns(braintree_result(id: 'transaction_id')) + + credit_card = network_tokenization_credit_card( + '4111111111111111', + brand: 'visa', + transaction_id: '123', + eci: '05', + payment_cryptogram: '111111111100cryptogram' ) - response = @gateway.authorize(100, credit_card, :test => true, :order_id => '1') + 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', - :order_id => '1', - :customer => {:id => nil, :email => nil, :phone => nil, - :first_name => 'Longbob', :last_name => 'Longsen'}, - :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, - :custom_fields => nil, - :android_pay_card => { - :number => '4111111111111111', - :expiration_month => '09', - :expiration_year => (Time.now.year + 1).to_s, - :cryptogram => '111111111100cryptogram', - :google_transaction_id => '1234567890', - :source_card_type => 'visa', - :source_card_last_four => '1111', - :eci_indicator => '05' + amount: '1.00', + order_id: '1', + customer: { id: nil, email: nil, phone: nil, + first_name: 'Longbob', last_name: 'Longsen' }, + options: { store_in_vault: false, submit_for_settlement: nil, hold_in_escrow: nil }, + custom_fields: nil, + google_pay_card: { + number: '4111111111111111', + expiration_month: '09', + expiration_year: (Time.now.year + 1).to_s, + cryptogram: '111111111100cryptogram', + google_transaction_id: '1234567890', + source_card_type: 'visa', + source_card_last_four: '1111', + eci_indicator: '05' } ). - returns(braintree_result(:id => 'transaction_id')) - - credit_card = network_tokenization_credit_card('4111111111111111', - :brand => 'visa', - :eci => '05', - :payment_cryptogram => '111111111100cryptogram', - :source => :android_pay, - :transaction_id => '1234567890' + returns(braintree_result(id: 'transaction_id')) + + credit_card = network_tokenization_credit_card( + '4111111111111111', + brand: 'visa', + eci: '05', + payment_cryptogram: '111111111100cryptogram', + source: :google_pay, + transaction_id: '1234567890' ) - response = @gateway.authorize(100, credit_card, :test => true, :order_id => '1') + 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', - :order_id => '1', - :customer => {:id => nil, :email => nil, :phone => nil, - :first_name => 'Longbob', :last_name => 'Longsen'}, - :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, - :custom_fields => nil, - :android_pay_card => { - :number => '4111111111111111', - :expiration_month => '09', - :expiration_year => (Time.now.year + 1).to_s, - :cryptogram => '111111111100cryptogram', - :google_transaction_id => '1234567890', - :source_card_type => 'visa', - :source_card_last_four => '1111', - :eci_indicator => '05' + amount: '1.00', + order_id: '1', + customer: { id: nil, email: nil, phone: nil, + first_name: 'Longbob', last_name: 'Longsen' }, + options: { store_in_vault: false, submit_for_settlement: nil, hold_in_escrow: nil }, + custom_fields: nil, + credit_card: { + number: '4111111111111111', + expiration_month: '09', + expiration_year: (Time.now.year + 1).to_s, + cardholder_name: 'Longbob Longsen', + network_tokenization_attributes: { + cryptogram: '111111111100cryptogram', + ecommerce_indicator: '05' + } } ). - returns(braintree_result(:id => 'transaction_id')) + 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') + response = @gateway.authorize(100, credit_card, test: true, order_id: '1') assert_equal 'transaction_id', response.authorization end @@ -873,7 +1096,7 @@ def test_supports_network_tokenization end def test_unsuccessful_transaction_returns_id_when_available - Braintree::TransactionGateway.any_instance.expects(:sale).returns(braintree_error_result(transaction: {id: 'transaction_id'})) + Braintree::TransactionGateway.any_instance.expects(:sale).returns(braintree_error_result(transaction: { id: 'transaction_id' })) assert response = @gateway.purchase(100, credit_card('41111111111111111111')) refute response.success? assert response.authorization.present? @@ -914,14 +1137,287 @@ def test_refund_unsettled_payment_forces_void_on_full_refund assert response.success? end + def test_refund_unsettled_payment_other_error_does_not_void + Braintree::TransactionGateway.any_instance. + expects(:refund). + returns(braintree_error_result(message: 'Some error message')) + + Braintree::TransactionGateway.any_instance. + expects(:void). + never + + response = @gateway.refund(1.00, 'transaction_id', force_full_refund_if_unsettled: true) + refute response.success? + end + + def test_stored_credential_recurring_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :recurring, :initial) }) + end + + def test_stored_credential_recurring_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :recurring, id: '123ABC') }) + end + + def test_stored_credential_recurring_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: 'recurring' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :recurring, :initial) }) + end + + def test_stored_credential_recurring_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: 'recurring' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :recurring, id: '123ABC') }) + end + + def test_stored_credential_installment_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :installment, :initial) }) + end + + def test_stored_credential_installment_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :installment, id: '123ABC') }) + end + + def test_stored_credential_installment_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: 'recurring' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :installment, :initial) }) + end + + def test_stored_credential_installment_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: 'recurring' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :installment, id: '123ABC') }) + end + + def test_stored_credential_unscheduled_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :unscheduled, :initial) }) + end + + def test_stored_credential_unscheduled_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: '' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :unscheduled, id: '123ABC') }) + end + + def test_stored_credential_unscheduled_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: 'unscheduled' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :unscheduled, :initial) }) + end + + def test_stored_credential_unscheduled_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + external_vault: { + status: 'vaulted', + previous_network_transaction_id: '123ABC' + }, + transaction_source: 'unscheduled' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: stored_credential(:merchant, :unscheduled, id: '123ABC') }) + end + + def test_stored_credential_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_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( + { + external_vault: { + status: 'will_vault' + }, + transaction_source: 'moto' + } + ) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), { test: true, order_id: '1', stored_credential: { initiator: 'merchant', reason_type: 'moto', initial_transaction: true } }) + end + + def test_raises_exeption_when_adding_bank_account_to_customer_without_billing_address + bank_account = check({ account_number: '1000000002', routing_number: '011000015' }) + + err = @gateway.store(bank_account, { customer: 'abc123' }) + assert_equal 'billing_address is required parameter to store and verify Bank accounts.', err.message + end + + def test_returns_error_on_authorize_when_passing_a_bank_account + bank_account = check({ account_number: '1000000002', routing_number: '011000015' }) + response = @gateway.authorize(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_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: { + address1: '1670', + address2: '1670 NW 82ND AVE', + city: 'Miami', + state: 'FL', + zip: '32191' + } + } + bank_account = check({ account_number: '1000000002', routing_number: '011000015' }) + response = @gateway.store(bank_account, options) + + assert_failure response + assert_match(/ach_mandate is a required parameter to process/, response.message) + end + + def test_scrub_sensitive_data + assert_equal filtered_success_token_nonce, @gateway.scrub(success_create_token_nonce) + end + private def braintree_result(options = {}) - Braintree::SuccessfulResult.new(:transaction => Braintree::Transaction._new(nil, {:id => 'transaction_id'}.merge(options))) + Braintree::SuccessfulResult.new(transaction: Braintree::Transaction._new(nil, { id: 'transaction_id' }.merge(options))) end def braintree_error_result(options = {}) - Braintree::ErrorResult.new(@internal_gateway, {errors: {}}.merge(options)) + Braintree::ErrorResult.new(@internal_gateway, { errors: {} }.merge(options)) end def with_braintree_configuration_restoration(&block) @@ -936,4 +1432,156 @@ def with_braintree_configuration_restoration(&block) # Reset the Braintree logger Braintree::Configuration.logger = nil end + + def standard_purchase_params + { + amount: '1.00', + order_id: '1', + customer: { id: nil, email: nil, phone: nil, + first_name: 'Longbob', last_name: 'Longsen' }, + options: { store_in_vault: false, submit_for_settlement: true, hold_in_escrow: nil }, + custom_fields: nil, + credit_card: { + number: '41111111111111111111', + cvv: '123', + expiration_month: '09', + expiration_year: (Time.now.year + 1).to_s, + cardholder_name: 'Longbob Longsen' + } + } + end + + def success_create_token_nonce + <<-RESPONSE + [Braintree] + [Braintree] 673970040 + [Braintree] tokenusbankacct_bc_tbf5zn_6xtcs8_wmknct_y3yfy5_sg6 + [Braintree] + [Braintree] network_check + [Braintree] + [Braintree] + [Braintree] + [Braintree] eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0aW9uRmluZ2VycHJpbnQiOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpGVXpJMU5pSXNJbXRwWkNJNklqSXdNVGd3TkRJMk1UWXRjMkZ1WkdKdmVDSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dllYQnBMbk5oYm1SaWIzZ3VZbkpoYVc1MGNtVmxaMkYwWlhkaGVTNWpiMjBpZlEuZXlKbGVIQWlPakUyTkRNeE5EazFNVEFzSW1wMGFTSTZJbVJpTkRJME1XRmpMVGMwTkdVdE5EWmpOQzFoTjJWakxUbGlNakpoWm1KaFl6QmxZU0lzSW5OMVlpSTZJbXRrTm5SaVkydGpaR1JtTm5sMlpHY2lMQ0pwYzNNaU9pSm9kSFJ3Y3pvdkwyRndhUzV6WVc1a1ltOTRMbUp5WVdsdWRISmxaV2RoZEdWM1lYa3VZMjl0SWl3aWJXVnlZMmhoYm5RaU9uc2ljSFZpYkdsalgybGtJam9pYTJRMmRHSmphMk5rWkdZMmVYWmtaeUlzSW5abGNtbG1lVjlqWVhKa1gySjVYMlJsWm1GMWJIUWlPblJ5ZFdWOUxDSnlhV2RvZEhNaU9sc2liV0Z1WVdkbFgzWmhkV3gwSWwwc0luTmpiM0JsSWpwYklrSnlZV2x1ZEhKbFpUcFdZWFZzZENKZExDSnZjSFJwYjI1eklqcDdmWDAuYnpKRUFZWWxSenhmOUJfNGJvN1JrUlZaMERtR1pEVFRieDVQWWxXdFNCSjhnc19pT3RTQ0MtWHRYcEM3NE5pV1A5a0g0MG9neWVKVzZaNkpTbnNOTGciLCJjb25maWdVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMva2Q2dGJja2NkZGY2eXZkZy9jbGllbnRfYXBpL3YxL2NvbmZpZ3VyYXRpb24iLCJncmFwaFFMIjp7InVybCI6Imh0dHBzOi8vcGF5bWVudHMuc2FuZGJveC5icmFpbnRyZWUtYXBpLmNvbS9ncmFwaHFsIiwiZGF0ZSI6IjIwMTgtMDUtMDgiLCJmZWF0dXJlcyI6WyJ0b2tlbml6ZV9jcmVkaXRfY2FyZHMiXX0sImNsaWVudEFwaVVybCI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb206NDQzL21lcmNoYW50cy9rZDZ0YmNrY2RkZjZ5dmRnL2NsaWVudF9hcGkiLCJlbnZpcm9ubWVudCI6InNhbmRib3giLCJtZXJjaGFudElkIjoia2Q2dGJja2NkZGY2eXZkZyIsImFzc2V0c1VybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXV0aFVybCI6Imh0dHBzOi8vYXV0aC52ZW5tby5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwidmVubW8iOiJvZmYiLCJjaGFsbGVuZ2VzIjpbXSwidGhyZWVEU2VjdXJlRW5hYmxlZCI6dHJ1ZSwiYW5hbHl0aWNzIjp7InVybCI6Imh0dHBzOi8vb3JpZ2luLWFuYWx5dGljcy1zYW5kLnNhbmRib3guYnJhaW50cmVlLWFwaS5jb20va2Q2dGJja2NkZGY2eXZkZyJ9LCJwYXlwYWxFbmFibGVkIjp0cnVlLCJicmFpbnRyZWVfYXBpIjp7InVybCI6Imh0dHBzOi8vcGF5bWVudHMuc2FuZGJveC5icmFpbnRyZWUtYXBpLmNvbSIsImFjY2Vzc190b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSkZVekkxTmlJc0ltdHBaQ0k2SWpJd01UZ3dOREkyTVRZdGMyRnVaR0p2ZUNJc0ltbHpjeUk2SW1oMGRIQnpPaTh2WVhCcExuTmhibVJpYjNndVluSmhhVzUwY21WbFoyRjBaWGRoZVM1amIyMGlmUS5leUpsZUhBaU9qRTJORE14TkRrMU1UQXNJbXAwYVNJNklqRmhNMkpqTm1OaExUY3hNalV0TkdKaU5TMWlOMk5tTFdReU5HUTNNMlEyWWpJd01TSXNJbk4xWWlJNkltdGtOblJpWTJ0alpHUm1ObmwyWkdjaUxDSnBjM01pT2lKb2RIUndjem92TDJGd2FTNXpZVzVrWW05NExtSnlZV2x1ZEhKbFpXZGhkR1YzWVhrdVkyOXRJaXdpYldWeVkyaGhiblFpT25zaWNIVmliR2xqWDJsa0lqb2lhMlEyZEdKamEyTmtaR1kyZVhaa1p5SXNJblpsY21sbWVWOWpZWEprWDJKNVgyUmxabUYxYkhRaU9uUnlkV1Y5TENKeWFXZG9kSE1pT2xzaWRHOXJaVzVwZW1VaUxDSnRZVzVoWjJWZmRtRjFiSFFpWFN3aWMyTnZjR1VpT2xzaVFuSmhhVzUwY21WbE9sWmhkV3gwSWwwc0ltOXdkR2x2Ym5NaU9udDlmUS52ZGtCVFVpOGtPdm1lSUVvdjRYMFBtVmpuLVFER2JNSWhyQ3JmVkpRcUIxVG5GSVYySkx3U2RxYlFXXzN6R2RIcUl6WkVzVEtQdXNxRF9nWUhwR2xjdyJ9LCJwYXlwYWwiOnsiYmlsbGluZ0FncmVlbWVudHNFbmFibGVkIjp0cnVlLCJlbnZpcm9ubWVudE5vTmV0d29yayI6dHJ1ZSwidW52ZXR0ZWRNZXJjaGFudCI6ZmFsc2UsImFsbG93SHR0cCI6dHJ1ZSwiZGlzcGxheU5hbWUiOiJlbmRhdmEiLCJjbGllbnRJZCI6bnVsbCwicHJpdmFjeVVybCI6Imh0dHA6Ly9leGFtcGxlLmNvbS9wcCIsInVzZXJBZ3JlZW1lbnRVcmwiOiJodHRwOi8vZXhhbXBsZS5jb20vdG9zIiwiYmFzZVVybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXNzZXRzVXJsIjoiaHR0cHM6Ly9jaGVja291dC5wYXlwYWwuY29tIiwiZGlyZWN0QmFzZVVybCI6bnVsbCwiZW52aXJvbm1lbnQiOiJvZmZsaW5lIiwiYnJhaW50cmVlQ2xpZW50SWQiOiJtYXN0ZXJjbGllbnQzIiwibWVyY2hhbnRBY2NvdW50SWQiOiJlbmRhdmEiLCJjdXJyZW5jeUlzb0NvZGUiOiJVU0QifX0= + [Braintree] + [Braintree] + [Braintree] 011000015 + [Braintree] 0000 + [Braintree] checking + [Braintree] Jon Doe + [Braintree] FEDERAL RESERVE BANK + [Braintree] + [Braintree] 2022-01-24T22:25:11Z + [Braintree] By clicking ["Checkout"], I authorize Braintree, a service of PayPal, on behalf of [your business name here] (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account. + [Braintree] + [Braintree] personal + [Braintree] true + [Braintree] 1000000000 + [Braintree] + [Braintree] true + [Braintree] + [Braintree] Jon + [Braintree] Doe + [Braintree] true + [Braintree] 9dkrvzg + [Braintree] 673970040 + [Braintree] Y3VzdG9tZXJfNjczOTcwMDQw + [Braintree] https://assets.braintreegateway.com/payment_method_logo/us_bank_account.png?environment=sandbox + [Braintree] + [Braintree] + [Braintree] verified + [Braintree] + [Braintree] endava + [Braintree] 1000 + [Braintree] Approved + [Braintree] d4gaqtek + [Braintree] network_check + [Braintree] 2022-01-24T22:25:12Z + [Braintree] + [Braintree] 9dkrvzg + [Braintree] 0000 + [Braintree] checking + [Braintree] Jon Doe + [Braintree] FEDERAL RESERVE BANK + [Braintree] 011000015 + [Braintree] true + [Braintree] personal + [Braintree] + [Braintree] 2022-01-24T22:25:12Z + [Braintree] 2022-01-24T22:25:12Z + [Braintree] dXNiYW5rYWNjb3VudHZlcmlmaWNhdGlvbl9kNGdhcXRlaw + [Braintree] + [Braintree] + [Braintree] cGF5bWVudG1ldGhvZF91c2JfOWRrcnZ6Zw + [Braintree] 2022-01-24T22:25:12Z + [Braintree] 2022-01-24T22:25:12Z + [Braintree] + RESPONSE + end + + def filtered_success_token_nonce + <<-RESPONSE + [Braintree] + [Braintree] 673970040 + [Braintree] [FILTERED] + [Braintree] + [Braintree] network_check + [Braintree] + [Braintree] + [Braintree] + [Braintree] [FILTERED] + [Braintree] + [Braintree] + [Braintree] 011000015 + [Braintree] 0000 + [Braintree] checking + [Braintree] Jon Doe + [Braintree] FEDERAL RESERVE BANK + [Braintree] + [Braintree] 2022-01-24T22:25:11Z + [Braintree] By clicking ["Checkout"], I authorize Braintree, a service of PayPal, on behalf of [your business name here] (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account. + [Braintree] + [Braintree] personal + [Braintree] true + [Braintree] [FILTERED] + [Braintree] + [Braintree] true + [Braintree] + [Braintree] Jon + [Braintree] Doe + [Braintree] true + [Braintree] [FILTERED] + [Braintree] 673970040 + [Braintree] Y3VzdG9tZXJfNjczOTcwMDQw + [Braintree] https://assets.braintreegateway.com/payment_method_logo/us_bank_account.png?environment=sandbox + [Braintree] + [Braintree] + [Braintree] verified + [Braintree] + [Braintree] endava + [Braintree] 1000 + [Braintree] Approved + [Braintree] d4gaqtek + [Braintree] network_check + [Braintree] 2022-01-24T22:25:12Z + [Braintree] + [Braintree] [FILTERED] + [Braintree] 0000 + [Braintree] checking + [Braintree] Jon Doe + [Braintree] FEDERAL RESERVE BANK + [Braintree] 011000015 + [Braintree] true + [Braintree] personal + [Braintree] + [Braintree] 2022-01-24T22:25:12Z + [Braintree] 2022-01-24T22:25:12Z + [Braintree] dXNiYW5rYWNjb3VudHZlcmlmaWNhdGlvbl9kNGdhcXRlaw + [Braintree] + [Braintree] + [Braintree] cGF5bWVudG1ldGhvZF91c2JfOWRrcnZ6Zw + [Braintree] 2022-01-24T22:25:12Z + [Braintree] 2022-01-24T22:25:12Z + [Braintree] + RESPONSE + end end diff --git a/test/unit/gateways/braintree_orange_test.rb b/test/unit/gateways/braintree_orange_test.rb index ee4149137bb..49a15e4abe6 100644 --- a/test/unit/gateways/braintree_orange_test.rb +++ b/test/unit/gateways/braintree_orange_test.rb @@ -5,14 +5,14 @@ class BraintreeOrangeTest < Test::Unit::TestCase def setup @gateway = BraintreeOrangeGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @credit_card = credit_card @amount = 100 - @options = { :billing_address => address } + @options = { billing_address: address } end def test_successful_purchase @@ -27,7 +27,7 @@ def test_successful_purchase def test_fractional_amounts response = stub_comms do @gateway.purchase(100, @credit_card, @options.merge(currency: 'JPY')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| refute_match(/amount=1.00/, data) end.respond_with(successful_purchase_response) @@ -47,7 +47,7 @@ def test_successful_store def test_add_processor result = {} - @gateway.send(:add_processor, result, {:processor => 'ccprocessorb'}) + @gateway.send(:add_processor, result, { processor: 'ccprocessorb' }) assert_equal ['processor_id'], result.stringify_keys.keys.sort assert_equal 'ccprocessorb', result[:processor_id] end @@ -86,8 +86,8 @@ def test_unsuccessful_verify def test_add_address result = {} - @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) - assert_equal ['address1', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, { address1: '164 Waverley Street', country: 'US', state: 'CO' }) + assert_equal %w[address1 city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'CO', result['state'] assert_equal '164 Waverley Street', result['address1'] assert_equal 'US', result['country'] @@ -96,8 +96,8 @@ def test_add_address def test_add_shipping_address result = {} - @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}, 'shipping') - assert_equal ['shipping_address1', 'shipping_city', 'shipping_company', 'shipping_country', 'shipping_phone', 'shipping_state', 'shipping_zip'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, { address1: '164 Waverley Street', country: 'US', state: 'CO' }, 'shipping') + assert_equal %w[shipping_address1 shipping_city shipping_company shipping_country shipping_phone shipping_state shipping_zip], result.stringify_keys.keys.sort assert_equal 'CO', result['shipping_state'] assert_equal '164 Waverley Street', result['shipping_address1'] assert_equal 'US', result['shipping_country'] @@ -106,8 +106,8 @@ def test_add_shipping_address def test_adding_store_adds_vault_id_flag result = {} - @gateway.send(:add_creditcard, result, @credit_card, :store => true) - assert_equal ['ccexp', 'ccnumber', 'customer_vault', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort + @gateway.send(:add_creditcard, result, @credit_card, store: true) + assert_equal %w[ccexp ccnumber customer_vault cvv firstname lastname], result.stringify_keys.keys.sort assert_equal 'add_customer', result[:customer_vault] end @@ -115,17 +115,17 @@ def test_blank_store_doesnt_add_vault_flag result = {} @gateway.send(:add_creditcard, result, @credit_card, {}) - assert_equal ['ccexp', 'ccnumber', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort + assert_equal %w[ccexp ccnumber cvv firstname lastname], result.stringify_keys.keys.sort assert_nil result[:customer_vault] end def test_accept_check post = {} - check = Check.new(:name => 'Fred Bloggs', - :routing_number => '111000025', - :account_number => '123456789012', - :account_holder_type => 'personal', - :account_type => 'checking') + check = Check.new(name: 'Fred Bloggs', + routing_number: '111000025', + account_number: '123456789012', + account_holder_type: 'personal', + account_type: 'checking') @gateway.send(:add_check, post, check, {}) assert_equal %w[account_holder_type account_type checkaba checkaccount checkname payment], post.stringify_keys.keys.sort end @@ -155,7 +155,7 @@ def test_add_eci @gateway.purchase(@amount, @credit_card, {}) @gateway.expects(:commit).with { |_, _, parameters| parameters[:billing_method] == 'recurring' } - @gateway.purchase(@amount, @credit_card, {:eci => 'recurring'}) + @gateway.purchase(@amount, @credit_card, { eci: 'recurring' }) end def test_transcript_scrubbing diff --git a/test/unit/gateways/braintree_test.rb b/test/unit/gateways/braintree_test.rb index 7cff9f6512f..28fb1d0bc87 100644 --- a/test/unit/gateways/braintree_test.rb +++ b/test/unit/gateways/braintree_test.rb @@ -1,20 +1,19 @@ require 'test_helper' class BraintreeTest < Test::Unit::TestCase - def test_new_with_login_password_creates_braintree_orange gateway = BraintreeGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) assert_instance_of BraintreeOrangeGateway, gateway end def test_new_with_merchant_id_creates_braintree_blue gateway = BraintreeGateway.new( - :merchant_id => 'MERCHANT_ID', - :public_key => 'PUBLIC_KEY', - :private_key => 'PRIVATE_KEY' + merchant_id: 'MERCHANT_ID', + public_key: 'PUBLIC_KEY', + private_key: 'PRIVATE_KEY' ) assert_instance_of BraintreeBlueGateway, gateway end @@ -28,7 +27,7 @@ def test_should_have_homepage_url end def test_should_have_supported_credit_card_types - assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro], BraintreeGateway.supported_cardtypes + assert_equal %i[visa master american_express discover jcb diners_club maestro], BraintreeGateway.supported_cardtypes end def test_should_have_default_currency 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/bridge_pay_test.rb b/test/unit/gateways/bridge_pay_test.rb index ce60e3e05f9..dde12fc7ee8 100644 --- a/test/unit/gateways/bridge_pay_test.rb +++ b/test/unit/gateways/bridge_pay_test.rb @@ -63,7 +63,7 @@ def test_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/OK2657/, data) end.respond_with(successful_capture_response) @@ -80,7 +80,7 @@ def test_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/OK9757/, data) end.respond_with(successful_refund_response) @@ -97,7 +97,7 @@ def test_void refund = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/OK9757/, data) end.respond_with(successful_refund_response) @@ -123,15 +123,15 @@ def test_store_and_purchase_with_token def test_passing_cvv stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_purchase_response) end def test_passing_billing_address stub_comms do - @gateway.purchase(@amount, @credit_card, :billing_address => address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/Street=456\+My\+Street/, data) assert_match(/Zip=K1C2N6/, data) end.respond_with(successful_purchase_response) diff --git a/test/unit/gateways/cams_test.rb b/test/unit/gateways/cams_test.rb index 23102e41c39..fcd0a53cc54 100644 --- a/test/unit/gateways/cams_test.rb +++ b/test/unit/gateways/cams_test.rb @@ -7,8 +7,8 @@ def setup password: 'password9' ) - @credit_card = credit_card('4111111111111111', :month => 5, :year => 10) - @bad_credit_card = credit_card('4242424245555555', :month => 5, :year => 10) + @credit_card = credit_card('4111111111111111', month: 5, year: 10) + @bad_credit_card = credit_card('4242424245555555', month: 5, year: 10) @amount = 100 @options = { @@ -116,46 +116,46 @@ def test_scrub private def pre_scrubbed - <<-PRE_SCRUBBED -opening connection to secure.centralams.com:443... -opened -starting SSL for secure.centralams.com:443... -SSL established -<- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" -<- "amount=1.03¤cy=USD&ccnumber=4111111111111111&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=password9&username=testintegrationc" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" --> "Server: Apache\r\n" --> "Content-Length: 132\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 132 bytes... --> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" -read 132 bytes -Conn close + <<~PRE_SCRUBBED + opening connection to secure.centralams.com:443... + opened + starting SSL for secure.centralams.com:443... + SSL established + <- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" + <- "amount=1.03¤cy=USD&ccnumber=4111111111111111&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=password9&username=testintegrationc" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" + -> "Server: Apache\r\n" + -> "Content-Length: 132\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 132 bytes... + -> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" + read 132 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-POST_SCRUBBED -opening connection to secure.centralams.com:443... -opened -starting SSL for secure.centralams.com:443... -SSL established -<- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" -<- "amount=1.03¤cy=USD&ccnumber=[FILTERED]&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=[FILTERED]&username=testintegrationc" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" --> "Server: Apache\r\n" --> "Content-Length: 132\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 132 bytes... --> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" -read 132 bytes -Conn close + <<~POST_SCRUBBED + opening connection to secure.centralams.com:443... + opened + starting SSL for secure.centralams.com:443... + SSL established + <- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" + <- "amount=1.03¤cy=USD&ccnumber=[FILTERED]&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=[FILTERED]&username=testintegrationc" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" + -> "Server: Apache\r\n" + -> "Content-Length: 132\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 132 bytes... + -> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" + read 132 bytes + Conn close POST_SCRUBBED end diff --git a/test/unit/gateways/card_connect_test.rb b/test/unit/gateways/card_connect_test.rb index e4a49a1541b..7188dea17dc 100644 --- a/test/unit/gateways/card_connect_test.rb +++ b/test/unit/gateways/card_connect_test.rb @@ -14,11 +14,122 @@ def setup billing_address: address, description: 'Store Purchase' } + + @three_ds_secure = { + version: '2.0', + cavv: 'AJkBByEyYgAAAASwgmEodQAAAAA=', + eci: '05', + xid: '3875d372-d96d-412a-a806-5ac367d095b1' + } + end + + def test_three_ds_2_object_construction + post = {} + @three_ds_secure[:ds_transaction_id] = '3875d372-d96d-412a-a806-5ac367d095b1' + @options[:three_d_secure] = @three_ds_secure + + @gateway.send(:add_three_ds_mpi_data, post, @options) + three_ds_options = @options[:three_d_secure] + assert_equal three_ds_options[:cavv], post[:securevalue] + assert_equal three_ds_options[:eci], post[:secureflag] + assert_equal three_ds_options[:ds_transaction_id], post[:securedstid] + end + + def test_purchase_with_three_ds + @options[:three_d_secure] = @three_ds_secure + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + three_ds_params = JSON.parse(data)['three_dsecure'] + assert_equal 'AJkBByEyYgAAAASwgmEodQAAAAA=', three_ds_params['cavv'] + assert_equal '05', three_ds_params['eci'] + assert_equal '3875d372-d96d-412a-a806-5ac367d095b1', three_ds_params['xid'] + end.respond_with(successful_purchase_response) + end + + def test_initial_purchase_with_stored_credential + stored_credential_options = { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + end.check_request do |_verb, _url, data, _headers| + request = JSON.parse(data) + assert_equal request['cof'], 'M' + assert_equal request['cofscheduled'], 'Y' + assert_equal request['cofpermission'], 'Y' + end.respond_with(successful_purchase_response) + end + + def test_subsequent_purchase_with_stored_credential + stored_credential_options = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'cardholder' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + end.check_request do |_verb, _url, data, _headers| + request = JSON.parse(data) + assert_equal request['cof'], 'C' + assert_equal request['cofscheduled'], 'Y' + assert_equal request['cofpermission'], 'N' + end.respond_with(successful_purchase_response) + end + + def test_purchase_with_ecomind + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ ecomind: 't' })) + end.check_request do |_verb, _url, data, _headers| + request = JSON.parse(data) + assert_equal request['ecomind'], 'T' + end.respond_with(successful_purchase_response) + end + + def test_purchase_with_recurring + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ recurring: true })) + end.check_request do |_verb, _url, data, _headers| + request = JSON.parse(data) + assert_equal request['ecomind'], 'R' + end.respond_with(successful_purchase_response) + end + + def test_purchase_without_recurring + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ recurring: false })) + end.check_request do |_verb, _url, data, _headers| + request = JSON.parse(data) + assert_equal request['ecomind'], 'E' + end.respond_with(successful_purchase_response) end - def test_incorrect_domain + def test_allow_domains_without_ports + assert CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id', domain: 'https://vendor.cardconnect.com/test') + end + + def test_add_address + result = {} + + @gateway.send(:add_address, result, billing_address: { address1: '123 Test St.', address2: '5F', city: 'Testville', zip: '12345', state: 'AK' }) + assert_equal '123 Test St.', result[:address] + assert_equal '5F', result[:address2] + assert_equal 'Testville', result[:city] + assert_equal 'AK', result[:region] + assert_equal '12345', result[:postal] + end + + def test_reject_domains_without_card_connect + assert_raise(ArgumentError) { + CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id', domain: 'https://www.google.com') + } + end + + def test_reject_domains_without_https assert_raise(ArgumentError) { - CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id', domain: 'www.google.com') + CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id', domain: 'ttps://cardconnect.com') } end @@ -185,7 +296,7 @@ def test_failed_store def test_successful_unstore stub_comms(@gateway, :ssl_request) do @gateway.unstore('1|16700875781344019340') - end.check_request do |verb, url, data, headers| + end.check_request do |verb, url, _data, _headers| assert_equal :delete, verb assert_match %r{16700875781344019340/1}, url end.respond_with(successful_unstore_response) @@ -199,6 +310,17 @@ def test_scrub assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end + def test_frontendid_is_added_to_post_data_parameters + @gateway.class.application_id = 'my_app' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_, _, body| + assert_equal 'my_app', JSON.parse(body)['frontendid'] + end.respond_with(successful_purchase_response) + ensure + @gateway.class.application_id = nil + end + private def pre_scrubbed diff --git a/test/unit/gateways/card_save_test.rb b/test/unit/gateways/card_save_test.rb index bb19495a1f1..6689ca22eef 100644 --- a/test/unit/gateways/card_save_test.rb +++ b/test/unit/gateways/card_save_test.rb @@ -3,10 +3,10 @@ class CardSaveTest < Test::Unit::TestCase def setup Base.mode = :test - @gateway = CardSaveGateway.new(:login => 'login', :password => 'password') + @gateway = CardSaveGateway.new(login: 'login', password: 'password') @credit_card = credit_card @amount = 100 - @options = {:order_id =>'1', :billing_address => address, :description =>'Store Purchase'} + @options = { order_id: '1', billing_address: address, description: 'Store Purchase' } end def test_successful_purchase @@ -273,5 +273,4 @@ def failed_refund ) end - end diff --git a/test/unit/gateways/card_stream_test.rb b/test/unit/gateways/card_stream_test.rb index e7f225346de..f648a960a42 100644 --- a/test/unit/gateways/card_stream_test.rb +++ b/test/unit/gateways/card_stream_test.rb @@ -5,45 +5,63 @@ class CardStreamTest < Test::Unit::TestCase def setup @gateway = CardStreamGateway.new( - :login => 'login', - :shared_secret => 'secret' + login: 'login', + shared_secret: 'secret' ) - @visacreditcard = credit_card('4929421234600821', - :month => '12', - :year => '2014', - :verification_value => '356', - :brand => :visa + @visacreditcard = credit_card( + '4929421234600821', + month: '12', + year: '2014', + verification_value: '356', + brand: :visa ) @visacredit_options = { - :billing_address => { - :address1 => 'Flat 6, Primrose Rise', - :address2 => '347 Lavender Road', - :city => '', - :state => 'Northampton', - :zip => 'NN17 8YG ' + billing_address: { + address1: 'Flat 6, Primrose Rise', + address2: '347 Lavender Road', + city: '', + state: 'Northampton', + zip: 'NN17 8YG ' }, - :order_id => generate_unique_id, - :description => 'AM test purchase' + order_id: generate_unique_id, + description: 'AM test purchase' + } + + @visacredit_three_ds_options = { + threeds_required: true, + three_ds_version: '2.1.0', + three_d_secure: { + enrolled: 'true', + authentication_response_status: 'Y', + eci: '05', + cavv: 'Y2FyZGluYWxjb21tZXJjZWF1dGg', + xid: '362DF058-6061-47F1-A504-CACCBDF422B7' + } } @visacredit_descriptor_options = { - :billing_address => { - :address1 => 'Flat 6, Primrose Rise', - :address2 => '347 Lavender Road', - :city => '', - :state => 'Northampton', - :zip => 'NN17 8YG ' + billing_address: { + address1: 'Flat 6, Primrose Rise', + address2: '347 Lavender Road', + city: '', + state: 'Northampton', + zip: 'NN17 8YG ' }, - :merchant_name => 'merchant', - :dynamic_descriptor => 'product' + merchant_name: 'merchant', + dynamic_descriptor: 'product' } - @declined_card = credit_card('4000300011112220', - :month => '9', - :year => '2014' + @amex = credit_card( + '374245455400001', + month: '12', + year: 2014, + verification_value: '4887', + brand: :american_express ) + + @declined_card = credit_card('4000300011112220', month: '9', year: '2014') end def test_successful_visacreditcard_authorization @@ -130,7 +148,7 @@ def test_successful_visacreditcard_purchase def test_successful_visacreditcard_purchase_with_descriptors stub_comms do @gateway.purchase(284, @visacreditcard, @visacredit_descriptor_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/statementNarrative1=merchant/, data) assert_match(/statementNarrative2=product/, data) end.respond_with(successful_purchase_response_with_descriptors) @@ -139,7 +157,7 @@ def test_successful_visacreditcard_purchase_with_descriptors def test_successful_visacreditcard_purchase_with_default_ip stub_comms do @gateway.purchase(284, @visacreditcard, @visacredit_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/remoteAddress=1\.1\.1\.1/, data) end.respond_with(successful_purchase_response_with_descriptors) end @@ -147,7 +165,7 @@ def test_successful_visacreditcard_purchase_with_default_ip def test_successful_visacreditcard_purchase_with_default_country stub_comms do @gateway.purchase(284, @visacreditcard, @visacredit_options.delete(:billing_address)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/customerCountryCode=GB/, data) end.respond_with(successful_purchase_response) end @@ -179,6 +197,14 @@ def test_declined_mastercard_purchase assert response.test? end + def test_successful_amex_purchase_with_localized_invoice_amount + stub_comms do + @gateway.purchase(28400, @amex, @visacredit_descriptor_options.merge(currency: 'JPY', order_id: '1234567890')) + end.check_request do |_endpoint, data, _headers| + assert_match(/item1GrossValue=284&/, data) + end.respond_with(successful_purchase_response) + end + def test_successful_verify response = stub_comms do @gateway.verify(@visacreditcard, @visacredit_options) @@ -199,7 +225,7 @@ def test_purchase_options # Default purchase = stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/type=1/, data) end.respond_with(successful_purchase_response) @@ -207,7 +233,7 @@ def test_purchase_options purchase = stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(type: 2)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/type=2/, data) end.respond_with(successful_purchase_response) @@ -215,7 +241,7 @@ def test_purchase_options purchase = stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: 'PEN')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/currencyCode=604/, data) end.respond_with(successful_purchase_response) @@ -224,7 +250,7 @@ def test_purchase_options def test_successful_purchase_without_street_address @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(142, @visacreditcard, billing_address: {state: 'Northampton'}) + assert response = @gateway.purchase(142, @visacreditcard, billing_address: { state: 'Northampton' }) assert_equal 'APPROVED', response.message end @@ -234,21 +260,39 @@ def test_successful_purchase_without_any_address assert_equal 'APPROVED', response.message end + def test_adding_country_code + %i[authorize purchase refund].each do |action| + stub_comms do + @gateway.send(action, 142, @visacreditcard, @visacredit_options.merge(country_code: 'US')) + end.check_request do |_endpoint, data, _headers| + assert_match(/&countryCode=US/, data) + end.respond_with(successful_purchase_response) + end + end + def test_hmac_signature_added_to_post post_params = "action=SALE&amount=10000&captureDelay=0&cardCVV=356&cardExpiryMonth=12&cardExpiryYear=14&cardNumber=4929421234600821&countryCode=GB¤cyCode=826&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerCountryCode=GB&customerName=Longbob+Longsen&customerPostCode=NN17+8YG+&merchantID=login&orderRef=AM+test+purchase&remoteAddress=1.1.1.1&threeDSRequired=N&transactionUnique=#{@visacredit_options[:order_id]}&type=1" expected_signature = Digest::SHA512.hexdigest("#{post_params}#{@gateway.options[:shared_secret]}") - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data.include?("signature=#{expected_signature}") end.returns(successful_authorization_response) @gateway.purchase(10000, @visacreditcard, @visacredit_options) end + def test_nonfractional_currency_handling + stub_comms do + @gateway.authorize(200, @visacreditcard, @visacredit_options.merge(currency: 'JPY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/amount=2¤cyCode=392/, data) + end.respond_with(successful_authorization_response) + end + def test_3ds_response purchase = stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(threeds_required: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/threeDSRequired=Y/, data) end.respond_with(successful_purchase_response_with_3dsecure) @@ -261,14 +305,14 @@ def test_3ds_response def test_deprecated_3ds_required assert_deprecation_warning(CardStreamGateway::THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE) do @gateway = CardStreamGateway.new( - :login => 'login', - :shared_secret => 'secret', - :threeDSRequired => true + login: 'login', + shared_secret: 'secret', + threeDSRequired: true ) end stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/threeDSRequired=Y/, data) end.respond_with(successful_purchase_response) end @@ -276,11 +320,45 @@ def test_deprecated_3ds_required def test_default_3dsecure_required stub_comms do @gateway.purchase(142, @visacreditcard, @visacredit_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/threeDSRequired=N/, data) end.respond_with(successful_purchase_response) end + def test_3ds2_data + stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match(/threeDSRequired=Y/, data) + assert_match(/threeDSEnrolled=Y/, data) + assert_match(/threeDSAuthenticated=Y/, data) + assert_match(/threeDSECI=05/, data) + assert_match(/threeDSCAVV=Y2FyZGluYWxjb21tZXJjZWF1dGg/, data) + assert_match(/threeDSXID=362DF058-6061-47F1-A504-CACCBDF422B7/, data) + end + end + + def test_3ds2_not_enrolled + stub_comms do + @visacredit_three_ds_options[:three_d_secure][:enrolled] = 'false' + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match(/threeDSRequired=Y/, data) + assert_match(/threeDSEnrolled=N/, data) + end + end + + def test_3ds2_not_authenticated + stub_comms do + @visacredit_three_ds_options[:three_d_secure][:authentication_response_status] = 'N' + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(@visacredit_three_ds_options)) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match(/threeDSRequired=Y/, data) + assert_match(/threeDSEnrolled=Y/, data) + assert_match(/threeDSAuthenticated=N/, data) + end + end + def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end @@ -340,16 +418,16 @@ def failed_reference_purchase_response end def transcript - <<-eos + <<-REQUEST POST /direct/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway.cardstream.com\r\nContent-Length: 501\r\n\r\n" amount=¤cyCode=826&transactionUnique=a017ca2ac0569188517ad8368c36a06d&orderRef=AM+test+purchase&customerName=Longbob+Longsen&cardNumber=4929421234600821&cardExpiryMonth=12&cardExpiryYear=14&cardCVV=356&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&merchantID=102922&action=SALE&type=1&countryCode=GB&threeDSRequired=N&signature=970b3fe099a85c9922a79af46c2cb798616b9fbd044a921ac5eb46cd1907a5e89b8c720aae59c7eb1d81a59563f209d5db51aa3c270838199f2bfdcbe2c1149d - eos + REQUEST end def scrubbed_transcript - <<-eos + <<-REQUEST POST /direct/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway.cardstream.com\r\nContent-Length: 501\r\n\r\n" amount=¤cyCode=826&transactionUnique=a017ca2ac0569188517ad8368c36a06d&orderRef=AM+test+purchase&customerName=Longbob+Longsen&cardNumber=[FILTERED]&cardExpiryMonth=12&cardExpiryYear=14&cardCVV=[FILTERED]&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&merchantID=102922&action=SALE&type=1&countryCode=GB&threeDSRequired=N&signature=970b3fe099a85c9922a79af46c2cb798616b9fbd044a921ac5eb46cd1907a5e89b8c720aae59c7eb1d81a59563f209d5db51aa3c270838199f2bfdcbe2c1149d - eos + REQUEST end end diff --git a/test/unit/gateways/cardknox_test.rb b/test/unit/gateways/cardknox_test.rb index bc10d5e0e70..11e05970f77 100644 --- a/test/unit/gateways/cardknox_test.rb +++ b/test/unit/gateways/cardknox_test.rb @@ -16,7 +16,7 @@ def setup def test_successful_purchase_passing_extra_info response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(order_id: '1337', description: 'socool')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/xOrderID=1337/, data) assert_match(/xDescription=socool/, data) end.respond_with(successful_purchase_response) @@ -66,7 +66,7 @@ def test_manual_entry_is_properly_indicated_on_purchase @credit_card.manual_entry = true response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{xCardNum=4242424242424242}, data assert_match %r{xCardPresent=true}, data end.respond_with(successful_purchase_response) @@ -75,7 +75,7 @@ def test_manual_entry_is_properly_indicated_on_purchase end def test_ip_is_being_sent # failed - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data =~ /xIP=123.123.123.123/ end.returns(successful_purchase_response) @@ -131,7 +131,7 @@ def test_successful_capture def test_failed_capture @gateway.expects(:ssl_post).returns(failed_capture_response) - assert capture = @gateway.capture(@amount-1, '') + assert capture = @gateway.capture(@amount - 1, '') assert_failure capture assert_equal 'Original transaction not specified', capture.message end diff --git a/test/unit/gateways/cardprocess_test.rb b/test/unit/gateways/cardprocess_test.rb index 5377cd16c73..97e8061f2ff 100644 --- a/test/unit/gateways/cardprocess_test.rb +++ b/test/unit/gateways/cardprocess_test.rb @@ -167,7 +167,7 @@ def test_error_code_parsing '800.800.202' => :invalid_zip } codes.each_pair do |code, key| - response = {'result' => {'code' => code}} + response = { 'result' => { 'code' => code } } assert_equal Gateway::STANDARD_ERROR_CODE[key], @gateway.send(:error_code_from, response), "expecting #{code} => #{key}" end end diff --git a/test/unit/gateways/cashnet_test.rb b/test/unit/gateways/cashnet_test.rb index 844702eac9a..7aeee072c3a 100644 --- a/test/unit/gateways/cashnet_test.rb +++ b/test/unit/gateways/cashnet_test.rb @@ -22,6 +22,35 @@ def test_successful_purchase assert_equal '1234', response.authorization end + def test_successful_purchase_with_multiple_items + options = { item_codes: { item_code: 'APPFEE', item_code2: 'LOBSTER', item_code3: 'CODES', amount: 100, amount2: 1234, amount3: 4321 } } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('itemcode=APPFEE&itemcode2=LOBSTER&itemcode3=CODES&amount=1.00&amount2=12.34&amount3=43.21', data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + assert_equal '1234', response.authorization + end + + def test_successful_purchase_with_filtered_itemcodes + options = { item_codes: { item_code: 'APPFEE', item_code2: 'LOBSTER', item_code3: 'CODES', bad_key: 'BADVALUE', amount: 100, amount2: 1234, amount3: 4321 } } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('itemcode=APPFEE&itemcode2=LOBSTER&itemcode3=CODES&amount=1.00&amount2=12.34&amount3=43.21', data) + refute_match('badkey=BADVALUE', data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + assert_equal '1234', response.authorization + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -55,12 +84,12 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], CashnetGateway.supported_cardtypes + assert_equal %i[visa master american_express discover diners_club jcb], CashnetGateway.supported_cardtypes end def test_add_invoice result = {} - @gateway.send(:add_invoice, result, order_id: '#1001') + @gateway.send(:add_invoice, result, 1000, order_id: '#1001') assert_equal '#1001', result[:order_number] end @@ -76,9 +105,9 @@ def test_add_creditcard def test_add_address result = {} - @gateway.send(:add_address, result, billing_address: {address1: '123 Test St.', address2: '5F', city: 'Testville', zip: '12345', state: 'AK'}) + @gateway.send(:add_address, result, billing_address: { address1: '123 Test St.', address2: '5F', city: 'Testville', zip: '12345', state: 'AK' }) - assert_equal ['addr_g', 'city_g', 'state_g', 'zip_g'], result.stringify_keys.keys.sort + assert_equal %w[addr_g city_g state_g zip_g], result.stringify_keys.keys.sort assert_equal '123 Test St.,5F', result[:addr_g] assert_equal 'Testville', result[:city_g] assert_equal 'AK', result[:state_g] @@ -93,11 +122,11 @@ def test_add_customer_data def test_action_meets_minimum_requirements params = { - amount: '1.01', + amount: '1.01' } @gateway.send(:add_creditcard, params, @credit_card) - @gateway.send(:add_invoice, params, {}) + @gateway.send(:add_invoice, params, 101, {}) assert data = @gateway.send(:post_data, 'SALE', params) minimum_requirements.each do |key| @@ -108,7 +137,7 @@ def test_action_meets_minimum_requirements def test_successful_purchase_with_fname_and_lname stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, {}) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/fname=Longbob/, data) assert_match(/lname=Longsen/, data) end.respond_with(successful_purchase_response) @@ -127,7 +156,7 @@ def test_passes_custcode_from_credentials gateway = CashnetGateway.new(merchant: 'X', operator: 'X', password: 'test123', merchant_gateway_name: 'X', custcode: 'TheCustCode') stub_comms(gateway, :ssl_request) do gateway.purchase(@amount, @credit_card, {}) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/custcode=TheCustCode/, data) end.respond_with(successful_purchase_response) end @@ -136,7 +165,7 @@ def test_allows_custcode_override gateway = CashnetGateway.new(merchant: 'X', operator: 'X', password: 'test123', merchant_gateway_name: 'X', custcode: 'TheCustCode') stub_comms(gateway, :ssl_request) do gateway.purchase(@amount, @credit_card, custcode: 'OveriddenCustCode') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/custcode=OveriddenCustCode/, data) end.respond_with(successful_purchase_response) end @@ -177,128 +206,128 @@ def invalid_response end def pre_scrubbed - <<-TRANSCRIPT -opening connection to train.cashnet.com:443... -opened -starting SSL for train.cashnet.com:443... -SSL established -<- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" -<- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" --> "HTTP/1.1 302 Found\r\n" --> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" --> "Content-Type: text/html; charset=utf-8\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Connection: close\r\n" --> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Cache-Control: private\r\n" --> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" --> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" --> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" --> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" --> "Strict-Transport-Security: max-age=31536000\r\n" --> "\r\n" --> "282\r\n" -reading 642 bytes... --> "Object moved\r\n

Object moved to here.

\r\n\r\n" -read 642 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close -opening connection to train.cashnet.com:443... -opened -starting SSL for train.cashnet.com:443... -SSL established -<- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" --> "Content-Type: text/html; charset=utf-8\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Connection: close\r\n" --> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Cache-Control: private\r\n" --> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" --> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" --> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" --> "Strict-Transport-Security: max-age=31536000\r\n" --> "\r\n" --> "3a\r\n" -reading 58 bytes... --> "result=0&tx=77972&busdate=7/25/2017" -read 58 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close -TRANSCRIPT + <<~TRANSCRIPT + opening connection to train.cashnet.com:443... + opened + starting SSL for train.cashnet.com:443... + SSL established + <- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" + <- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" + -> "HTTP/1.1 302 Found\r\n" + -> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" + -> "Content-Type: text/html; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Cache-Control: private\r\n" + -> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" + -> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" + -> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" + -> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "\r\n" + -> "282\r\n" + reading 642 bytes... + -> "Object moved\r\n

Object moved to here.

\r\n\r\n" + read 642 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to train.cashnet.com:443... + opened + starting SSL for train.cashnet.com:443... + SSL established + <- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" + -> "Content-Type: text/html; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Cache-Control: private\r\n" + -> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" + -> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" + -> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "\r\n" + -> "3a\r\n" + reading 58 bytes... + -> "result=0&tx=77972&busdate=7/25/2017" + read 58 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT end def post_scrubbed - <<-SCRUBBED -opening connection to train.cashnet.com:443... -opened -starting SSL for train.cashnet.com:443... -SSL established -<- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" -<- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" --> "HTTP/1.1 302 Found\r\n" --> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" --> "Content-Type: text/html; charset=utf-8\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Connection: close\r\n" --> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Cache-Control: private\r\n" --> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" --> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" --> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" --> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" --> "Strict-Transport-Security: max-age=31536000\r\n" --> "\r\n" --> "282\r\n" -reading 642 bytes... --> "Object moved\r\n

Object moved to here.

\r\n\r\n" -read 642 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close -opening connection to train.cashnet.com:443... -opened -starting SSL for train.cashnet.com:443... -SSL established -<- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" --> "Content-Type: text/html; charset=utf-8\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Connection: close\r\n" --> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" --> "Cache-Control: private\r\n" --> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" --> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" --> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" --> "Strict-Transport-Security: max-age=31536000\r\n" --> "\r\n" --> "3a\r\n" -reading 58 bytes... --> "result=0&tx=77972&busdate=7/25/2017" -read 58 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close -SCRUBBED + <<~SCRUBBED + opening connection to train.cashnet.com:443... + opened + starting SSL for train.cashnet.com:443... + SSL established + <- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" + <- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" + -> "HTTP/1.1 302 Found\r\n" + -> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" + -> "Content-Type: text/html; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Cache-Control: private\r\n" + -> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" + -> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" + -> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" + -> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "\r\n" + -> "282\r\n" + reading 642 bytes... + -> "Object moved\r\n

Object moved to here.

\r\n\r\n" + read 642 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to train.cashnet.com:443... + opened + starting SSL for train.cashnet.com:443... + SSL established + <- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" + -> "Content-Type: text/html; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" + -> "Cache-Control: private\r\n" + -> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" + -> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" + -> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" + -> "Strict-Transport-Security: max-age=31536000\r\n" + -> "\r\n" + -> "3a\r\n" + reading 58 bytes... + -> "result=0&tx=77972&busdate=7/25/2017" + read 58 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + SCRUBBED end end diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb new file mode 100644 index 00000000000..87ba73ef771 --- /dev/null +++ b/test/unit/gateways/cecabank_rest_json_test.rb @@ -0,0 +1,236 @@ +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' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + description: 'Store Purchase' + } + 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_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_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def transcript + "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: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiNDUwNzY3MDAwMTAwMDAwOSIsImNhZHVjaWRhZCI6IjIwMjMxMiIsImN2djIiOiI5ODkiLCJleGVuY2lvblNDQSI6bnVsbCwiVGhyZWVEc1Jlc3BvbnNlIjoie1wiZXhlbXB0aW9uX3R5cGVcIjpudWxsLFwidGhyZWVfZHNfdmVyc2lvblwiOlwiMi4yLjBcIixcImF1dGhlbnRpY2F0aW9uX3ZhbHVlXCI6XCI0RjgwREY1MEFEQjBGOTUwMkI5MTYxOEU5QjcwNDc5MEVBQkEzNUZERkM5NzJEREREMEJGNDk4QzZBNzVFNDkyXCIsXCJkaXJlY3Rvcnlfc2VydmVyX3RyYW5zYWN0aW9uX2lkXCI6XCJhMmJmMDg5Zi1jZWZjLTRkMmMtODUwZi05MTUzODI3ZmUwNzBcIixcImFjc190cmFuc2FjdGlvbl9pZFwiOlwiMThjMzUzYjAtNzZlMy00YTRjLTgwMzMtZjE0ZmU5Y2UzOWRjXCIsXCJhdXRoZW50aWNhdGlvbl9yZXNwb25zZV9zdGF0dXNcIjpcIllcIixcInRocmVlX2RzX3NlcnZlcl90cmFuc19pZFwiOlwiOWJkOWFhOWMtM2JlYi00MDEyLThlNTItMjE0Y2NjYjI1ZWM1XCIsXCJlY29tbWVyY2VfaW5kaWNhdG9yXCI6XCIwMlwiLFwiZW5yb2xsZWRcIjpudWxsLFwiYW1vdW50XCI6XCIxMDBcIn0iLCJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIn0=\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 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: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n" + end + + def scrubbed_transcript + "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: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiW0ZJTFRFUkVEXSIsImNhZHVjaWRhZCI6IltGSUxURVJFRF0iLCJjdnYyIjoiW0ZJTFRFUkVEXSIsImV4ZW5jaW9uU0NBIjpudWxsLCJUaHJlZURzUmVzcG9uc2UiOiJ7XCJleGVtcHRpb25fdHlwZVwiOm51bGwsXCJ0aHJlZV9kc192ZXJzaW9uXCI6XCIyLjIuMFwiLFwiYXV0aGVudGljYXRpb25fdmFsdWVcIjpcIjRGODBERjUwQURCMEY5NTAyQjkxNjE4RTlCNzA0NzkwRUFCQTM1RkRGQzk3MkREREQwQkY0OThDNkE3NUU0OTJcIixcImRpcmVjdG9yeV9zZXJ2ZXJfdHJhbnNhY3Rpb25faWRcIjpcImEyYmYwODlmLWNlZmMtNGQyYy04NTBmLTkxNTM4MjdmZTA3MFwiLFwiYWNzX3RyYW5zYWN0aW9uX2lkXCI6XCIxOGMzNTNiMC03NmUzLTRhNGMtODAzMy1mMTRmZTljZTM5ZGNcIixcImF1dGhlbnRpY2F0aW9uX3Jlc3BvbnNlX3N0YXR1c1wiOlwiWVwiLFwidGhyZWVfZHNfc2VydmVyX3RyYW5zX2lkXCI6XCI5YmQ5YWE5Yy0zYmViLTQwMTItOGU1Mi0yMTRjY2NiMjVlYzVcIixcImVjb21tZXJjZV9pbmRpY2F0b3JcIjpcIjAyXCIsXCJlbnJvbGxlZFwiOm51bGwsXCJhbW91bnRcIjpcIjEwMFwifSIsIm1lcmNoYW50SUQiOiIxMDY5MDA2NDAiLCJhY3F1aXJlckJJTiI6IjAwMDA1NTQwMDAiLCJ0ZXJtaW5hbElEIjoiMDAwMDAwMDMifQ==\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 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: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n" + 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 87ad7e20ab5..e2bfe3c749e 100644 --- a/test/unit/gateways/cecabank_test.rb +++ b/test/unit/gateways/cecabank_test.rb @@ -4,19 +4,19 @@ class CecabankTest < Test::Unit::TestCase include CommStub def setup - @gateway = CecabankGateway.new( - :merchant_id => '12345678', - :acquirer_bin => '12345678', - :terminal_id => '00000003', - :key => 'enc_key' + @gateway = CecabankXmlGateway.new( + merchant_id: '12345678', + acquirer_bin: '12345678', + terminal_id: '00000003', + cypher_key: 'enc_key' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :description => 'Store Purchase' + order_id: '1', + description: 'Store Purchase' } end @@ -43,7 +43,7 @@ def test_invalid_xml_response_handling def test_expiration_date_sent_correctly stub_comms do @gateway.purchase(@amount, credit_card('4242424242424242', month: 1, year: 2014), @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/Caducidad=201401&/, data, 'Expected expiration date format is yyyymm') end.respond_with(successful_purchase_response) end @@ -81,60 +81,60 @@ def test_transcript_scrubbing private def successful_purchase_response - <<-RESPONSE - - - - 171.00 Euros - - 101000 - 12345678901234567890 - ##PAN## - - + <<~RESPONSE + + + + 171.00 Euros + + 101000 + 12345678901234567890 + ##PAN## + + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - - 27 - - - + <<~RESPONSE + + + + 27 + + + RESPONSE end def invalid_xml_purchase_response - <<-RESPONSE -
- -Invalid unparsable xml in the response + <<~RESPONSE +
+ + Invalid unparsable xml in the response RESPONSE end def successful_refund_response - <<-RESPONSE - - - - 1.00 Euros - - + <<~RESPONSE + + + + 1.00 Euros + + RESPONSE end def failed_refund_response - <<-RESPONSE - - - - 15 - ]]> - - + <<~RESPONSE + + + + 15 + ]]> + + RESPONSE end diff --git a/test/unit/gateways/cenpos_test.rb b/test/unit/gateways/cenpos_test.rb index 950701578bd..0fd8f452a1b 100644 --- a/test/unit/gateways/cenpos_test.rb +++ b/test/unit/gateways/cenpos_test.rb @@ -5,9 +5,9 @@ class CenposTest < Test::Unit::TestCase def setup @gateway = CenposGateway.new( - :merchant_id => 'merchant_id', - :password => 'password', - :user_id => 'user_id' + merchant_id: 'merchant_id', + password: 'password', + user_id: 'user_id' ) @credit_card = credit_card @@ -115,7 +115,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1760035844/, data) end.respond_with(successful_capture_response) @@ -151,7 +151,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1760035844/, data) end.respond_with(successful_void_response) @@ -161,7 +161,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('1758584451|4242|1.00') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1758584451/, data) end.respond_with(failed_void_response) @@ -178,7 +178,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1609995363/, data) end.respond_with(successful_refund_response) diff --git a/test/unit/gateways/checkout_test.rb b/test/unit/gateways/checkout_test.rb index ab70056694e..d58e55ee55e 100644 --- a/test/unit/gateways/checkout_test.rb +++ b/test/unit/gateways/checkout_test.rb @@ -5,8 +5,8 @@ class CheckoutTest < Test::Unit::TestCase def setup @gateway = ActiveMerchant::Billing::CheckoutGateway.new( - :merchant_id => 'SBMTEST', # Merchant Code - :password => 'Password1!' # Processing Password + merchant_id: 'SBMTEST', # Merchant Code + password: 'Password1!' # Processing Password ) @options = { order_id: generate_unique_id @@ -72,21 +72,20 @@ def test_unsuccessful_purchase def test_passes_correct_currency stub_comms do - @gateway.purchase(100, credit_card, @options.merge( - currency: 'EUR' - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, credit_card, @options.merge(currency: 'EUR')) + end.check_request do |_endpoint, data, _headers| assert_match(/EUR<\/bill_currencycode>/, data) end.respond_with(successful_purchase_response) end def test_passes_descriptors + options = @options.merge( + descriptor_name: 'ZahName', + descriptor_city: 'Oakland' + ) stub_comms do - @gateway.purchase(100, credit_card, @options.merge( - descriptor_name: 'ZahName', - descriptor_city: 'Oakland' - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match(/ZahName<\/descriptor_name>/, data) assert_match(/Oakland<\/descriptor_city>/, data) end.respond_with(successful_purchase_response) @@ -96,7 +95,7 @@ def test_successful_void @options['orderid'] = '9c38d0506da258e216fa072197faaf37' void = stub_comms(@gateway, :ssl_request) do @gateway.void('36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| # Should only be one pair of track id tags. assert_equal 2, data.scan(/trackid/).count end.respond_with(successful_void_response) diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb index bb3f1eda89a..f6c33802138 100644 --- a/test/unit/gateways/checkout_v2_test.rb +++ b/test/unit/gateways/checkout_v2_test.rb @@ -7,23 +7,37 @@ def setup @gateway = CheckoutV2Gateway.new( secret_key: '1111111111111' ) - + @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) assert_success response - assert_equal 'charge_test_941CA9CE174U76BD29C8', response.authorization + assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization assert response.test? 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) @@ -34,15 +48,228 @@ 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) assert_equal 'Y', response.cvv_result['code'] end + def test_successful_purchase_using_vts_network_token_without_eci + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :network_token, brand: 'visa' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'vts') + assert_equal(request_data['source']['eci'], '05') + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_passing_processing_channel_id + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { processing_channel_id: '123456abcde' }) + 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(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, { incremental_authorization: 'abcd1234' }) + end.check_request do |_method, endpoint, _data, _headers| + assert_include endpoint, 'abcd1234' + end.respond_with(successful_incremental_authorize_response) + + assert_success response + end + + def test_successful_passing_authorization_type + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { authorization_type: 'Estimated' }) + 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(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { execute_threed: true, exemption: 'no_preference', challenge_indicator: 'trusted_listing' }) + 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') + end.respond_with(successful_purchase_response) + end + + def test_successful_passing_capture_type + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, 'abc', { capture_type: 'NonFinal' }) + 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) + end + + def test_successful_purchase_using_vts_network_token_with_eci + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :network_token, brand: 'visa', eci: '06' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'vts') + assert_equal(request_data['source']['eci'], '06') + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_purchase_using_mdes_network_token + network_token = network_tokenization_credit_card( + '5436031030606378', + { source: :network_token, brand: 'master' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'mdes') + assert_equal(request_data['source']['eci'], nil) + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_purchase_using_apple_pay_network_token + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :apple_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'applepay') + assert_equal(request_data['source']['eci'], '05') + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_purchase_using_android_pay_network_token + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :android_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'googlepay') + assert_equal(request_data['source']['eci'], '05') + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_purchase_using_google_pay_network_token + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :google_pay, eci: '05', payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA' } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'googlepay') + assert_equal(request_data['source']['eci'], '05') + assert_equal(request_data['source']['cryptogram'], network_token.payment_cryptogram) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + def test_successful_purchase_using_google_pay_pan_only_network_token + network_token = network_tokenization_credit_card( + '4242424242424242', + { source: :google_pay } + ) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, network_token) + end.check_request do |_method, _endpoint, data, _headers| + request_data = JSON.parse(data) + + assert_equal(request_data['source']['type'], 'network_token') + assert_equal(request_data['source']['token'], network_token.number) + assert_equal(request_data['source']['token_type'], 'googlepay') + assert_equal(request_data['source']['eci'], nil) + assert_equal(request_data['source']['cryptogram'], nil) + end.respond_with(successful_purchase_with_network_token_response) + + assert_success response + assert_equal '2FCFE326D92D4C27EDD699560F484', response.params['source']['payment_account_reference'] + assert response.test? + end + + 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) + 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) @@ -53,7 +280,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) @@ -61,17 +288,28 @@ def test_successful_authorize_includes_cvv_result end def test_purchase_with_additional_fields - response = stub_comms do - @gateway.purchase(@amount, @credit_card, {descriptor_city: 'london', descriptor_name: 'sherlock'}) - end.check_request do |endpoint, data, headers| - assert_match(/"descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { descriptor_city: 'london', descriptor_name: 'sherlock' }) + 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_successful_purchase_passing_metadata_with_mada_card_type + @credit_card.brand = 'mada' + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + 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 @@ -79,14 +317,14 @@ def test_failed_purchase 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 'charge_test_AF1A29AD350Q748C7EA8', response.authorization + 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) @@ -94,23 +332,270 @@ 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: 'charge_123' + previous_charge_id: 'pay_123', + processing_channel_id: 'pc_123' + } + @gateway.authorize(@amount, @credit_card, options) + 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) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_successful_purchase_with_stored_credentials + 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 |_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) + + assert_success initial_response + assert_equal 'pay_7jcf4ovmwnqedhtldca3fjli2y', initial_response.params['id'] + network_transaction_id = initial_response.params['id'] + + response = stub_comms(@gateway, :ssl_request) do + options = { + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: network_transaction_id + } + } + @gateway.purchase(@amount, @credit_card, options) + 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 + end.respond_with(successful_purchase_using_stored_credential_response) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_stored_credentials_merchant_initiated_transaction_id + response = stub_comms(@gateway, :ssl_request) do + options = { + stored_credential: { + initial_transaction: false + }, + merchant_initiated_transaction_id: 'pay_7jcf4ovmwnqedhtldca3fjli2y' + } + @gateway.purchase(@amount, @credit_card, options) + 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 + end.respond_with(successful_purchase_using_stored_credential_response) + + assert_success response + 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(@gateway, :ssl_request) do + options = { + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + } + @gateway.purchase(@amount, @credit_card, options) + 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) + + 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(@gateway, :ssl_request) do + options = { + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + } + @gateway.authorize(@amount, @credit_card, options) + 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) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_moto_transaction_is_properly_set + response = stub_comms(@gateway, :ssl_request) do + options = { + metadata: { + manual_entry: true + } + } + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{"payment_type":"MOTO"}, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_3ds_passed + 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 |_method, _endpoint, data, _headers| + assert_match(%r{"success_url"}, data) + assert_match(%r{"failure_url"}, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_verify_payment + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify_payment('testValue') + end.respond_with(successful_verify_payment_response) + assert_success response + end + + def test_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 + + def test_failed_verify_payment + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify_payment('testValue') + end.respond_with(failed_verify_payment_response) + + assert_failure response + end + + def test_successful_authorize_and_capture_with_3ds + response = stub_comms(@gateway, :ssl_request) do + options = { + execute_threed: true, + attempt_n3d: true, + three_d_secure: { + version: '1.0.2', + eci: '05', + cryptogram: '1234', + xid: '1234', + authentication_response_status: 'Y' + } } @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| - assert_match(%r{"cardOnFile":true}, data) - assert_match(%r{"transactionIndicator":2}, data) - assert_match(%r{"previousChargeId":"charge_123"}, data) end.respond_with(successful_authorize_response) assert_success response - assert_equal 'charge_test_AF1A29AD350Q748C7EA8', response.authorization + 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) + + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds2 + response = stub_comms(@gateway, :ssl_request) do + options = { + execute_threed: true, + three_d_secure: { + version: '2.0.0', + eci: '05', + cryptogram: '1234', + ds_transaction_id: '1234', + authentication_response_status: 'Y' + } + } + @gateway.authorize(@amount, @credit_card, options) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, response.authorization) end.respond_with(successful_capture_response) @@ -118,7 +603,7 @@ def test_successful_authorize_and_capture_with_additional_options 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) @@ -128,7 +613,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) @@ -136,14 +621,38 @@ 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 'charge_test_AF1A29AD350Q748C7EA8', response.authorization + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + void = stub_comms(@gateway, :ssl_request) do + @gateway.void(response.authorization) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_successful_void_with_metadata + response = stub_comms(@gateway, :ssl_request) do + options = { + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + } + @gateway.authorize(@amount, @credit_card, options) + 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) + + 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) @@ -151,22 +660,173 @@ def test_successful_void 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 end + def test_successfully_passes_fund_type_and_fields + options = { + funds_transfer_type: 'FD', + source_type: 'currency_account', + source_id: 'ca_spwmped4qmqenai7hcghquqle4', + account_holder_type: 'individual' + } + 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']['funds_transfer_type'], options[:funds_transfer_type] + assert_equal request['source']['type'], options[:source_type] + assert_equal request['source']['id'], options[:source_id] + assert_equal request['destination']['account_holder']['type'], options[:account_holder_type] + assert_equal request['destination']['account_holder']['first_name'], @credit_card.first_name + assert_equal request['destination']['account_holder']['last_name'], @credit_card.last_name + end.respond_with(successful_credit_response) + 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 'charge_test_941CA9CE174U76BD29C8', response.authorization + 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) + + assert_success refund + end + + def test_successful_refund_with_metadata + response = stub_comms(@gateway, :ssl_request) do + options = { + metadata: { + coupon_code: 'NY2018', + partner_id: '123989' + } + } + @gateway.authorize(@amount, @credit_card, options) + 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) + + assert_success response + assert_equal 'pay_bgv5tmah6fmuzcmcrcro6exe6m', response.authorization + + refund = stub_comms(@gateway, :ssl_request) do @gateway.refund(@amount, response.authorization) end.respond_with(successful_refund_response) @@ -174,7 +834,7 @@ def test_successful_refund 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) @@ -182,106 +842,214 @@ 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_authorize_response, failed_void_response) + end.respond_with(successful_verify_response) assert_success response assert_equal 'Succeeded', response.message end def test_failed_verify - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.verify(@credit_card) - end.respond_with(failed_authorize_response, successful_void_response) + end.respond_with(failed_verify_response) assert_failure response - assert_equal 'Invalid Card Number', response.message + 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 + def test_network_transaction_scrubbing + assert_equal network_transaction_post_scrubbed, @gateway.scrub(network_transaction_pre_scrubbed) + 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) assert_failure response - assert_match %r{Invalid JSON response}, response.message + assert_match %r{Invalid JSON response received from Checkout.com Unified Payments Gateway. Please contact Checkout.com if you continue to receive this message.}, response.message 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) assert_failure response - assert_match(/70000: 70077/, response.error_code) + assert_match(/request_invalid: card_expired/, response.error_code) + end + + def test_4xx_error_message + @gateway.expects(:ssl_request).raises(error_4xx_response) + + assert response = @gateway.purchase(@amount, @credit_card) + + assert_failure response + assert_match(/401: Unauthorized/, response.message) end def test_supported_countries - assert_equal ['AD', 'AE', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'], @gateway.supported_countries + 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 %q( - <- "POST /v2/charges/card HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: sk_test_ab12301d-e432-4ea7-97d1-569809518aaf\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api2.checkout.com\r\nContent-Length: 346\r\n\r\n" - <- "{\"autoCapture\":\"n\",\"value\":\"200\",\"trackId\":\"1\",\"currency\":\"USD\",\"card\":{\"name\":\"Longbob Longsen\",\"number\":\"4242424242424242\",\"cvv\":\"100\",\"expiryYear\":\"2018\" + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: sk_test_ab12301d-e432-4ea7-97d1-569809518aaf\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"capture\":false,\"amount\":\"200\",\"reference\":\"1\",\"currency\":\"USD\",\"source\":{\"type\":\"card\",\"name\":\"Longbob Longsen\",\"number\":\"4242424242424242\",\"cvv\":\"100\",\"expiry_year\":\"2025\" + ) + end + + def network_transaction_pre_scrubbed + %q( + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: sk_test_ab12301d-e432-4ea7-97d1-569809518aaf\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"amount\":\"100\",\"reference\":\"1\",\"currency\":\"USD\",\"metadata\":{\"udf5\":\"ActiveMerchant\"},\"source\":{\"type\":\"network_token\",\"token\":\"4242424242424242\",\"token_type\":\"applepay\",\"cryptogram\":\"AgAAAAAAAIR8CQrXcIhbQAAAAAA\",\"eci\":\"05\",\"expiry_year\":\"2025\",\"expiry_month\":\"10\",\"billing_address\":{\"address_line1\":\"456 My Street\",\"address_line2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"country\":\"CA\",\"zip\":\"K1C2N6\"}},\"customer\":{\"email\":\"longbob.longsen@example.com\"}}" + ) + end + + def network_transaction_post_scrubbed + %q( + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"amount\":\"100\",\"reference\":\"1\",\"currency\":\"USD\",\"metadata\":{\"udf5\":\"ActiveMerchant\"},\"source\":{\"type\":\"network_token\",\"token\":\"[FILTERED]\",\"token_type\":\"applepay\",\"cryptogram\":\"[FILTERED]\",\"eci\":\"05\",\"expiry_year\":\"2025\",\"expiry_month\":\"10\",\"billing_address\":{\"address_line1\":\"456 My Street\",\"address_line2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"country\":\"CA\",\"zip\":\"K1C2N6\"}},\"customer\":{\"email\":\"longbob.longsen@example.com\"}}" ) end def post_scrubbed %q( - <- "POST /v2/charges/card HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api2.checkout.com\r\nContent-Length: 346\r\n\r\n" - <- "{\"autoCapture\":\"n\",\"value\":\"200\",\"trackId\":\"1\",\"currency\":\"USD\",\"card\":{\"name\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiryYear\":\"2018\" + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"capture\":false,\"amount\":\"200\",\"reference\":\"1\",\"currency\":\"USD\",\"source\":{\"type\":\"card\",\"name\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiry_year\":\"2025\" ) end def successful_purchase_response %( - { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, - "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Approved", - "responseAdvancedInfo":"Approved", - "responseCode":"10000", - "card": { - "cvvCheck":"Y", - "avsCheck":"S" - } - } + {"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' + purchase_response.to_json + end + + def successful_purchase_initial_stored_credential_response + %( + {"id":"pay_7jcf4ovmwnqedhtldca3fjli2y","action_id":"act_7jcf4ovmwnqedhtldca3fjli2y","amount":200,"currency":"USD","approved":true,"status":"Authorized","auth_code":"587541","eci":"05","scheme_id":"776561034288791","response_code":"10000","response_summary":"Approved","risk":{"flagged":false},"source":{"id":"src_m2ooveyd2dxuzh277ft4obgkwm","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_tr53e5z2dlmetpo2ehbsuk76yu","email":"longbob.longsen@example.com","name":"Longbob Longsen"},"processed_on":"2021-03-29T20:22:48Z","reference":"1","processing":{"acquirer_transaction_id":"8266949399","retrieval_reference_number":"731420439000"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_7jcf4ovmwnqedhtldca3fjli2y"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_7jcf4ovmwnqedhtldca3fjli2y/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_7jcf4ovmwnqedhtldca3fjli2y/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_7jcf4ovmwnqedhtldca3fjli2y/voids"}}} + ) + end + + def successful_purchase_using_stored_credential_response + %( + {"id":"pay_udodtu4ogljupp2jvy2cxf4jme","action_id":"act_udodtu4ogljupp2jvy2cxf4jme","amount":200,"currency":"USD","approved":true,"status":"Authorized","auth_code":"680745","eci":"05","scheme_id":"491049486700108","response_code":"10000","response_summary":"Approved","risk":{"flagged":false},"source":{"id":"src_m2ooveyd2dxuzh277ft4obgkwm","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_tr53e5z2dlmetpo2ehbsuk76yu","email":"longbob.longsen@example.com","name":"Longbob Longsen"},"processed_on":"2021-03-29T20:22:49Z","reference":"1","processing":{"acquirer_transaction_id":"4026777708","retrieval_reference_number":"633985559433"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_udodtu4ogljupp2jvy2cxf4jme"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_udodtu4ogljupp2jvy2cxf4jme/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_udodtu4ogljupp2jvy2cxf4jme/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_udodtu4ogljupp2jvy2cxf4jme/voids"}}} ) end def failed_purchase_response %( { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, + "id":"pay_awjzhfj776gulbp2nuslj4agbu", + "amount":200, "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Invalid Card Number", - "responseAdvancedInfo":"If credit card number contains characters other digits, or bank does not recognize this number as a valid credit card number", - "responseCode":"20014", - "card": { + "reference":"1", + "response_summary": "Invalid Card Number", + "response_code":"20014", + "customer": { + "id": "cus_zvnv7gsblfjuxppycd7bx4erue", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "source": { "cvvCheck":"Y", "avsCheck":"S" } @@ -291,66 +1059,61 @@ def failed_purchase_response def successful_authorize_response %( - { - "id":"charge_test_AF1A29AD350Q748C7EA8", - "liveMode":false, - "created":"2017-11-13T14:05:27Z", - "value":200, - "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@example.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Approved", - "responseAdvancedInfo":"Approved", - "responseCode":"10000", - "status":"Authorised", - "authCode":"923189", - "isCascaded":false, - "autoCapture":"N", - "autoCapTime":0.0, - "card":{"customerId": - "cust_12DCEB24-ACEA-48AB-BEF2-35A3C09BE581", - "expiryMonth":"06", - "expiryYear":"2018", - "billingDetails":{ - "addressLine1":"456 My Street", - "addressLine2":"Apt 1", - "postcode":"K1C2N6", - "country":"CA", - "city":"Ottawa", - "state":"ON", - "phone":{"number":"(555)555-5555"} - }, - "id":"card_CFA314F4-388D-4CF4-BE6F-940D894C9E64", - "last4":"4242", - "bin":"424242", - "paymentMethod":"Visa", - "fingerprint":"F639CAB2745BEE4140BF86DF6B6D6E255C5945AAC3788D923FA047EA4C208622", - "name":"Longbob Longsen", - "cvvCheck":"Y", - "avsCheck":"S" + { + "id": "pay_fj3xswqe3emuxckocjx6td73ni", + "action_id": "act_fj3xswqe3emuxckocjx6td73ni", + "amount": 200, + "currency": "USD", + "approved": true, + "status": "Authorized", + "auth_code": "858188", + "eci": "05", + "scheme_id": "638284745624527", + "response_code": "10000", + "response_summary": "Approved", + "risk": { + "flagged": false }, - "riskCheck":true, - "customerPaymentPlans":null, - "metadata":{}, - "shippingDetails":{ - "addressLine1":null, - "addressLine2":null, - "postcode":null, - "country":null, - "city":null, - "state":null, - "phone":{} + "source": { + "id": "src_nq6m5dqvxmsunhtzf7adymbq3i", + "type": "card", + "expiry_month": 8, + "expiry_year": 2025, + "name": "Sarah Mitchell", + "scheme": "Visa", + "last4": "4242", + "fingerprint": "5CD3B9CB15338683110959D165562D23084E1FF564F420FE9A990DF0BCD093FC", + "bin": "424242", + "card_type": "Credit", + "card_category": "Consumer", + "issuer": "JPMORGAN CHASE BANK NA", + "issuer_country": "US", + "product_id": "A", + "product_type": "Visa Traditional", + "avs_check": "S", + "cvv_check": "Y" }, - "products":[], - "udf1":null, - "udf2":null, - "udf3":null, - "udf4":null, - "udf5":null + "customer": { + "id": "cus_ssxcidkqvfde7lfn5n7xzmgv2a", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "processed_on": "2019-03-24T10:14:32Z", + "reference": "ORD-5023-4E89", + "_links": { + "self": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni" + }, + "actions": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/actions" + }, + "capture": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/captures" + }, + "void": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/voids" + } + } } ) end @@ -358,126 +1121,177 @@ def successful_authorize_response def failed_authorize_response %( { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, + "id":"pay_awjzhfj776gulbp2nuslj4agbu", + "amount":200, "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Invalid Card Number", - "responseAdvancedInfo":"If credit card number contains characters other digits, or bank does not recognize this number as a valid credit card number", - "responseCode":"20014" + "reference":"1", + "customer": { + "id": "cus_zvnv7gsblfjuxppycd7bx4erue", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "response_summary": "Invalid Card Number", + "response_code":"20014" } ) end - def successful_capture_response + def successful_incremental_authorize_response %( - { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, - "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Captured", - "responseAdvancedInfo":"Captured", - "responseCode":"10000" + { + "action_id": "act_q4dbxom5jbgudnjzjpz7j2z6uq", + "amount": 50, + "currency": "USD", + "approved": true, + "status": "Authorized", + "auth_code": "503198", + "expires_on": "2020-04-20T10:11:12Z", + "eci": "05", + "scheme_id": "511129554406717", + "response_code": "10000", + "response_summary": "Approved", + "balances": { + "total_authorized": 150, + "total_voided": 0, + "available_to_void": 150, + "total_captured": 0, + "available_to_capture": 150, + "total_refunded": 0, + "available_to_refund": 0 + }, + "processed_on": "2020-03-16T22:11:24Z", + "reference": "ORD-752-814", + "processing": { + "acquirer_transaction_id": "8367314942", + "retrieval_reference_number": "162588399162" + }, + "_links": { + "self": { + "href": "https://api.sandbox.checkout.com/payments/pay_tqgk5c6k2nnexagtcuom5ktlua" + }, + "actions": { + "href": "https://api.sandbox.checkout.com/payments/pay_tqgk5c6k2nnexagtcuom5ktlua/actions" + }, + "authorize": { + "href": "https://api.sandbox.checkout.com/payments/pay_tqgk5c6k2nnexagtcuom5ktlua/authorizations" + }, + "capture": { + "href": "https://api.sandbox.checkout.com/payments/pay_tqgk5c6k2nnexagtcuom5ktlua/captures" + }, + "void": { + "href": "https://api.sandbox.checkout.com/payments/pay_tqgk5c6k2nnexagtcuom5ktlua/voids" + } + } } ) end - def failed_capture_response + def successful_capture_response %( { - "errorCode":"405", - "message":"You tried to access the endpoint with an invalid method", + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" } ) end + def failed_capture_response + %( + ) + end + def successful_refund_response %( - { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, - "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Refunded", - "responseAdvancedInfo":"Refunded", - "responseCode":"10000" - } + { + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" + } ) end def failed_refund_response + %( + ) + end + + def successful_void_response %( { - "errorCode":"405", - "message":"You tried to access the endpoint with an invalid method", + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" } ) end - def successful_void_response + def successful_credit_response %( - { - "id":"charge_test_941CA9CE174U76BD29C8", - "liveMode":false, - "created":"2015-05-27T20:45:58Z", - "value":200.0, - "currency":"USD", - "trackId":"1", - "description":null, - "email":"longbob.longsen@gmail.com", - "chargeMode":1, - "transactionIndicator":1, - "customerIp":null, - "responseMessage":"Voided", - "responseAdvancedInfo":"Voided", - "responseCode":"10000" + { + "id": "pay_jhzh3u7vxcgezlcek7ymzyy6be", + "status": "Pending", + "reference": "ORD-5023-4E89", + "instruction": { + "value_date": "2022-08-09T06:11:37.2306547+00:00" + }, + "_links": { + "self": { + "href": "https://api.sandbox.checkout.com/payments/pay_jhzh3u7vxcgezlcek7ymzyy6be" + }, + "actions": { + "href": "https://api.sandbox.checkout.com/payments/pay_jhzh3u7vxcgezlcek7ymzyy6be/actions" + } } + } ) end def failed_void_response %( - { - "errorCode":"405", - "message":"You tried to access the endpoint with an invalid method", - } ) end def invalid_json_response %( { - "id": "charge_test_123456", + "id": "pay_123", ) end def error_code_response %( { - "eventId":"1b206f69-b4db-4259-9713-b72dfe0f19da","errorCode":"70000","message":"Validation error","errorMessageCodes":["70077"],"errors":["Expired Card"] + "request_id": "e5a3ce6f-a4e9-4445-9ec7-e5975e9a6213","error_type": "request_invalid","error_codes": ["card_expired"] } ) end + + def error_4xx_response + mock_response = Net::HTTPUnauthorized.new('1.1', '401', 'Unauthorized') + mock_response.stubs(:body).returns('') + + ActiveMerchant::ResponseError.new(mock_response) + end + + def successful_verify_payment_response + %( + {"id":"pay_tkvif5mf54eerhd3ysuawfcnt4","requested_on":"2019-08-14T18:13:54Z","source":{"id":"src_lot2ch4ygk3ehi4fugxmk7r2di","type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"0907","fingerprint":"E4048195442B0059D73FD47F6E1961A02CD085B0B34B7703CE4A93750DB5A0A1","bin":"457382","avs_check":"S","cvv_check":"Y"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"Dvy8EMaEphrMWolKsLVHcUqPsyx","status":"Authorized","approved":true,"3ds":{"downgraded":false,"enrolled":"Y","authentication_response":"Y","cryptogram":"ce49b5c1-5d3c-4864-bd16-2a8c","xid":"95202312-f034-48b4-b9b2-54254a2b49fb","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_zt5pspdtkypuvifj7g6roy7p6y","name":"Jane Doe"},"billing_descriptor":{"name":"","city":"London"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"eci":"05","scheme_id":"638284745624527","actions":[{"id":"act_tkvif5mf54eerhd3ysuawfcnt4","type":"Authorization","response_code":"10000","response_summary":"Approved"}],"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/voids"}}} + ) + end + + def 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"}}} + ) + end + + def successful_verify_response + %({"id":"pay_ij6bctwxpzdulm53xyksio7gm4","action_id":"act_ij6bctwxpzdulm53xyksio7gm4","amount":0,"currency":"USD","approved":true,"status":"Card Verified","auth_code":"881790","eci":"05","scheme_id":"305756859646779","response_code":"10000","response_summary":"Approved","risk":{"flagged":false},"source":{"id":"src_nica37p5k7aufhs3rsv2te7xye","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_r2yb7f2upmsuhm6nbruoqn657y","email":"longbob.longsen@example.com","name":"Longbob Longsen"},"processed_on":"2020-09-18T18:17:45Z","reference":"1","processing":{"acquirer_transaction_id":"4932795322","retrieval_reference_number":"954188232380"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_ij6bctwxpzdulm53xyksio7gm4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_ij6bctwxpzdulm53xyksio7gm4/actions"}}}) + end + + def failed_verify_response + %({"request_id":"911829c3-519a-47e8-bbc1-17337789fda0","error_type":"request_invalid","error_codes":["card_number_invalid"]}) + end end diff --git a/test/unit/gateways/citrus_pay_test.rb b/test/unit/gateways/citrus_pay_test.rb index 40b126e3782..bc39c2034bd 100644 --- a/test/unit/gateways/citrus_pay_test.rb +++ b/test/unit/gateways/citrus_pay_test.rb @@ -48,7 +48,7 @@ def test_authorize_and_capture capture = stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/f3d100a7-18d9-4609-aabc-8a710ad0e210/, data) end.respond_with(successful_capture_response) @@ -65,7 +65,7 @@ def test_refund refund = stub_comms(@gateway, :ssl_request) do @gateway.refund(@amount, response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) end.respond_with(successful_refund_response) @@ -82,7 +82,7 @@ def test_void void = stub_comms(@gateway, :ssl_request) do @gateway.void(response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) end.respond_with(successful_void_response) @@ -91,16 +91,16 @@ def test_void def test_passing_alpha3_country_code stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'US'}) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { country: 'US' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/USA/, data) end.respond_with(successful_authorize_response) end def test_non_existent_country stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'Blah'}) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { country: 'Blah' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/"country":null/, data) end.respond_with(successful_authorize_response) end @@ -108,15 +108,15 @@ def test_non_existent_country def test_passing_cvv stub_comms(@gateway, :ssl_request) do @gateway.authorize(@amount, @credit_card) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_authorize_response) end def test_passing_billing_address stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => address) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: address) + end.check_request do |_method, _endpoint, data, _headers| parsed = JSON.parse(data) assert_equal('456 My Street', parsed['billing']['address']['street']) assert_equal('K1C2N6', parsed['billing']['address']['postcodeZip']) @@ -125,8 +125,8 @@ def test_passing_billing_address def test_passing_shipping_name stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :shipping_address => address) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, shipping_address: address) + end.check_request do |_method, _endpoint, data, _headers| parsed = JSON.parse(data) assert_equal('Jim', parsed['shipping']['firstName']) assert_equal('Smith', parsed['shipping']['lastName']) @@ -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', @@ -166,24 +166,8 @@ 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) + end.check_request do |_method, endpoint, _data, _headers| + assert_match(/secure.uat.tnspayments.com/, endpoint) end.respond_with(successful_capture_response) assert_success response diff --git a/test/unit/gateways/clearhaus_test.rb b/test/unit/gateways/clearhaus_test.rb index b877de05e4f..b5d74ff46ec 100644 --- a/test/unit/gateways/clearhaus_test.rb +++ b/test/unit/gateways/clearhaus_test.rb @@ -55,7 +55,7 @@ def test_successful_authorize_with_threed response = @gateway.authorize(@amount, @credit_card, @options.merge(pares: '123')) assert_success response assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| expr = { card: { pares: '123' } }.to_query assert_match expr, data end.respond_with(successful_authorize_response) @@ -66,9 +66,9 @@ def test_additional_params response = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: '123', text_on_statement: 'test')) assert_success response assert response.test? - end.check_request do |endpoint, data, headers| - order_expr = { reference: '123'}.to_query - tos_expr = { text_on_statement: 'test'}.to_query + end.check_request do |_endpoint, data, _headers| + order_expr = { reference: '123' }.to_query + tos_expr = { text_on_statement: 'test' }.to_query assert_match order_expr, data assert_match tos_expr, data @@ -82,7 +82,7 @@ def test_successful_authorize_with_card assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_match %r{/cards/4110/authorizations}, endpoint end.respond_with(successful_authorize_response) end @@ -222,7 +222,7 @@ def test_signing_request assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization assert response.test? - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, _data, headers| assert headers['Signature'] assert_match %r{7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2 RS256-hex}, headers['Signature'] assert_match %r{25f8283c3cc43911d7$}, headers['Signature'] @@ -241,7 +241,7 @@ def test_cleans_whitespace_from_private_key assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization assert response.test? - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, _data, headers| assert headers['Signature'] assert_match %r{7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2 RS256-hex}, headers['Signature'] assert_match %r{25f8283c3cc43911d7$}, headers['Signature'] @@ -249,7 +249,7 @@ def test_cleans_whitespace_from_private_key end def test_unsuccessful_signing_request_with_invalid_key - gateway = ClearhausGateway.new(api_key: 'test_key', signing_key: @test_signing_key, private_key: 'foo') + gateway = ClearhausGateway.new(api_key: 'test_key', signing_key: @test_signing_key, private_key: 'foo') # stub actual network access, but this shouldn't be reached gateway.stubs(:ssl_post).returns(nil) @@ -266,6 +266,14 @@ def test_scrub assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end + def test_nonfractional_currency_handling + stub_comms do + @gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/amount=2&card/, data) + end.respond_with(successful_authorize_response) + end + private def pre_scrubbed @@ -366,11 +374,11 @@ def successful_authorize_response { 'id' => '84412a34-fa29-4369-a098-0165a80e8fda', 'status' => { - 'code' => 20000 + 'code' => 20000 }, 'processed_at' => '2014-07-09T09:53:41+00:00', '_links' => { - 'captures' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/captures' } + 'captures' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/captures' } } }.to_json end @@ -381,20 +389,20 @@ def failed_authorize_response def successful_capture_response { - 'id' => 'd8e92a70-3030-4d4d-8ad2-684b230c1bed', - 'status' => { - 'code' => 20000 + 'id' => 'd8e92a70-3030-4d4d-8ad2-684b230c1bed', + 'status' => { + 'code' => 20000 + }, + 'processed_at' => '2014-07-09T11:47:28+00:00', + 'amount' => 1000, + '_links' => { + 'authorization' => { + 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' }, - 'processed_at' => '2014-07-09T11:47:28+00:00', - 'amount' => 1000, - '_links' => { - 'authorization' => { - 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' - }, - 'refunds' => { - 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/refunds' - } + 'refunds' => { + 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/refunds' } + } }.to_json end @@ -406,12 +414,12 @@ def successful_refund_response { 'id' => 'f04c0872-47ce-4683-8d8c-e154221bba14', 'status' => { - 'code' => 20000 + 'code' => 20000 }, 'processed_at' => '2014-07-09T11:57:58+00:00', 'amount' => 500, '_links' => { - 'authorization' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' } + 'authorization' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' } } }.to_json end @@ -437,8 +445,8 @@ def successful_void_response 'captures' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/captures' }, - 'voids' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/voids'}, - 'refunds' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/refunds'} + 'voids' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/voids' }, + 'refunds' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/refunds' } } } end @@ -451,14 +459,14 @@ def successful_store_response { 'id' => '58dabba0-e9ea-4133-8c38-bfa1028c1ed2', 'status' => { - 'code'=> 20000 + 'code' => 20000 }, 'processed_at' => '2014-07-09T12:14:31+00:00', 'last4' => '0004', 'scheme' => 'mastercard', '_links' => { - 'authorizations' => { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/authorizations' }, - 'credits'=> { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/credits' } + 'authorizations' => { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/authorizations' }, + 'credits' => { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/credits' } } }.to_json end @@ -468,7 +476,6 @@ def failed_store_response end def failed_ch_response - { 'status' => { 'code' => 40000, 'message' => 'General input error' }}.to_json + { 'status' => { 'code' => 40000, 'message' => 'General input error' } }.to_json end - end diff --git a/test/unit/gateways/commerce_hub_test.rb b/test/unit/gateways/commerce_hub_test.rb new file mode 100644 index 00000000000..b2b085be356 --- /dev/null +++ b/test/unit/gateways/commerce_hub_test.rb @@ -0,0 +1,806 @@ +require 'test_helper' + +class CommerceHubTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CommerceHubGateway.new(api_key: 'login', api_secret: 'password', merchant_id: '12345', terminal_id: '0001') + + @amount = 1204 + @credit_card = credit_card('4005550000000019', month: '02', year: '2035', verification_value: '123') + @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' + ) + @no_supported_source = network_tokenization_credit_card( + '4005550000000019', + brand: 'visa', + eci: '05', + month: '02', + year: '2035', + source: :no_source, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + @declined_card = credit_card('4000300011112220', month: '02', year: '2035', verification_value: '123') + @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) + + assert_success response + end + + def test_successful_purchase_with_google_pay + response = stub_comms do + @gateway.purchase(@amount, @google_pay, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['transactionDetails']['captureFlag'], true + 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'], @google_pay.number + assert_equal request['source']['cavv'], @google_pay.payment_cryptogram + assert_equal request['source']['walletType'], 'GOOGLE_PAY' + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_apple_pay + response = stub_comms do + @gateway.purchase(@amount, @apple_pay, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['transactionDetails']['captureFlag'], true + 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'], @apple_pay.number + assert_equal request['source']['cavv'], @apple_pay.payment_cryptogram + assert_equal request['source']['walletType'], 'APPLE_PAY' + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_no_supported_source_as_apple_pay + response = stub_comms do + @gateway.purchase(@amount, @no_supported_source, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['transactionDetails']['captureFlag'], true + 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'], @apple_pay.number + assert_equal request['source']['cavv'], @apple_pay.payment_cryptogram + assert_equal request['source']['walletType'], 'APPLE_PAY' + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['transactionDetails']['captureFlag'], false + 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['source']['card']['securityCodeIndicator'], 'PROVIDED' + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_purchase_and_authorize + @gateway.expects(:ssl_post).returns(failed_purchase_and_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'string', response.error_code + end + + def test_successful_parsing_of_billing_and_shipping_addresses + address_with_phone = address.merge({ phone_number: '000-000-00-000' }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ billing_address: address_with_phone, shipping_address: address_with_phone })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + %w(shipping billing).each do |key| + assert_equal request[key + 'Address']['address']['street'], address_with_phone[:address1] + assert_equal request[key + 'Address']['address']['houseNumberOrName'], address_with_phone[:address2] + assert_equal request[key + 'Address']['address']['recipientNameOrAddress'], address_with_phone[:name] + assert_equal request[key + 'Address']['address']['city'], address_with_phone[:city] + assert_equal request[key + 'Address']['address']['stateOrProvince'], address_with_phone[:state] + assert_equal request[key + 'Address']['address']['postalCode'], address_with_phone[:zip] + assert_equal request[key + 'Address']['address']['country'], address_with_phone[:country] + assert_equal request[key + 'Address']['phone']['phoneNumber'], address_with_phone[:phone_number] + end + end.respond_with(successful_authorize_response) + end + + def test_successful_void + response = stub_comms do + @gateway.void('abc123|authorization123', @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + 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) + + assert_success response + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(nil, '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_nil request['transactionDetails']['captureFlag'] + assert_nil request['amount'] + end.respond_with(successful_void_and_refund_response) + + assert_success response + end + + def test_successful_partial_refund + response = stub_comms do + @gateway.refund(@amount - 1, '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_nil request['transactionDetails']['captureFlag'] + assert_equal request['amount']['total'], ((@amount - 1) / 100.0).to_f + assert_equal request['amount']['currency'], 'USD' + end.respond_with(successful_void_and_refund_response) + + assert_success 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) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['merchantDetails']['terminalId'], @gateway.options[:terminal_id] + assert_equal request['merchantDetails']['merchantId'], @gateway.options[:merchant_id] + assert_equal request['source']['card']['cardData'], @credit_card.number + assert_equal request['source']['card']['securityCode'], @credit_card.verification_value + assert_equal request['source']['card']['securityCodeIndicator'], 'PROVIDED' + end.respond_with(successful_store_response) + + assert_success response + assert_equal response.params['paymentTokens'].first['tokenData'], response.authorization + end + + def test_successful_verify + stub_comms do + @gateway.verify(@credit_card, @options) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_match %r{verification}, endpoint + assert_equal request['source']['sourceType'], 'PaymentCard' + end.respond_with(successful_authorize_response) + end + + 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 + assert @gateway.supports_scrubbing? + 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 + <<~RESPONSE + { + "gatewayResponse": { + "transactionType": "CHARGE", + "transactionState": "CAPTURED", + "transactionOrigin": "ECOM", + "transactionProcessingDetails": { + "orderId": "CHG018048a66aafc64d789cb018a53c30fd74", + "transactionTimestamp": "2022-10-06T11:27:45.593359Z", + "apiTraceId": "6304d53be8d94312a620962afc9c012d", + "clientRequestId": "5106241", + "transactionId": "6304d53be8d94312a620962afc9c012d" + } + }, + "source": { + "sourceType": "PaymentCard", + "card": { + "expirationMonth": "02", + "expirationYear": "2035", + "securityCodeIndicator": "PROVIDED", + "bin": "400555", + "last4": "0019", + "scheme": "VISA" + } + }, + "paymentReceipt": { + "approvedAmount": { + "total": 12.04, + "currency": "USD" + }, + "processorResponseDetails": { + "approvalStatus": "APPROVED", + "approvalCode": "000238", + "referenceNumber": "962afc9c012d", + "processor": "FISERV", + "host": "NASHVILLE", + "networkInternationalId": "0001", + "responseCode": "000", + "responseMessage": "Approved", + "hostResponseCode": "00", + "additionalInfo": [ + { + "name": "HOST_RAW_PROCESSOR_RESPONSE", + "value": "ARAyIAHvv70O77+9AAAAAAAAAAAAEgQQBhAnRQE1JAABWTk2MmFmYzljMDEyZDAwMDIzODAwMDk5OTk5OTk=" + } + ] + } + }, + "transactionDetails": { + "captureFlag": true, + "transactionCaptureType": "hcs", + "processingCode": "000000", + "merchantInvoiceNumber": "123456789012", + "createToken": true, + "retrievalReferenceNumber": "962afc9c012d" + }, + "transactionInteraction": { + "posEntryMode": "UNSPECIFIED", + "posConditionCode": "CARD_NOT_PRESENT_ECOM", + "additionalPosInformation": { + "dataEntrySource": "UNSPECIFIED", + "posFeatures": { + "pinAuthenticationCapability": "UNSPECIFIED", + "terminalEntryCapability": "UNSPECIFIED" + } + }, + "hostPosConditionCode": "59" + }, + "merchantDetails": { + "tokenType": "BBY0", + "terminalId": "10000001", + "merchantId": "100008000003683" + }, + "networkDetails": { + "network": { + "network": "Visa" + } + } + } + RESPONSE + end + + def successful_authorize_response + <<~RESPONSE + { + "gatewayResponse": { + "transactionType": "CHARGE", + "transactionState": "AUTHORIZED", + "transactionOrigin": "ECOM", + "transactionProcessingDetails": { + "orderId": "CHG01fb29348b9f8a48ed875e6bea3af41744", + "transactionTimestamp": "2022-10-06T11:28:27.131701Z", + "apiTraceId": "000bc22420f448288f1226d28dfdf275", + "clientRequestId": "9573527", + "transactionId": "000bc22420f448288f1226d28dfdf275" + } + }, + "source": { + "sourceType": "PaymentCard", + "card": { + "expirationMonth": "02", + "expirationYear": "2035", + "bin": "400555", + "last4": "0019", + "scheme": "VISA" + } + }, + "paymentReceipt": { + "approvedAmount": { + "total": 12.04, + "currency": "USD" + }, + "processorResponseDetails": { + "approvalStatus": "APPROVED", + "approvalCode": "000239", + "referenceNumber": "26d28dfdf275", + "processor": "FISERV", + "host": "NASHVILLE", + "networkInternationalId": "0001", + "responseCode": "000", + "responseMessage": "Approved", + "hostResponseCode": "00", + "additionalInfo": [ + { + "name": "HOST_RAW_PROCESSOR_RESPONSE", + "value": "ARAyIAHvv70O77+9AAAAAAAAAAAAEgQQBhAoJzQ2aAABWTI2ZDI4ZGZkZjI3NTAwMDIzOTAwMDk5OTk5OTk=" + } + ] + } + }, + "transactionDetails": { + "captureFlag": false, + "transactionCaptureType": "hcs", + "processingCode": "000000", + "merchantInvoiceNumber": "123456789012", + "createToken": true, + "retrievalReferenceNumber": "26d28dfdf275" + }, + "transactionInteraction": { + "posEntryMode": "UNSPECIFIED", + "posConditionCode": "CARD_NOT_PRESENT_ECOM", + "additionalPosInformation": { + "dataEntrySource": "UNSPECIFIED", + "posFeatures": { + "pinAuthenticationCapability": "UNSPECIFIED", + "terminalEntryCapability": "UNSPECIFIED" + } + }, + "hostPosConditionCode": "59" + }, + "merchantDetails": { + "tokenType": "BBY0", + "terminalId": "10000001", + "merchantId": "100008000003683" + }, + "networkDetails": { + "network": { + "network": "Visa" + } + } + } + RESPONSE + end + + def failed_purchase_and_authorize_response + <<~RESPONSE + { + "gatewayResponse": { + "transactionType": "CHARGE", + "transactionState": "AUTHORIZED", + "transactionOrigin": "ECOM", + "transactionProcessingDetails": { + "orderId": "R-3b83fca8-2f9c-4364-86ae-12c91f1fcf16", + "transactionTimestamp": "2016-04-16T16:06:05Z", + "apiTraceId": "1234567a1234567b1234567c1234567d", + "clientRequestId": "30dd879c-ee2f-11db-8314-0800200c9a66", + "transactionId": "838916029301" + } + }, + "error": [ + { + "type": "HOST", + "code": "string", + "field": "source.sourceType", + "message": "Missing type ID property.", + "additionalInfo": "The Reauthorization request was not successful and the Cancel of referenced authorization transaction was not processed, per Auth_before_Cancel configuration" + } + ] + } + RESPONSE + end + + def successful_void_and_refund_response + <<~RESPONSE + { + "gatewayResponse": { + "transactionType": "CANCEL", + "transactionState": "AUTHORIZED", + "transactionOrigin": "ECOM", + "transactionProcessingDetails": { + "transactionTimestamp": "2021-06-20T23:42:48Z", + "orderId": "RKOrdID-525133851837", + "apiTraceId": "362866ac81864d7c9d1ff8b5aa6e98db", + "clientRequestId": "4345791", + "transactionId": "84356531338" + } + }, + "source": { + "sourceType": "PaymentCard", + "card": { + "bin": "40055500", + "last4": "0019", + "scheme": "VISA", + "expirationMonth": "10", + "expirationYear": "2030" + } + }, + "paymentReceipt": { + "approvedAmount": { + "total": 12.04, + "currency": "USD" + }, + "merchantName": "Merchant Name", + "merchantAddress": "123 Peach Ave", + "merchantCity": "Atlanta", + "merchantStateOrProvince": "GA", + "merchantPostalCode": "12345", + "merchantCountry": "US", + "merchantURL": "https://www.somedomain.com", + "processorResponseDetails": { + "approvalStatus": "APPROVED", + "approvalCode": "OK5882", + "schemeTransactionId": "0225MCC625628", + "processor": "FISERV", + "host": "NASHVILLE", + "responseCode": "000", + "responseMessage": "APPROVAL", + "hostResponseCode": "00", + "hostResponseMessage": "APPROVAL", + "localTimestamp": "2021-06-20T23:42:48Z", + "bankAssociationDetails": { + "associationResponseCode": "000", + "transactionTimestamp": "2021-06-20T23:42:48Z" + } + } + }, + "transactionDetails": { + "merchantInvoiceNumber": "123456789012" + } + } + RESPONSE + end + + def successful_store_response + <<~RESPONSE + { + "gatewayResponse": { + "transactionType": "TOKENIZE", + "transactionState": "AUTHORIZED", + "transactionOrigin": "ECOM", + "transactionProcessingDetails": { + "transactionTimestamp": "2021-06-20T23:42:48Z", + "orderId": "RKOrdID-525133851837", + "apiTraceId": "362866ac81864d7c9d1ff8b5aa6e98db", + "clientRequestId": "4345791", + "transactionId": "84356531338" + } + }, + "source": { + "sourceType": "PaymentCard", + "card": { + "bin": "40055500", + "last4": "0019", + "scheme": "VISA", + "expirationMonth": "10", + "expirationYear": "2030" + } + }, + "paymentTokens": [ + { + "tokenData": "8519371934460009", + "tokenSource": "TRANSARMOR", + "tokenResponseCode": "000", + "tokenResponseDescription": "SUCCESS" + }, + { + "tokenData": "8519371934460010", + "tokenSource": "CHASE", + "tokenResponseCode": "000", + "tokenResponseDescription": "SUCCESS" + } + ], + "processorResponseDetails": { + "approvalStatus": "APPROVED", + "approvalCode": "OK5882", + "schemeTransactionId": "0225MCC625628", + "processor": "FISERV", + "host": "NASHVILLE", + "responseCode": "000", + "responseMessage": "APPROVAL", + "hostResponseCode": "00", + "hostResponseMessage": "APPROVAL", + "localTimestamp": "2021-06-20T23:42:48Z", + "bankAssociationDetails": { + "associationResponseCode": "000", + "transactionTimestamp": "2021-06-20T23:42:48Z" + } + } + } + RESPONSE + end + + def pre_scrubbed + <<~PRE_SCRUBBED + opening connection to cert.api.fiservapps.com:443... + opened + starting SSL for cert.api.fiservapps.com:443... + SSL established + <- "POST /ch/payments/v1/charges HTTP/1.1\r\nContent-Type: application/json\r\nClient-Request-Id: 3473900\r\nApi-Key: nEcoHEQZjKtkKW9dN6yH7x4gO2EIARKe\r\nTimestamp: 1670258885014\r\nAccept-Language: application/json\r\nAuth-Token-Type: HMAC\r\nAccept: application/json\r\nAuthorization: TQh0nE38Mv7cbxbX3oSIUxZ4RzMkTmS2hpUSd6Rgi98=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: cert.api.fiservapps.com\r\nContent-Length: 500\r\n\r\n" + <- "{\"transactionDetails\":{\"captureFlag\":true,\"merchantInvoiceNumber\":\"995952121195\"},\"amount\":{\"total\":12.04,\"currency\":\"USD\"},\"source\":{\"sourceType\":\"PaymentCard\",\"card\":{\"cardData\":\"4005550000000019\",\"expirationMonth\":\"02\",\"expirationYear\":\"2035\",\"securityCode\":\"123\",\"securityCodeIndicator\":\"PROVIDED\"}},\"transactionInteraction\":{\"origin\":\"ECOM\",\"eciIndicator\":\"CHANNEL_ENCRYPTED\",\"posConditionCode\":\"CARD_NOT_PRESENT_ECOM\"},\"merchantDetails\":{\"terminalId\":\"10000001\",\"merchantId\":\"100008000003683\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 05 Dec 2022 16:48:06 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1709\r\n" + -> "Connection: close\r\n" + -> "Expires: 0\r\n" + -> "Referrer-Policy: no-referrer\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Vcap-Request-Id: 30397096-5cb9-46e1-7c63-3ac2494ca38e\r\n" + -> "targetServerReceivedEndTimestamp: 1670258886388\r\n" + -> "targetServerSentStartTimestamp: 1670258885212\r\n" + -> "X-Xss-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Access-Control-Max-Age: 86400\r\n" + -> "ApiTraceId: 19d178570f274a2196540af6e2e0bf55\r\n" + -> "Via: 1.1 dca1-bit16021\r\n" + -> "\r\n" + reading 1709 bytes... + -> "{\"gatewayResponse\":{\"transactionType\":\"CHARGE\",\"transactionState\":\"CAPTURED\",\"transactionOrigin\":\"ECOM\",\"transactionProcessingDetails\":{\"orderId\":\"CHG0147086beb95194e808a3bf88e052285d7\",\"transactionTimestamp\":\"2022-12-05T16:48:05.358725Z\",\"apiTraceId\":\"19d178570f274a2196540af6e2e0bf55\",\"clientRequestId\":\"3473900\",\"transactionId\":\"19d178570f274a2196540af6e2e0bf55\"}},\"source\":{\"sourceType\":\"PaymentCard\",\"card\":{\"expirationMonth\":\"02\",\"expirationYear\":\"2035\",\"securityCodeIndicator\":\"PROVIDED\",\"bin\":\"400555\",\"last4\":\"0019\",\"scheme\":\"VISA\"}},\"paymentReceipt\":{\"approvedAmount\":{\"total\":12.04,\"currency\":\"USD\"},\"processorResponseDetails\":{\"approvalStatus\":\"APPROVED\",\"approvalCode\":\"000119\",\"referenceNumber\":\"0af6e2e0bf55\",\"processor\":\"FISERV\",\"host\":\"NASHVILLE\",\"networkInternationalId\":\"0001\",\"responseCode\":\"000\",\"responseMessage\":\"Approved\",\"hostResponseCode\":\"00\",\"additionalInfo\":[{\"name\":\"HOST_RAW_PROCESSOR_RESPONSE\",\"value\":\"ARAyIAHvv70O77+9AAIAAAAAAAAAEgQSBRZIBTNCVQABWTBhZjZlMmUwYmY1NTAwMDExOTAwMDk5OTk5OTkABAACMTQ=\"}]}},\"transactionDetails\":{\"captureFlag\":true,\"transactionCaptureType\":\"hcs\",\"processingCode\":\"000000\",\"merchantInvoiceNumber\":\"995952121195\",\"createToken\":true,\"retrievalReferenceNumber\":\"0af6e2e0bf55\",\"cavvInPrimary\":false},\"transactionInteraction\":{\"posEntryMode\":\"UNSPECIFIED\",\"posConditionCode\":\"CARD_NOT_PRESENT_ECOM\",\"additionalPosInformation\":{\"dataEntrySource\":\"UNSPECIFIED\",\"posFeatures\":{\"pinAuthenticationCapability\":\"UNSPECIFIED\",\"terminalEntryCapability\":\"UNSPECIFIED\"}},\"hostPosEntryMode\":\"000\",\"hostPosConditionCode\":\"59\"},\"merchantDetails\":{\"tokenType\":\"BBY0\",\"terminalId\":\"10000001\",\"merchantId\":\"100008000003683\"},\"networkDetails\":{\"network\":{\"network\":\"Visa\"}}}" + read 1709 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<~POST_SCRUBBED + opening connection to cert.api.fiservapps.com:443... + opened + starting SSL for cert.api.fiservapps.com:443... + SSL established + <- "POST /ch/payments/v1/charges HTTP/1.1\r\nContent-Type: application/json\r\nClient-Request-Id: 3473900\r\nApi-Key: [FILTERED]\r\nTimestamp: 1670258885014\r\nAccept-Language: application/json\r\nAuth-Token-Type: HMAC\r\nAccept: application/json\r\nAuthorization: [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: cert.api.fiservapps.com\r\nContent-Length: 500\r\n\r\n" + <- "{\"transactionDetails\":{\"captureFlag\":true,\"merchantInvoiceNumber\":\"995952121195\"},\"amount\":{\"total\":12.04,\"currency\":\"USD\"},\"source\":{\"sourceType\":\"PaymentCard\",\"card\":{\"cardData\":\"[FILTERED]\",\"expirationMonth\":\"02\",\"expirationYear\":\"2035\",\"securityCode\":\"[FILTERED]\",\"securityCodeIndicator\":\"PROVIDED\"}},\"transactionInteraction\":{\"origin\":\"ECOM\",\"eciIndicator\":\"CHANNEL_ENCRYPTED\",\"posConditionCode\":\"CARD_NOT_PRESENT_ECOM\"},\"merchantDetails\":{\"terminalId\":\"10000001\",\"merchantId\":\"100008000003683\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 05 Dec 2022 16:48:06 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1709\r\n" + -> "Connection: close\r\n" + -> "Expires: 0\r\n" + -> "Referrer-Policy: no-referrer\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Vcap-Request-Id: 30397096-5cb9-46e1-7c63-3ac2494ca38e\r\n" + -> "targetServerReceivedEndTimestamp: 1670258886388\r\n" + -> "targetServerSentStartTimestamp: 1670258885212\r\n" + -> "X-Xss-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Access-Control-Max-Age: 86400\r\n" + -> "ApiTraceId: 19d178570f274a2196540af6e2e0bf55\r\n" + -> "Via: 1.1 dca1-bit16021\r\n" + -> "\r\n" + reading 1709 bytes... + -> "{\"gatewayResponse\":{\"transactionType\":\"CHARGE\",\"transactionState\":\"CAPTURED\",\"transactionOrigin\":\"ECOM\",\"transactionProcessingDetails\":{\"orderId\":\"CHG0147086beb95194e808a3bf88e052285d7\",\"transactionTimestamp\":\"2022-12-05T16:48:05.358725Z\",\"apiTraceId\":\"19d178570f274a2196540af6e2e0bf55\",\"clientRequestId\":\"3473900\",\"transactionId\":\"19d178570f274a2196540af6e2e0bf55\"}},\"source\":{\"sourceType\":\"PaymentCard\",\"card\":{\"expirationMonth\":\"02\",\"expirationYear\":\"2035\",\"securityCodeIndicator\":\"PROVIDED\",\"bin\":\"400555\",\"last4\":\"0019\",\"scheme\":\"VISA\"}},\"paymentReceipt\":{\"approvedAmount\":{\"total\":12.04,\"currency\":\"USD\"},\"processorResponseDetails\":{\"approvalStatus\":\"APPROVED\",\"approvalCode\":\"000119\",\"referenceNumber\":\"0af6e2e0bf55\",\"processor\":\"FISERV\",\"host\":\"NASHVILLE\",\"networkInternationalId\":\"0001\",\"responseCode\":\"000\",\"responseMessage\":\"Approved\",\"hostResponseCode\":\"00\",\"additionalInfo\":[{\"name\":\"HOST_RAW_PROCESSOR_RESPONSE\",\"value\":\"ARAyIAHvv70O77+9AAIAAAAAAAAAEgQSBRZIBTNCVQABWTBhZjZlMmUwYmY1NTAwMDExOTAwMDk5OTk5OTkABAACMTQ=\"}]}},\"transactionDetails\":{\"captureFlag\":true,\"transactionCaptureType\":\"hcs\",\"processingCode\":\"000000\",\"merchantInvoiceNumber\":\"995952121195\",\"createToken\":true,\"retrievalReferenceNumber\":\"0af6e2e0bf55\",\"cavvInPrimary\":false},\"transactionInteraction\":{\"posEntryMode\":\"UNSPECIFIED\",\"posConditionCode\":\"CARD_NOT_PRESENT_ECOM\",\"additionalPosInformation\":{\"dataEntrySource\":\"UNSPECIFIED\",\"posFeatures\":{\"pinAuthenticationCapability\":\"UNSPECIFIED\",\"terminalEntryCapability\":\"UNSPECIFIED\"}},\"hostPosEntryMode\":\"000\",\"hostPosConditionCode\":\"59\"},\"merchantDetails\":{\"tokenType\":\"BBY0\",\"terminalId\":\"10000001\",\"merchantId\":\"100008000003683\"},\"networkDetails\":{\"network\":{\"network\":\"Visa\"}}}" + read 1709 bytes + Conn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/conekta_test.rb b/test/unit/gateways/conekta_test.rb index 215a50b1ba3..710e7f063a3 100644 --- a/test/unit/gateways/conekta_test.rb +++ b/test/unit/gateways/conekta_test.rb @@ -4,41 +4,41 @@ class ConektaTest < Test::Unit::TestCase include CommStub def setup - @gateway = ConektaGateway.new(:key => 'key_eYvWV7gSDkNYXsmr') + @gateway = ConektaGateway.new(key: 'key_eYvWV7gSDkNYXsmr') @amount = 300 @credit_card = ActiveMerchant::Billing::CreditCard.new( - :number => '4242424242424242', - :verification_value => '183', - :month => '01', - :year => '2018', - :first_name => 'Mario F.', - :last_name => 'Moreno Reyes' + number: '4242424242424242', + verification_value: '183', + month: '01', + year: '2018', + first_name: 'Mario F.', + last_name: 'Moreno Reyes' ) @declined_card = ActiveMerchant::Billing::CreditCard.new( - :number => '4000000000000002', - :verification_value => '183', - :month => '01', - :year => '2018', - :first_name => 'Mario F.', - :last_name => 'Moreno Reyes' + number: '4000000000000002', + verification_value: '183', + month: '01', + year: '2018', + first_name: 'Mario F.', + last_name: 'Moreno Reyes' ) @options = { - :device_fingerprint => '41l9l92hjco6cuekf0c7dq68v4', - :description => 'Blue clip', - :success_url => 'https://www.example.com/success', - :failure_url => 'https://www.example.com/failure', - :address1 => 'Rio Missisipi #123', - :address2 => 'Paris', - :city => 'Guerrero', - :country => 'Mexico', - :zip => '5555', - :customer => 'Mario Reyes', - :phone => '12345678', - :carrier => 'Estafeta' + device_fingerprint: '41l9l92hjco6cuekf0c7dq68v4', + description: 'Blue clip', + success_url: 'https://www.example.com/success', + failure_url: 'https://www.example.com/failure', + address1: 'Rio Missisipi #123', + address2: 'Paris', + city: 'Guerrero', + country: 'Mexico', + zip: '5555', + customer: 'Mario Reyes', + phone: '12345678', + carrier: 'Estafeta' } end @@ -69,8 +69,8 @@ def test_unsuccessful_purchase def test_successful_purchase_with_installments response = stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({monthly_installments: '3'})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ monthly_installments: '3' })) + end.check_request do |_method, _endpoint, data, _headers| assert_match %r{monthly_installments=3}, data end.respond_with(successful_purchase_response) @@ -139,7 +139,7 @@ def test_unsuccessful_capture end def test_invalid_key - gateway = ConektaGateway.new(:key => 'invalid_token') + gateway = ConektaGateway.new(key: 'invalid_token') gateway.expects(:ssl_request).returns(failed_login_response) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -154,8 +154,8 @@ def test_adds_application_and_meta_headers } response = stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, 'tok_xxxxxxxxxxxxxxxx', @options.merge(application: application, meta: {its_so_meta: 'even this acronym'})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, 'tok_xxxxxxxxxxxxxxxx', @options.merge(application: application, meta: { its_so_meta: 'even this acronym' })) + end.check_request do |_method, _endpoint, _data, headers| assert_match(/\"application\"/, headers['X-Conekta-Client-User-Agent']) assert_match(/\"name\":\"app\"/, headers['X-Conekta-Client-User-Agent']) assert_match(/\"version\":\"1.0\"/, headers['X-Conekta-Client-User-Agent']) diff --git a/test/unit/gateways/creditcall_test.rb b/test/unit/gateways/creditcall_test.rb index 146606ff742..9103162f6cf 100644 --- a/test/unit/gateways/creditcall_test.rb +++ b/test/unit/gateways/creditcall_test.rb @@ -112,7 +112,7 @@ def test_failed_verify def test_verification_value_sent stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(123)m, data) end.respond_with(successful_authorize_response) end @@ -121,7 +121,7 @@ def test_verification_value_not_sent @credit_card.verification_value = ' ' stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/CSC/, data) end.respond_with(successful_authorize_response) end @@ -129,25 +129,25 @@ def test_verification_value_not_sent def test_options_add_avs_additional_verification_fields stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/AdditionalVerification/, data) end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'false', verify_address: 'false')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/AdditionalVerification/, data) end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true', verify_address: 'true')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\n K1C2N6<\/Zip>\n
/, data) end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true', verify_address: 'false')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/ \n K1C2N6<\/Zip>\n <\/AdditionalVerification>\n/, data) end.respond_with(successful_authorize_response) end diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb index c93b3987470..625953f57f0 100644 --- a/test/unit/gateways/credorax_test.rb +++ b/test/unit/gateways/credorax_test.rb @@ -12,18 +12,75 @@ def setup billing_address: address, description: 'Store Purchase' } + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address, + shipping_address: address, + order_id: '123', + execute_threed: true, + three_ds_initiate: '03', + f23: '1', + three_ds_reqchallengeind: '04', + three_ds_challenge_window_size: '01', + stored_credential: { reason_type: 'unscheduled' }, + three_ds_2: { + channel: 'browser', + notification_url: 'www.example.com', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } + } + + @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 + klass = @gateway.class + assert_equal %i[visa master maestro american_express jcb discover diners_club], klass.supported_cardtypes end def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/i8=sample-eci%3Asample-cavv%3Asample-xid/, data) end.respond_with(successful_purchase_response) assert_success response assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert_equal 'Succeeded', response.message assert response.test? end @@ -47,11 +104,12 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/8a829449535154bc0153595952a2517a/, data) end.respond_with(successful_capture_response) assert_success capture + assert_equal 'Succeeded', response.message end def test_failed_authorize @@ -70,6 +128,7 @@ def test_failed_capture end.respond_with(failed_capture_response) assert_failure response + assert_equal '2. At least one of input parameters is malformed.: Parameter [g4] cannot be empty.', response.message end def test_successful_void @@ -82,21 +141,23 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/8a829449535154bc0153595952a2517a/, data) end.respond_with(successful_void_response) assert_success void + assert_equal 'Succeeded', void.message end def test_failed_void response = stub_comms do @gateway.void('5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) end.respond_with(failed_void_response) assert_failure response + assert_equal '2. At least one of input parameters is malformed.: Parameter [g4] cannot be empty.', response.message end def test_successful_refund @@ -109,10 +170,30 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/8a82944a5351570601535955efeb513c/, data) end.respond_with(successful_refund_response) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_successful_refund_with_recipient_fields + refund_options = { + recipient_street_address: 'street', + recipient_city: 'chicago', + recipient_province_code: '312', + recipient_country_code: 'US' + } + refund = stub_comms do + @gateway.refund(@amount, '123', refund_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/j6=street/, data) + assert_match(/j7=chicago/, data) + assert_match(/j8=312/, data) + assert_match(/j9=USA/, data) + end.respond_with(successful_refund_response) + assert_success refund end @@ -122,6 +203,43 @@ def test_failed_refund end.respond_with(failed_refund_response) assert_failure response + assert_equal '2. At least one of input parameters is malformed.: Parameter [g4] cannot be empty.', response.message + end + + def test_successful_referral_cft + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + + referral_cft = stub_comms do + @gateway.refund(@amount, response.authorization, { referral_cft: true, first_name: 'John', last_name: 'Smith' }) + end.check_request do |_endpoint, data, _headers| + assert_match(/8a82944a5351570601535955efeb513c/, data) + # Confirm that `j5` (first name) and `j13` (surname) parameters are present + # These fields are required for CFT payouts as of Sept 1, 2020 + assert_match(/j5=John/, data) + assert_match(/j13=Smith/, data) + # Confirm that the transaction type is `referral_cft` + assert_match(/O=34/, data) + end.respond_with(successful_referral_cft_response) + + assert_success referral_cft + assert_equal 'Succeeded', referral_cft.message + end + + def test_failed_referral_cft + response = stub_comms do + @gateway.refund(nil, '', referral_cft: true) + end.check_request do |_endpoint, data, _headers| + # Confirm that the transaction type is `referral_cft` + assert_match(/O=34/, data) + end.respond_with(failed_referral_cft_response) + + assert_failure response + assert_equal 'Referred to transaction has not been found.', response.message end def test_successful_credit @@ -132,9 +250,27 @@ def test_successful_credit assert_success response assert_equal '8a82944a53515706015359604c135301;;868f8b942fae639d28e27e8933d575d4;credit', response.authorization + assert_equal 'Succeeded', response.message assert response.test? end + def test_credit_sends_correct_action_code + stub_comms do + @gateway.credit(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_match(/O=35/, data) + end.respond_with(successful_credit_response) + end + + def test_credit_sends_customer_name + stub_comms do + @gateway.credit(@amount, @credit_card, { first_name: 'Test', last_name: 'McTest' }) + end.check_request do |_endpoint, data, _headers| + assert_match(/j5=Test/, data) + assert_match(/j13=McTest/, data) + end.respond_with(successful_credit_response) + end + def test_failed_credit response = stub_comms do @gateway.credit(@amount, @credit_card) @@ -174,13 +310,144 @@ def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end - def test_adds_3d_secure_fields - options_with_3ds = @options.merge({eci: 'sample-eci', cavv: 'sample-cavv', xid: 'sample-xid'}) + def test_adds_3d2_secure_fields + options_with_3ds = @normalized_3ds_2_options response = stub_comms do @gateway.purchase(@amount, @credit_card, options_with_3ds) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| + assert_match(/3ds_channel=02/, data) + assert_match(/3ds_transtype=01/, data) + assert_match(/3ds_initiate=03/, data) + assert_match(/f23=1/, data) + assert_match(/3ds_reqchallengeind=04/, data) + assert_match(/3ds_redirect_url=www.example.com/, data) + assert_match(/3ds_challengewindowsize=01/, data) + assert_match(/d5=unknown/, data) + assert_match(/3ds_browsertz=-120/, data) + assert_match(/3ds_browserscreenwidth=500/, data) + assert_match(/3ds_browserscreenheight=1000/, data) + assert_match(/3ds_browsercolordepth=100/, data) + assert_match(/d6=US/, data) + assert_match(/3ds_browserjavaenabled=false/, data) + assert_match(/3ds_browseracceptheader=unknown/, data) + assert_match(/3ds_shipaddrstate=ON/, data) + assert_match(/3ds_shipaddrpostcode=K1C2N6/, data) + assert_match(/3ds_shipaddrline2=Apt\+1/, data) + assert_match(/3ds_shipaddrline1=456\+My\+Street/, data) + assert_match(/3ds_shipaddrcountry=CA/, data) + assert_match(/3ds_shipaddrcity=Ottawa/, data) + refute_match(/3ds_version/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_does_not_add_incomplete_3d2_shipping_address + incomplete_shipping_address = { + state: 'ON', + zip: 'K1C2N6', + address1: '456 My Street', + address2: '', + country: 'CA', + city: 'Ottawa' + } + options_with_3ds = @normalized_3ds_2_options.merge(shipping_address: incomplete_shipping_address) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/3ds_initiate=03/, data) + assert_not_match(/3ds_shipaddrstate=/, data) + assert_not_match(/3ds_shipaddrpostcode=/, data) + assert_not_match(/3ds_shipaddrline1=/, data) + assert_not_match(/3ds_shipaddrline2=/, data) + assert_not_match(/3ds_shipaddrcountry=/, data) + assert_not_match(/3ds_shipaddrcity=/, data) + end.respond_with(successful_purchase_response) + assert_success response + assert response.test? + end + + def test_adds_correct_3ds_browsercolordepth_when_color_depth_is_30 + @normalized_3ds_2_options[:three_ds_2][:browser_info][:depth] = 30 + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @normalized_3ds_2_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/3ds_browsercolordepth=32/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_adds_3d2_secure_fields_with_3ds_transtype_specified + options_with_3ds = @normalized_3ds_2_options.merge(three_ds_transtype: '03') + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/3ds_channel=02/, data) + assert_match(/3ds_transtype=03/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_purchase_adds_3d_secure_fields + options_with_3ds = @options.merge({ eci: 'sample-eci', cavv: 'sample-cavv', xid: 'sample-xid', three_ds_version: '1.0.2' }) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| assert_match(/i8=sample-eci%3Asample-cavv%3Asample-xid/, data) + assert_match(/3ds_version=1.0&/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_purchase_adds_3d_secure_fields_via_normalized_hash + version = '1.0.2' + eci = 'sample-eci' + cavv = 'sample-cavv' + xid = 'sample-xid' + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + xid: xid + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/i8=#{eci}%3A#{cavv}%3A#{xid}/, data) + assert_match(/3ds_version=1.0&/, data) + end.respond_with(successful_purchase_response) + end + + def test_3ds_channel_field_set_by_stored_credential_initiator + options_with_3ds = @normalized_3ds_2_options.merge(stored_credential_options(:merchant, :unscheduled, id: 'abc123')) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/3ds_channel=03/, data) end.respond_with(successful_purchase_response) assert_success response @@ -189,12 +456,28 @@ def test_adds_3d_secure_fields assert response.test? end + def test_authorize_adds_3d_secure_fields + options_with_3ds = @options.merge({ eci: 'sample-eci', cavv: 'sample-cavv', xid: 'sample-xid' }) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/i8=sample-eci%3Asample-cavv%3Asample-xid/, data) + assert_match(/3ds_version=1.0/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;authorize', response.authorization + assert response.test? + end + def test_defaults_3d_secure_cavv_field_to_none_if_not_present - options_with_3ds = @options.merge({eci: 'sample-eci', xid: 'sample-xid'}) + options_with_3ds = @options.merge({ eci: 'sample-eci', xid: 'sample-xid' }) response = stub_comms do @gateway.purchase(@amount, @credit_card, options_with_3ds) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/i8=sample-eci%3Anone%3Asample-xid/, data) end.respond_with(successful_purchase_response) @@ -204,36 +487,622 @@ def test_defaults_3d_secure_cavv_field_to_none_if_not_present assert response.test? end - def test_adds_a9_field - options_with_3ds = @options.merge({transaction_type: '8'}) + def test_adds_3ds2_fields_via_normalized_hash + version = '2' + eci = '05' + cavv = '637574652070757070792026206b697474656e73' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/i8=#{eci}%3A#{cavv}%3Anone/, data) + assert_match(/3ds_version=2.0/, data) + assert_match(/3ds_dstrxid=#{ds_transaction_id}/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_default_cavv_when_omitted_from_normalized_hash + version = '2.2.0' + eci = '05' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: version, + eci: eci, + ds_transaction_id: ds_transaction_id + } + ) + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/i8=#{eci}%3Anone%3Anone/, data) + assert_match(/3ds_version=2.0/, data) + assert_match(/3ds_dstrxid=#{ds_transaction_id}/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_adds_a9_field + options_with_3ds = @options.merge({ transaction_type: '8' }) stub_comms do @gateway.purchase(@amount, @credit_card, options_with_3ds) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/a9=8/, data) end.respond_with(successful_purchase_response) end - def test_adds_submerchant_id + def test_authorize_adds_a9_field + options_with_3ds = @options.merge({ transaction_type: '8' }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + end.respond_with(successful_authorize_response) + end + + def test_credit_adds_a9_field + options_with_3ds = @options.merge({ transaction_type: '8' }) + stub_comms do + @gateway.credit(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + end.respond_with(successful_credit_response) + end + + def test_authorize_adds_authorization_details + options_with_auth_details = @options.merge({ authorization_type: '2', multiple_capture_count: '5' }) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_auth_details) + end.check_request do |_endpoint, data, _headers| + assert_match(/a10=2/, data) + assert_match(/a11=5/, data) + end.respond_with(successful_authorize_response) + end + + def test_purchase_adds_submerchant_id @options[:submerchant_id] = '12345' stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/h3=12345/, data) end.respond_with(successful_purchase_response) end - def test_supports_billing_descriptor + def test_adds_moto_a2_field + @options[:metadata] = { manual_entry: true } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a2=3/, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_adds_submerchant_id + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_authorize_response) + end + + def test_capture_adds_submerchant_id + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.capture(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_capture_response) + end + + def test_void_adds_submerchant_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.void(response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_void_response) + end + + def test_refund_adds_submerchant_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.refund(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_refund_response) + end + + def test_credit_adds_submerchant_id + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_credit_response) + end + + def test_purchase_adds_billing_descriptor @options[:billing_descriptor] = 'abcdefghijkl' stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/i2=abcdefghijkl/, data) end.respond_with(successful_purchase_response) end + def test_authorize_adds_billing_descriptor + @options[:billing_descriptor] = 'abcdefghijkl' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/i2=abcdefghijkl/, data) + end.respond_with(successful_authorize_response) + end + + def test_capture_adds_billing_descriptor + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:billing_descriptor] = 'abcdefghijkl' + stub_comms do + @gateway.capture(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/i2=abcdefghijkl/, data) + end.respond_with(successful_capture_response) + end + + def test_refund_adds_billing_descriptor + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + @options[:billing_descriptor] = 'abcdefghijkl' + stub_comms do + @gateway.refund(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/i2=abcdefghijkl/, data) + end.respond_with(successful_refund_response) + end + + def test_credit_adds_billing_descriptor + @options[:billing_descriptor] = 'abcdefghijkl' + stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/i2=abcdefghijkl/, data) + end.respond_with(successful_credit_response) + end + + def test_purchase_adds_processor_fields + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_adds_processor_fields + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_authorize_response) + end + + def test_capture_adds_processor_fields + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.capture(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_capture_response) + end + + def test_void_adds_processor_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.void(response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_void_response) + end + + def test_refund_adds_processor_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.refund(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_refund_response) + end + + def test_credit_adds_processor_fields + @options[:processor] = 'TEST' + @options[:processor_merchant_id] = '123' + stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/r1=TEST/, data) + assert_match(/r2=123/, data) + end.respond_with(successful_credit_response) + end + + def test_purchase_adds_echo_field + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_adds_echo_field + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_authorize_response) + end + + def test_capture_adds_echo_field + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.capture(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_capture_response) + end + + def test_void_adds_echo_field + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.void(response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_void_response) + end + + def test_refund_adds_echo_field + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.refund(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_refund_response) + end + + def test_credit_adds_echo_field + @options[:echo] = 'Echo Parameter' + stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/d2=Echo\+Parameter/, data) + end.respond_with(successful_credit_response) + end + + def test_purchase_omits_phone_when_nil + # purchase passes the phone number when provided + @options[:billing_address][:phone] = '555-444-3333' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/c2=555-444-3333/, data) + end.respond_with(successful_purchase_response) + + # purchase doesn't pass the phone number when nil + @options[:billing_address][:phone] = nil + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/c2=/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_omits_3ds_homephonecountry_when_phone_is_nil + # purchase passes 3ds_homephonecountry when it and phone number are provided + @options[:billing_address][:phone] = '555-444-3333' + @options[:three_ds_2] = { optional: { '3ds_homephonecountry': 'US' } } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/c2=555-444-3333/, data) + assert_match(/3ds_homephonecountry=US/, data) + end.respond_with(successful_purchase_response) + + # purchase doesn't pass 3ds_homephonecountry when phone number is nil + @options[:billing_address][:phone] = nil + @options[:three_ds_2] = { optional: { '3ds_homephonecountry': 'US' } } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/c2=/, data) + assert_not_match(/3ds_homephonecountry=/, data) + end.respond_with(successful_purchase_response) + end + + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_cit_used + options = stored_credential_options(:cardholder, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_initial + options = stored_credential_options(:merchant, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=1/, data) + end.respond_with(successful_authorize_response) + assert_match(/z50=abc123/, successful_authorize_response) + assert_success response + end + + def test_stored_credential_recurring_mit_used + options = stored_credential_options(:merchant, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=2/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_cit_initial + options = stored_credential_options(:cardholder, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_cit_used + options = stored_credential_options(:cardholder, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_mit_initial + options = stored_credential_options(:merchant, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_mit_used + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_initial + options = stored_credential_options(:cardholder, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_used + options = stored_credential_options(:cardholder, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=9/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_initial + options = stored_credential_options(:merchant, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_used + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=8/, data) + assert_match(/g6=abc123/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_purchase_with_stored_credential + options = stored_credential_options(:merchant, :recurring, id: 'abc123') + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=2/, data) + assert_match(/g6=abc123/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_add_transaction_type_overrides_stored_credential_option + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123').merge(transaction_type: '6') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/a9=6/, data) + 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: 'ISK')) + end.check_request do |_endpoint, data, _headers| + assert_match(/a4=2&a1=/, data) + end.respond_with(successful_authorize_response) + end + + def test_3ds_2_optional_fields_adds_fields_to_the_root_of_the_post + post = {} + options = { three_ds_2: { optional: { '3ds_optional_field_1': :a, '3ds_optional_field_2': :b } } } + + @gateway.add_3ds_2_optional_fields(post, options) + + assert_equal post, { '3ds_optional_field_1': :a, '3ds_optional_field_2': :b } + end + + def test_3ds_2_optional_fields_does_not_overwrite_fields + post = { '3ds_optional_field_1': :existing_value } + options = { three_ds_2: { optional: { '3ds_optional_field_1': :a, '3ds_optional_field_2': :b } } } + + @gateway.add_3ds_2_optional_fields(post, options) + + assert_equal post, { '3ds_optional_field_1': :existing_value, '3ds_optional_field_2': :b } + end + + def test_3ds_2_optional_fields_does_not_empty_fields + post = {} + options = { three_ds_2: { optional: { '3ds_optional_field_1': '', '3ds_optional_field_2': 'null', '3ds_optional_field_3': nil } } } + + @gateway.add_3ds_2_optional_fields(post, options) + + 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) + { + order_id: '#1001', + description: 'AM test', + currency: 'GBP', + customer: '123', + stored_credential: stored_credential(*args, id: id) + } + end + def successful_purchase_response 'M=SPREE978&O=1&T=03%2F09%2F2016+03%3A05%3A16&V=413&a1=02617cf5f02ccaed239b6521748298c5&a2=2&a4=100&a9=6&z1=8a82944a5351570601535955efeb513c&z13=606944188282&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006596&z5=0&z6=00&z9=X&K=057e123af2fba5a37b4df76a7cb5cfb6' end @@ -243,7 +1112,7 @@ def failed_purchase_response end def successful_authorize_response - 'M=SPREE978&O=2&T=03%2F09%2F2016+03%3A08%3A58&V=413&a1=90f7449d555f7bed0a2c5d780475f0bf&a2=2&a4=100&a9=6&z1=8a829449535154bc0153595952a2517a&z13=606944188284&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006597&z5=0&z6=00&z9=X&K=00effd2c80ab7ecd45b499c0bbea3d20' + 'M=SPREE978&O=2&T=03%2F09%2F2016+03%3A08%3A58&V=413&a1=90f7449d555f7bed0a2c5d780475f0bf&a2=2&a4=100&a9=6&z1=8a829449535154bc0153595952a2517a&z13=606944188284&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006597&z5=0&z6=00&z9=X&K=00effd2c80ab7ecd45b499c0bbea3d20z50=abc123' end def failed_authorize_response @@ -274,12 +1143,20 @@ def failed_refund_response 'M=SPREE978&O=5&T=03%2F09%2F2016+03%3A16%3A06&V=413&a1=c2b481deffe0e27bdef1439655260092&a2=2&a4=-&a5=EUR&b1=-&z1=1A-1&z2=-9&z3=2.+At+least+one+of+input+parameters+is+malformed.%3A+Parameter+%5Bg4%5D+cannot+be+empty.&K=c2f6112b40c61859d03684ac8e422766' end + def successful_referral_cft_response + 'M=SPREE978&O=34&T=11%2F15%2F2019+15%3A56%3A08&V=413&a1=e852c517da0ffb0cde45671b39165449&a2=2&a4=100&a9=9&b2=2&g2=XZZ72c3228fc3b58525STV56T7YMFAJB&z1=XZZ72e64209459e8C2BAMTBS65MCNGIF&z13=931924132623&z2=0&z3=Transaction+has+been+executed+successfully.&z33=CREDORAX&z34=59990010&z39=XZZ72e64209459e8C2BAMTBS65MCNGIF&z4=HOSTOK&z6=00&K=76f8a35c3357a7613d63438bd86c06d9' + end + + def failed_referral_cft_response + 'T=11%2F15%2F2019+17%3A17%3A45&a1=896ffaf13766fff647d863e8ab0a707c&z1=XZZ7246087744e7993DRONGBWN4RNFWJ&z2=-9&z3=Referred+to+transaction+has+not+been+found.' + end + def successful_credit_response - 'M=SPREE978&O=6&T=03%2F09%2F2016+03%3A16%3A35&V=413&a1=868f8b942fae639d28e27e8933d575d4&a2=2&a4=100&z1=8a82944a53515706015359604c135301&z13=606944188289&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z5=0&z6=00&K=51ba24f6ef3aa161f86e53c34c9616ac' + 'M=SPREE978&O=35&T=03%2F09%2F2016+03%3A16%3A35&V=413&a1=868f8b942fae639d28e27e8933d575d4&a2=2&a4=100&z1=8a82944a53515706015359604c135301&z13=606944188289&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z5=0&z6=00&K=51ba24f6ef3aa161f86e53c34c9616ac' end def failed_credit_response - 'M=SPREE978&O=6&T=03%2F09%2F2016+03%3A16%3A59&V=413&a1=ff28246cfc811b1c686a52d08d075d9c&a2=2&a4=100&z1=8a829449535154bc01535960a962524f&z13=606944188290&z15=100&z2=05&z3=Transaction+has+been+declined.&z5=0&z6=57&K=cf34816d5c25dc007ef3525505c4c610' + 'M=SPREE978&O=35&T=03%2F09%2F2016+03%3A16%3A59&V=413&a1=ff28246cfc811b1c686a52d08d075d9c&a2=2&a4=100&z1=8a829449535154bc01535960a962524f&z13=606944188290&z15=100&z2=05&z3=Transaction+has+been+declined.&z5=0&z6=57&K=cf34816d5c25dc007ef3525505c4c610' end def empty_purchase_response 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..f6ba1b40eba --- /dev/null +++ b/test/unit/gateways/cyber_source_rest_test.rb @@ -0,0 +1,585 @@ +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 + ) + @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' + } + @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_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') + assert_equal true, request.dig('processingInformation', 'authorizationOptions', 'initiator', 'merchantInitiatedTransaction', 'storedCredentialUsed') + 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', 'merchantInitiatedTransaction', 'storedCredentialUsed') + 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_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 + + 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 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 77602767222..0e67c44700c 100644 --- a/test/unit/gateways/cyber_source_test.rb +++ b/test/unit/gateways/cyber_source_test.rb @@ -8,49 +8,63 @@ def setup Base.mode = :test @gateway = CyberSourceGateway.new( - :login => 'l', - :password => 'p' + login: 'l', + password: 'p' ) @amount = 100 @customer_ip = '127.0.0.1' - @credit_card = credit_card('4111111111111111', :brand => 'visa') - @declined_card = credit_card('801111111111111', :brand => 'visa') + @credit_card = credit_card('4111111111111111', brand: 'visa') + @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) + @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 = { - :ip => @customer_ip, - :order_id => '1000', - :line_items => [ + ip: @customer_ip, + order_id: '1000', + line_items: [ { - :declared_value => @amount, - :quantity => 2, - :code => 'default', - :description => 'Giant Walrus', - :sku => 'WA323232323232323' - }, - { - :declared_value => @amount, - :quantity => 2, - :description => 'Marble Snowcone', - :sku => 'FAKE1232132113123' + declared_value: @amount, + quantity: 2, + code: 'default', + description: 'Giant Walrus', + sku: 'WA323232323232323', + tax_amount: '10', + national_tax: '5' } ], - :currency => 'USD' + currency: 'USD' } @subscription_options = { - :order_id => generate_unique_id, - :credit_card => @credit_card, - :setup_fee => 100, - :subscription => { - :frequency => 'weekly', - :start_date => Date.today.next_week, - :occurrences => 4, - :automatic_renew => true, - :amount => 100 + order_id: generate_unique_id, + credit_card: @credit_card, + setup_fee: 100, + subscription: { + frequency: 'weekly', + start_date: Date.today.next_week, + occurrences: 4, + automatic_renew: true, + amount: 100 } } + + @issuer_additional_data = 'PR25000000000011111111111112222222sk111111111111111111111111111' + + '1111111115555555222233101abcdefghijkl7777777777777777777777777promotionCde' end def test_successful_credit_card_purchase @@ -59,7 +73,51 @@ def test_successful_credit_card_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;;credit_card", response.authorization + assert response.test? + end + + 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, 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 + + def test_successful_purchase_with_purchase_totals_data + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(discount_management_indicator: 'T', purchase_tax_amount: 7.89, original_amount: 1.23, invoice_amount: 1.23)) + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+USD<\/currency>\s+T<\/discountManagementIndicator>\s+7.89<\/taxAmount>\s+1.00<\/grandTotalAmount>\s+1.23<\/originalAmount>\s+1.23<\/invoiceAmount>\s+<\/purchaseTotals>/m, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_authorize_with_national_tax_indicator + national_tax_indicator = 1 + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(national_tax_indicator: national_tax_indicator)) + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+#{national_tax_indicator}<\/nationalTaxIndicator>\s+<\/otherTax>/m, data) + end.respond_with(successful_authorization_response) + end + + def test_successful_authorize_with_cc_auth_service_fields + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(mobile_remote_payment_type: 'T')) + end.check_request do |_endpoint, data, _headers| + assert_match(/T<\/mobileRemotePaymentType>/, data) + end.respond_with(successful_authorization_response) + end + + def test_successful_credit_card_purchase_with_elo + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;;credit_card", response.authorization assert response.test? end @@ -72,19 +130,232 @@ def test_purchase_includes_customer_ip @gateway.purchase(@amount, @credit_card, @options) end + def test_purchase_includes_issuer_additional_data + stub_comms do + @gateway.purchase(100, @credit_card, order_id: '1', issuer_additional_data: @issuer_additional_data) + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_purchase_response) + end + def test_purchase_includes_mdd_fields stub_comms do @gateway.purchase(100, @credit_card, order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3') - end.check_request do |endpoint, data, headers| - assert_match(/field2>CustomValue2.*field3>CustomValue3CustomValue2181537<\/reconciliationID>/, data) + end.respond_with(successful_purchase_response) + end + + def test_merchant_description + stub_comms do + @gateway.authorize(100, @credit_card, merchant_descriptor_name: 'Test Name', merchant_descriptor_address1: '123 Main Dr', merchant_descriptor_locality: 'Durham') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*Test Name.*)m, data) + assert_match(%r(.*123 Main Dr.*)m, data) + assert_match(%r(.*Durham.*)m, data) + end.respond_with(successful_purchase_response) + end + + def test_allows_nil_values_in_billing_address + billing_address = { + address1: '123 Fourth St', + city: 'Fiveton', + state: '', + country: 'CA' + } + + stub_comms do + @gateway.authorize(100, @credit_card, billing_address: billing_address) + end.check_request do |_endpoint, data, _headers| + assert_nil billing_address[:zip] + assert_nil billing_address[:phone] + assert_match(%r(.*123 Fourth St.*)m, data) + assert_match(%r(.*Fiveton.*)m, data) + assert_match(%r(.*NC.*)m, data) + assert_match(%r(.*00000.*)m, data) + assert_match(%r(.*CA.*)m, data) + end.respond_with(successful_purchase_response) + end + + def test_uses_names_from_billing_address_if_present + name = 'Wesley Crusher' + + stub_comms do + @gateway.authorize(100, @credit_card, billing_address: { name: name }) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*Wesley.*)m, data) + assert_match(%r(.*Crusher.*)m, data) + end.respond_with(successful_purchase_response) + end + + def test_uses_names_from_shipping_address_if_present + name = 'Wesley Crusher' + + stub_comms do + @gateway.authorize(100, @credit_card, shipping_address: { name: name }) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*Wesley.*)m, data) + assert_match(%r(.*Crusher.*)m, data) + end.respond_with(successful_purchase_response) + end + + def test_uses_names_from_the_payment_method + stub_comms do + @gateway.authorize(100, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*#{@credit_card.first_name}.*)m, data) + assert_match(%r(.*#{@credit_card.last_name}.*)m, data) + assert_match(%r(.*#{@credit_card.first_name}.*)m, data) + assert_match(%r(.*#{@credit_card.last_name}.*)m, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_includes_invoice_header + stub_comms do + @gateway.purchase(100, @credit_card, merchant_descriptor: 'Spreedly', reference_data_code: '3A', invoice_number: '1234567') + end.check_request do |_endpoint, data, _headers| + assert_match(/Spreedly<\/merchantDescriptor>/, data) + assert_match(/3A<\/referenceDataCode>/, data) + assert_match(/1234567<\/invoiceNumber>/, data) + 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) + end.check_request do |_endpoint, data, _headers| + assert_match(/3<\/taxManagementIndicator>/, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_includes_issuer_additional_data + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', issuer_additional_data: @issuer_additional_data) + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_authorization_response) + end + def test_authorize_includes_mdd_fields stub_comms do @gateway.authorize(100, @credit_card, order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3') - end.check_request do |endpoint, data, headers| - assert_match(/field2>CustomValue2.*field3>CustomValue3CustomValue2181537<\/reconciliationID>/, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_commerce_indicator + stub_comms do + @gateway.authorize(100, @credit_card, commerce_indicator: 'internet') + end.check_request do |_endpoint, data, _headers| + assert_match(/internet<\/commerceIndicator>/m, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_installment_data + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', installment_total_count: 5, installment_plan_type: 1, first_installment_date: '300101', installment_total_amount: 5.05, installment_annual_interest_rate: 1.09, installment_grace_period_duration: 3) + end.check_request do |_endpoint, data, _headers| + assert_xml_valid_to_xsd(data) + assert_match(/\s+5<\/totalCount>\s+5.05<\/totalAmount>\s+1<\/planType>\s+300101<\/firstInstallmentDate>\s+1.09<\/annualInterestRate>\s+3<\/gracePeriodDuration>\s+<\/installment>/, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_less_installment_data + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', installment_grace_period_duration: 3) + end.check_request do |_endpoint, data, _headers| + assert_xml_valid_to_xsd(data) + assert_match(/\s+3<\/gracePeriodDuration>\s+<\/installment>/, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_customer_id + stub_comms do + @gateway.authorize(100, @credit_card, customer_id: '5afefb801188d70023b7debb') + end.check_request do |_endpoint, data, _headers| + assert_match(/5afefb801188d70023b7debb<\/customerID>/, data) + 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') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*123.*)m, data) + assert_not_match(%r(.*123.*)m, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_sales_slip_number + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', sales_slip_number: '123') + end.check_request do |_endpoint, data, _headers| + assert_match(/123<\/salesSlipNumber>/, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_airline_agent_code + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', airline_agent_code: '7Q') + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+7Q<\/agentCode>\s+<\/airlineData>/, data) + end.respond_with(successful_authorization_response) + end + + def test_bank_account_purchase_includes_sec_code + stub_comms do + @gateway.purchase(@amount, @check, order_id: '1', sec_code: 'WEB') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(.*WEB.*)m, data) end.respond_with(successful_authorization_response) end @@ -94,35 +365,46 @@ def test_successful_check_purchase assert response = @gateway.purchase(@amount, @check, @options) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;;check", response.authorization assert response.test? end def test_successful_pinless_debit_card_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:pinless_debit_card => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(pinless_debit_card: true)) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;;credit_card", response.authorization assert response.test? end def test_successful_credit_cart_purchase_single_request_ignore_avs - @gateway.expects(:ssl_post).with do |host, request_body| + @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, @credit_card, options) + 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) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge( - ignore_avs: true - )) + 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| + @gateway.expects(:ssl_post).with do |_host, request_body| assert_not_match %r'', request_body assert_not_match %r'', request_body true @@ -131,35 +413,123 @@ def test_successful_credit_cart_purchase_single_request_without_ignore_avs # globally ignored AVS for gateway instance: @gateway.options[:ignore_avs] = true - assert response = @gateway.purchase(@amount, @credit_card, @options.merge( - ignore_avs: false - )) + options = @options.merge(ignore_avs: false) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + + @gateway.expects(:ssl_post).with do |_host, request_body| + assert_not_match %r'', request_body + assert_not_match %r'', request_body + true + end.returns(successful_purchase_response) + + options = @options.merge(ignore_avs: 'false') + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response end def test_successful_credit_cart_purchase_single_request_ignore_ccv - @gateway.expects(:ssl_post).with do |host, request_body| + @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) + + 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) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge( - ignore_cvv: true - )) + options = @options.merge(ignore_cvv: true) + assert response = @gateway.purchase(@amount, @network_token, options) assert_success response end def test_successful_credit_cart_purchase_single_request_without_ignore_ccv - @gateway.expects(:ssl_post).with do |host, request_body| + @gateway.expects(:ssl_post).with do |_host, request_body| + assert_not_match %r'', request_body + assert_not_match %r'', request_body + true + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(ignore_cvv: false)) + assert_success response + + @gateway.expects(:ssl_post).with do |_host, request_body| assert_not_match %r'', request_body assert_not_match %r'', request_body 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 + + def test_successful_network_token_purchase_subsequent_auth_visa + @gateway.expects(:ssl_post).with do |_host, request_body| + assert_match %r'111111111100cryptogram', request_body + assert_match %r'vbv', request_body + assert_not_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, @network_token, options) assert_success response end @@ -198,6 +568,14 @@ def test_successful_auth_request assert response.test? end + def test_successful_auth_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + def test_successful_credit_card_tax_request @gateway.stubs(:ssl_post).returns(successful_tax_response) assert response = @gateway.calculate_tax(@credit_card, @options) @@ -206,6 +584,32 @@ def test_successful_credit_card_tax_request assert response.test? end + def test_successful_credit_card_tax_request_with_amounts + stub_comms do + @gateway.calculate_tax(@credit_card, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + REXML::XPath.each(doc, '//item') do |item| + request_item = @options[:line_items][item.attributes['id'].to_i] + assert_match(request_item[:tax_amount], item.get_elements('taxAmount')[0].text) + assert_match(request_item[:national_tax], item.get_elements('nationalTax')[0].text) + end + end.respond_with(successful_tax_response) + end + + def test_successful_credit_card_authorize_request_with_line_items + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + REXML::XPath.each(doc, '//item') do |item| + request_item = @options[:line_items][item.attributes['id'].to_i] + assert_match(request_item[:tax_amount], item.get_elements('taxAmount')[0].text) + assert_match(request_item[:national_tax], item.get_elements('nationalTax')[0].text) + end + end.respond_with(successful_tax_response) + end + def test_successful_credit_card_capture_request @gateway.stubs(:ssl_post).returns(successful_authorization_response, successful_capture_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -216,6 +620,58 @@ def test_successful_credit_card_capture_request assert response_capture.test? end + def test_capture_includes_local_tax_amount + stub_comms do + @gateway.capture(100, '1842651133440156177166', local_tax_amount: '0.17') + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+0.17<\/localTaxAmount>\s+<\/otherTax>/, data) + end.respond_with(successful_capture_response) + end + + def test_capture_includes_national_tax_amount + stub_comms do + @gateway.capture(100, '1842651133440156177166', national_tax_amount: '0.05') + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+0.05<\/nationalTaxAmount>\s+<\/otherTax>/, data) + end.respond_with(successful_capture_response) + end + + def test_capture_with_additional_tax_fields + stub_comms do + @gateway.capture(100, '1842651133440156177166', user_po: 'ABC123', taxable: true, national_tax_indicator: 1) + end.check_request do |_endpoint, data, _headers| + assert_match(/ABC123<\/userPO>/, data) + assert_match(/true<\/taxable>/, data) + assert_match(/1<\/nationalTaxIndicator>/, data) + end.respond_with(successful_capture_response) + end + + def test_capture_includes_gratuity_amount + stub_comms do + @gateway.capture(100, '1842651133440156177166', gratuity_amount: '3.05') + end.check_request do |_endpoint, data, _headers| + assert_match(/3.05<\/gratuityAmount>/, data) + end.respond_with(successful_capture_response) + end + + def test_successful_credit_card_capture_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response, successful_capture_response) + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert response.success? + assert response.test? + assert response_capture = @gateway.capture(@amount, response.authorization) + assert response_capture.success? + assert response_capture.test? + end + + def test_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| + assert_match(/CustomValue2 generate_unique_id) + assert response = @gateway.unstore(response.authorization, order_id: generate_unique_id) assert response.success? assert response.test? end @@ -270,7 +746,7 @@ def test_successful_credit_card_retrieve_request assert response = @gateway.store(@credit_card, @subscription_options) assert response.success? assert response.test? - assert response = @gateway.retrieve(response.authorization, :order_id => generate_unique_id) + assert response = @gateway.retrieve(response.authorization, order_id: generate_unique_id) assert response.success? assert response.test? end @@ -296,8 +772,36 @@ def test_successful_refund_request assert_success(@gateway.refund(@amount, response.authorization)) end - def test_successful_credit_request - @gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_credit_response) + def test_successful_refund_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_capture_response, successful_refund_response) + assert_success(response = @gateway.purchase(@amount, @elo_credit_card, @options)) + + assert_success(@gateway.refund(@amount, response.authorization)) + end + + def test_successful_credit_to_card_request + @gateway.stubs(:ssl_post).returns(successful_card_credit_response) + + assert_success(@gateway.credit(@amount, @credit_card, @options)) + end + + def test_successful_adjust_auth_request + @gateway.stubs(:ssl_post).returns(successful_incremental_auth_response) + assert_success(response = @gateway.authorize(@amount, @credit_card, @options)) + + assert_success(@gateway.adjust(@amount, response.authorization, @options)) + end + + def test_authorization_under_review_request + @gateway.stubs(:ssl_post).returns(authorization_review_response) + + assert_failure(response = @gateway.authorize(@amount, @credit_card, @options)) + assert response.fraud_review? + assert_equal(response.authorization, "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};authorize;100;USD;;") + end + + def test_successful_credit_to_subscription_request + @gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_subscription_credit_response) assert response = @gateway.store(@credit_card, @subscription_options) assert response.success? @@ -305,42 +809,124 @@ def test_successful_credit_request assert_success(@gateway.credit(@amount, response.authorization, @options)) end + def test_credit_includes_merchant_descriptor + stub_comms do + @gateway.credit(@amount, @credit_card, merchant_descriptor: 'Spreedly') + end.check_request do |_endpoint, data, _headers| + assert_match(/Spreedly<\/merchantDescriptor>/, data) + end.respond_with(successful_card_credit_response) + end + + def test_credit_includes_issuer_additional_data + stub_comms do + @gateway.credit(@amount, @credit_card, issuer_additional_data: @issuer_additional_data) + end.check_request do |_endpoint, data, _headers| + assert_match(/\s+#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_card_credit_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| + assert_match(/CustomValue2\s+#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_void_response) + end + + def test_void_includes_mdd_fields + authorization = '1000;1842651133440156177166;AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/;authorize;100;USD;' + + stub_comms do + @gateway.void(authorization, mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3') + end.check_request do |_endpoint, data, _headers| + assert_match(/CustomValue21.00<\/amount>), data end.respond_with(successful_update_subscription_response) end def test_successful_verify response = stub_comms(@gateway, :ssl_request) do - @gateway.verify(@credit_card, @options) + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response) + assert_success response + end + + def test_successful_verify_zero_amount_request + @options[:zero_amount_auth] = true + stub_comms(@gateway, :ssl_post) do + @gateway.verify(@credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data| + assert_match %r(0.00<\/grandTotalAmount>), data + end + end + + def test_successful_verify_request + stub_comms(@gateway, :ssl_post) do + @gateway.verify(@credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data| + assert_match %r(1.00<\/grandTotalAmount>), data + end + end + + def test_successful_verify_with_elo + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@elo_credit_card, @options) end.respond_with(successful_authorization_response) assert_success response end @@ -354,53 +940,40 @@ 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'\n 111111111100cryptogram\n vbv\n 111111111100cryptogram\n\n\n\n\n 1\n', 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 + assert_match %r'.+?'m, body end.respond_with(successful_purchase_response) assert_success response end def test_successful_auth_with_network_tokenization_for_mastercard - @gateway.expects(:ssl_post).with do |host, request_body| + @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 + assert_match %r'\n 111111111100cryptogram\n 2\n\n\n spa\n\n\n\n\n 1\n', request_body true end.returns(successful_purchase_response) - credit_card = network_tokenization_credit_card('5555555555554444', - :brand => 'mastercard', - :transaction_id => '123', - :eci => '05', - :payment_cryptogram => '111111111100cryptogram' + credit_card = network_tokenization_credit_card( + '5555555555554444', + brand: 'master', + transaction_id: '123', + eci: '05', + payment_cryptogram: '111111111100cryptogram' ) assert response = @gateway.authorize(@amount, credit_card, @options) @@ -408,25 +981,165 @@ def test_successful_auth_with_network_tokenization_for_mastercard end def test_successful_auth_with_network_tokenization_for_amex - @gateway.expects(:ssl_post).with do |host, request_body| + @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'\n MTExMTExMTExMTAwY3J5cHRvZ3I=\n\n aesk\n YW0=\n\n\n\n\n\n 1\n', request_body true end.returns(successful_purchase_response) - credit_card = network_tokenization_credit_card('378282246310005', - :brand => 'american_express', - :transaction_id => '123', - :eci => '05', - :payment_cryptogram => Base64.encode64('111111111100cryptogram') + credit_card = network_tokenization_credit_card( + '378282246310005', + brand: 'american_express', + transaction_id: '123', + eci: '05', + payment_cryptogram: Base64.encode64('111111111100cryptogram') ) assert response = @gateway.authorize(@amount, credit_card, @options) assert_success response end + def test_cof_first + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/\true/, data) + assert_not_match(/\true/, data) + assert_not_match(/\/, data) + assert_not_match(/\/, data) + assert_match(/\internet/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + + def test_cof_cit_auth + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: 'unscheduled', + initial_transaction: false, + network_transaction_id: '' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/\/, data) + assert_match(/\/, data) + assert_not_match(/\/, data) + assert_not_match(/\/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + + def test_cof_unscheduled_mit_auth + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'unscheduled', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/\/, data) + assert_match(/\true/, data) + assert_match(/\true/, data) + assert_match(/\016150703802094/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + + def test_cof_installment_mit_auth + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'installment', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/\/, data) + assert_not_match(/\/, data) + assert_match(/\true/, data) + assert_match(/\016150703802094/, data) + assert_match(/\install/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + + def test_cof_recurring_mit_auth + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'recurring', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/\/, data) + assert_not_match(/\/, data) + assert_match(/\true/, data) + assert_match(/\016150703802094/, data) + assert_match(/\recurring/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + + def test_cof_recurring_mit_purchase + @options[:stored_credential] = { + initiator: 'merchant', + reason_type: 'recurring', + initial_transaction: false, + network_transaction_id: '016150703802094' + } + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/\/, data) + assert_not_match(/\/, data) + assert_match(/\true/, data) + assert_match(/\016150703802094/, data) + assert_match(/\recurring/, data) + end.respond_with(successful_purchase_response) + assert response.success? + end + + def test_cof_first_with_overrides + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:stored_credential_overrides] = { + subsequent_auth: 'true', + subsequent_auth_first: 'false', + subsequent_auth_stored_credential: 'true', + subsequent_auth_transaction_id: '54321' + } + @options[:commerce_indicator] = 'internet' + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/\false/, data) + assert_match(/\true/, data) + assert_match(/\true/, data) + assert_match(/\54321/, data) + assert_match(/\internet/, data) + end.respond_with(successful_authorization_response) + assert response.success? + end + def test_nonfractional_currency_handling - @gateway.expects(:ssl_post).with do |host, request_body| + @gateway.expects(:ssl_post).with do |_host, request_body| assert_match %r(1), request_body assert_match %r(JPY), request_body true @@ -448,7 +1161,7 @@ def test_malformed_xml_handling def test_3ds_enroll_response purchase = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(payer_auth_enroll_service: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\/, data) end.respond_with(threedeesecure_purchase_response) @@ -461,7 +1174,7 @@ def test_3ds_enroll_response def test_3ds_validate_response validation = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(payer_auth_validate_service: true, pares: 'ABC123')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\/, data) assert_match(/\ABC123\<\/signedPARes\>/, data) end.respond_with(successful_threedeesecure_validate_response) @@ -469,6 +1182,320 @@ def test_3ds_validate_response assert_success validation end + def test_adds_3ds_brand_based_commerce_indicator + %w(visa maestro master american_express jcb discover diners_club).each do |brand| + @credit_card.brand = brand + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure: { cavv: 'anything but empty' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/commerceIndicator\>#{CyberSourceGateway::ECI_BRAND_MAPPING[brand.to_sym]}#{eci}/, data) + assert_match(/#{cavv}/, data) + assert_match(/#{version}/, data) + assert_match(/#{ds_transaction_id}/, data) + assert_match(/#{authentication_response_status}/, data) + assert_match(/#{cavv_algorithm}/, data) + assert_match(/#{commerce_indicator}/, data) + assert_match(/#{enrolled}/, data) + end.respond_with(successful_purchase_response) + end + + def test_does_not_add_3ds2_fields_via_normalized_hash_when_cavv_and_commerce_indicator_absent + options = options_with_normalized_3ds(cavv: nil, commerce_indicator: nil) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_, data, _| + assert_not_match(/#{options[:three_d_secure][:eci]}#{options[:three_d_secure][:cavv]}#{options[:three_d_secure][:version]}#{options[:three_d_secure][:ds_transaction_id]}#{options[:three_d_secure][:authentication_response_status]}#{options[:three_d_secure][:cavv_algorithm]}#{options[:three_d_secure][:enrolled]}#{options[:commerce_indicator]}#{options[:three_d_secure][:eci]}#{options[:three_d_secure][:version]}#{options[:three_d_secure][:ds_transaction_id]}#{options[:three_d_secure][:authentication_response_status]}#{options[:three_d_secure][:cavv_algorithm]}#{options[:three_d_secure][:enrolled]}#{options[:three_d_secure][:eci]}#{options[:three_d_secure][:version]}#{options[:three_d_secure][:ds_transaction_id]}#{options[:three_d_secure][:authentication_response_status]}#{options[:three_d_secure][:cavv_algorithm]}#{options[:three_d_secure][:enrolled]}#{options[:commerce_indicator]}#{options[:three_d_secure][:eci]}#{options[:three_d_secure][:cavv]}#{options[:three_d_secure][:version]}#{options[:three_d_secure][:ds_transaction_id]}#{options[:three_d_secure][:authentication_response_status]}#{options[:three_d_secure][:cavv_algorithm]}#{options[:three_d_secure][:enrolled]}#{eci}/, data) + assert_match(/#{cavv}/, data) + assert_match(/#{version}/, data) + assert_match(/#{ds_transaction_id}/, data) + assert_match(/#{cavv_algorithm}/, data) + assert_match(/#{commerce_indicator}/, data) + assert_match(/#{collection_indicator}/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_mastercard_3ds2_default_collection_indicator + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: '637574652070757070792026206b697474656e73', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 'vbv' + } + ) + + stub_comms do + @gateway.purchase(@amount, @master_credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/2/, data) + end.respond_with(successful_purchase_response) + end + + def test_send_xid_for_3ds_1_regardless_of_cc_brand + options_with_normalized_3ds = @options.merge( + three_d_secure: { + eci: '05', + cavv: '637574652070757070792026206b697474656e73', + xid: 'this-is-an-xid', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 'vbv' + } + ) + + stub_comms do + @gateway.purchase(@amount, @elo_credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/this-is-an-xid/, data) + end.respond_with(successful_purchase_response) + end + + def test_dont_send_cavv_as_xid_in_3ds2_for_mastercard + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: '637574652070757070792026206b697474656e73', + xid: 'this-is-an-xid', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 'vbv' + } + ) + + stub_comms do + @gateway.purchase(@amount, @master_credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/this-is-an-xid/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_cavv_as_xid_for_3ds2 + cavv = '637574652070757070792026206b697474656e73' + + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: cavv, + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 'vbv' + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/#{cavv}/, data) + end.respond_with(successful_purchase_response) + end + + def test_does_not_add_cavv_as_xid_if_xid_is_present + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: '2.0', + eci: '05', + cavv: '637574652070757070792026206b697474656e73', + xid: 'this-is-an-xid', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + cavv_algorithm: 'vbv' + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/this-is-an-xid/, data) + 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), 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 @@ -491,8 +1518,215 @@ def test_does_not_throw_on_invalid_xml assert_failure response end + def test_address_email_has_a_default_when_email_option_is_empty + stub_comms do + @gateway.authorize(100, @credit_card, email: '') + end.check_request do |_endpoint, data, _headers| + assert_match('null@cybersource.com', data) + end.respond_with(successful_capture_response) + end + + def test_country_code_sent_as_default_when_submitted_as_empty_string + stub_comms do + @gateway.authorize(100, @credit_card, billing_address: { country: '' }) + end.check_request do |_endpoint, data, _headers| + assert_match('US', data) + end.respond_with(successful_capture_response) + end + + def test_default_address_does_not_override_when_hash_keys_are_strings + stub_comms do + @gateway.authorize(100, @credit_card, billing_address: { + 'address1' => '221B Baker Street', + 'city' => 'London', + 'zip' => 'NW16XE', + 'country' => 'GB' + }) + end.check_request do |_endpoint, data, _headers| + assert_match('221B Baker Street', data) + assert_match('London', data) + assert_match('NW16XE', data) + assert_match('GB', data) + end.respond_with(successful_capture_response) + end + + def test_adds_application_id_as_partner_solution_id + partner_id = 'partner_id' + CyberSourceGateway.application_id = partner_id + + stub_comms do + @gateway.authorize(100, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match("#{partner_id}", data) + end.respond_with(successful_capture_response) + ensure + CyberSourceGateway.application_id = nil + end + + def test_partner_solution_id_position_follows_schema + partner_id = 'partner_id' + CyberSourceGateway.application_id = partner_id + + @options[:stored_credential] = { + initiator: 'cardholder', + reason_type: '', + initial_transaction: true, + network_transaction_id: '' + } + @options[:commerce_indicator] = 'internet' + + stub_comms do + @gateway.authorize(100, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match("\n#{partner_id}\ntrue\n\n", data) + end.respond_with(successful_capture_response) + ensure + CyberSourceGateway.application_id = nil + end + + def test_missing_field + @gateway.expects(:ssl_post).returns(missing_field_response) + + response = @gateway.purchase(@amount, credit_card, @options) + + assert_failure response + assert_equal 'c:billTo/c:country', response.params['missingField'] + end + + def test_invalid_field + @gateway.expects(:ssl_post).returns(invalid_field_response) + + response = @gateway.purchase(@amount, credit_card, @options) + + assert_failure response + 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) + + stub_comms do + @gateway.authorize(@amount, credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, body, _headers| + assert_xml_valid_to_xsd(body) + first_half = Base64.encode64(Base64.decode64(long_cryptogram)[0...20]) + second_half = Base64.encode64(Base64.decode64(long_cryptogram)[20...40]) + assert_match %r{#{first_half}}, body + assert_match %r{#{second_half}}, body + end + end + + def test_able_to_properly_handle_20bytes_cryptogram + credit_card = network_tokenization_credit_card('4111111111111111', brand: 'american_express', payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=') + + stub_comms do + @gateway.authorize(@amount, credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, body, _headers| + assert_xml_valid_to_xsd(body) + assert_match %r{#{credit_card.payment_cryptogram}\n}, body + assert_not_match %r{}, body + 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=') + + @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 + 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=') + + @gateway.authorize(100, credit_card, @options) + end + + 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 + 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 + def options_with_normalized_3ds( + cavv: '637574652070757070792026206b697474656e73', + commerce_indicator: 'commerce_indicator' + ) + xid = 'Y2FyZGluYWxjb21tZXJjZWF1dGg=' + authentication_response_status = 'Y' + cavv_algorithm = 2 + collection_indicator = 2 + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + eci = '05' + enrolled = 'Y' + version = '2.0' + @options.merge( + three_d_secure: { + version: version, + eci: eci, + xid: xid, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + cavv_algorithm: cavv_algorithm, + enrolled: enrolled, + authentication_response_status: authentication_response_status + }, + commerce_indicator: commerce_indicator, + collection_indicator: collection_indicator + ).compact + end + + def supported_cc_brand_without_inferred_commerce_indicator + (ActiveMerchant::Billing::CyberSourceGateway.supported_cardtypes - + ActiveMerchant::Billing::CyberSourceGateway::ECI_BRAND_MAPPING.keys).first + end + def pre_scrubbed <<-PRE_SCRUBBED opening connection to ics2wstest.ic3.com:443... @@ -542,26 +1776,26 @@ def post_scrubbed end def successful_purchase_response - <<-XML - - -2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00123456YYMM2008-01-15T21:42:03Z00U + <<~XML + + + 2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00123456YYMM2008-01-15T21:42:03Z00U XML end def successful_authorization_response - <<-XML - - -2007-07-12T18:31:53.838ZTEST111111111111842651133440156177166ACCEPT100AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/USD1001.00004542AI72007-07-12T18:31:53Z10023439130C40VZ2FB + <<~XML + + + 2007-07-12T18:31:53.838ZTEST111111111111842651133440156177166ACCEPT100AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/USD1001.00004542AI72007-07-12T18:31:53Z10023439130C40VZ2FB XML end def unsuccessful_authorization_response - <<-XML - - -2008-01-15T21:50:41.580Za1efca956703a2a5037178a8a28f73572004338415330008402434REJECT231Afvvj7KfIgU12gooCFE2/DanQIApt+G1OgTSA+R9PTnyhFTb0KRjgFY+ynyIFNdoKKAghwgx231 + <<~XML + + + 2008-01-15T21:50:41.580Za1efca956703a2a5037178a8a28f73572004338415330008402434REJECT231Afvvj7KfIgU12gooCFE2/DanQIApt+G1OgTSA+R9PTnyhFTb0KRjgFY+ynyIFNdoKKAghwgx231 XML end @@ -624,10 +1858,10 @@ def unsuccessful_authorization_response_with_reply end def successful_tax_response - <<-XML - - -2007-07-11T18:27:56.314ZTEST111111111111841784762620176127166ACCEPT100AMYJY9fl62i+vx2OEQYAx9zv/9UBZAAA5h5D1001.000Madison000WI0537170 + <<~XML + + + 2007-07-11T18:27:56.314ZTEST111111111111841784762620176127166ACCEPT100AMYJY9fl62i+vx2OEQYAx9zv/9UBZAAA5h5D1001.000Madison000WI0537170 XML end @@ -656,88 +1890,128 @@ def successful_delete_subscription_response end def successful_capture_response - <<-XML - 2007-07-17T17:15:32.642Ztest11111111111111111846925324700976124593ACCEPT100AP4JZB883WKS/34BEZAzMTE1OTI5MVQzWE0wQjEzBTUt3wbOAQUy3D7oDgMMmvQAnQglGBP1002007-07-17T17:15:32Z1.0031159291T3XM2B13 + <<~XML + 2007-07-17T17:15:32.642Ztest11111111111111111846925324700976124593ACCEPT100AP4JZB883WKS/34BEZAzMTE1OTI5MVQzWE0wQjEzBTUt3wbOAQUy3D7oDgMMmvQAnQglGBP1002007-07-17T17:15:32Z1.0031159291T3XM2B13 XML end def successful_refund_response - <<-XML - - -2008-01-21T16:00:38.927ZTEST111111111112009312387810008401927ACCEPT100Af/vj7OzPmut/eogHFCrBiwYsWTJy1r127CpCn0KdOgyTZnzKwVYCmzPmVgr9ID5H1WGTSTKuj0i30IE4+zsz2d/QNzwBwAACCPAUSD1002008-01-21T16:00:38Z1.00010112295WW70TBOPSSP2 + <<~XML + + + 2008-01-21T16:00:38.927ZTEST111111111112009312387810008401927ACCEPT100Af/vj7OzPmut/eogHFCrBiwYsWTJy1r127CpCn0KdOgyTZnzKwVYCmzPmVgr9ID5H1WGTSTKuj0i30IE4+zsz2d/QNzwBwAACCPAUSD1002008-01-21T16:00:38Z1.00010112295WW70TBOPSSP2 XML end - def successful_credit_response - <<-XML - - -2008-01-21T16:00:38.927ZTEST111111111112009312387810008401927ACCEPT100Af/vj7OzPmut/eogHFCrBiwYsWTJy1r127CpCn0KdOgyTZnzKwVYCmzPmVgr9ID5H1WGTSTKuj0i30IE4+zsz2d/QNzwBwAACCPAUSD1002012-09-28T16:59:25Z1.00010112295WW70TBOPSSP2 + def successful_incremental_auth_response + <<~XML + + + 2021-06-28T14:24:21.043Z26be3073f9e9d3ae20a1219085d66a316248902608336713204007ACCEPT120Axj37wSTUsDxxi1huMEn/6As2ZNHDlgybMGrJs2bOHLZg0YMWACojiSd78TSAAk3DxpJl6MV4IPICcmpYHjhsUsHHNVA1F6vUSD1200.00831000002021-06-28T14:24:21Z6248902605266689604010016153570198200A 118962!0100.00V1001BPDiscount: 0.10 ; Max Units: 20.002.00BP VISA Discount XML end - def successful_retrieve_subscription_response - <<-XML + def successful_card_credit_response + <<~XML + \n\n2019-05-16T20:25:05.234Z329b25a4540e05c731a4fb16112e4c725580383051126990804008ACCEPT100Ahj/7wSTLoNfMt0KyZQoGxDdm1ctGjlmo0/RdCA4BUafouhAdpAfJHYQyaSZbpAdvSeAnJl0GvmW6FZMoUAA/SE0USD1002019-05-16T20:25:05Z1.007359449300012345678901201234567 + XML + end + + def successful_subscription_credit_response + <<~XML - 2012-05-15T14:29:52.833Z0da9f4799515bfbfb85cbf6ab8839cde3370921927710176056428ACCEPT100AhjzbwSRbXng4q9oFCjYIAKb7zXE/n0gAQsQyaSZV0ekrf+AaAAA+Q2H100falsefalse411111XXXXXX1111092013001OttawaWidgets IncCAUSDsomeguy1232@fakeemail.net99991231JIMon-demandSMITHcredit card0K1C2N620120521ONCURRENT1234 My StreetApt 133709219062501760564280 + 2008-01-21T16:00:38.927ZTEST111111111112009312387810008401927ACCEPT100Af/vj7OzPmut/eogHFCrBiwYsWTJy1r127CpCn0KdOgyTZnzKwVYCmzPmVgr9ID5H1WGTSTKuj0i30IE4+zsz2d/QNzwBwAACCPAUSD1002012-09-28T16:59:25Z1.00010112295WW70TBOPSSP2 XML end - def successful_validate_pinless_debit_card + def successful_retrieve_subscription_response <<-XML - - -2013-05-13T13:52:57.159Z64270133684531771310176056442ACCEPT100AhijbwSRj3pM2QqPs2j0Ip+xoJXIsAMPYZNJMq6PSbs5ATAA6z421002013-05-13T13:52:57ZY + + + 2012-05-15T14:29:52.833Z0da9f4799515bfbfb85cbf6ab8839cde3370921927710176056428ACCEPT100AhjzbwSRbXng4q9oFCjYIAKb7zXE/n0gAQsQyaSZV0ekrf+AaAAA+Q2H100falsefalse411111XXXXXX1111092013001OttawaWidgets IncCAUSDsomeguy1232@fakeemail.net99991231JIMon-demandSMITHcredit card0K1C2N620120521ONCURRENT1234 My StreetApt 133709219062501760564280 XML end def successful_auth_reversal_response - <<-XML - - -2016-07-25T21:10:31.506Z296805293329eea14917a8d04c63a0c44694810311256262804010ACCEPT100Ahj//wSR/QMpn9U9RwRUIkG7Nm4cMm7KVRrS4tppCS5TonESgFLhgHRTp0gPkYP4ZNJMt0gO3pPFAnI/oGUyy27D1uIA+xVKUSD1001.001002016-07-25T21:10:31Z + <<~XML + + + 2016-07-25T21:10:31.506Z296805293329eea14917a8d04c63a0c44694810311256262804010ACCEPT100Ahj//wSR/QMpn9U9RwRUIkG7Nm4cMm7KVRrS4tppCS5TonESgFLhgHRTp0gPkYP4ZNJMt0gO3pPFAnI/oGUyy27D1uIA+xVKUSD1001.001002016-07-25T21:10:31Z XML end def successful_void_response - <<-XML - - -2016-07-25T20:50:50.583Zbb3b1bb530192c9dd20f121686c91c404694798504476543904007ACCEPT100Ahj//wSR/QLVu2z/GtIOIkG7Nm4bNW7KPRrRY0mvYS4YB0I7QFLgkgkAA0gAwfwyaSZbpAdvSeeBOR/QLVqII/qE+QAA3yVtUSD1002016-07-25T20:50:50Z1.00usd + <<~XML + + + 2016-07-25T20:50:50.583Zbb3b1bb530192c9dd20f121686c91c404694798504476543904007ACCEPT100Ahj//wSR/QLVu2z/GtIOIkG7Nm4bNW7KPRrRY0mvYS4YB0I7QFLgkgkAA0gAwfwyaSZbpAdvSeeBOR/QLVqII/qE+QAA3yVtUSD1002016-07-25T20:50:50Z1.00usd XML end def successful_nonfractional_authorization_response - <<-XML - - -2007-07-12T18:31:53.838ZTEST111111111111842651133440156177166ACCEPT100AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/JPY1001004542AI72007-07-12T18:31:53Z10023439130C40VZ2FB + <<~XML + + + 2007-07-12T18:31:53.838ZTEST111111111111842651133440156177166ACCEPT100AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/JPY1001004542AI72007-07-12T18:31:53Z10023439130C40VZ2FB + XML + end + + def authorization_review_response + <<~XML + + + 2007-07-12T18:31:53.838ZTEST111111111111842651133440156177166REVIEW480AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/USD1001.00004542AI72007-07-12T18:31:53Z10023439130C40VZ2FB XML end def malformed_xml_response - <<-XML - - -2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00123456YYMM2008-01-15T21:42:03Z00U

+ <<~XML + + + 2008-01-15T21:42:03.343Zb0a6cf9aa07f1a8495f89c364bbd6a9a2004333231260008401927ACCEPT100Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtTUSD1001.00123456YYMM2008-01-15T21:42:03Z00U

XML end def threedeesecure_purchase_response - <<-XML - - -2017-10-17T20:39:27.392Z1a5ba4804da54b384c6e8a2d8057ea995082727663166909004012REJECT475AhjzbwSTE4kEGDR65zjsGwFLjtwzsJ0gXLJx6Xb0ky3SA7ek8AYA/A17475https://0eafstag.cardinalcommerce.com/EAFService/jsp/v1/redirecteNpVUe9PwjAQ/d6/ghA/r2tBYMvRBEUFFEKQEP1Yu1Om7gfdJoy/3nZsgk2a3Lveu757B+utRhw/oyo0CphjlskPbIXBsC25TvuPD/lkc3xn2d2R6y+3LWA5WuFOwA/qLExiwRzX4UAbSEwLrbYyzgVItbuZLkS353HWA1pDAhHq6Vgw3ule9/pAT5BALCMUqnwznZJCKwRaZQiopIhzXYpB1wXaAAKF/hbbPE8zn9L9fu9cUB2VREBtAQF6FrQsbJSZOQ9hIF7Xs1KNg6dVZzXdxGk0f1nc4+eslMfREKitIBDIHAV3WZ+Z2+Ku3/F8bjRXeQIysmrEFeOOa0yoIYHUfjQ6Icbt02XGTFRojbFqRmoQATykSYymxlD+YjPDWfntxBqrcusg8wbmWGcrXNFD4w3z2IkfVkZRy6H13mi9YhP9W/0vhyyqPw==1198888YTJycDdLR3RIVnpmMXNFejJyazA=<AuthProof><Time>2017 Oct 17 20:39:27</Time><DSUrl>https://csrtestcustomer34.cardinalcommerce.com/merchantacsfrontend/vereq.jsp?acqid=CYBS</DSUrl><VEReqProof><Message id="a2rp7KGtHVzf1sEz2rk0"><VEReq><version>1.0.2</version><pan>XXXXXXXXXXXX0002</pan><Merchant><acqBIN>469216</acqBIN><merID>1234567</merID></Merchant><Browser><deviceCategory>0</deviceCategory></Browser></VEReq></Message></VEReqProof><VEResProof><Message id="a2rp7KGtHVzf1sEz2rk0"><VERes><version>1.0.2</version><CH><enrolled>Y</enrolled><acctID>1198888</acctID></CH><url>https://testcustomer34.cardinalcommerce.com/merchantacsfrontend/pareq.jsp?vaa=b&amp;gold=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</url><protocol>ThreeDSecure</protocol></VERes></Message></VEResProof></AuthProof>YENROLLED - XML + <<~XML + + + 2017-10-17T20:39:27.392Z1a5ba4804da54b384c6e8a2d8057ea995082727663166909004012REJECT475AhjzbwSTE4kEGDR65zjsGwFLjtwzsJ0gXLJx6Xb0ky3SA7ek8AYA/A17475https://0eafstag.cardinalcommerce.com/EAFService/jsp/v1/redirecteNpVUe9PwjAQ/d6/ghA/r2tBYMvRBEUFFEKQEP1Yu1Om7gfdJoy/3nZsgk2a3Lveu757B+utRhw/oyo0CphjlskPbIXBsC25TvuPD/lkc3xn2d2R6y+3LWA5WuFOwA/qLExiwRzX4UAbSEwLrbYyzgVItbuZLkS353HWA1pDAhHq6Vgw3ule9/pAT5BALCMUqnwznZJCKwRaZQiopIhzXYpB1wXaAAKF/hbbPE8zn9L9fu9cUB2VREBtAQF6FrQsbJSZOQ9hIF7Xs1KNg6dVZzXdxGk0f1nc4+eslMfREKitIBDIHAV3WZ+Z2+Ku3/F8bjRXeQIysmrEFeOOa0yoIYHUfjQ6Icbt02XGTFRojbFqRmoQATykSYymxlD+YjPDWfntxBqrcusg8wbmWGcrXNFD4w3z2IkfVkZRy6H13mi9YhP9W/0vhyyqPw==1198888YTJycDdLR3RIVnpmMXNFejJyazA=<AuthProof><Time>2017 Oct 17 20:39:27</Time><DSUrl>https://csrtestcustomer34.cardinalcommerce.com/merchantacsfrontend/vereq.jsp?acqid=CYBS</DSUrl><VEReqProof><Message id="a2rp7KGtHVzf1sEz2rk0"><VEReq><version>1.0.2</version><pan>XXXXXXXXXXXX0002</pan><Merchant><acqBIN>469216</acqBIN><merID>1234567</merID></Merchant><Browser><deviceCategory>0</deviceCategory></Browser></VEReq></Message></VEReqProof><VEResProof><Message id="a2rp7KGtHVzf1sEz2rk0"><VERes><version>1.0.2</version><CH><enrolled>Y</enrolled><acctID>1198888</acctID></CH><url>https://testcustomer34.cardinalcommerce.com/merchantacsfrontend/pareq.jsp?vaa=b&amp;gold=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</url><protocol>ThreeDSecure</protocol></VERes></Message></VEResProof></AuthProof>YENROLLED + XML end def successful_threedeesecure_validate_response - <<-XML - - -2018-05-01T14:28:36.773Z23751b5aeb076ea5940c5b656284bf6a5251849164756591904009ACCEPT100Ahj//wSTHLQMXdtQnQUJGxDds0bNnDRoo0+VcdXMBUafKuOrnpAuWT9zDJpJlukB29J4YBpMctAxd21CdBQkwQ3gUSD10012.02831000YY2018-05-01T14:28:36Z00ZLIU5GM27GBP0110322000000E10000200000000000000120205011428360272225A4C495535474D32374742503833313030303030000159004400103232415050524F56414C00223134573031363135303730333830323039344730363400065649435241201002018-05-01T14:28:36Z12.02764668441000SuccessAAABAWFlmQAAAABjRWWZEEFgFz+=2vbv0505S2R4eGtHbEZqbnozeGhBRHJ6QzA=Y + <<~XML + + + 2018-05-01T14:28:36.773Z23751b5aeb076ea5940c5b656284bf6a5251849164756591904009ACCEPT100Ahj//wSTHLQMXdtQnQUJGxDds0bNnDRoo0+VcdXMBUafKuOrnpAuWT9zDJpJlukB29J4YBpMctAxd21CdBQkwQ3gUSD10012.02831000YY2018-05-01T14:28:36Z00ZLIU5GM27GBP0110322000000E10000200000000000000120205011428360272225A4C495535474D32374742503833313030303030000159004400103232415050524F56414C00223134573031363135303730333830323039344730363400065649435241201002018-05-01T14:28:36Z12.02764668441000SuccessAAABAWFlmQAAAABjRWWZEEFgFz+=2vbv0505S2R4eGtHbEZqbnozeGhBRHJ6QzA=Y + XML + end + + def missing_field_response + <<~XML + + + 2019-09-05T01:02:20.132Z9y2A7XGxMSOUqppiEXkiN8T38Jj5676453399086696204061REJECT101c:billTo/c:countryAhjz7wSTM7ido1SNM4cdGwFRfPELvH+kE/QkEg+jLpJlXR6RuUgJMmZ3E7RqkaZw46AAniPV101 + XML + end + + def invalid_field_response + <<~XML + + + 2019-09-05T14:10:46.665Z5676926465076767004068REJECT102c:billTo/c:postalCodeAhjzbwSTM78uTleCsJWkEAJRqivRidukDssiQgRm0ky3SA7oegDUiwLm + + XML + end + + def cvv_mismatch_response + <<~XML + + + 2019-09-05T14:10:46.665Z5676926465076767004068REJECT230c:billTo/c:postalCodeAhjzbwSTM78uTleCsJWkEAJRqivRidukDssiQgRm0ky3SA7oegDUiwLm + XML end @@ -746,7 +2020,7 @@ def invalid_xml_response end def assert_xml_valid_to_xsd(data, root_element = '//s:Body/*') - schema_file = File.open("#{File.dirname(__FILE__)}/../../schema/cyber_source/CyberSourceTransaction_#{CyberSourceGateway::XSD_VERSION}.xsd") + schema_file = File.open("#{File.dirname(__FILE__)}/../../schema/cyber_source/CyberSourceTransaction_#{CyberSourceGateway::TEST_XSD_VERSION}.xsd") doc = Nokogiri::XML(data) root = Nokogiri::XML(doc.xpath(root_element).to_s) xsd = Nokogiri::XML::Schema(schema_file) diff --git a/test/unit/gateways/d_local_test.rb b/test/unit/gateways/d_local_test.rb index 744ba53cbc2..7bed8cc718a 100644 --- a/test/unit/gateways/d_local_test.rb +++ b/test/unit/gateways/d_local_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class DLocalTest < Test::Unit::TestCase + include CommStub + def setup @gateway = DLocalGateway.new(login: 'login', trans_key: 'password', secret_key: 'shhhhh_key') @credit_card = credit_card @@ -10,6 +12,18 @@ def setup order_id: '1', billing_address: address } + @three_ds_secure = { + version: '1.0', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + xid: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'true', + authentication_response_status: 'Y' + } + end + + def test_supported_countries + assert_equal %w[AR BD BO BR CL CM CN CO CR DO EC EG GH GT IN ID JP KE MY MX MA NG PA PY PE PH SN SV TH TR TZ UG UY VN ZA], DLocalGateway.supported_countries end def test_successful_purchase @@ -22,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) @@ -30,6 +52,115 @@ def test_failed_purchase assert_equal '300', response.error_code end + def test_purchase_with_installments + installments = '6' + installments_id = 'INS54434' + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(installments: installments, installments_id: installments_id)) + end.check_request do |_endpoint, data, _headers| + assert_equal installments, JSON.parse(data)['card']['installments'] + assert_equal installments_id, JSON.parse(data)['card']['installments_id'] + end.respond_with(successful_purchase_response) + end + + def test_purchase_with_network_tokens + 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| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + end.respond_with(successful_purchase_response) + 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=') + stub_comms do + @gateway.purchase(@amount, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + assert_equal 'SUBSCRIPTION', JSON.parse(data)['card']['stored_credential_type'] + end.respond_with(successful_purchase_response) + 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=') + stub_comms do + @gateway.purchase(@amount, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + assert_equal 'UNSCHEDULED_CARD_ON_FILE', JSON.parse(data)['card']['stored_credential_type'] + end.respond_with(successful_purchase_response) + end + + 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=') + stub_comms do + @gateway.purchase(@amount, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + assert_equal 'FIRST', JSON.parse(data)['card']['stored_credential_usage'] + end.respond_with(successful_purchase_response) + 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=') + stub_comms do + @gateway.purchase(@amount, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + assert_equal 'CARD_ON_FILE', JSON.parse(data)['card']['stored_credential_type'] + assert_equal 'USED', JSON.parse(data)['card']['stored_credential_usage'] + end.respond_with(successful_purchase_response) + 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=') + stub_comms do + @gateway.purchase(@amount, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', JSON.parse(data)['card']['cryptogram'] + assert_equal '4242424242424242', JSON.parse(data)['card']['network_token'] + assert_equal 'USED', JSON.parse(data)['card']['stored_credential_usage'] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_additional_data + additional_data = { 'submerchant' => { 'name' => 'socks' } } + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(additional_data: additional_data)) + end.check_request do |_endpoint, data, _headers| + assert_equal additional_data, JSON.parse(data)['additional_risk_data'] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_force_type + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(force_type: 'debit')) + end.check_request do |_endpoint, data, _headers| + assert_equal 'DEBIT', JSON.parse(data)['card']['force_type'] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_original_order_id + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(original_order_id: '123ABC')) + end.check_request do |_endpoint, data, _headers| + assert_equal '123ABC', JSON.parse(data)['original_order_id'] + end.respond_with(successful_purchase_response) + end + def test_successful_authorize @gateway.expects(:ssl_post).returns(successful_authorize_response) @@ -48,6 +179,71 @@ def test_successful_authorize_without_address assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization end + def test_passing_billing_address + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"state\":\"ON\"/, data) + assert_match(/"city\":\"Ottawa\"/, data) + assert_match(/"zip_code\":\"K1C2N6\"/, data) + assert_match(/"street\":\"My Street\"/, data) + assert_match(/"number\":\"456\"/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_incomplete_billing_address + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: address(address1: 'Just a Street'))) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"state\":\"ON\"/, data) + assert_match(/"city\":\"Ottawa\"/, data) + assert_match(/"zip_code\":\"K1C2N6\"/, data) + assert_match(/"street\":\"Just a Street\"/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_nil_address_1 + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: address(address1: nil))) + end.check_request do |_method, _endpoint, data, _headers| + refute_match(/"street\"/, data) + end.respond_with(successful_authorize_response) + end + + def test_successful_inquire_with_payment_id + stub_comms(@gateway, :ssl_request) do + @gateway.inquire('D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f', {}) + end.check_request do |_method, endpoint, data, _headers| + refute_match(/"https:\/\/sandbox.dlocal.com\/payments\/D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f\/status\/"/, endpoint) + refute_match(nil, data) + end.respond_with(successful_payment_status_response) + end + + def test_successful_inquire_with_order_id + stub_comms(@gateway, :ssl_request) do + @gateway.inquire(nil, { order_id: '62595c5db10fdf7b5d5bb3a16d130992' }) + end.check_request do |_method, endpoint, data, _headers| + refute_match(/"https:\/\/sandbox.dlocal.com\/orders\/62595c5db10fdf7b5d5bb3a16d130992\/"/, endpoint) + refute_match(nil, data) + end.respond_with(successful_orders_response) + end + + def test_passing_country_as_string + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"country\":\"CA\"/, data) + end.respond_with(successful_authorize_response) + end + + def test_invalid_country + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options.merge(billing_address: address(country: 'INVALID'))) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/\"country\":null/, data) + end.respond_with(successful_authorize_response) + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(failed_authorize_response) @@ -118,29 +314,20 @@ 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 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization - end - - def test_successful_verify_with_failed_void - @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, failed_void_response) + @gateway.expects(:ssl_post).returns(successful_verify_response) response = @gateway.verify(@credit_card, @options) assert_success response - assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization + assert_equal 'T-15104-bb204de6-2708-4398-955f-2b16cf633687', response.authorization end def test_failed_verify - @gateway.expects(:ssl_post).returns(failed_authorize_response) + @gateway.expects(:ssl_post).returns(failed_verify_response) response = @gateway.verify(@credit_card, @options) assert_failure response - assert_equal '309', response.error_code + assert_equal '315', response.error_code end def test_scrub @@ -148,6 +335,135 @@ def test_scrub assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end + def test_api_version_param_header + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, _data, headers| + assert_equal '2.1', headers['X-Version'] + end.respond_with(successful_purchase_response) + end + + def test_idempotency_header + options = @options.merge(idempotency_key: '12345') + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, _data, headers| + assert_equal '12345', headers['X-Idempotency-Key'] + end.respond_with(successful_purchase_response) + end + + def test_three_ds_v1_object_construction + post = {} + @options[:three_d_secure] = @three_ds_secure + + @gateway.send(:add_three_ds, post, @options) + + assert post[:three_dsecure] + ds_data = post[:three_dsecure] + ds_options = @options[:three_d_secure] + + assert_equal ds_options[:version], ds_data[:three_dsecure_version] + assert_equal ds_options[:cavv], ds_data[:cavv] + assert_equal ds_options[:eci], ds_data[:eci] + assert_equal ds_options[:xid], ds_data[:xid] + assert_equal nil, ds_data[:ds_transaction_id] + assert_equal 'Y', ds_data[:enrollment_response] + assert_equal ds_options[:authentication_response_status], ds_data[:authentication_response] + end + + def test_three_ds_v2_object_construction + post = {} + @three_ds_secure[:ds_transaction_id] = 'ODUzNTYzOTcwODU5NzY3Qw==' + @three_ds_secure[:version] = '2.2.0' + @options[:three_d_secure] = @three_ds_secure + + @gateway.send(:add_three_ds, post, @options) + + assert post[:three_dsecure] + ds_data = post[:three_dsecure] + ds_options = @options[:three_d_secure] + + assert_equal ds_options[:version], ds_data[:three_dsecure_version] + assert_equal ds_options[:cavv], ds_data[:cavv] + assert_equal ds_options[:eci], ds_data[:eci] + assert_equal nil, ds_data[:xid] + assert_equal ds_options[:ds_transaction_id], ds_data[:ds_transaction_id] + assert_equal 'Y', ds_data[:enrollment_response] + assert_equal ds_options[:authentication_response_status], ds_data[:authentication_response] + end + + def test_three_ds_version_validation + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_three_ds, post, @options) + resp = @gateway.send(:validate_three_ds_params, post[:three_dsecure]) + + assert_equal nil, resp + post[:three_dsecure][:three_dsecure_version] = '4.0' + resp = @gateway.send(:validate_three_ds_params, post[:three_dsecure]) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'ThreeDs version not supported', resp.params['three_ds_version'] + end + + def test_three_ds_enrollment_validation + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_three_ds, post, @options) + post[:three_dsecure][:enrollment_response] = 'P' + resp = @gateway.send(:validate_three_ds_params, post[:three_dsecure]) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'Enrollment value not supported', resp.params['enrollment'] + end + + def test_three_ds_auth_response_validation + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_three_ds, post, @options) + post[:three_dsecure][:authentication_response] = 'P' + resp = @gateway.send(:validate_three_ds_params, post[:three_dsecure]) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'Authentication response value not supported', resp.params['auth_response'] + end + + def test_purchase_with_three_ds + @options[:three_d_secure] = @three_ds_secure + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + three_ds_params = JSON.parse(data)['three_dsecure'] + assert_equal '1.0', three_ds_params['three_dsecure_version'] + assert_equal '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', three_ds_params['cavv'] + assert_equal '05', three_ds_params['eci'] + assert_equal 'ODUzNTYzOTcwODU5NzY3Qw==', three_ds_params['xid'] + assert_equal 'Y', three_ds_params['enrollment_response'] + assert_equal 'Y', three_ds_params['authentication_response'] + end.respond_with(successful_purchase_response) + end + + def test_unsuccessfully_purchase_with_wrong_three_ds_data + @three_ds_secure.delete(:version) + @options[:three_d_secure] = @three_ds_secure + resp = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'ThreeDs version not supported', resp.params['three_ds_version'] + 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 @@ -192,6 +508,10 @@ def successful_purchase_response '{"id":"D-15104-05b0ec0c-5a1e-470a-b342-eb5f20758ef7","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-993903e4-0b33-48fd-8d9b-99fd6c3f0d1a"},"created_date":"2018-12-06T20:20:41.000+0000","approved_date":"2018-12-06T20:20:42.000+0000","status":"PAID","status_detail":"The payment was paid","status_code":"200","order_id":"15940ef43d39331bc64f31341f8ccd93"}' end + def successful_purchase_with_installments_response + '{"id":"D-4-e2227981-8ec8-48fd-8e9a-19fedb08d73a","amount":1000,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Thiago Gabriel","expiration_month":10,"expiration_year":2040,"brand":"VI","last4":"1111"},"created_date":"2019-02-06T21:04:43.000+0000","approved_date":"2019-02-06T21:04:44.000+0000","status":"PAID","status_detail":"The payment was paid.","status_code":"200","order_id":"657434343","notification_url":"http://merchant.com/notifications"}' + end + def failed_purchase_response '{"id":"D-15104-c3027e67-21f8-4308-8c94-06c44ffcea67","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-529b0bb1-8b8a-42f4-b5e4-d358ffb2c978"},"created_date":"2018-12-06T20:22:40.000+0000","status":"REJECTED","status_detail":"The payment was rejected.","status_code":"300","order_id":"7aa5cd3200f287fbac51dcee32184260"}' end @@ -212,10 +532,26 @@ def failed_capture_response '{"code":4000,"message":"Payment not found"}' end + def successful_verify_response + '{"id":"T-15104-bb204de6-2708-4398-955f-2b16cf633687","amount":0,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen", "expiration_month":9, "expiration_year":2022, "brand":"VI", "last4":"1111", "verify":true},"three_dsecure":{},"created_date":"2021-11-05T19:54:34.000+0000","approved_date":"2021-11-05T19:54:35.000+0000","status":"VERIFIED","status_detail":"The payment was verified.","status_code":"700","order_id":"e3ec1f40e9cb06b2d9c61f35bd5115e9"}' + end + + def failed_verify_response + '{"id":"T-15104-585b4fb0-8fc5-4ae2-bb87-41218b744ca0","amount":0,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen", "expiration_month":9, "expiration_year":2022, "brand":"VI", "last4":"1111", "verify":true},"three_dsecure":{},"created_date":"2021-11-05T19:54:34.000+0000","status":"REJECTED","status_detail":"Invalid security code.","status_code":"315","order_id":"e013030bd5a7330a5d490247a9ca2bf47","description":"315"}' + end + def successful_refund_response '{"id":"REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570","payment_id":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f","status":"SUCCESS","currency":"BRL","created_date":"2018-12-06T20:28:37.000+0000","amount":1.00,"status_code":200,"status_detail":"The refund was paid","notification_url":"http://example.com","amount_refunded":1.00,"id_payment":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f"}' end + def successful_payment_status_response + '{"code":100,"message":"The payment is pending."}' + end + + def successful_orders_response + '{"order_id":"b809a1aa481b88aaa858144798da656d","payment_id":"T-15104-15f4044d-c4b1-4a38-9b47-bb8be126491d","currency":"BRL","amount":2.0,"created_date":"2022-09-19T13:16:22.000+0000","approved_date":"2022-09-19T13:16:22.000+0000","status":"PAID","status_detail":"The payment was paid.","status_code":"200"}' + end + # I can't invoke a pending response and there is no example in docs, so this response is speculative def pending_refund_response '{"id":"REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570","payment_id":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f","status":"PENDING","currency":"BRL","created_date":"2018-12-06T20:28:37.000+0000","amount":1.00,"status_code":100,"status_detail":"The refund is pending","notification_url":"http://example.com","amount_refunded":1.00,"id_payment":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f"}' diff --git a/test/unit/gateways/data_cash_test.rb b/test/unit/gateways/data_cash_test.rb index 2fb5ac40d10..313f5ea71ea 100644 --- a/test/unit/gateways/data_cash_test.rb +++ b/test/unit/gateways/data_cash_test.rb @@ -3,8 +3,8 @@ class DataCashTest < Test::Unit::TestCase def setup @gateway = DataCashGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @credit_card = credit_card('4242424242424242') @@ -12,19 +12,19 @@ def setup @amount = 100 @address = { - :name => 'Mark McBride', - :address1 => 'Flat 12/3', - :address2 => '45 Main Road', - :city => 'London', - :state => 'None', - :country => 'GBR', - :zip => 'A987AA', - :phone => '(555)555-5555' + name: 'Mark McBride', + address1: 'Flat 12/3', + address2: '45 Main Road', + city: 'London', + state: 'None', + country: 'GBR', + zip: 'A987AA', + phone: '(555)555-5555' } @options = { - :order_id => generate_unique_id, - :billing_address => @address + order_id: generate_unique_id, + billing_address: @address } end @@ -83,7 +83,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro ], DataCashGateway.supported_cardtypes + assert_equal %i[visa master american_express discover diners_club jcb maestro], DataCashGateway.supported_cardtypes end def test_purchase_with_missing_order_id_option @@ -96,7 +96,7 @@ def test_authorize_with_missing_order_id_option def test_purchase_does_not_raise_exception_with_missing_billing_address @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert @gateway.authorize(100, @credit_card, {:order_id => generate_unique_id }).is_a?(ActiveMerchant::Billing::Response) + assert @gateway.authorize(100, @credit_card, { order_id: generate_unique_id }).is_a?(ActiveMerchant::Billing::Response) end def test_continuous_authority_purchase_with_missing_continuous_authority_reference @@ -124,69 +124,69 @@ def test_capture_method_is_ecomm private def failed_purchase_response - <<-XML - - - NOT AUTHORISED - Mastercard - Japan - - 4500203037300784 - 85613a50952067796b1c6ab61c2cac - TEST - DECLINED - 7 - - + <<~XML + + + NOT AUTHORISED + Mastercard + Japan + + 4500203037300784 + 85613a50952067796b1c6ab61c2cac + TEST + DECLINED + 7 + + XML end def successful_purchase_response - <<-XML - - - - - notprovided - - matched - ACCEPTED - - notprovided - - 123456789 - Visa - United Kingdom - - 4400200050664928 - 2d24cc91284c1ed5c65d8821f1e752c7 - TEST - ACCEPTED - 1 - - + <<~XML + + + + + notprovided + + matched + ACCEPTED + + notprovided + + 123456789 + Visa + United Kingdom + + 4400200050664928 + 2d24cc91284c1ed5c65d8821f1e752c7 + TEST + ACCEPTED + 1 + + XML end def successful_purchase_using_continuous_authority_response - <<-XML - - - 123456789 - VISA Debit - United Kingdom - Barclays Bank PLC - - - Using account ref 4500203037301241. CONT_AUTH transaction complete - - 4400200050664928 - 3fc2b05ab38b70f0eb3a6b6d35c0de - TEST - ACCEPTED - 1 - - + <<~XML + + + 123456789 + VISA Debit + United Kingdom + Barclays Bank PLC + + + Using account ref 4500203037301241. CONT_AUTH transaction complete + + 4400200050664928 + 3fc2b05ab38b70f0eb3a6b6d35c0de + TEST + ACCEPTED + 1 + + XML end @@ -196,62 +196,62 @@ def test_transcript_scrubbing end def pre_scrub - <<-RAW -opening connection to testserver.datacash.com:443... -opened -starting SSL for testserver.datacash.com:443... -SSL established -<- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" -<- "\n\n \n 99626800\n 9YM3DjUa6\n \n \n \n auth\n \n 4539792100000003\n 03/20\n \n 444\n \n \n \n \n \n \n \n \n \n d36e05ce3604313963854fca858d11\n 1.98\n ecomm\n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" --> "Server: Apache\r\n" --> "Connection: close\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Content-Type: text/plain; charset=iso-8859-1\r\n" --> "\r\n" --> "559\r\n" -reading 1369 bytes... --> "" --> "\n\n \n \n \n notchecked\n \n matched\n ACCEPTED\n \n notchecked\n \n 698899\n VISA Debit\n United Kingdom\n Barclays Bank PLC\n 00\n Approved or completed successfully\n D4B1B0558173CAE56E87293F9E9E899C8002F7B6\n \n RBS\n 4700204504678897\n d36e05ce3604313963854fca858d11\n 99626800\n TEST\n ACCEPTED\n 1\n \n\n\n" -read 1369 bytes -reading 2 bytes... --> "" --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close + <<~RAW + opening connection to testserver.datacash.com:443... + opened + starting SSL for testserver.datacash.com:443... + SSL established + <- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" + <- "\n\n \n 99626800\n 9YM3DjUa6\n \n \n \n auth\n \n 4539792100000003\n 03/20\n \n 444\n \n \n \n \n \n \n \n \n \n d36e05ce3604313963854fca858d11\n 1.98\n ecomm\n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" + -> "Server: Apache\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: text/plain; charset=iso-8859-1\r\n" + -> "\r\n" + -> "559\r\n" + reading 1369 bytes... + -> "" + -> "\n\n \n \n \n notchecked\n \n matched\n ACCEPTED\n \n notchecked\n \n 698899\n VISA Debit\n United Kingdom\n Barclays Bank PLC\n 00\n Approved or completed successfully\n D4B1B0558173CAE56E87293F9E9E899C8002F7B6\n \n RBS\n 4700204504678897\n d36e05ce3604313963854fca858d11\n 99626800\n TEST\n ACCEPTED\n 1\n \n\n\n" + read 1369 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close RAW end def post_scrub - <<-SCRUBBED -opening connection to testserver.datacash.com:443... -opened -starting SSL for testserver.datacash.com:443... -SSL established -<- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" -<- "\n\n \n 99626800\n [FILTERED]\n \n \n \n auth\n \n [FILTERED]\n 03/20\n \n [FILTERED]\n \n \n \n \n \n \n \n \n \n d36e05ce3604313963854fca858d11\n 1.98\n ecomm\n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" --> "Server: Apache\r\n" --> "Connection: close\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Content-Type: text/plain; charset=iso-8859-1\r\n" --> "\r\n" --> "559\r\n" -reading 1369 bytes... --> "" --> "\n\n \n \n \n notchecked\n \n matched\n ACCEPTED\n \n notchecked\n \n 698899\n VISA Debit\n United Kingdom\n Barclays Bank PLC\n 00\n Approved or completed successfully\n D4B1B0558173CAE56E87293F9E9E899C8002F7B6\n \n RBS\n 4700204504678897\n d36e05ce3604313963854fca858d11\n 99626800\n TEST\n ACCEPTED\n 1\n \n\n\n" -read 1369 bytes -reading 2 bytes... --> "" --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close -SCRUBBED + <<~SCRUBBED + opening connection to testserver.datacash.com:443... + opened + starting SSL for testserver.datacash.com:443... + SSL established + <- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" + <- "\n\n \n 99626800\n [FILTERED]\n \n \n \n auth\n \n [FILTERED]\n 03/20\n \n [FILTERED]\n \n \n \n \n \n \n \n \n \n d36e05ce3604313963854fca858d11\n 1.98\n ecomm\n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" + -> "Server: Apache\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: text/plain; charset=iso-8859-1\r\n" + -> "\r\n" + -> "559\r\n" + reading 1369 bytes... + -> "" + -> "\n\n \n \n \n notchecked\n \n matched\n ACCEPTED\n \n notchecked\n \n 698899\n VISA Debit\n United Kingdom\n Barclays Bank PLC\n 00\n Approved or completed successfully\n D4B1B0558173CAE56E87293F9E9E899C8002F7B6\n \n RBS\n 4700204504678897\n d36e05ce3604313963854fca858d11\n 99626800\n TEST\n ACCEPTED\n 1\n \n\n\n" + read 1369 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + SCRUBBED end end diff --git a/test/unit/gateways/decidir_plus_test.rb b/test/unit/gateways/decidir_plus_test.rb new file mode 100644 index 00000000000..26a783a2984 --- /dev/null +++ b/test/unit/gateways/decidir_plus_test.rb @@ -0,0 +1,368 @@ +require 'test_helper' + +class DecidirPlusTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = DecidirPlusGateway.new(public_key: 'public_key', private_key: 'private_key') + @credit_card = credit_card + @payment_reference = '2bf7bffb-1257-4b45-8d42-42d090409b8a|448459' + @amount = 100 + + @options = { + billing_address: address, + description: 'Store Purchase' + } + + @sub_payments = [ + { + site_id: '04052018', + installments: 1, + amount: 1500 + }, + { + site_id: '04052018', + installments: 1, + amount: 1500 + } + ] + @fraud_detection = { + send_to_cs: false, + channel: 'Web', + dispatch_method: 'Store Pick Up', + csmdds: [ + { + code: 17, + description: 'Campo MDD17' + } + ] + } + end + + def test_successful_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, @options) + end.check_request do |_action, _endpoint, data, _headers| + assert_match(/2bf7bffb-1257-4b45-8d42-42d090409b8a/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_purchase_response) + + assert_failure response + end + + def test_failed_purchase_response + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_purchase_message_response) + + assert_failure response + assert_equal response.message, 'invalid_card: TRANS. NO PERMITIDA' + end + + def test_successful_capture + authorization = '12420186' + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, authorization) + end.check_request do |_action, endpoint, data, _headers| + request = JSON.parse(data) + assert_includes endpoint, "payments/#{authorization}" + assert_equal @amount, request['amount'] + end.respond_with(successful_purchase_response) + end + + def test_failed_authorize + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal response.error_code, response.params['status_details']['error']['reason']['id'] + end + + def test_successful_refund + response = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, @payment_reference) + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_failed_refund + response = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, @payment_reference) + end.respond_with(failed_purchase_response) + + assert_failure response + end + + def test_successful_store + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options) + end.check_request do |_action, _endpoint, data, _headers| + assert_match(/#{@credit_card.number}/, data) + assert_match(/#{@credit_card.name}/, data) + end.respond_with(successful_store_response) + + assert_success response + end + + def test_successful_store_with_additional_data_validation + options = { + card_holder_identification_type: 'dni', + card_holder_identification_number: '44567890', + card_holder_door_number: '348', + card_holder_birthday: '01012017' + } + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, options) + end.check_request do |_action, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal(options[:card_holder_identification_type], request['card_holder_identification']['type']) + assert_equal(options[:card_holder_identification_number], request['card_holder_identification']['number']) + assert_equal(options[:card_holder_door_number].to_i, request['card_holder_door_number']) + assert_equal(options[:card_holder_birthday], request['card_holder_birthday']) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_unstore + token_id = '3d5992f9-90f8-4ac4-94dd-6baa7306941f' + response = stub_comms(@gateway, :ssl_request) do + @gateway.unstore(token_id) + end.check_request do |_action, endpoint, data, _headers| + assert_includes endpoint, "cardtokens/#{token_id}" + assert_empty JSON.parse(data) + end.respond_with(successful_unstore_response) + + assert_success response + end + + def test_successful_purchase_with_options + options = @options.merge(sub_payments: @sub_payments) + options[:installments] = 4 + options[:payment_type] = 'distributed' + options[:debit] = 'true' + options[:card_brand] = 'visa_debit' + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, options) + end.check_request do |_action, _endpoint, data, _headers| + assert_equal(@sub_payments, JSON.parse(data, symbolize_names: true)[:sub_payments]) + assert_match(/#{options[:installments]}/, data) + assert_match(/#{options[:payment_type]}/, data) + assert_match(/\"payment_method_id\":31/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_fraud_detection + options = @options.merge(fraud_detection: @fraud_detection) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, options) + end.check_request do |_action, _endpoint, data, _headers| + assert_equal(@fraud_detection, JSON.parse(data, symbolize_names: true)[:fraud_detection]) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_establishment_name + establishment_name = 'Heavenly Buffaloes' + options = @options.merge(establishment_name: establishment_name) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, options) + end.check_request do |_action, _endpoint, data, _headers| + assert_equal(establishment_name, JSON.parse(data)['establishment_name']) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_aggregate_data + aggregate_data = { + indicator: '1', + identification_number: '308103480', + bill_to_pay: 'test1', + bill_to_refund: 'test2', + merchant_name: 'Heavenly Buffaloes', + street: 'Sesame', + number: '123', + postal_code: '22001', + category: 'yum', + channel: '005', + geographic_code: 'C1234', + city: 'Ciudad de Buenos Aires', + merchant_id: 'dec_agg', + province: 'Buenos Aires', + country: 'Argentina', + merchant_email: 'merchant@mail.com', + merchant_phone: '2678433111' + } + options = @options.merge(aggregate_data: aggregate_data) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, options) + end.check_request do |_action, _endpoint, data, _headers| + assert_equal(aggregate_data, JSON.parse(data, symbolize_names: true)[:aggregate_data]) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_fraud_detection_without_csmdds + @fraud_detection.delete(:csmdds) + options = @options.merge(fraud_detection: @fraud_detection) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @payment_reference, options) + end.check_request do |_action, _endpoint, data, _headers| + fraud_detection_fields = JSON.parse(data, symbolize_names: true)[:fraud_detection] + assert_equal(@fraud_detection, fraud_detection_fields) + assert_nil fraud_detection_fields[:csmdds] + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_void + authorization = '418943' + response = stub_comms(@gateway, :ssl_request) do + @gateway.void(authorization) + end.check_request do |_action, endpoint, data, _headers| + assert_includes endpoint, "payments/#{authorization}/refunds" + assert_equal '{}', data + end.respond_with(successful_void_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/tokens HTTP/1.1\r\nContent-Type: application/json\r\nApikey: 96e7f0d36a0648fb9a8dcb50ac06d260\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: developers.decidir.com\r\nContent-Length: 207\r\n\r\n" + <- "{\"card_number\":\"4484590159923090\",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"22\",\"security_code\":\"123\",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{\"type\":null,\"number\":null}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 342\r\n" + -> "Connection: close\r\n" + -> "Date: Wed, 15 Dec 2021 15:04:23 GMT\r\n" + -> "vary: Origin\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 42\r\n" + -> "X-Kong-Proxy-Latency: 2\r\n" + -> "Via: kong/2.0.5\r\n" + -> "Strict-Transport-Security: max-age=16070400; includeSubDomains\r\n" + -> "Set-Cookie: TS017a11a6=012e46d8ee3b62f63065925e2c71ee113cba96e0166c66ac2397184d6961bbe2cd1b41d64f6ee14cb9d440cf66a097465e0a31a786; Path=/; Domain=.developers.decidir.com\r\n" + -> "\r\n" + reading 342 bytes... + -> "{\"id\":\"2e416527-b757-47e1-80e1-51b2cb77092f\",\"status\":\"active\",\"card_number_length\":16,\"date_created\":\"2021-12-14T16:20Z\",\"bin\":\"448459\",\"last_four_digits\":\"3090\",\"security_code_length\":3,\"expiration_month\":9,\"expiration_year\":22,\"date_due\":\"2021-12-14T16:35Z\",\"cardholder\":{\"identification\":{\"type\":\"\",\"number\":\"\"},\"name\":\"Longbob Longsen\"}}" + read 342 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/tokens HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [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: developers.decidir.com\r\nContent-Length: 207\r\n\r\n" + <- "{\"card_number\":\"[FILTERED]",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"22\",\"security_code\":\"[FILTERED]",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{\"type\":null,\"number\":null}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 342\r\n" + -> "Connection: close\r\n" + -> "Date: Wed, 15 Dec 2021 15:04:23 GMT\r\n" + -> "vary: Origin\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 42\r\n" + -> "X-Kong-Proxy-Latency: 2\r\n" + -> "Via: kong/2.0.5\r\n" + -> "Strict-Transport-Security: max-age=16070400; includeSubDomains\r\n" + -> "Set-Cookie: TS017a11a6=012e46d8ee3b62f63065925e2c71ee113cba96e0166c66ac2397184d6961bbe2cd1b41d64f6ee14cb9d440cf66a097465e0a31a786; Path=/; Domain=.developers.decidir.com\r\n" + -> "\r\n" + reading 342 bytes... + -> "{\"id\":\"2e416527-b757-47e1-80e1-51b2cb77092f\",\"status\":\"active\",\"card_number_length\":16,\"date_created\":\"2021-12-14T16:20Z\",\"bin\":\"448459\",\"last_four_digits\":\"3090\",\"security_code_length\":3,\"expiration_month\":9,\"expiration_year\":22,\"date_due\":\"2021-12-14T16:35Z\",\"cardholder\":{\"identification\":{\"type\":\"\",\"number\":\"\"},\"name\":\"Longbob Longsen\"}}" + read 342 bytes + Conn close + ) + end + + def successful_store_response + %{ + {\"id\":\"cd4ba1c0-4b41-4c5c-8530-d0c757df8603\",\"status\":\"active\",\"card_number_length\":16,\"date_created\":\"2022-01-07T17:37Z\",\"bin\":\"448459\",\"last_four_digits\":\"3090\",\"security_code_length\":3,\"expiration_month\":9,\"expiration_year\":23,\"date_due\":\"2022-01-07T17:52Z\",\"cardholder\":{\"identification\":{\"type\":\"\",\"number\":\"\"},\"name\":\"Longbob Longsen\"}} + } + end + + def successful_unstore_response; end + + def successful_purchase_response + %{ + {\"id\":12232003,\"site_transaction_id\":\"d80cb4c7430b558cb9362b7bb89d2d38\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":100,\"currency\":\"ars\",\"status\":\"approved\",\"status_details\":{\"ticket\":\"4588\",\"card_authorization_code\":\"173710\",\"address_validation_code\":\"VTE0011\",\"error\":null},\"date\":\"2022-01-07T17:37Z\",\"customer\":null,\"bin\":\"448459\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"99999999\",\"fraud_detection\":null,\"aggregate_data\":null,\"establishment_name\":null,\"spv\":null,\"confirmed\":null,\"pan\":\"48d2eeca7a9041dc4b2008cf495bc5a8c4\",\"customer_token\":null,\"card_data\":\"/tokens/12232003\",\"token\":\"cd4ba1c0-4b41-4c5c-8530-d0c757df8603\"} + } + end + + def failed_purchase_response + %{ + {\"error_type\":\"invalid_request_error\",\"validation_errors\":[{\"code\":\"invalid_param\",\"param\":\"site_transaction_id\"}]} + } + end + + def failed_purchase_message_response + %{ + {\"id\":552537664,\"site_transaction_id\":\"9a59630fd51de97fbd8390adf796c683\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":74416,\"currency\":\"ars\",\"status\":\"rejected\",\"status_details\":{\"ticket\":\"2\",\"card_authorization_code\":\"\",\"address_validation_code\":\"VTE0000\",\"error\":{\"type\":\"invalid_card\",\"reason\":{\"id\":57,\"description\":\"TRANS. NO PERMITIDA\",\"additional_description\":\"\"}}},\"date\":\"2022-02-07T10:16Z\",\"customer\":null,\"bin\":\"466057\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"92003011\",\"fraud_detection\":{\"status\":null},\"aggregate_data\":null,\"establishment_name\":null,\"spv\":null,\"confirmed\":null,\"pan\":null,\"customer_token\":null,\"card_data\":\"/tokens/552537664\",\"token\":\"ea1bde57-5bdf-4f58-8586-df45c4359664\"} + } + end + + def successful_refund_response + %{ + {\"id\":417921,\"amount\":100,\"sub_payments\":null,\"error\":null,\"status\":\"approved\",\"status_details\":{\"ticket\":\"4589\",\"card_authorization_code\":\"173711\",\"address_validation_code\":\"VTE0011\",\"error\":null}} + } + end + + def failed_refund_response + %{ + {\"error_type\":\"not_found_error\",\"entity_name\":\"\",\"id\":\"\"} + } + end + + def failed_authorize_response + %{ + {\"id\": 12516088, \"site_transaction_id\": \"e77f40284fd5e0fba8e8ef7d1b784c5e\", \"payment_method_id\": 1, \"card_brand\": \"Visa\", \"amount\": 100, \"currency\": \"ars\", \"status\": \"rejected\", \"status_details\": { \"ticket\": \"393\", \"card_authorization_code\": \"\", \"address_validation_code\": \"VTE0000\", \"error\": { \"type\": \"invalid_card\", \"reason\": { \"id\": 3, \"description\": \"COMERCIO INVALIDO\", \"additional_description\": \"\" } } }, \"date\": \"2022-03-21T13:08Z\", \"customer\": null, \"bin\": \"400030\", \"installments\": 1, \"first_installment_expiration_date\": null, \"payment_type\": \"single\", \"sub_payments\": [], \"site_id\": \"92002480\", \"fraud_detection\": { \"status\": null }, \"aggregate_data\": null, \"establishment_name\": null, \"spv\": null, \"confirmed\": null, \"pan\": null, \"customer_token\": null, \"card_data\": \"/tokens/12516088\", \"token\": \"058972ba-4235-4452-bfdf-fc9f61e2c0f9\"} + } + end + + def successful_void_response + %{ + {"id":418966,"amount":100,"sub_payments":null,"error":null,"status":"approved","status_details":{"ticket":"4630","card_authorization_code":"074206","address_validation_code":"VTE0011","error":null}} + } + end + + def successful_verify_response + %{ + {"id":12421487,"site_transaction_id":"e6936a3fbc65cfa1fded1e84d4bbeaf9","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"approved","status_details":{"ticket":"4747","card_authorization_code":"094329","address_validation_code":"VTE0011","error":null},"date":"2022-01-20T09:43Z","customer":null,"bin":"448459","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999999","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"48d2eeca7a9041dc4b2008cf495bc5a8c4","customer_token":null,"card_data":"/tokens/12421487","token":"a36cadd5-5b06-41f5-972d-fffd524e2a35"} + } + end +end diff --git a/test/unit/gateways/decidir_test.rb b/test/unit/gateways/decidir_test.rb new file mode 100644 index 00000000000..3c82aada301 --- /dev/null +++ b/test/unit/gateways/decidir_test.rb @@ -0,0 +1,699 @@ +require 'test_helper' + +class DecidirTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway_for_purchase = DecidirGateway.new(api_key: 'api_key') + @gateway_for_auth = DecidirGateway.new(api_key: 'api_key', preauth_mode: true) + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + @fraud_detection = { + send_to_cs: false, + channel: 'Web', + dispatch_method: 'Store Pick Up', + csmdds: [ + { + code: 17, + description: 'Campo MDD17' + } + ], + device_unique_id: '111' + } + @sub_payments = [ + { + site_id: '04052018', + installments: 1, + amount: 1500 + }, + { + site_id: '04052018', + installments: 1, + amount: 1500 + } + ] + + @network_token = network_tokenization_credit_card( + '4012001037141112', + brand: 'visa', + eci: '05', + payment_cryptogram: '000203016912340000000FA08400317500000000' + ) + end + + def test_successful_purchase + @gateway_for_purchase.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 7719132, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_purchase_with_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com', + card_holder_door_number: '1234', + card_holder_birthday: '01011980', + card_holder_identification_type: 'dni', + card_holder_identification_number: '123456', + establishment_name: 'Heavenly Buffaloes', + installments: 12, + site_id: '99999999' + } + + response = stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(options)) + end.check_request do |_method, _endpoint, data, _headers| + assert data =~ /"card_holder_door_number":1234/ + assert data =~ /"card_holder_birthday":"01011980"/ + assert data =~ /"type":"dni"/ + assert data =~ /"number":"123456"/ + assert data =~ /"establishment_name":"Heavenly Buffaloes"/ + assert data =~ /"site_id":"99999999"/ + end.respond_with(successful_purchase_response) + + assert_equal 7719132, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_purchase_with_aggregate_data + options = { + aggregate_data: { + indicator: 1, + identification_number: '308103480', + bill_to_pay: 'test1', + bill_to_refund: 'test2', + merchant_name: 'Heavenly Buffaloes', + street: 'Sesame', + number: '123', + postal_code: '22001', + category: 'yum', + channel: '005', + geographic_code: 'C1234', + city: 'Ciudad de Buenos Aires', + merchant_id: 'dec_agg', + province: 'Buenos Aires', + country: 'Argentina', + merchant_email: 'merchant@mail.com', + merchant_phone: '2678433111' + } + } + + response = stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(options)) + end.check_request do |_method, _endpoint, data, _headers| + assert data =~ /"aggregate_data":{"indicator":1/ + assert data =~ /"identification_number":"308103480"/ + assert data =~ /"bill_to_pay":"test1"/ + assert data =~ /"bill_to_refund":"test2"/ + assert data =~ /"merchant_name":"Heavenly Buffaloes"/ + assert data =~ /"street":"Sesame"/ + assert data =~ /"number":"123"/ + assert data =~ /"postal_code":"22001"/ + assert data =~ /"category":"yum"/ + assert data =~ /"channel":"005"/ + assert data =~ /"geographic_code":"C1234"/ + assert data =~ /"city":"Ciudad de Buenos Aires"/ + assert data =~ /"merchant_id":"dec_agg"/ + assert data =~ /"province":"Buenos Aires"/ + assert data =~ /"country":"Argentina"/ + assert data =~ /"merchant_email":"merchant@mail.com"/ + assert data =~ /"merchant_phone":"2678433111"/ + end.respond_with(successful_purchase_response) + + assert_equal 7719132, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_purchase_with_fraud_detection + options = @options.merge(fraud_detection: @fraud_detection) + + 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_equal(@fraud_detection, JSON.parse(data, symbolize_names: true)[:fraud_detection]) + assert_match(/device_unique_identifier/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_sub_payments + options = @options.merge(sub_payments: @sub_payments) + options[:installments] = 4 + options[:payment_type] = 'distributed' + + 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_equal(@sub_payments, JSON.parse(data, symbolize_names: true)[:sub_payments]) + assert_match(/#{options[:installments]}/, data) + assert_match(/#{options[:payment_type]}/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + @gateway_for_purchase.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'TARJETA INVALIDA | invalid_number', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_failed_purchase_with_invalid_field + @gateway_for_purchase.expects(:ssl_request).returns(failed_purchase_with_invalid_field_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(installments: -1)) + assert_failure response + assert_equal 'invalid_param: installments', response.message + assert_match 'invalid_request_error', response.error_code + end + + def test_failed_purchase_with_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_auth.purchase(@amount, @credit_card, @options) + end + end + + def test_failed_purchase_error_response + @gateway_for_purchase.expects(:ssl_request).returns(unique_purchase_error_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'invalid_request_error | invalid_param | payment_type', response.error_code + end + + def test_failed_purchase_error_response_with_error_code + @gateway_for_purchase.expects(:ssl_request).returns(error_response_with_error_code) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match '14, invalid_number', response.error_code + end + + def test_successful_authorize + @gateway_for_auth.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 7720214, response.authorization + assert_equal 'pre_approved', response.message + assert response.test? + end + + def test_failed_authorize + @gateway_for_auth.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_failure response + + assert_equal 7719358, response.authorization + assert_equal 'TARJETA INVALIDA | invalid_number', response.message + assert response.test? + end + + def test_failed_authorize_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.authorize(@amount, @credit_card, @options) + end + end + + def test_successful_capture + @gateway_for_auth.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway_for_auth.capture(@amount, 7720214) + assert_success response + + assert_equal 7720214, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_partial_capture + @gateway_for_auth.expects(:ssl_request).returns(failed_partial_capture_response) + + response = @gateway_for_auth.capture(@amount, '') + assert_failure response + + assert_nil response.authorization + assert_equal 'amount: Amount out of ranges: 100 - 100', response.message + assert_equal 'invalid_request_error', response.error_code + assert response.test? + end + + def test_failed_capture + @gateway_for_auth.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway_for_auth.capture(@amount, '') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_failed_capture_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.capture(@amount, @credit_card, @options) + end + end + + def test_successful_refund + @gateway_for_purchase.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway_for_purchase.refund(@amount, 81931, @options) + assert_success response + + assert_equal 81931, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_partial_refund + @gateway_for_purchase.expects(:ssl_request).returns(partial_refund_response) + + response = @gateway_for_purchase.refund(@amount - 1, 81932, @options) + assert_success response + + assert_equal 81932, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_refund + @gateway_for_purchase.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway_for_purchase.refund(@amount, '') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_successful_void + @gateway_for_auth.expects(:ssl_request).returns(successful_void_response) + + response = @gateway_for_auth.void(@amount, '') + assert_success response + + assert_equal 82814, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_void + @gateway_for_auth.expects(:ssl_request).returns(failed_void_response) + + response = @gateway_for_auth.void('') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_successful_verify + @gateway_for_auth.expects(:ssl_request).at_most(3).returns(successful_void_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway_for_auth.expects(:ssl_request).at_most(3).returns(failed_void_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_failure response + + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_successful_verify_with_failed_void_unique_error_message + @gateway_for_auth.expects(:ssl_request).at_most(3).returns(unique_void_error_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_failure response + + assert_equal 'invalid_status_error - status: refunded', response.message + assert response.test? + end + + def test_failed_verify + @gateway_for_auth.expects(:ssl_request).at_most(2).returns(failed_authorize_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_failure response + + assert_equal 'TARJETA INVALIDA | invalid_number', response.message + assert response.test? + end + + def test_failed_verify_for_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.verify(@amount, @credit_card, @options) + end + end + + def test_successful_inquire_with_authorization + @gateway_for_purchase.expects(:ssl_request).returns(successful_inquire_response) + response = @gateway_for_purchase.inquire('818423490') + assert_success response + + assert_equal 544453, response.authorization + assert_equal 'rejected', response.message + 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 + end + + def test_payment_method_id_with_visa + post = {} + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, @credit_card, @options) + assert_equal 1, post[:payment_method_id] + end + + def test_payment_method_id_with_mastercard + post = {} + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, credit_card('5299910010000015'), @options) + assert_equal 104, post[:payment_method_id] + end + + def test_payment_method_id_with_amex + post = {} + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, credit_card('373953192351004'), @options) + assert_equal 65, post[:payment_method_id] + end + + def test_payment_method_id_with_diners + post = {} + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, credit_card('36463664750005'), @options) + assert_equal 8, post[:payment_method_id] + end + + def test_payment_method_id_with_cabal + post = {} + credit_card = credit_card('5896570000000008') + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, credit_card, @options) + assert_equal 63, post[:payment_method_id] + end + + def test_payment_method_id_with_naranja + post = {} + credit_card = credit_card('5895627823453005') + @gateway_for_purchase.send(:add_auth_purchase_params, post, @amount, credit_card, @options) + assert_equal 24, post[:payment_method_id] + end + + def test_payment_method_id_with_visa_debit + visa_debit_card = credit_card('4517721004856075') + debit_options = @options.merge(debit: true) + + stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, visa_debit_card, debit_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"payment_method_id":31/, data) + end.respond_with(successful_purchase_response) + end + + def test_payment_method_id_with_mastercard_debit + # currently lacking a valid MasterCard debit card number, so using the MasterCard credit card number + mastercard = credit_card('5299910010000015') + debit_options = @options.merge(debit: true) + + stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, mastercard, debit_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"payment_method_id":105/, data) + end.respond_with(successful_purchase_response) + end + + def test_payment_method_id_with_maestro_debit + # currently lacking a valid Maestro debit card number, so using a generated test card number + maestro_card = credit_card('6759649826438453') + debit_options = @options.merge(debit: true) + + stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, maestro_card, debit_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"payment_method_id":106/, data) + end.respond_with(successful_purchase_response) + end + + def test_payment_method_id_with_cabal_debit + # currently lacking a valid Cabal debit card number, so using the Cabal credit card number + cabal_card = credit_card('5896570000000008') + debit_options = @options.merge(debit: true) + + stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, cabal_card, debit_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"payment_method_id":108/, data) + end.respond_with(successful_purchase_response) + end + + private + + def pre_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/payments HTTP/1.1\r\nContent-Type: application/json\r\nApikey: 5df6b5764c3f4822aecdc82d56f26b9d\r\nCache-Control: no-cache\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: developers.decidir.com\r\nContent-Length: 414\r\n\r\n" + <- "{\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"bin\":\"450799\",\"payment_type\":\"single\",\"installments\":1,\"description\":\"Store Purchase\",\"sub_payments\":[],\"amount\":100,\"currency\":\"ARS\",\"card_data\":{\"card_number\":\"4507990000004905\",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"20\",\"security_code\":\"123\",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 24 Jun 2019 18:38:42 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 659\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 159\r\n" + -> "X-Kong-Proxy-Latency: 0\r\n" + -> "Via: kong/0.8.3\r\n" + -> "\r\n" + reading 659 bytes... + -> "{\"id\":7721017,\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":100,\"currency\":\"ars\",\"status\":\"approved\",\"status_details\":{\"ticket\":\"7297\",\"card_authorization_code\":\"153842\",\"address_validation_code\":\"VTE0011\",\"error\":null},\"date\":\"2019-06-24T15:38Z\",\"customer\":null,\"bin\":\"450799\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"99999999\",\"fraud_detection\":{\"status\":null},\"aggregate_data\":null,\"establishment_name\":\"Heavenly Buffaloes\",\"spv\":null,\"confirmed\":null,\"pan\":\"345425f15b2c7c4584e0044357b6394d7e\",\"customer_token\":null,\"card_data\":\"/tokens/7721017\"}" + read 659 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/payments HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nCache-Control: no-cache\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: developers.decidir.com\r\nContent-Length: 414\r\n\r\n" + <- "{\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"bin\":\"450799\",\"payment_type\":\"single\",\"installments\":1,\"description\":\"Store Purchase\",\"sub_payments\":[],\"amount\":100,\"currency\":\"ARS\",\"card_data\":{\"card_number\":\"[FILTERED]\",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"20\",\"security_code\":\"[FILTERED]\",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 24 Jun 2019 18:38:42 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 659\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 159\r\n" + -> "X-Kong-Proxy-Latency: 0\r\n" + -> "Via: kong/0.8.3\r\n" + -> "\r\n" + reading 659 bytes... + -> "{\"id\":7721017,\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":100,\"currency\":\"ars\",\"status\":\"approved\",\"status_details\":{\"ticket\":\"7297\",\"card_authorization_code\":\"153842\",\"address_validation_code\":\"VTE0011\",\"error\":null},\"date\":\"2019-06-24T15:38Z\",\"customer\":null,\"bin\":\"450799\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"99999999\",\"fraud_detection\":{\"status\":null},\"aggregate_data\":null,\"establishment_name\":\"Heavenly Buffaloes\",\"spv\":null,\"confirmed\":null,\"pan\":\"345425f15b2c7c4584e0044357b6394d7e\",\"customer_token\":null,\"card_data\":\"/tokens/7721017\"}" + read 659 bytes + Conn close + ) + end + + def successful_purchase_response + %( + {"id":7719132,"site_transaction_id":"ebcb2db7-7aab-4f33-a7d1-6617a5749fce","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"approved","status_details":{"ticket":"7156","card_authorization_code":"174838","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T17:48Z","customer":null,"bin":"450799","installments":1,"establishment_name":"Heavenly Buffaloes","first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999999","fraud_detection":{"status":null},"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7719132"} + ) + end + + def failed_purchase_response + %( + {"id":7719351,"site_transaction_id":"73e3ed66-37b1-4c97-8f69-f9cb96422383","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"rejected","status_details":{"ticket":"7162","card_authorization_code":"","address_validation_code":null,"error":{"type":"invalid_number","reason":{"id":14,"description":"TARJETA INVALIDA","additional_description":""}}},"date":"2019-06-21T17:57Z","customer":null,"bin":"400030","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999999","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"11b076fbc8fa6a55783b2f5d03f6938d8a","customer_token":null,"card_data":"/tokens/7719351"} + ) + end + + def failed_purchase_with_invalid_field_response + %( + {\"error_type\":\"invalid_request_error\",\"validation_errors\":[{\"code\":\"invalid_param\",\"param\":\"installments\"}]} ) + end + + def successful_authorize_response + %( + {"id":7720214,"site_transaction_id":"0fcedc95-4fbc-4299-80dc-f77e9dd7f525","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"pre_approved","status_details":{"ticket":"8187","card_authorization_code":"180548","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T18:05Z","customer":null,"bin":"450799","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7720214"} + ) + end + + def failed_authorize_response + %( + {"id":7719358,"site_transaction_id":"ff1c12c1-fb6d-4c1a-bc20-2e77d4322c61","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"rejected","status_details":{"ticket":"8189","card_authorization_code":"","address_validation_code":null,"error":{"type":"invalid_number","reason":{"id":14,"description":"TARJETA INVALIDA","additional_description":""}}},"date":"2019-06-21T18:07Z","customer":null,"bin":"400030","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"11b076fbc8fa6a55783b2f5d03f6938d8a","customer_token":null,"card_data":"/tokens/7719358"} + ) + end + + def successful_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"} + ) + end + + def failed_partial_capture_response + %( + {"error_type":"invalid_request_error","validation_errors":[{"code":"amount","param":"Amount out of ranges: 100 - 100"}]} + ) + end + + def failed_capture_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end + + def successful_refund_response + %( + {"id":81931,"amount":100,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def partial_refund_response + %( + {"id":81932,"amount":99,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def failed_refund_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end + + def successful_void_response + %( + {"id":82814,"amount":100,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def failed_void_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end + + def successful_inquire_response + %( + { "id": 544453,"site_transaction_id": "52139443","token": "ef4504fc-21f1-4608-bb75-3f73aa9b9ede","user_id": null,"card_brand": "visa","bin": "483621","amount": 10,"currency": "ars","installments": 1,"description": "","payment_type": "single","sub_payments": [],"status": "rejected","status_details": null,"date": "2016-12-15T15:12Z","merchant_id": null,"fraud_detection": {}} + ) + end + + def unique_purchase_error_response + %{ + {\"error\":{\"error_type\":\"invalid_request_error\",\"validation_errors\":[{\"code\":\"invalid_param\",\"param\":\"payment_type\"}]}} + } + end + + def unique_void_error_response + %{ + {\"error_type\":\"invalid_status_error\",\"validation_errors\":{\"status\":\"refunded\"}} + } + end + + def error_response_with_error_code + %{ + {\"error\":{\"type\":\"invalid_number\",\"reason\":{\"id\":14,\"description\":\"TARJETA INVALIDA\",\"additional_description\":\"\"}}} + } + end +end 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/dibs_test.rb b/test/unit/gateways/dibs_test.rb index af127ba0be8..0f639e4df34 100644 --- a/test/unit/gateways/dibs_test.rb +++ b/test/unit/gateways/dibs_test.rb @@ -62,7 +62,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1066662996/, data) end.respond_with(successful_capture_response) @@ -97,7 +97,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1066662996/, data) end.respond_with(successful_void_response) @@ -107,7 +107,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) end.respond_with(failed_void_response) @@ -124,7 +124,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1066662996/, data) end.respond_with(successful_refund_response) diff --git a/test/unit/gateways/digitzs_test.rb b/test/unit/gateways/digitzs_test.rb index 99b57ddc432..ef5b67a844b 100644 --- a/test/unit/gateways/digitzs_test.rb +++ b/test/unit/gateways/digitzs_test.rb @@ -38,8 +38,8 @@ def test_successful_purchase def test_successful_card_split_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options_with_split) - end.check_request do |endpoint, data, headers| - if data =~ /"cardSplit"/ + end.check_request do |_endpoint, data, _headers| + if /"cardSplit"/.match?(data) assert_match(%r(split), data) assert_match(%r("merchantId":"spreedly-susanswidg-32270590-2095203-148657924"), data) end @@ -52,8 +52,8 @@ def test_successful_card_split_purchase def test_successful_token_split_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options_with_split) - end.check_request do |endpoint, data, headers| - if data =~ /"tokenSplit"/ + end.check_request do |_endpoint, data, _headers| + if /"tokenSplit"/.match?(data) assert_match(%r(split), data) assert_match(%r("merchantId":"spreedly-susanswidg-32270590-2095203-148657924"), data) end @@ -103,7 +103,7 @@ def test_successful_store_creates_new_customer @gateway.expects(:ssl_get).returns(customer_id_exists_response) @gateway.expects(:ssl_post).times(3).returns(successful_app_token_response, successful_create_customer_response, successful_token_response) - assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'pre_existing_customer_id'})) + assert response = @gateway.store(@credit_card, @options.merge({ customer_id: 'pre_existing_customer_id' })) assert_success response assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226|c0302d83-a694-4bec-9086-d1886b9eefd9-148710226', response.authorization end diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb index c5e93bc8e13..423a1c0f83f 100644 --- a/test/unit/gateways/ebanx_test.rb +++ b/test/unit/gateways/ebanx_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class EbanxTest < Test::Unit::TestCase + include CommStub + def setup @gateway = EbanxGateway.new(integration_key: 'key') @credit_card = credit_card @@ -17,12 +19,33 @@ def test_successful_purchase @gateway.expects(:ssl_request).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response assert_equal '592db57ad6933455efbb62a48d1dfa091dd7cd092109db99', response.authorization assert response.test? end + def test_successful_purchase_with_optional_processing_type_header + response = stub_comms(@gateway, :ssl_request) do + @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) + + assert_success response + end + + def test_successful_purchase_with_soft_descriptor + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge(soft_descriptor: 'ActiveMerchant')) + end.check_request do |_method, _endpoint, data, _headers| + assert_match %r{"soft_descriptor\":\"ActiveMerchant\"}, data + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_failed_purchase @gateway.expects(:ssl_request).returns(failed_purchase_response) @@ -54,11 +77,20 @@ def test_successful_capture response = @gateway.capture(@amount, 'authorization', @options) assert_success response - - assert_equal 'Sandbox - Test credit card, transaction captured', response.message + assert_equal '5dee94502bd59660b801c441ad5a703f2c4123f5fc892ccb', response.authorization + assert_equal 'Accepted', response.message assert response.test? end + def test_failed_partial_capture + @gateway.expects(:ssl_request).returns(failed_partial_capture_response) + + response = @gateway.capture(@amount, 'authorization', @options.merge(include_capture_amount: true)) + assert_failure response + assert_equal 'BP-CAP-11', response.error_code + assert_equal 'Partial capture not available', response.message + end + def test_failed_capture @gateway.expects(:ssl_request).returns(failed_capture_response) @@ -104,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 @@ -120,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 @@ -140,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) @@ -157,7 +193,7 @@ def test_scrub def pre_scrubbed %q( - request_body={\"integration_key\":\"1231000\",\"operation\":\"request\",\"payment\":{\"amount_total\":\"1.00\",\"currency_code\":\"USD\",\"merchant_payment_code\":\"2bed75b060e936834e354d944aeaa892\",\"name\":\"Longbob Longsen\",\"email\":\"unspecified@example.com\",\"document\":\"853.513.468-93\",\"payment_type_code\":\"visa\",\"creditcard\":{\"card_number\":\"4111111111111111\",\"card_name\":\"Longbob Longsen\",\"card_due_date\":\"9/2018\",\"card_cvv\":\"123\"},\"address\":\"Rua E\",\"street_number\":\"1040\",\"city\":\"Maracana\u{fa}\",\"state\":\"CE\",\"zipcode\":\"61919-230\",\"country\":\"BR\",\"phone_number\":\"(555)555-5555\"}} + request_body={\"integration_key\":\"Ac1EwnH0ud2UIndICS37l0\",\"operation\":\"request\",\"payment\":{\"amount_total\":\"1.00\",\"currency_code\":\"USD\",\"merchant_payment_code\":\"2bed75b060e936834e354d944aeaa892\",\"name\":\"Longbob Longsen\",\"email\":\"unspecified@example.com\",\"document\":\"853.513.468-93\",\"payment_type_code\":\"visa\",\"creditcard\":{\"card_number\":\"4111111111111111\",\"card_name\":\"Longbob Longsen\",\"card_due_date\":\"9/2018\",\"card_cvv\":\"123\"},\"address\":\"Rua E\",\"street_number\":\"1040\",\"city\":\"Maracana\u{fa}\",\"state\":\"CE\",\"zipcode\":\"61919-230\",\"country\":\"BR\",\"phone_number\":\"(555)555-5555\"}} ) end @@ -191,9 +227,27 @@ 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":"592dd65824427e4f5f50564c118f399869637bfb30d54f5b","pin":"081043654","merchant_payment_code":"8424e3000d64d056fbd58639957dc1c4","order_number":null,"status":"CO","status_date":"2017-05-30 17:30:16","open_date":"2017-05-30 17:30:15","confirm_date":"2017-05-30 17:30:16","transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"OK","description":"Sandbox - Test credit card, transaction captured"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + {"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"} + ) + end + + def failed_partial_capture_response + %( + {"status":"ERROR", "status_code":"BP-CAP-11", "status_message":"Partial capture not available"} ) end diff --git a/test/unit/gateways/efsnet_test.rb b/test/unit/gateways/efsnet_test.rb index e437e2aa422..15c79aecf1f 100644 --- a/test/unit/gateways/efsnet_test.rb +++ b/test/unit/gateways/efsnet_test.rb @@ -1,16 +1,15 @@ require 'test_helper' class EfsnetTest < Test::Unit::TestCase - def setup @gateway = EfsnetGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @credit_card = credit_card('4242424242424242') @amount = 100 - @options = { :order_id => 1, :billing_address => address } + @options = { order_id: 1, billing_address: address } end def test_successful_purchase @@ -36,28 +35,28 @@ def test_unsuccessful_purchase def test_credit @gateway.expects(:ssl_post).with(anything, regexp_matches(/AccountNumber>#{@credit_card.number}<\/AccountNumber/), anything).returns('') - @gateway.credit(@amount, @credit_card, :order_id => 5) + @gateway.credit(@amount, @credit_card, order_id: 5) end def test_deprecated_credit @gateway.expects(:ssl_post).with(anything, regexp_matches(/transaction_id<\/OriginalTransactionID>/), anything).returns('') assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do - @gateway.credit(@amount, 'transaction_id', :order_id => 5) + @gateway.credit(@amount, 'transaction_id', order_id: 5) end end def test_refund @gateway.expects(:ssl_post).with(anything, regexp_matches(/transaction_id<\/OriginalTransactionID>/), anything).returns('') - @gateway.refund(@amount, 'transaction_id', :order_id => 5) + @gateway.refund(@amount, 'transaction_id', order_id: 5) end def test_authorize_is_valid_xml params = { - :order_id => 'order1', - :transaction_amount => '1.01', - :account_number => '4242424242424242', - :expiration_month => '12', - :expiration_year => '2029', + order_id: 'order1', + transaction_amount: '1.01', + account_number: '4242424242424242', + expiration_month: '12', + expiration_year: '2029' } assert data = @gateway.send(:post_data, :credit_card_authorize, params) @@ -66,10 +65,10 @@ def test_authorize_is_valid_xml def test_settle_is_valid_xml params = { - :order_id => 'order1', - :transaction_amount => '1.01', - :original_transaction_amount => '1.01', - :original_transaction_id => '1', + order_id: 'order1', + transaction_amount: '1.01', + original_transaction_amount: '1.01', + original_transaction_id: '1' } assert data = @gateway.send(:post_data, :credit_card_settle, params) @@ -93,48 +92,48 @@ def test_cvv_result private def successful_purchase_response - <<-XML - - - - 0 - 00 - APPROVED - 100018347764 - N - M - 123456 - 123456 - 080117 - 163222 - 1 - XXXXXXXXXXXX2224 - 1.00 - - + <<~XML + + + + 0 + 00 + APPROVED + 100018347764 + N + M + 123456 + 123456 + 080117 + 163222 + 1 + XXXXXXXXXXXX2224 + 1.00 + + XML end def unsuccessful_purchase_response - <<-XML - - - - 256 - 04 - DECLINED - 100018347784 - N - - - - 080117 - 163946 - 1 - XXXXXXXXXXXX2224 - 1.56 - - + <<~XML + + + + 256 + 04 + DECLINED + 100018347784 + N + + + + 080117 + 163946 + 1 + XXXXXXXXXXXX2224 + 1.56 + + XML end end diff --git a/test/unit/gateways/elavon_test.rb b/test/unit/gateways/elavon_test.rb index cf8557ce0af..da97607f95a 100644 --- a/test/unit/gateways/elavon_test.rb +++ b/test/unit/gateways/elavon_test.rb @@ -5,18 +5,32 @@ class ElavonTest < Test::Unit::TestCase def setup @gateway = ElavonGateway.new( - :login => 'login', - :user => 'user', - :password => 'password' - ) + login: 'login', + user: 'user', + password: 'password' + ) + + @multi_currency_gateway = ElavonGateway.new( + login: 'login', + user: 'user', + password: 'password', + multi_currency: true + ) + + @gateway_with_ssl_vendor_id = ElavonGateway.new( + login: 'login', + user: 'user', + password: 'password', + ssl_vendor_id: 'ABC123' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -25,7 +39,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal '093840;180820AD3-27AEE6EF-8CA7-4811-8D1F-E420C3B5041E', response.authorization assert response.test? end @@ -36,8 +50,8 @@ def test_successful_authorization assert_instance_of Response, response assert_success response - assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization - assert_equal 'APPROVED', response.message + assert_equal '259404;150920ED4-3EB7A2DF-A5A7-48E6-97B6-D98A9DC0BD59', response.authorization + assert_equal 'APPROVAL', response.message assert response.test? end @@ -51,44 +65,44 @@ def test_failed_authorization def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - authorization = '123456;00000000-0000-0000-0000-00000000000' + authorization = '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520' - assert response = @gateway.capture(@amount, authorization, :credit_card => @credit_card) + assert response = @gateway.capture(@amount, authorization, credit_card: @credit_card) assert_instance_of Response, response assert_success response - assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520', response.authorization assert_equal 'APPROVAL', response.message assert response.test? end def test_successful_capture_with_auth_code @gateway.expects(:ssl_post).returns(successful_capture_response) - authorization = '123456;00000000-0000-0000-0000-00000000000' + authorization = '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520' assert response = @gateway.capture(@amount, authorization) assert_instance_of Response, response assert_success response - assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520', response.authorization assert_equal 'APPROVAL', response.message assert response.test? end def test_successful_capture_with_additional_options - authorization = '123456;00000000-0000-0000-0000-00000000000' + authorization = '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520' response = stub_comms do - @gateway.capture(@amount, authorization, :test_mode => true, :partial_shipment_flag => true) - end.check_request do |endpoint, data, headers| - assert_match(/ssl_transaction_type=CCCOMPLETE/, data) - assert_match(/ssl_test_mode=TRUE/, data) - assert_match(/ssl_partial_shipment_flag=Y/, data) + @gateway.capture(@amount, authorization, test_mode: true, partial_shipment_flag: true) + end.check_request do |_endpoint, data, _headers| + assert_match(/CCCOMPLETE<\/ssl_transaction_type>/, data) + assert_match(/TRUE<\/ssl_test_mode>/, data) + assert_match(/Y<\/ssl_partial_shipment_flag>/, data) end.respond_with(successful_capture_response) assert_instance_of Response, response assert_success response - assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal '070213;110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520', response.authorization assert_equal 'APPROVAL', response.message assert response.test? end @@ -97,8 +111,7 @@ def test_successful_purchase_with_ip response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(ip: '203.0.113.0')) end.check_request do |_endpoint, data, _headers| - parsed = CGI.parse(data) - assert_equal ['203.0.113.0'], parsed['ssl_cardholder_ip'] + assert_match(/203.0.113.0/, data) end.respond_with(successful_purchase_response) assert_success response @@ -108,18 +121,83 @@ def test_successful_authorization_with_ip response = stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(ip: '203.0.113.0')) end.check_request do |_endpoint, data, _headers| - parsed = CGI.parse(data) - assert_equal ['203.0.113.0'], parsed['ssl_cardholder_ip'] + assert_match(/203.0.113.0<\/ssl_cardholder_ip>/, data) end.respond_with(successful_authorization_response) assert_success response end + def test_successful_purchase_with_dynamic_dba + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(dba: 'MANYMAG*BAKERS MONTHLY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/MANYMAG\*BAKERS MONTHLY<\/ssl_dynamic_dba>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_unscheduled + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(merchant_initiated_unscheduled: 'Y')) + end.check_request do |_endpoint, data, _headers| + assert_match(/Y<\/ssl_merchant_initiated_unscheduled>/, 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')) + end.check_request do |_endpoint, data, _headers| + assert_match(/Y<\/ssl_add_token>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_sends_ssl_token_field + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(ssl_token: '8675309')) + end.check_request do |_endpoint, data, _headers| + assert_match(/8675309<\/ssl_token>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_authorization_with_dynamic_dba + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(dba: 'MANYMAG*BAKERS MONTHLY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/MANYMAG\*BAKERS MONTHLY<\/ssl_dynamic_dba>/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_successful_purchase_with_multi_currency + response = stub_comms(@multi_currency_gateway) do + @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/JPY<\/ssl_transaction_currency>/, data) + end.respond_with(successful_purchase_with_multi_currency_response) + + assert_success response + end + + def test_successful_purchase_without_multi_currency + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR', multi_currency: false)) + end.check_request do |_endpoint, data, _headers| + assert_no_match(/ssl_transaction_currency=EUR/, data) + end.respond_with(successful_purchase_response) + end + def test_failed_capture @gateway.expects(:ssl_post).returns(failed_authorization_response) authorization = '123456INVALID;00000000-0000-0000-0000-00000000000' - assert response = @gateway.capture(@amount, authorization, :credit_card => @credit_card) + assert response = @gateway.capture(@amount, authorization, credit_card: @credit_card) assert_instance_of Response, response assert_failure response end @@ -161,28 +239,20 @@ def test_unsuccessful_refund assert response = @gateway.refund(123, '456') assert_failure response - assert_equal 'The refund amount exceeds the original transaction amount.', response.message + assert_equal 'The amount exceeded the original transaction amount. Amount must be equal or lower than the original transaction amount.', response.message end def test_successful_verify response = stub_comms do @gateway.verify(@credit_card) - end.respond_with(successful_authorization_response, successful_void_response) - assert_success response - end - - def test_successful_verify_failed_void - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(successful_authorization_response, failed_void_response) + end.respond_with(successful_verify_response) assert_success response - assert_equal 'APPROVED', response.message end def test_unsuccessful_verify response = stub_comms do @gateway.verify(@credit_card, @options) - end.respond_with(failed_authorization_response, successful_void_response) + end.respond_with(failed_verify_response) assert_failure response assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message end @@ -192,25 +262,25 @@ def test_invalid_login assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal '7000', response.params['result'] - assert_equal 'The VirtualMerchant ID and/or User ID supplied in the authorization request is invalid.', response.message + assert_equal '4025', response.params['errorCode'] + assert_equal 'The credentials supplied in the authorization request are invalid.', response.message assert_failure response end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover], ElavonGateway.supported_cardtypes + assert_equal %i[visa master american_express discover], ElavonGateway.supported_cardtypes end def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) - assert_equal 'X', response.avs_result['code'] + assert_equal 'M', response.avs_result['code'] end def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) - assert_equal 'P', response.cvv_result['code'] + assert_equal 'M', response.cvv_result['code'] end def test_successful_store @@ -218,7 +288,7 @@ def test_successful_store assert response = @gateway.store(@credit_card, @options) assert_success response - assert_equal '7595301425001111', response.params['token'] + assert_equal '4421912014039990', response.params['token'] assert response.test? end @@ -252,7 +322,7 @@ def test_stripping_non_word_characters_from_zip @options[:billing_address][:zip] = bad_zip - @gateway.expects(:commit).with(anything, anything, has_entries(:avs_zip => stripped_zip), anything) + @gateway.expects(:commit).with(includes("#{stripped_zip}")) @gateway.purchase(@amount, @credit_card, @options) end @@ -260,18 +330,253 @@ def test_stripping_non_word_characters_from_zip def test_zip_codes_with_letters_are_left_intact @options[:billing_address][:zip] = '.K1%Z_5E3-' - @gateway.expects(:commit).with(anything, anything, has_entries(:avs_zip => 'K1Z5E3'), anything) + @gateway.expects(:commit).with(includes('K1Z5E3')) @gateway.purchase(@amount, @credit_card, @options) end + def test_strip_ampersands + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: { address1: 'Bats & Cats' })) + end.check_request do |_endpoint, data, _headers| + refute_match(/&/, data) + end.respond_with(successful_purchase_response) + end + + def test_split_full_network_transaction_id + oar_data = '010012318808182231420000047554200000000000093840023122123188' + ps2000_data = 'A8181831435010530042VE' + network_transaction_id = "#{oar_data}|#{ps2000_data}" + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: { network_transaction_id: network_transaction_id })) + end.check_request do |_endpoint, data, _headers| + assert_match(/#{oar_data}<\/ssl_oar_data>/, data) + assert_match(/#{ps2000_data}<\/ssl_ps2000_data>/, data) + end.respond_with(successful_purchase_response) + end + + def test_oar_only_network_transaction_id + oar_data = '010012318808182231420000047554200000000000093840023122123188' + ps2000_data = nil + network_transaction_id = "#{oar_data}|#{ps2000_data}" + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: { network_transaction_id: network_transaction_id })) + end.check_request do |_endpoint, data, _headers| + assert_match(/#{oar_data}<\/ssl_oar_data>/, data) + refute_match(//, data) + end.respond_with(successful_purchase_response) + end + + def test_ps2000_only_network_transaction_id + oar_data = nil + ps2000_data = 'A8181831435010530042VE' + network_transaction_id = "#{oar_data}|#{ps2000_data}" + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: { network_transaction_id: network_transaction_id })) + end.check_request do |_endpoint, data, _headers| + refute_match(//, data) + assert_match(/#{ps2000_data}<\/ssl_ps2000_data>/, data) + end.respond_with(successful_purchase_response) + end + + def test_oar_transaction_id_without_pipe + oar_data = '010012318808182231420000047554200000000000093840023122123188' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: { network_transaction_id: oar_data })) + end.check_request do |_endpoint, data, _headers| + assert_match(/#{oar_data}<\/ssl_oar_data>/, data) + refute_match(//, data) + end.respond_with(successful_purchase_response) + end + + def test_ps2000_transaction_id_without_pipe + ps2000_data = 'A8181831435010530042VE' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential: { network_transaction_id: ps2000_data })) + end.check_request do |_endpoint, data, _headers| + refute_match(//, data) + assert_match(/#{ps2000_data}<\/ssl_ps2000_data>/, data) + end.respond_with(successful_purchase_response) + end + def test_custom_fields_in_request stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:customer_number => '123', :custom_fields => {:a_key => 'a value'})) - end.check_request do |endpoint, data, headers| - assert_match(/customer_number=123/, data) - assert_match(/a_key/, data) - refute_match(/ssl_a_key/, data) + @gateway.purchase(@amount, @credit_card, @options.merge(customer_number: '123', custom_fields: { a_key: 'a value' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/123<\/ssl_customer_number>/, data) + assert_match(/a value<\/a_key>/, data) + end.respond_with(successful_purchase_response) + end + + def test_ssl_vendor_id_from_gateway_credentials + stub_comms do + @gateway_with_ssl_vendor_id.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/ABC123<\/ssl_vendor_id>/, data) + end.respond_with(successful_purchase_response) + end + + def test_ssl_vendor_id_from_options + stub_comms do + @gateway_with_ssl_vendor_id.purchase(@amount, @credit_card, @options.merge(ssl_vendor_id: 'My special ID')) + end.check_request do |_endpoint, data, _headers| + assert_match(/My special ID<\/ssl_vendor_id>/, data) + end.respond_with(successful_purchase_response) + end + + def test_truncate_special_characters + first_name = 'Ricky ™ Martínez įncogníto' + credit_card = @credit_card + credit_card.first_name = first_name + + stub_comms do + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_endpoint, data, _headers| + check = 'Ricky %E2%84%A2 Mart' + assert_match(/#{check}<\/ssl_first_name>/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_special_character_encoding_truncation + special_card = @credit_card + special_card.first_name = 'Fear & Loathing' + special_card.last_name = 'Castañeda' + + stub_comms do + @gateway.purchase(@amount, special_card, @options) + end.check_request do |_endpoint, data, _headers| + first = 'Fear %26amp; Loathin' + last = 'Casta%C3%B1eda' + assert_match(/#{first}<\/ssl_first_name>/, data) + assert_match(/#{last}<\/ssl_last_name>/, data) + end.respond_with(successful_purchase_response) + end + + def test_level_3_fields_in_request + level_3_data = { + customer_code: 'bob', + salestax: '3.45', + salestax_indicator: 'Y', + level3_indicator: 'Y', + ship_to_zip: '12345', + ship_to_country: 'US', + shipping_amount: '1234', + ship_from_postal_code: '54321', + discount_amount: '5', + duty_amount: '2', + national_tax_indicator: '0', + national_tax_amount: '10', + order_date: '280810', + other_tax: '3', + summary_commodity_code: '123', + merchant_vat_number: '222', + customer_vat_number: '333', + freight_tax_amount: '4', + vat_invoice_number: '26', + tracking_number: '45', + shipping_company: 'UFedzon', + other_fees: '2', + line_items: [ + { + description: 'thing', + product_code: '23', + commodity_code: '444', + quantity: '15', + unit_of_measure: 'kropogs', + unit_cost: '4.5', + discount_indicator: 'Y', + tax_indicator: 'Y', + discount_amount: '1', + tax_rate: '8.25', + tax_amount: '12', + tax_type: 'state', + extended_total: '500', + total: '525', + alternative_tax: '111' + }, + { + description: 'thing2', + product_code: '23', + commodity_code: '444', + quantity: '15', + unit_of_measure: 'kropogs', + unit_cost: '4.5', + discount_indicator: 'Y', + tax_indicator: 'Y', + discount_amount: '1', + tax_rate: '8.25', + tax_amount: '12', + tax_type: 'state', + extended_total: '500', + total: '525', + alternative_tax: '111' + } + ] + } + + options = @options.merge(level_3_data: level_3_data) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/bob/, data) + assert_match(/3.45/, data) + assert_match(/Y/, data) + assert_match(/Y/, data) + assert_match(/12345/, data) + assert_match(/US/, data) + assert_match(/1234/, data) + assert_match(/54321/, data) + assert_match(/5/, data) + assert_match(/2/, data) + assert_match(/0/, data) + assert_match(/10/, data) + assert_match(/280810/, data) + assert_match(/3/, data) + assert_match(/123/, data) + assert_match(/222/, data) + assert_match(/333/, data) + assert_match(/4/, data) + assert_match(/26/, data) + assert_match(/45/, data) + assert_match(/UFedzon/, data) + assert_match(/2/, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + assert_match(//, data) + end.respond_with(successful_purchase_response) + end + + def test_shipping_address_in_request + shipping_address = { + address1: '733 Foster St.', + city: 'Durham', + state: 'NC', + phone: '8887277750', + country: 'USA', + zip: '27701' + } + options = @options.merge(shipping_address: shipping_address) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/733 Foster St./, data) + assert_match(/Durham/, data) + assert_match(/NC/, data) + assert_match(/8887277750/, data) + assert_match(/USA/, data) + assert_match(/27701/, data) end.respond_with(successful_purchase_response) end @@ -283,189 +588,434 @@ def test_transcript_scrubbing private def successful_purchase_response - "ssl_card_number=42********4242 - ssl_exp_date=0910 - ssl_amount=1.00 - ssl_invoice_number= - ssl_description=Test Transaction - ssl_result=0 - ssl_result_message=APPROVED - ssl_txn_id=00000000-0000-0000-0000-00000000000 - ssl_approval_code=123456 - ssl_cvv2_response=P - ssl_avs_response=X - ssl_account_balance=0.00 - ssl_txn_time=08/07/2009 09:54:18 PM" + <<-XML + + + 00 + Longsen + Widgets Inc + (555)555-5555 + 41**********9990 + + 010012318808182231420000047554200000000000093840023122123188 + 0 + 180820AD3-27AEE6EF-8CA7-4811-8D1F-E420C3B5041E + M + 093840 + paul@domain.com + 100.00 + K1C2N6 + 08/18/2020 06:31:42 PM + 0921 + VISA + + Apt 1 + CA + CREDITCARD + AUTHONLY + + 456 My Street + 0.00 + A8181831435010530042VE + ON + Ottawa + APPROVAL + Longbob + + M + VM + + XML + end + + def successful_purchase_with_multi_currency_response + <<-XML + + + 00 + + 41**********9990 + + 010012316708182238060000047554200000000000093864023122123167 + 0 + 180820ED3-1DD371B9-64DF-4902-B377-EBD095E6DAF0 + + M + 093864 + + 100 + JPY + 08/18/2020 06:38:06 PM + + 0921 + VISA + + CREDITCARD + + SALE + + 0.00 + + 0.00 + A8181838065010780213VE + APPROVAL + + + + VM + + XML end def successful_refund_response - "ssl_card_number=42*****2222 - ssl_exp_date= - ssl_amount=1.00 - ssl_customer_code= - ssl_invoice_number= - ssl_description= - ssl_company= - ssl_first_name= - ssl_last_name= - ssl_avs_address= - ssl_address2= - ssl_city= - ssl_state= - ssl_avs_zip= - ssl_country= - ssl_phone= - ssl_email= - ssl_result=0 - ssl_result_message=APPROVAL - ssl_txn_id=AA49315-C3D2B7BA-237C-1168-405A-CD5CAF928B0C - ssl_approval_code= - ssl_cvv2_response= - ssl_avs_response= - ssl_account_balance=0.00 - ssl_txn_time=08/21/2012 05:43:46 PM" + <<-XML + + + 00 + Longsen + Widgets Inc + (555)555-5555 + 41**********9990 + + 0 + 180820AD3-4BACDE38-63F3-427D-BFC1-1B3EB046056B + + 094012 + paul@domain.com + 100.00 + K1C2N6 + 08/18/2020 07:04:49 PM + 0921 + VISA + + Apt 1 + + CA + CREDITCARD + RETURN + + 456 My Street + 0.00 + ON + Ottawa + APPROVAL + Longbob + + + VM + + XML end def successful_void_response - "ssl_card_number=42*****2222 - ssl_exp_date=0913 - ssl_amount=1.00 - ssl_invoice_number= - ssl_description= - ssl_company= - ssl_first_name= - ssl_last_name= - ssl_avs_address= - ssl_address2= - ssl_city= - ssl_state= - ssl_avs_zip= - ssl_country= - ssl_phone= - ssl_email= - ssl_result=0 - ssl_result_message=APPROVAL - ssl_txn_id=AA49315-F04216E3-E556-E2E0-ADE9-4186A5F69105 - ssl_approval_code= - ssl_cvv2_response= - ssl_avs_response= - ssl_account_balance=1.00 - ssl_txn_time=08/21/2012 05:37:19 PM" + <<-XML + + + Longsen + + Widgets Inc + (555)555-5555 + 41**********9990 + 0 + 180820AD3-2E02E02D-A1FB-4926-A957-3930D3F7B869 + paul@domain.com + 100.00 + K1C2N6 + 08/18/2020 06:56:27 PM + 0921 + VISA + Apt 1 + + CA + CREDITCARD + DELETE + + 456 My Street + ON + Ottawa + APPROVAL + Longbob + + VM + + XML + end + + def successful_verify_response + <<-XML + + + 85 + CARDVERIFICATION + 41**********9990 + 010012309508182257450000047554200000000000093964023122123095 + 0 + 180820ED4-85DA9146-51AB-4FEC-8004-91C607047E5C + M + 093964 + 456 My Street + K1C2N6 + 08/18/2020 06:57:45 PM + 0.00 + A8181857455011610042VE + 0921 + APPROVAL + VISA + CREDITCARD + M + VM + + XML end def failed_purchase_response - "errorCode=5000 - errorName=Credit Card Number Invalid - errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + <<-XML + + + 5000 + Credit Card Number Invalid + The Credit Card Number supplied in the authorization request appears to be invalid. + + XML end def failed_refund_response - "errorCode=5091 - errorName=Invalid Refund Amount - errorMessage=The refund amount exceeds the original transaction amount." + <<-XML + + + 5091 + Invalid amount + The amount exceeded the original transaction amount. Amount must be equal or lower than the original transaction amount. + + XML end def failed_void_response - "errorCode=5040 - errorName=Invalid Transaction ID - errorMessage=The transaction ID is invalid for this transaction type" + <<-XML + + + 5040 + Invalid Transaction ID + The transaction ID is invalid for this transaction type + + XML + end + + def failed_verify_response + <<-XML + + + 5000 + Credit Card Number Invalid + The Credit Card Number supplied in the authorization request appears to be invalid. + + XML end def invalid_login_response - <<-RESPONSE - ssl_result=7000\r - ssl_result_message=The VirtualMerchant ID and/or User ID supplied in the authorization request is invalid.\r - RESPONSE + <<-XML + + + 4025 + Invalid Credentials + The credentials supplied in the authorization request are invalid. + + XML end def successful_authorization_response - "ssl_card_number=42********4242 - ssl_exp_date=0910 - ssl_amount=1.00 - ssl_invoice_number= - ssl_description=Test Transaction - ssl_result=0 - ssl_result_message=APPROVED - ssl_txn_id=00000000-0000-0000-0000-00000000000 - ssl_approval_code=123456 - ssl_cvv2_response=P - ssl_avs_response=X - ssl_account_balance=0.00 - ssl_txn_time=08/07/2009 09:56:11 PM" + <<-XML + + + 00 + AUTHONLY + 41**********9990 + + 010012312309152159540000047554200000000000259404025921123123 + 0 + 150920ED4-3EB7A2DF-A5A7-48E6-97B6-D98A9DC0BD59 + M + 259404 + + 100.00 + 09/15/2020 05:59:54 PM + 0.00 + A9151759546571260030VE + 0921 + APPROVAL + VISA + + 3 + CREDITCARD + + M + 01 + + XML end def failed_authorization_response - "errorCode=5000 - errorName=Credit Card Number Invalid - errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + <<-XML + + + 5000 + Credit Card Number Invalid + The Credit Card Number supplied in the authorization request appears to be invalid. + + XML end def successful_capture_response - "ssl_card_number=42********4242 - ssl_exp_date=0910 - ssl_amount=1.00 - ssl_customer_code= - ssl_salestax= - ssl_invoice_number= - ssl_result=0 - ssl_result_message=APPROVAL - ssl_txn_id=00000000-0000-0000-0000-00000000000 - ssl_approval_code=123456 - ssl_cvv2_response=P - ssl_avs_response=X - ssl_account_balance=0.00 - ssl_txn_time=08/07/2009 09:56:11 PM" + <<~XML + + Longsen + Widgets Inc + (555)555-5555 + 41**********9990 + + 0 + 110820ED4-23CA2F2B-A88C-40E1-AC46-9219F800A520 + + 070213 + paul@domain.com + 100.00 + K1C2N6 + 08/11/2020 10:08:14 PM + 0921 + VISA + + Apt 1 + CA + CREDITCARD + FORCE + + 456 My Street + 0.00 + ON + Ottawa + APPROVAL + Longbob + + + VM + + XML end def failed_capture_response - "errorCode=5040 - errorName=Invalid Transaction ID - errorMessage=The transaction ID is invalid for this transaction type" + <<-XML + + + 5004 + Invalid Approval Code + The FORCE Approval Code supplied in the authorization request appears to be invalid or blank. The FORCE Approval Code must be 6 or less alphanumeric characters. + + XML end def successful_store_response - "ssl_transaction_type=CCGETTOKEN - ssl_result=0 - ssl_token=7595301425001111 - ssl_card_number=41**********1111 - ssl_token_response=SUCCESS - ssl_add_token_response=Card Updated - vu_aamc_id=" + <<-XML + + + Longsen + Widgets Inc + (555)555-5555 + 41**********9990 + 0 + + + + paul@domain.com + K1C2N6 + 08/18/2020 07:01:16 PM + 0921 + VISA + Apt 1 + SUCCESS + CA + CREDITCARD + GETTOKEN + + 456 My Street + + 0.00 + ON + Ottawa + + Longbob + + + 4421912014039990 + Card Updated + + XML end def failed_store_response - "errorCode=5000 - errorName=Credit Card Number Invalid - errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + <<-XML + + + 5000 + Credit Card Number Invalid + The Credit Card Number supplied in the authorization request appears to be invalid. + + XML end def successful_update_response - "ssl_token=7595301425001111 - ssl_card_type=VISA - ssl_card_number=************1111 - ssl_exp_date=1015 - ssl_company= - ssl_customer_id= - ssl_first_name=John - ssl_last_name=Doe - ssl_avs_address= - ssl_address2= - ssl_avs_zip= - ssl_city= - ssl_state= - ssl_country= - ssl_phone= - ssl_email= - ssl_description= - ssl_user_id=webpage - ssl_token_response=SUCCESS - ssl_result=0" + <<-XML + + + 4421912014039990 + VISA + ************9990 + 1021 + Widgets Inc + + Longbob + Longsen + 456 My Street + Apt 1 + Ottawa + ON + K1C2N6 + CA + (555)555-5555 + paul@domain.com + + webpage + SUCCESS + 0 + + XML end def failed_update_response - "errorCode=5000 - errorName=Credit Card Number Invalid - errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + <<-XML + + + 4421912014039990 + VISA + ************9990 + 1021 + Widgets Inc + + Longbob + Longsen + 456 My Street + Apt 1 + Ottawa + ON + K1C2N6 + CA + (555)555-5555 + paul@domain.com + + apiuser + Failed + 1 + + XML end def pre_scrub @@ -474,35 +1024,38 @@ def pre_scrub opened starting SSL for api.demo.convergepay.com:443... SSL established -<- "POST /VirtualMerchantDemo/process.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.convergepay.com\r\nContent-Length: 616\r\n\r\n" -<- "ssl_merchant_id=000127&ssl_pin=IERAOBEE5V0D6Q3Q6R51TG89XAIVGEQ3LGLKMKCKCVQBGGGAU7FN627GPA54P5HR&ssl_show_form=false&ssl_result_format=ASCII&ssl_user_id=ssltest&ssl_invoice_number=&ssl_description=Test+Transaction&ssl_card_number=4124939999999990&ssl_exp_date=0919&ssl_cvv2cvc2=123&ssl_cvv2cvc2_indicator=1&ssl_first_name=Longbob&ssl_last_name=Longsen&ssl_avs_address=456+My+Street&ssl_address2=Apt+1&ssl_avs_zip=K1C2N6&ssl_city=Ottawa&ssl_state=ON&ssl_company=Widgets+Inc&ssl_phone=%28555%29555-5555&ssl_country=CA&ssl_email=paul%40domain.com&ssl_cardholder_ip=203.0.113.0&ssl_amount=1.00&ssl_transaction_type=CCSALE" +<- "POST /VirtualMerchantDemo/processxml.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/xml\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.demo.convergepay.com\r\nContent-Length: 1026\r\n\r\n" +<- "xmldata=\n 2020701\n apiuser\n ULV2VQJXA5UR19KFXZ8TUWEFWMFY5MYXJVVOS8JN69EWV8XTN8Y0HYCR8B11DIUU\n CCSALE\n 100\n 4124939999999990\n 0921\n 123\n 1\n Longbob\n Longsen\n \n Test Transaction\n 456 My Street\n Apt 1\n K1C2N6\n Ottawa\n ON\n Widgets Inc\n (555)555-5555\n CA\n paul@domain.com\n N\n\n" -> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 21:40:26 GMT\r\n" --> "Pragma: no-cache\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Date: Tue, 15 Sep 2020 23:09:31 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" -> "Expires: 0\r\n" --> "Content-Disposition: inline; filename=response.txt\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" -> "AuthApproved: true\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Security-Policy: frame-ancestors 'self'\r\n" +-> "Content-Disposition: inline; filename=response.xml\r\n" +-> "CPID: ED4-dff741a6-df1a-463c-920e-2e4842eda7bf\r\n" -> "AuthResponse: AA\r\n" --> "Set-Cookie: JSESSIONID=00007wKfJV3-JFME8QiC_RCDjuI:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" --> "Set-Cookie: JSESSIONID=0000uW6woWZ84eAJunhFLfJz8hS:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Content-Type: text/xml\r\n" +-> "Set-Cookie: JSESSIONID=UtM16S1VJSFsHChVlcYvM0cGVDWHMW1XD0vZ5T47.svplknxcnvrgdapp02; path=/VirtualMerchantDemo; secure; HttpOnly\r\n" -> "Connection: close\r\n" --> "Content-Type: text/plain\r\n" --> "Content-Language: en-US\r\n" --> "Content-Encoding: gzip\r\n" -> "Transfer-Encoding: chunked\r\n" -> "\r\n" --> "1A5 \r\n" -reading 421 bytes... --> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03MR\xEFk\xDB0\x10\xFD\xDE\xBF\xC2\x1F\xB7\x81[\xC9\xB1\x1D\xBB \x98\x7FtP\xD66!+\xDBGs\xB1o\x99\xC0\x96\x84%{q\xFF\xFA\xC9R\x12f\x10\xDC\xBDw\xBEw\xEF8\xAD\xFB\xA6\x85\xB1k\xC44\x1Cqd1\xFDr\xFB\xF2<'w\xDA\x16\xE0Y5\x1D\x18d$\xA7\xB9C`\x90\x930\x8C\xDE\x13_\xA1\xA1Gm\xE0\xCC\\\xC6\xC5,y\x8B\xD7\x9E\x0E\x130\xA0\x8FV9\x1Fu\xA8`4\xD3\x88\xBE\xFB\xDD;WM\xE1;\xFBJ9\xA8\x1E\r\x97\xE2Rp\x05A,\xEC\x17\xEFNht\xF0,Z\x87\xFF\xE6\xA36^\xE6E\x8A\xD3Q\x1E\x1D\xDC\xC3\xFF\xA8F\xE1P\x98u\x03]7\xA2\xD6,N\xD2\xE0u\t~\x98\x11\xD1x\xD63\x11+\x94\t\xA8W\xE5fa;c\xE0/\xB8\xDC\x9A\xB5\x03\xED\xDEn\xDD>\xB8b\xDFi\x15\xBD\xA5\xBE~u1.\xAC*\\\xAA\xFEH\x81\xECS\x92$\x9F\xED\v\xEDK\x1C\x8E\x03\xF0\x9E)\x98\xFA\xAF\x9D\xB4\xB1\xB8\xB7\xF6\x1Cc\a\x98z\xC3\xFCz}\xD2\fv(8!+\xF6\xFB\xC3\xEEg\xF1\xE28s\x16\r\xEF\x18\xD9\x10J\xB3\x82&!\xA9\xD3:O\xF3*|\x8A\xEA2\x8C\xB34\t\xB3o\xDB$,\xD3\xA2,\xB3tC\xB7E\xE9\xFE\x04\xA5F9\xC3:l\x87,\xDEnI\x1C9\xA2\x9D\xE7h\xD5TR\xE8\xCB\xD6W\x8B7\xE4\xE2\xBAu&\x9B#\xF4 Z{\x1C\xD7cX'2\xDCn\x9C\xD0\a\xB2y\x88\b\xCD\x02\x12?\xC6\xE41\xDA\x06\xFBW/\xB1\xDE\x9CY\x14\xB2\xEA\xF0T?\xBFW\xC5\xA1\xFE\aC\x85\x1DS\x8C\x02\x00\x00" -read 421 bytes +-> "44b\r\n" +reading 1099 bytes... +-> "\n00SALE41**********99900100123443091523092800000475542000000000002598490259231234430150920ED4-48E1CA31-F2C5-411B-9543-AEA81EFB81B9M259849100.0009/15/2020 07:09:28 PM0.00A9151909286574590030VE0921APPROVALVISA3CREDITCARDM01" +read 1099 bytes reading 2 bytes... -> "\r\n" read 2 bytes -> "0\r\n" -> "\r\n" Conn close -}} + } end def post_scrub @@ -511,34 +1064,37 @@ def post_scrub opened starting SSL for api.demo.convergepay.com:443... SSL established -<- "POST /VirtualMerchantDemo/process.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.convergepay.com\r\nContent-Length: 616\r\n\r\n" -<- "ssl_merchant_id=000127&ssl_pin=[FILTERED]&ssl_show_form=false&ssl_result_format=ASCII&ssl_user_id=ssltest&ssl_invoice_number=&ssl_description=Test+Transaction&ssl_card_number=[FILTERED]&ssl_exp_date=0919&ssl_cvv2cvc2=[FILTERED]&ssl_cvv2cvc2_indicator=1&ssl_first_name=Longbob&ssl_last_name=Longsen&ssl_avs_address=456+My+Street&ssl_address2=Apt+1&ssl_avs_zip=K1C2N6&ssl_city=Ottawa&ssl_state=ON&ssl_company=Widgets+Inc&ssl_phone=%28555%29555-5555&ssl_country=CA&ssl_email=paul%40domain.com&ssl_cardholder_ip=203.0.113.0&ssl_amount=1.00&ssl_transaction_type=CCSALE" +<- "POST /VirtualMerchantDemo/processxml.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/xml\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.demo.convergepay.com\r\nContent-Length: 1026\r\n\r\n" +<- "xmldata=\n 2020701\n apiuser\n [FILTERED]\n CCSALE\n 100\n [FILTERED]\n 0921\n [FILTERED]\n 1\n Longbob\n Longsen\n \n Test Transaction\n 456 My Street\n Apt 1\n K1C2N6\n Ottawa\n ON\n Widgets Inc\n (555)555-5555\n CA\n paul@domain.com\n N\n\n" -> "HTTP/1.1 200 OK\r\n" --> "Date: Wed, 03 Jan 2018 21:40:26 GMT\r\n" --> "Pragma: no-cache\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Date: Tue, 15 Sep 2020 23:09:31 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" -> "Expires: 0\r\n" --> "Content-Disposition: inline; filename=response.txt\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" -> "AuthApproved: true\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Security-Policy: frame-ancestors 'self'\r\n" +-> "Content-Disposition: inline; filename=response.xml\r\n" +-> "CPID: ED4-dff741a6-df1a-463c-920e-2e4842eda7bf\r\n" -> "AuthResponse: AA\r\n" --> "Set-Cookie: JSESSIONID=00007wKfJV3-JFME8QiC_RCDjuI:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" --> "Set-Cookie: JSESSIONID=0000uW6woWZ84eAJunhFLfJz8hS:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Content-Type: text/xml\r\n" +-> "Set-Cookie: JSESSIONID=UtM16S1VJSFsHChVlcYvM0cGVDWHMW1XD0vZ5T47.svplknxcnvrgdapp02; path=/VirtualMerchantDemo; secure; HttpOnly\r\n" -> "Connection: close\r\n" --> "Content-Type: text/plain\r\n" --> "Content-Language: en-US\r\n" --> "Content-Encoding: gzip\r\n" -> "Transfer-Encoding: chunked\r\n" -> "\r\n" --> "1A5 \r\n" -reading 421 bytes... --> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03MR\xEFk\xDB0\x10\xFD\xDE\xBF\xC2\x1F\xB7\x81[\xC9\xB1\x1D\xBB \x98\x7FtP\xD66!+\xDBGs\xB1o\x99\xC0\x96\x84%{q\xFF\xFA\xC9R\x12f\x10\xDC\xBDw\xBEw\xEF8\xAD\xFB\xA6\x85\xB1k\xC44\x1Cqd1\xFDr\xFB\xF2<'w\xDA\x16\xE0Y5\x1D\x18d$\xA7\xB9C`\x90\x930\x8C\xDE\x13_\xA1\xA1Gm\xE0\xCC\\\xC6\xC5,y\x8B\xD7\x9E\x0E\x130\xA0\x8FV9\x1Fu\xA8`4\xD3\x88\xBE\xFB\xDD;WM\xE1;\xFBJ9\xA8\x1E\r\x97\xE2Rp\x05A,\xEC\x17\xEFNht\xF0,Z\x87\xFF\xE6\xA36^\xE6E\x8A\xD3Q\x1E\x1D\xDC\xC3\xFF\xA8F\xE1P\x98u\x03]7\xA2\xD6,N\xD2\xE0u\t~\x98\x11\xD1x\xD63\x11+\x94\t\xA8W\xE5fa;c\xE0/\xB8\xDC\x9A\xB5\x03\xED\xDEn\xDD>\xB8b\xDFi\x15\xBD\xA5\xBE~u1.\xAC*\\\xAA\xFEH\x81\xECS\x92$\x9F\xED\v\xEDK\x1C\x8E\x03\xF0\x9E)\x98\xFA\xAF\x9D\xB4\xB1\xB8\xB7\xF6\x1Cc\a\x98z\xC3\xFCz}\xD2\fv(8!+\xF6\xFB\xC3\xEEg\xF1\xE28s\x16\r\xEF\x18\xD9\x10J\xB3\x82&!\xA9\xD3:O\xF3*|\x8A\xEA2\x8C\xB34\t\xB3o\xDB$,\xD3\xA2,\xB3tC\xB7E\xE9\xFE\x04\xA5F9\xC3:l\x87,\xDEnI\x1C9\xA2\x9D\xE7h\xD5TR\xE8\xCB\xD6W\x8B7\xE4\xE2\xBAu&\x9B#\xF4 Z{\x1C\xD7cX'2\xDCn\x9C\xD0\a\xB2y\x88\b\xCD\x02\x12?\xC6\xE41\xDA\x06\xFBW/\xB1\xDE\x9CY\x14\xB2\xEA\xF0T?\xBFW\xC5\xA1\xFE\aC\x85\x1DS\x8C\x02\x00\x00" -read 421 bytes +-> "44b\r\n" +reading 1099 bytes... +-> "\n00SALE[FILTERED]0100123443091523092800000475542000000000002598490259231234430150920ED4-48E1CA31-F2C5-411B-9543-AEA81EFB81B9M259849100.0009/15/2020 07:09:28 PM0.00A9151909286574590030VE0921APPROVALVISA3CREDITCARDM01" +read 1099 bytes reading 2 bytes... -> "\r\n" read 2 bytes -> "0\r\n" -> "\r\n" Conn close -}} + } end end diff --git a/test/unit/gateways/element_test.rb b/test/unit/gateways/element_test.rb index 253cfcdccbf..694af43d9a9 100644 --- a/test/unit/gateways/element_test.rb +++ b/test/unit/gateways/element_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class ElementTest < Test::Unit::TestCase + include CommStub + def setup @gateway = ElementGateway.new(account_id: '', account_token: '', application_id: '', acceptor_id: '', application_name: '', application_version: '') @credit_card = credit_card @@ -23,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) @@ -128,32 +142,236 @@ def test_failed_void end def test_successful_verify - @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + @gateway.expects(:ssl_post).returns(successful_verify_response) response = @gateway.verify(@credit_card, @options) assert_success response end - def test_successful_verify_with_failed_void - @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + def test_handles_error_response + @gateway.expects(:ssl_post).returns(error_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal response.message, 'TargetNamespace required' + assert_failure response + end + + def test_successful_purchase_with_card_present_code + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(card_present_code: 'Present')) + end.check_request do |_endpoint, data, _headers| + assert_match 'Present', data + end.respond_with(successful_purchase_response) - response = @gateway.verify(@credit_card, @options) assert_success response end - def test_failed_verify - @gateway.expects(:ssl_post).returns(failed_authorize_response) + 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 - response = @gateway.verify(@credit_card, @options) - assert_failure response + def test_successful_purchase_with_payment_type + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(payment_type: 'NotUsed')) + end.check_request do |_endpoint, data, _headers| + assert_match 'NotUsed', data + end.respond_with(successful_purchase_response) + + assert_success response end - def test_handles_error_response - @gateway.expects(:ssl_post).returns(error_response) + def test_successful_purchase_with_submission_type + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(submission_type: 'NotUsed')) + end.check_request do |_endpoint, data, _headers| + assert_match 'NotUsed', data + end.respond_with(successful_purchase_response) - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.message, 'TargetNamespace required' - assert_failure response + assert_success response + end + + def test_successful_purchase_with_duplicate_check_disable_flag + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: true)) + end.check_request do |_endpoint, data, _headers| + assert_match 'True', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'true')) + end.check_request do |_endpoint, data, _headers| + assert_match 'True', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: false)) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'xxx')) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_check_disable_flag: 'False')) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + # when duplicate_check_disable_flag is NOT passed, should not be in XML at all + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_no_match %r(False), data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_duplicate_override_flag + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: true)) + end.check_request do |_endpoint, data, _headers| + assert_match 'True', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'true')) + end.check_request do |_endpoint, data, _headers| + assert_match 'True', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: false)) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'xxx')) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(duplicate_override_flag: 'False')) + end.check_request do |_endpoint, data, _headers| + assert_match 'False', data + end.respond_with(successful_purchase_response) + + assert_success response + + # when duplicate_override_flag is NOT passed, should not be in XML at all + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_no_match %r(False), data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_terminal_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(terminal_id: '02')) + end.check_request do |_endpoint, data, _headers| + assert_match '02', data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_merchant_descriptor + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(merchant_descriptor: 'Flowerpot Florists')) + end.check_request do |_endpoint, data, _headers| + assert_match 'Flowerpot Florists', data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_billing_email + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(email: 'test@example.com')) + end.check_request do |_endpoint, data, _headers| + assert_match 'test@example.com', data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_credit_with_extra_fields + credit_options = @options.merge({ ticket_number: '1', market_code: 'FoodRestaurant', merchant_supplied_transaction_id: '123' }) + stub_comms do + @gateway.credit(@amount, @credit_card, credit_options) + end.check_request do |_endpoint, data, _headers| + assert_match '1FoodRestaurant', data + assert_match '123', data + end.respond_with(successful_credit_response) end def test_scrub @@ -164,104 +382,116 @@ def test_scrub private def pre_scrubbed - <<-XML -\n\n \n \n \n 1013963\n 683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n 4000100011112224\n 09\n 16\n Longbob Longsen\n 123\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n

\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n
\n \n
\n
\n + <<~XML + \n\n \n \n \n 1013963\n 683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n 4000100011112224\n 09\n 16\n Longbob Longsen\n 123\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n
\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n
\n
\n
\n
\n XML end def post_scrubbed - <<-XML -\n\n \n \n \n 1013963\n [FILTERED]\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n [FILTERED]\n 09\n 16\n Longbob Longsen\n [FILTERED]\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n
\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n
\n
\n
\n
\n + <<~XML + \n\n \n \n \n 1013963\n [FILTERED]\n 3928907\n \n \n 5211\n Spreedly\n 1\n \n \n [FILTERED]\n 09\n 16\n Longbob Longsen\n [FILTERED]\n \n \n 1.00\n Default\n \n \n 01\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n UseDefault\n \n
\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n
\n
\n
\n
\n XML end def error_response - <<-XML -103TargetNamespace required + <<~XML + 103TargetNamespace required XML end def successful_purchase_response - <<-XML -0Approved20151201104518UTC-05:00000APRegularTotals1962962.00FullBatchCurrentDefaultNMVisa2005831886000045SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 0Approved20151201104518UTC-05:00000APRegularTotals1962962.00FullBatchCurrentDefaultNMVisa2005831886000045SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def successful_purchase_with_echeck_response - <<-XML -0Success20151202090320UTC-05:000Transaction ProcessedRegularTotalsFullBatchCurrentDefault2005838412347520966b3df3e93051b5dc85c355a54e3012c2SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTPending10FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 0Success20151202090320UTC-05:000Transaction ProcessedRegularTotalsFullBatchCurrentDefault2005838412347520966b3df3e93051b5dc85c355a54e3012c2SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTPending10FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def successful_purchase_with_payment_account_token_response - <<-XML -0Approved20151202090144UTC-05:00000APRegularTotals11552995.00FullBatchCurrentDefaultNVisa2005838405000001c0d498aa3c2c07169d13a989a7af91af5bc4e6a0SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownC875D86C-5913-487D-822E-76B27E2C2A4ECreditCard147b0b90f74faac13afb618fdabee3a4e75bf03bNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 0Approved20151202090144UTC-05:00000APRegularTotals11552995.00FullBatchCurrentDefaultNVisa2005838405000001c0d498aa3c2c07169d13a989a7af91af5bc4e6a0SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1False1.00DefaultUnknownC875D86C-5913-487D-822E-76B27E2C2A4ECreditCard147b0b90f74faac13afb618fdabee3a4e75bf03bNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ XML + end + + def successful_credit_response + <<~XML + 0Approved20211122174635UTC-06:00000APRegularTotals1102103.00FullBatchCurrentVisa4000101228162530000461SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownNotUsedNotUsedCreditCardNullNull
OneTimeFutureFalseActivePersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefault XML end def failed_purchase_with_echeck_response - <<-XML -101CardNumber Required20151202090342UTC-05:00RegularTotals1FullBatchCurrentDefault8fe3b762a2a4344d938c32be31f36e354fb28ee3SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 101CardNumber Required20151202090342UTC-05:00RegularTotals1FullBatchCurrentDefault8fe3b762a2a4344d938c32be31f36e354fb28ee3SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def failed_purchase_with_payment_account_token_response - <<-XML -103PAYMENT ACCOUNT NOT FOUND20151202090245UTC-05:00RegularTotals1FullBatchCurrentDefault564bd4943761a37bdbb3f201faa56faa091781b5SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownasdfCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 103PAYMENT ACCOUNT NOT FOUND20151202090245UTC-05:00RegularTotals1FullBatchCurrentDefault564bd4943761a37bdbb3f201faa56faa091781b5SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTFalseDefaultUnknownasdfCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def failed_purchase_response - <<-XML -20Declined20151201104817UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005831909SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 20Declined20151201104817UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005831909SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def successful_authorize_response - <<-XML -0Approved20151201120220UTC-05:00000APRegularTotals1FullBatchCurrentDefaultNMVisa2005832533000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTAuthorized5False1.00DefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 0Approved20151201120220UTC-05:00000APRegularTotals1FullBatchCurrentDefaultNMVisa2005832533000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTAuthorized5False1.00DefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def failed_authorize_response - <<-XML -20Declined20151201120315UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005832537SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 20Declined20151201120315UTC-05:00007DECLINEDRegularTotals1FullBatchCurrentDefaultNMVisa2005832537SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTDeclined2FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def successful_capture_response - <<-XML -0Success20151201120222UTC-05:00000APRegularTotals1972963.00FullBatchCurrentDefaultVisa2005832535000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 0Success20151201120222UTC-05:00000APRegularTotals1972963.00FullBatchCurrentDefaultVisa2005832535000002SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def failed_capture_response - <<-XML -101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def successful_refund_response - <<-XML -0Approved20151201120437UTC-05:00000APRegularTotals1992963.00FullBatchCurrentDefaultVisa2005832540000004SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
+ <<~XML + 0Approved20151201120437UTC-05:00000APRegularTotals1992963.00FullBatchCurrentDefaultVisa2005832540000004SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTApproved1FalseDefaultUnknownCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull
XML end def failed_refund_response - <<-XML -101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 101TransactionID requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def successful_void_response - <<-XML -0Success20151201120516UTC-05:00006REVERSEDRegularTotalsFullBatchCurrentDefaultVisa2005832551000005SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTSuccess8FalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 0Success20151201120516UTC-05:00006REVERSEDRegularTotalsFullBatchCurrentDefaultVisa2005832551000005SystemDefaultaVb001234567810425c0425d5e00FalseFalseFalseFalseNULL_PROCESSOR_TESTSuccess8FalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull XML end def failed_void_response - <<-XML -101TransactionAmount requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + <<~XML + 101TransactionAmount requiredUTC-05:00RegularTotalsFullBatchCurrentDefaultSystemDefaultFalseFalseFalseFalseFalseDefaultUnknownCreditCardNullNull
OneTimeFutureFalseActiveCheckingPersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefaultNull + XML + end + + def successful_verify_response + <<~XML + 0Success20200505094556UTC-05:00000APRegularTotalsFullBatchCurrentNVisa400010481381541SystemDefaultFalseFalseFalseFalseNULL_PROCESSOR_TESTSuccess8FalseDefaultUnknownNotUsedNotUsedCreditCardNullNull
456 My StreetK1C2N6
OneTimeFutureFalseActivePersonalNullNullFalseFalseFalseNotUsedUnknownUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultUseDefaultRegularNotUsedDefaultUnusedUnusedNoAdjustmentsFalseNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNotSpecifiedLedgerBalancePositiveNonParticipantDefaultDefault
XML end end diff --git a/test/unit/gateways/epay_test.rb b/test/unit/gateways/epay_test.rb index 59becd05499..91a03ed50e5 100644 --- a/test/unit/gateways/epay_test.rb +++ b/test/unit/gateways/epay_test.rb @@ -5,11 +5,12 @@ def setup Base.mode = :test @gateway = EpayGateway.new( - :login => '10100111001', - :password => 'http://example.com' + login: '10100111001', + password: 'http://example.com' ) @credit_card = credit_card + @options = { three_d_secure: { eci: '7', xid: '123', cavv: '456', version: '2', ds_transaction_id: '798' } } end def test_successful_purchase @@ -25,8 +26,23 @@ 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 + @gateway.expects(:raw_ssl_request).returns(valid_authorize_3ds_response) + + assert response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '123', response.authorization + end + + def test_failed_3ds_purchase + @gateway.expects(:raw_ssl_request).returns(invalid_authorize_3ds_response) + + assert response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '123', response.authorization end def test_invalid_characters_in_response @@ -34,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 @@ -105,13 +120,13 @@ def test_deprecated_credit def test_authorize_sends_order_number @gateway.expects(:raw_ssl_request).with(anything, anything, regexp_matches(/orderid=1234/), anything).returns(valid_authorize_response) - @gateway.authorize(100, '123', :order_id => '#1234') + @gateway.authorize(100, '123', order_id: '#1234') end def test_purchase_sends_order_number @gateway.expects(:raw_ssl_request).with(anything, anything, regexp_matches(/orderid=1234/), anything).returns(valid_authorize_response) - @gateway.purchase(100, '123', :order_id => '#1234') + @gateway.purchase(100, '123', order_id: '#1234') end def test_transcript_scrubbing @@ -132,6 +147,14 @@ def invalid_authorize_response_with_invalid_characters { 'Location' => 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1&error=209&errortext=The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.
Denied - Call your bank for information' } end + def valid_authorize_3ds_response + { 'Location' => 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1&tid=123&&amount=100&cur=208&date=20101117&time=2357&cardnopostfix=3000&fraud=1&cardid=18&transfee=0&eci=7&xci=123&cavv=456&threeds_version=2&ds_transaction_id=798' } + end + + def invalid_authorize_3ds_response + { 'Location' => 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1&tid=123&&amount=100&cur=208&date=20101117&time=2357&cardnopostfix=3000&fraud=1&cardid=18&transfee=0&eci=5&xci=1234&cavv=3456&threeds_version=1&ds_transaction_id=6798' } + end + def valid_capture_response 'true0-1' end diff --git a/test/unit/gateways/evo_ca_test.rb b/test/unit/gateways/evo_ca_test.rb index f2fe062e658..75b4513fa67 100644 --- a/test/unit/gateways/evo_ca_test.rb +++ b/test/unit/gateways/evo_ca_test.rb @@ -2,19 +2,19 @@ class EvoCaTest < Test::Unit::TestCase def setup - @gateway = EvoCaGateway.new(:username => 'demo', :password => 'password') + @gateway = EvoCaGateway.new(username: 'demo', password: 'password') @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :tracking_number => '123456789-0', - :shipping_carrier => 'fedex', - :email => 'evo@example.com', - :ip => '127.0.0.1' + order_id: '1', + billing_address: address, + description: 'Store Purchase', + tracking_number: '123456789-0', + shipping_carrier: 'fedex', + email: 'evo@example.com', + ip: '127.0.0.1' } end @@ -84,7 +84,7 @@ def test_successful_void def test_successful_update @gateway.expects(:ssl_post).returns(successful_update_response) - assert response = @gateway.update('1812639342', :tracking_number => '1234', :shipping_carrier => 'fedex') + assert response = @gateway.update('1812639342', tracking_number: '1234', shipping_carrier: 'fedex') assert_success response assert_equal '1812639342', response.authorization end @@ -99,7 +99,7 @@ def test_successful_refund def test_add_address result = {} - @gateway.send(:add_address, result, :address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'}) + @gateway.send(:add_address, result, address: { address1: '123 Main Street', country: 'CA', state: 'BC' }) assert_equal %w{address1 address2 city company country firstname lastname phone state zip}, result.stringify_keys.keys.sort assert_equal 'BC', result[:state] assert_equal '123 Main Street', result[:address1] @@ -109,7 +109,7 @@ def test_add_address def test_add_shipping_address result = {} - @gateway.send(:add_address, result, :shipping_address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'}) + @gateway.send(:add_address, result, shipping_address: { address1: '123 Main Street', country: 'CA', state: 'BC' }) assert_equal %w{shipping_address1 shipping_address2 shipping_city shipping_company shipping_country shipping_firstname shipping_lastname shipping_state shipping_zip}, result.stringify_keys.keys.sort assert_equal 'BC', result[:shipping_state] assert_equal '123 Main Street', result[:shipping_address1] diff --git a/test/unit/gateways/eway_managed_test.rb b/test/unit/gateways/eway_managed_test.rb index 02666e0bc71..a920ac70c67 100644 --- a/test/unit/gateways/eway_managed_test.rb +++ b/test/unit/gateways/eway_managed_test.rb @@ -4,10 +4,10 @@ class EwayManagedTest < Test::Unit::TestCase def setup Base.mode = :test - @gateway = EwayManagedGateway.new(:username => 'username', :login => 'login', :password => 'password') + @gateway = EwayManagedGateway.new(username: 'username', login: 'login', password: 'password') - @valid_card='4444333322221111' - @valid_customer_id='9876543211000' + @valid_card = '4444333322221111' + @valid_customer_id = '9876543211000' @credit_card = credit_card(@valid_card) @declined_card = credit_card('4444111111111111') @@ -15,60 +15,60 @@ def setup @amount = 100 @options = { - :billing_address => { - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'au', - :title => 'Mr.', - :phone => '(555)555-5555' + billing_address: { + address1: '1234 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'au', + title: 'Mr.', + phone: '(555)555-5555' }, - :email => 'someguy1232@fakeemail.net', - :order_id => '1000', - :customer => 'mycustomerref', - :description => 'My Description', - :invoice => 'invoice-4567' + email: 'someguy1232@fakeemail.net', + order_id: '1000', + customer: 'mycustomerref', + description: 'My Description', + invoice: 'invoice-4567' } end def test_should_require_billing_address_on_store assert_raise ArgumentError do - @gateway.store(@credit_card, { }) + @gateway.store(@credit_card, {}) end assert_raise ArgumentError do - @gateway.store(@credit_card, { :billing_address => {} }) + @gateway.store(@credit_card, { billing_address: {} }) end assert_raise ArgumentError do - @gateway.store(@credit_card, { :billing_address => { :title => 'Mr.' } }) + @gateway.store(@credit_card, { billing_address: { title: 'Mr.' } }) end assert_raise ArgumentError do - @gateway.store(@credit_card, { :billing_address => { :country => 'au' } }) + @gateway.store(@credit_card, { billing_address: { country: 'au' } }) end assert_nothing_raised do @gateway.expects(:ssl_post).returns(successful_store_response) - @gateway.store(@credit_card, { :billing_address => { :title => 'Mr.', :country => 'au' } }) + @gateway.store(@credit_card, { billing_address: { title: 'Mr.', country: 'au' } }) end end def test_should_require_billing_address_on_update assert_raise ArgumentError do - @gateway.update(@valid_customer_id, @credit_card, { }) + @gateway.update(@valid_customer_id, @credit_card, {}) end assert_raise ArgumentError do - @gateway.update(@valid_customer_id, @credit_card, { :billing_address => {} }) + @gateway.update(@valid_customer_id, @credit_card, { billing_address: {} }) end assert_raise ArgumentError do - @gateway.update(@valid_customer_id, @credit_card, { :billing_address => { :title => 'Mr.' } }) + @gateway.update(@valid_customer_id, @credit_card, { billing_address: { title: 'Mr.' } }) end assert_raise ArgumentError do - @gateway.update(@valid_customer_id, @credit_card, { :billing_address => { :country => 'au' } }) + @gateway.update(@valid_customer_id, @credit_card, { billing_address: { country: 'au' } }) end assert_nothing_raised do @gateway.expects(:ssl_post).returns(successful_update_response) - @gateway.update(@valid_customer_id, @credit_card, { :billing_address => { :title => 'Mr.', :country => 'au' } }) + @gateway.update(@valid_customer_id, @credit_card, { billing_address: { title: 'Mr.', country: 'au' } }) end end @@ -85,7 +85,7 @@ def test_successful_purchase end def test_expected_request_on_purchase - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| # Compare the actual and expected XML documents, by converting them to Hashes first expected = Hash.from_xml(expected_purchase_request) actual = Hash.from_xml(data) @@ -101,7 +101,7 @@ def test_purchase_invoice_reference_comes_from_order_id_or_invoice options[:order_id] = 'order_id' options.delete(:invoice) - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['ProcessPayment']['invoiceReference'] == 'order_id' }.returns(successful_purchase_response) @@ -111,7 +111,7 @@ def test_purchase_invoice_reference_comes_from_order_id_or_invoice options[:invoice] = 'invoice' options.delete(:order_id) - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['ProcessPayment']['invoiceReference'] == 'invoice' }.returns(successful_purchase_response) @@ -121,7 +121,7 @@ def test_purchase_invoice_reference_comes_from_order_id_or_invoice options[:order_id] = 'order_id' options[:invoice] = 'invoice' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['ProcessPayment']['invoiceReference'] == 'order_id' }.returns(successful_purchase_response) @@ -148,7 +148,7 @@ def test_successful_store end def test_expected_request_on_store - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| # Compare the actual and expected XML documents, by converting them to Hashes first expected = Hash.from_xml(expected_store_request) actual = Hash.from_xml(data) @@ -164,7 +164,7 @@ def test_email_on_store_may_come_from_options_root_or_billing_address options.delete(:email) options[:billing_address][:email] = 'email+billing@example.com' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['Email'] == 'email+billing@example.com' }.returns(successful_store_response) @@ -174,7 +174,7 @@ def test_email_on_store_may_come_from_options_root_or_billing_address options[:billing_address].delete(:email) options[:email] = 'email+root@example.com' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['Email'] == 'email+root@example.com' }.returns(successful_store_response) @@ -184,7 +184,7 @@ def test_email_on_store_may_come_from_options_root_or_billing_address options[:billing_address][:email] = 'email+billing@example.com' options[:email] = 'email+root@example.com' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['Email'] == 'email+billing@example.com' }.returns(successful_store_response) @@ -198,7 +198,7 @@ def test_customer_ref_on_store_may_come_from_options_root_or_billing_address options.delete(:customer) options[:billing_address][:customer_ref] = 'customer_ref+billing' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['CustomerRef'] == 'customer_ref+billing' }.returns(successful_store_response) @@ -208,7 +208,7 @@ def test_customer_ref_on_store_may_come_from_options_root_or_billing_address options[:billing_address].delete(:customer_ref) options[:customer] = 'customer_ref+root' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['CustomerRef'] == 'customer_ref+root' }.returns(successful_store_response) @@ -218,7 +218,7 @@ def test_customer_ref_on_store_may_come_from_options_root_or_billing_address options[:billing_address][:customer_ref] = 'customer_ref+billing' options[:customer] = 'customer_ref+root' - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['Envelope']['Body']['CreateCustomer']['CustomerRef'] == 'customer_ref+billing' }.returns(successful_store_response) @@ -246,7 +246,7 @@ def test_successful_retrieve end def test_expected_retrieve_response - @gateway.expects(:ssl_post).with { |endpoint, data, headers| + @gateway.expects(:ssl_post).with { |_endpoint, data, _headers| # Compare the actual and expected XML documents, by converting them to Hashes first expected = Hash.from_xml(expected_retrieve_request) actual = Hash.from_xml(data) @@ -262,53 +262,53 @@ def test_default_currency private def successful_purchase_response - <<-XML - - - - - - 00,Transaction Approved(Test Gateway) - True - 123456 - 100 - 123456 - - - - + <<~XML + + + + + + 00,Transaction Approved(Test Gateway) + True + 123456 + 100 + 123456 + + + + XML end def unsuccessful_authorization_response - <<-XML -soap:SenderLogin failed + <<~XML + soap:SenderLogin failed XML end def successful_store_response - <<-XML - - - - - 1234567 - - - + <<~XML + + + + + 1234567 + + + XML end def successful_update_response - <<-XML - - - - - true - - - + <<~XML + + + + + true + + + XML end @@ -322,8 +322,8 @@ def successful_retrieve_response #{@credit_card.first_name} #{@credit_card.last_name} #{@credit_card.number} - #{sprintf("%.2i", @credit_card.month)} - #{sprintf("%.4i", @credit_card.year)[-2..-1]} + #{sprintf('%.2i', @credit_card.month)} + #{sprintf('%.4i', @credit_card.year)[-2..-1]} @@ -333,66 +333,66 @@ def successful_retrieve_response # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=CreateCustomer def expected_store_request - <<-XML - - - - - login - username - password - - - - - Mr. - #{@credit_card.first_name} - #{@credit_card.last_name} -
#{@options[:billing_address][:address1]}
- #{@options[:billing_address][:city]} - #{@options[:billing_address][:state]} - #{@options[:billing_address][:company]} - #{@options[:billing_address][:zip]} - #{@options[:billing_address][:country]} - #{@options[:email]} - - #{@options[:billing_address][:phone]} - - #{@options[:customer]} - - #{@options[:description]} - - #{@credit_card.number} - #{@credit_card.first_name} #{@credit_card.last_name} - #{sprintf("%.2i", @credit_card.month)} - #{sprintf("%.4i", @credit_card.year)[-2..-1]} -
-
-
+ <<~XML + + + + + login + username + password + + + + + Mr. + #{@credit_card.first_name} + #{@credit_card.last_name} +
#{@options[:billing_address][:address1]}
+ #{@options[:billing_address][:city]} + #{@options[:billing_address][:state]} + #{@options[:billing_address][:company]} + #{@options[:billing_address][:zip]} + #{@options[:billing_address][:country]} + #{@options[:email]} + + #{@options[:billing_address][:phone]} + + #{@options[:customer]} + + #{@options[:description]} + + #{@credit_card.number} + #{@credit_card.first_name} #{@credit_card.last_name} + #{sprintf('%.2i', @credit_card.month)} + #{sprintf('%.4i', @credit_card.year)[-2..-1]} +
+
+
XML end # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=CreateCustomer def expected_purchase_request - <<-XML - - - - - login - username - password - - - - - #{@valid_customer_id} - #{@amount} - #{@options[:order_id] || @options[:invoice]} - #{@options[:description]} - - - + <<~XML + + + + + login + username + password + + + + + #{@valid_customer_id} + #{@amount} + #{@options[:order_id] || @options[:invoice]} + #{@options[:description]} + + + XML end diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb index 15788011e99..ec70917a51a 100644 --- a/test/unit/gateways/eway_rapid_test.rb +++ b/test/unit/gateways/eway_rapid_test.rb @@ -6,12 +6,42 @@ class EwayRapidTest < Test::Unit::TestCase def setup ActiveMerchant::Billing::EwayRapidGateway.partner_id = nil @gateway = EwayRapidGateway.new( - :login => 'login', - :password => 'password' + login: 'login', + password: 'password' ) @credit_card = credit_card @amount = 100 + + @address = { + name: 'John Smith', + title: 'Test Title', + company: 'Test Company, Inc.', + address1: '14701 Test Road', + address2: 'Test Unit 100', + city: 'TestCity', + state: 'NC', + zip: '27517', + country: 'USA', + phone: '555-555-1000', + fax: '555-555-2000' + } + + @shipping_address = { + name: 'John Smith', + title: 'Test Title', + company: 'Test Company, Inc.', + address1: '14701 Test Road', + address2: 'Test Unit 100', + city: 'TestCity', + state: 'NC', + zip: '27517', + country: 'USA', + phone_number: '555-555-1000', + fax: '555-555-2000' + } + + @email = 'john.smith@example.com' end def test_successful_purchase @@ -25,16 +55,106 @@ def test_successful_purchase assert response.test? end + def test_purchase_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.purchase(@amount, @credit_card, { email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_customer_data_from_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, { billing_address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_customer_data_from_address + stub_comms do + @gateway.purchase(@amount, @credit_card, { address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_shipping_data + stub_comms do + @gateway.purchase(@amount, @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_purchase_response) + end + + def test_purchase_3ds1_data + eci = '05' + cavv = 'AgAAAAAA4n1uzQPRaATeQAAAAAA=' + xid = 'AAAAAAAA4n1uzQPRaATeQAAAAAA=' + authentication_response_status = 'Y' + options_with_3ds1 = { + eci: eci, + cavv: cavv, + xid: xid, + authentication_response_status: authentication_response_status + } + + stub_comms do + @gateway.purchase(@amount, @credit_card, { three_d_secure: options_with_3ds1 }) + end.check_request do |_endpoint, data, _headers| + assert_3ds_data_passed(data, options_with_3ds1) + end.respond_with(successful_purchase_response) + end + + def test_purchase_3ds2_data + eci = '05' + cavv = 'AgAAAAAA4n1uzQPRaATeQAAAAAA=' + authentication_response_status = 'Y' + version = '2.1.0' + ds_transaction_id = '8fe2e850-a028-407e-9a18-c8cf7598ca10' + + options_with_3ds2 = { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + authentication_response_status: authentication_response_status + } + + stub_comms do + @gateway.purchase(@amount, @credit_card, { three_d_secure: options_with_3ds2 }) + end.check_request do |_endpoint, data, _headers| + assert_3ds_data_passed(data, options_with_3ds2) + end.respond_with(successful_purchase_response) + end + def test_localized_currency stub_comms do - @gateway.purchase(100, @credit_card, :currency => 'CAD') - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, @credit_card, currency: 'CAD') + end.check_request do |_endpoint, data, _headers| assert_match '"TotalAmount":"100"', data end.respond_with(successful_purchase_response) stub_comms do - @gateway.purchase(100, @credit_card, :currency => 'JPY') - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, @credit_card, currency: 'JPY') + end.check_request do |_endpoint, data, _headers| assert_match '"TotalAmount":"1"', data end.respond_with(successful_purchase_response) end @@ -74,45 +194,47 @@ def test_failed_purchase_with_multiple_messages def test_purchase_with_all_options response = stub_comms do - @gateway.purchase(200, @credit_card, - :transaction_type => 'CustomTransactionType', - :redirect_url => 'http://awesomesauce.com', - :ip => '0.0.0.0', - :application_id => 'Woohoo', - :partner_id => 'SomePartner', - :description => 'The Really Long Description More Than Sixty Four Characters Gets Truncated', - :order_id => 'orderid1', - :invoice => 'I1234', - :currency => 'INR', - :email => 'jim@example.com', - :billing_address => { - :title => 'Mr.', - :name => 'Jim Awesome Smith', - :company => 'Awesome Co', - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'CA', - :phone => '(555)555-5555', - :fax => '(555)555-6666' + @gateway.purchase( + 200, + @credit_card, + transaction_type: 'CustomTransactionType', + redirect_url: 'http://awesomesauce.com', + ip: '0.0.0.0', + application_id: 'Woohoo', + partner_id: 'SomePartner', + description: 'The Really Long Description More Than Sixty Four Characters Gets Truncated', + order_id: 'orderid1', + invoice: 'I1234', + currency: 'INR', + email: 'jim@example.com', + billing_address: { + title: 'Mr.', + name: 'Jim Awesome Smith', + company: 'Awesome Co', + address1: '1234 My Street', + address2: 'Apt 1', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' }, - :shipping_address => { - :title => 'Ms.', - :name => 'Baker', - :company => 'Elsewhere Inc.', - :address1 => '4321 Their St.', - :address2 => 'Apt 2', - :city => 'Chicago', - :state => 'IL', - :zip => '60625', - :country => 'US', - :phone => '1115555555', - :fax => '1115556666' + shipping_address: { + title: 'Ms.', + name: 'Baker', + company: 'Elsewhere Inc.', + address1: '4321 Their St.', + address2: 'Apt 2', + city: 'Chicago', + state: 'IL', + zip: '60625', + country: 'US', + phone: '1115555555', + fax: '1115556666' } ) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{"TransactionType":"CustomTransactionType"}, data) assert_match(%r{"RedirectUrl":"http://awesomesauce.com"}, data) assert_match(%r{"CustomerIP":"0.0.0.0"}, data) @@ -150,7 +272,7 @@ def test_purchase_with_all_options assert_match(%r{"Country":"us"}, data) assert_match(%r{"Phone":"1115555555"}, data) assert_match(%r{"Fax":"1115556666"}, data) - assert_match(%r{"Email":null}, data) + assert_match(%r{"Email":"jim@example\.com"}, data) end.respond_with(successful_purchase_response) assert_success response @@ -162,7 +284,7 @@ def test_partner_id_class_attribute ActiveMerchant::Billing::EwayRapidGateway.partner_id = 'SomePartner' stub_comms do @gateway.purchase(200, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{"PartnerID":"SomePartner"}, data) end.respond_with(successful_purchase_response) end @@ -171,7 +293,7 @@ def test_partner_id_params_overrides_class_attribute ActiveMerchant::Billing::EwayRapidGateway.partner_id = 'SomePartner' stub_comms do @gateway.purchase(200, @credit_card, partner_id: 'OtherPartner') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{"PartnerID":"OtherPartner"}, data) end.respond_with(successful_purchase_response) end @@ -179,7 +301,7 @@ def test_partner_id_params_overrides_class_attribute def test_partner_id_is_omitted_when_not_set stub_comms do @gateway.purchase(200, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{"PartnerID":}, data) end.respond_with(successful_purchase_response) end @@ -188,7 +310,7 @@ def test_partner_id_truncates_to_50_characters partner_string = 'EWay Rapid PartnerID is capped at 50 characters and will truncate if it is too long.' stub_comms do @gateway.purchase(200, @credit_card, partner_id: partner_string) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{"PartnerID":"#{partner_string.slice(0, 50)}"}, data) end.respond_with(successful_purchase_response) end @@ -203,6 +325,55 @@ def test_successful_authorize assert_equal 10774952, response.authorization end + def test_authorize_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.authorize(@amount, @credit_card, { email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_customer_data_from_billing_address + stub_comms do + @gateway.authorize(@amount, @credit_card, { billing_address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_customer_data_from_address + stub_comms do + @gateway.authorize(@amount, @credit_card, { address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_shipping_data + stub_comms do + @gateway.authorize(@amount, @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_authorize_response) + end + def test_successful_capture response = stub_comms do @gateway.capture(nil, 'auth') @@ -255,20 +426,20 @@ def test_failed_void def test_successful_store response = stub_comms do - @gateway.store(@credit_card, :billing_address => { - :title => 'Mr.', - :name => 'Jim Awesome Smith', - :company => 'Awesome Co', - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'CA', - :phone => '(555)555-5555', - :fax => '(555)555-6666' - }) - end.check_request do |endpoint, data, headers| + @gateway.store(@credit_card, billing_address: { + title: 'Mr.', + name: 'Jim Awesome Smith', + company: 'Awesome Co', + address1: '1234 My Street', + address2: 'Apt 1', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }) + end.check_request do |_endpoint, data, _headers| assert_match '"Method":"CreateTokenCustomer"', data end.respond_with(successful_store_response) @@ -278,9 +449,34 @@ def test_successful_store assert response.test? end + def test_store_passes_customer_data_from_billing_address + stub_comms do + @gateway.store(@credit_card, { billing_address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_store_response) + end + + def test_store_passes_shipping_data + stub_comms do + @gateway.store( + @credit_card, + { shipping_address: @shipping_address, billing_address: @address, email: @email } + ) + end.check_request do |_endpoint, data, _headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_store_response) + end + def test_failed_store response = stub_comms do - @gateway.store(@credit_card, :billing_address => {}) + @gateway.store(@credit_card, billing_address: {}) end.respond_with(failed_store_response) assert_failure response @@ -292,7 +488,7 @@ def test_failed_store def test_successful_update response = stub_comms do @gateway.update('faketoken', nil) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '"Method":"UpdateTokenCustomer"', data end.respond_with(successful_update_response) @@ -302,10 +498,59 @@ def test_successful_update assert response.test? end + def test_update_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.update('token', @credit_card, { email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_customer_data_from_billing_address + stub_comms do + @gateway.update('token', @credit_card, { billing_address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_customer_data_from_address + stub_comms do + @gateway.update('token', @credit_card, { address: @address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_shipping_data + stub_comms do + @gateway.update('token', @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |_endpoint, data, _headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_update_response) + end + def test_successful_refund response = stub_comms do @gateway.refund(@amount, '1234567') - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| assert_match %r{Transaction\/1234567\/Refund$}, endpoint json = JSON.parse(data) assert_equal '100', json['Refund']['TotalAmount'] @@ -332,7 +577,7 @@ def test_failed_refund def test_successful_stored_card_purchase response = stub_comms do @gateway.purchase(100, 'the_customer_token', transaction_type: 'MOTO') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '"Method":"TokenPayment"', data assert_match '"TransactionType":"MOTO"', data end.respond_with(successful_store_purchase_response) @@ -346,7 +591,7 @@ def test_successful_stored_card_purchase def test_verification_results response = stub_comms do @gateway.purchase(100, @credit_card) - end.respond_with(successful_purchase_response(:verification_status => 'Valid')) + end.respond_with(successful_purchase_response(verification_status: 'Valid')) assert_success response assert_equal 'M', response.cvv_result['code'] @@ -354,7 +599,7 @@ def test_verification_results response = stub_comms do @gateway.purchase(100, @credit_card) - end.respond_with(successful_purchase_response(:verification_status => 'Invalid')) + end.respond_with(successful_purchase_response(verification_status: 'Invalid')) assert_success response assert_equal 'N', response.cvv_result['code'] @@ -362,7 +607,7 @@ def test_verification_results response = stub_comms do @gateway.purchase(100, @credit_card) - end.respond_with(successful_purchase_response(:verification_status => 'Unchecked')) + end.respond_with(successful_purchase_response(verification_status: 'Unchecked')) assert_success response assert_equal 'P', response.cvv_result['code'] @@ -375,6 +620,58 @@ def test_transcript_scrubbing private + def assert_customer_data_passed(data, first_name, last_name, email, address = nil) + parsed_data = JSON.parse(data) + customer = parsed_data['Customer'] + + assert_equal customer['FirstName'], first_name + assert_equal customer['LastName'], last_name + assert_equal customer['Email'], email + + if address + assert_equal customer['Title'], address[:title] + assert_equal customer['CompanyName'], address[:company] + assert_equal customer['Street1'], address[:address1] + assert_equal customer['Street2'], address[:address2] + assert_equal customer['City'], address[:city] + assert_equal customer['State'], address[:state] + assert_equal customer['PostalCode'], address[:zip] + assert_equal customer['Country'], address[:country].downcase + assert_equal customer['Phone'], address[:phone] + assert_equal customer['Fax'], address[:fax] + end + end + + def assert_3ds_data_passed(data, threedsoption) + parsed_data = JSON.parse(data) + threeds = parsed_data['PaymentInstrument']['ThreeDSecureAuth'] + + assert_equal threeds['Cryptogram'], threedsoption[:cavv] + assert_equal threeds['ECI'], threedsoption[:eci] + assert_equal threeds['XID'], threedsoption[:xid] + assert_equal threeds['AuthStatus'], threedsoption[:authentication_response_status] + assert_equal threeds['dsTransactionId'], threedsoption[:ds_transaction_id] + assert_equal threeds['Version'], threedsoption[:version] + end + + def assert_shipping_data_passed(data, address, email) + parsed_data = JSON.parse(data) + shipping = parsed_data['ShippingAddress'] + + assert_equal shipping['FirstName'], address[:name].split[0] + assert_equal shipping['LastName'], address[:name].split[1] + assert_equal shipping['Title'], address[:title] + assert_equal shipping['Street1'], address[:address1] + assert_equal shipping['Street2'], address[:address2] + assert_equal shipping['City'], address[:city] + assert_equal shipping['State'], address[:state] + assert_equal shipping['PostalCode'], address[:zip] + assert_equal shipping['Country'], address[:country].downcase + assert_equal shipping['Phone'], address[:phone_number] + assert_equal shipping['Fax'], address[:fax] + assert_equal shipping['Email'], email + end + def successful_purchase_response(options = {}) verification_status = options[:verification_status] || 0 verification_status = %Q{"#{verification_status}"} if verification_status.is_a? String diff --git a/test/unit/gateways/eway_test.rb b/test/unit/gateways/eway_test.rb index 5c1059163e5..898567d50b6 100644 --- a/test/unit/gateways/eway_test.rb +++ b/test/unit/gateways/eway_test.rb @@ -3,7 +3,7 @@ class EwayTest < Test::Unit::TestCase def setup @gateway = EwayGateway.new( - :login => '87654321' + login: '87654321' ) @amount = 100 @@ -11,17 +11,17 @@ def setup @credit_card = credit_card('4646464646464646') @options = { - :order_id => '1230123', - :email => 'bob@testbob.com', - :billing_address => { - :address1 => '1234 First St.', - :address2 => 'Apt. 1', - :city => 'Melbourne', - :state => 'ACT', - :country => 'AU', - :zip => '12345' + order_id: '1230123', + email: 'bob@testbob.com', + billing_address: { + address1: '1234 First St.', + address2: 'Apt. 1', + city: 'Melbourne', + state: 'ACT', + country: 'AU', + zip: '12345' }, - :description => 'purchased items' + description: 'purchased items' } end diff --git a/test/unit/gateways/exact_test.rb b/test/unit/gateways/exact_test.rb index 664f3d27ebf..2986777bfa6 100644 --- a/test/unit/gateways/exact_test.rb +++ b/test/unit/gateways/exact_test.rb @@ -2,15 +2,15 @@ class ExactTest < Test::Unit::TestCase def setup - @gateway = ExactGateway.new(:login => 'A00427-01', - :password => 'testus') + @gateway = ExactGateway.new(login: 'A00427-01', + password: 'testus') @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -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 @@ -63,11 +64,11 @@ def test_soap_fault end def test_supported_countries - assert_equal ['CA', 'US'], ExactGateway.supported_countries + assert_equal %w[CA US], ExactGateway.supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :jcb, :discover], ExactGateway.supported_cardtypes + assert_equal %i[visa master american_express jcb discover], ExactGateway.supported_cardtypes end def test_avs_result @@ -87,120 +88,120 @@ def test_cvv_result private def successful_purchase_response - <<-RESPONSE -A00427-01#######00104242424242424242106625152ET17000909Longbob Longsen123100001Store Purchase0Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}0falsetrue00Transaction Normal00VER UNAVAILABLE 377UM200801181700E-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= + <<~RESPONSE + A00427-01#######00104242424242424242106625152ET17000909Longbob Longsen123100001Store Purchase0Processed by: + E-xact Transaction Gateway :- Version 8.4.0 B19b + Copyright 2006 + {34:2652}0falsetrue00Transaction Normal00VER UNAVAILABLE 377UM200801181700E-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com + E-xact ConnectionShop + Suite 400 - 1152 Mainland St. + Vancouver, BC V6B 4X2 + www.e-xact.com -TYPE: Purchase + TYPE: Purchase -ACCT: Visa $1.00 USD + ACCT: Visa $1.00 USD -CARD NUMBER : ############4242 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:17:00 -REFERENCE # : 5999 377 M -AUTHOR.# : ET1700 + CARD NUMBER : ############4242 + TRANS. REF. : 1 + CARD HOLDER : Longbob Longsen + EXPIRY DATE : xx/xx + DATE/TIME : 18 Jan 08 14:17:00 + REFERENCE # : 5999 377 M + AUTHOR.# : ET1700 - Approved - Thank You 00 + Approved - Thank You 00 -SIGNATURE + SIGNATURE -_______________________________________ + _______________________________________ - + RESPONSE end def successful_refund_response - <<-RESPONSE -A00427-01#######00104242424242424242106625152ET17000909Longbob Longsen123100001Store Purchase0Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}0falsetrue00Transaction Normal00VER UNAVAILABLE 377UM200801181700E-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= + <<~RESPONSE + A00427-01#######00104242424242424242106625152ET17000909Longbob Longsen123100001Store Purchase0Processed by: + E-xact Transaction Gateway :- Version 8.4.0 B19b + Copyright 2006 + {34:2652}0falsetrue00Transaction Normal00VER UNAVAILABLE 377UM200801181700E-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com + E-xact ConnectionShop + Suite 400 - 1152 Mainland St. + Vancouver, BC V6B 4X2 + www.e-xact.com -TYPE: Refund + TYPE: Refund -ACCT: Visa $1.00 USD + ACCT: Visa $1.00 USD -CARD NUMBER : ############4242 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:17:00 -REFERENCE # : 5999 377 M + CARD NUMBER : ############4242 + TRANS. REF. : 1 + CARD HOLDER : Longbob Longsen + EXPIRY DATE : xx/xx + DATE/TIME : 18 Jan 08 14:17:00 + REFERENCE # : 5999 377 M - Approved - Thank You 00 + Approved - Thank You 00 -SIGNATURE + SIGNATURE -Please retain this copy for your records. + Please retain this copy for your records. - ========================================= + ========================================= - + RESPONSE end def failed_purchase_response - <<-RESPONSE -A00427-01#######005013041111111111111111066246680909Longbob Longsen123100001Store Purchase0Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}0falsefalse00Transaction Normal13AMOUNT ERR376UME-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= + <<~RESPONSE + A00427-01#######005013041111111111111111066246680909Longbob Longsen123100001Store Purchase0Processed by: + E-xact Transaction Gateway :- Version 8.4.0 B19b + Copyright 2006 + {34:2652}0falsefalse00Transaction Normal13AMOUNT ERR376UME-xact ConnectionShopSuite 400 - 1152 Mainland St.VancouverBCCanadaV6B 4X2www.e-xact.com========== TRANSACTION RECORD ========= -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com + E-xact ConnectionShop + Suite 400 - 1152 Mainland St. + Vancouver, BC V6B 4X2 + www.e-xact.com -TYPE: Purchase + TYPE: Purchase -ACCT: Visa $5,013.00 USD + ACCT: Visa $5,013.00 USD -CARD NUMBER : ############1111 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:11:09 -REFERENCE # : 5999 376 M -AUTHOR.# : + CARD NUMBER : ############1111 + TRANS. REF. : 1 + CARD HOLDER : Longbob Longsen + EXPIRY DATE : xx/xx + DATE/TIME : 18 Jan 08 14:11:09 + REFERENCE # : 5999 376 M + AUTHOR.# : - Transaction not Approved 13 + Transaction not Approved 13 - + RESPONSE end def soap_fault_response - <<-RESPONSE - - - - - soap:Client - Unable to handle request without a valid action parameter. Please supply a valid soap action. - - - - + <<~RESPONSE + + + + + soap:Client + Unable to handle request without a valid action parameter. Please supply a valid soap action. + + + + RESPONSE end end diff --git a/test/unit/gateways/ezic_test.rb b/test/unit/gateways/ezic_test.rb index 5fa29b4004b..d79f3ece845 100644 --- a/test/unit/gateways/ezic_test.rb +++ b/test/unit/gateways/ezic_test.rb @@ -166,5 +166,4 @@ def failed_void_response def successful_authorize_raw_response MockResponse.succeeded(successful_authorize_response) end - end diff --git a/test/unit/gateways/fat_zebra_test.rb b/test/unit/gateways/fat_zebra_test.rb index 4b319c1ce18..4e3e8a2b00f 100644 --- a/test/unit/gateways/fat_zebra_test.rb +++ b/test/unit/gateways/fat_zebra_test.rb @@ -5,17 +5,18 @@ class FatZebraTest < Test::Unit::TestCase def setup @gateway = FatZebraGateway.new( - :username => 'TEST', - :token => 'TEST' - ) + username: 'TEST', + token: 'TEST' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => rand(10000), - :billing_address => address, - :description => 'Store Purchase' + order_id: rand(10000), + billing_address: address, + description: 'Store Purchase', + extra: { card_on_file: false } } end @@ -29,8 +30,20 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_metadata + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| + body.match '"metadata":{"foo":"bar"}' + }.returns(successful_purchase_response_with_metadata) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(metadata: { 'foo' => 'bar' })) + assert_success response + + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + def test_successful_purchase_with_token - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| body.match '"card_token":"e1q7dbj2"' }.returns(successful_purchase_response) @@ -42,7 +55,7 @@ def test_successful_purchase_with_token end def test_successful_purchase_with_token_string - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| body.match '"card_token":"e1q7dbj2"' }.returns(successful_purchase_response) @@ -54,11 +67,11 @@ def test_successful_purchase_with_token_string end def test_successful_multi_currency_purchase - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| body.match '"currency":"USD"' }.returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(:currency => 'USD')) + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(currency: 'USD')) assert_success response assert_equal '001-P-12345AA|purchases', response.authorization @@ -68,18 +81,18 @@ def test_successful_multi_currency_purchase def test_successful_purchase_with_recurring_flag stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options.merge(recurring: true)) - end.check_request do |method, endpoint, data, headers| - assert_match(%r("extra":{"ecm":"32"}), data) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r("extra":{"ecm":"32"), data) end.respond_with(successful_purchase_response) end def test_successful_purchase_with_descriptor - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| json = JSON.parse(body) json['extra']['name'] == 'Merchant' && json['extra']['location'] == 'Location' }.returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(:merchant => 'Merchant', :merchant_location => 'Location')) + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(merchant: 'Merchant', merchant_location: 'Location')) assert_success response assert_equal '001-P-12345AA|purchases', response.authorization @@ -87,7 +100,7 @@ def test_successful_purchase_with_descriptor end def test_successful_authorization - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, _url, body, _headers| body.match '"capture":false' }.returns(successful_purchase_response) @@ -99,7 +112,7 @@ def test_successful_authorization end def test_successful_capture - @gateway.expects(:ssl_request).with { |method, url, body, headers| + @gateway.expects(:ssl_request).with { |_method, url, _body, _headers| url =~ %r[purchases/e1q7dbj2/capture\z] }.returns(successful_purchase_response) @@ -157,6 +170,26 @@ def test_unsuccessful_tokenization assert_failure response end + def test_successful_tokenization_without_cvv + credit_card = @credit_card + credit_card.verification_value = nil + @gateway.expects(:ssl_request).returns(successful_no_cvv_tokenize_response) + + assert response = @gateway.store(credit_card, recurring: true) + assert_success response + assert_equal 'ep3c05nzsqvft15wsf1z|credit_cards', response.authorization + end + + def test_unsuccessful_tokenization_without_cvv + credit_card = @credit_card + credit_card.verification_value = nil + @gateway.expects(:ssl_request).returns(failed_no_cvv_tokenize_response) + + assert response = @gateway.store(credit_card) + assert_failure response + assert_equal 'CVV is required', response.message + end + def test_successful_refund @gateway.expects(:ssl_request).returns(successful_refund_response) @@ -182,197 +215,296 @@ def test_scrub private def pre_scrubbed - <<-'PRE_SCRUBBED' -opening connection to gateway.sandbox.fatzebra.com.au:443... -opened -starting SSL for gateway.sandbox.fatzebra.com.au:443... -SSL established -<- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic VEVTVDpURVNU\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" -<- "{\"card_number\":\"5123456789012346\",\"card_expiry\":\"5/2017\",\"cvv\":\"111\",\"card_holder\":\"Foo Bar\"}" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: application/json; charset=utf-8\r\n" --> "Connection: close\r\n" --> "Status: 200 OK\r\n" --> "Cache-control: no-store\r\n" --> "Pragma: no-cache\r\n" --> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" --> "X-Runtime: 0.142463\r\n" --> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" --> "X-Rack-Cache: invalidate, pass\r\n" --> "X-Sandbox: true\r\n" --> "X-Backend-Server: app-3\r\n" --> "\r\n" -reading all... --> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"512345XXXXXX2346\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" -read 214 bytes -Conn close + <<~'PRE_SCRUBBED' + opening connection to gateway.sandbox.fatzebra.com.au:443... + opened + starting SSL for gateway.sandbox.fatzebra.com.au:443... + SSL established + <- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic VEVTVDpURVNU\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" + <- "{\"card_number\":\"5123456789012346\",\"card_expiry\":\"5/2017\",\"cvv\":\"111\",\"card_holder\":\"Foo Bar\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Connection: close\r\n" + -> "Status: 200 OK\r\n" + -> "Cache-control: no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" + -> "X-Runtime: 0.142463\r\n" + -> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" + -> "X-Rack-Cache: invalidate, pass\r\n" + -> "X-Sandbox: true\r\n" + -> "X-Backend-Server: app-3\r\n" + -> "\r\n" + reading all... + -> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"512345XXXXXX2346\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" + read 214 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-'POST_SCRUBBED' -opening connection to gateway.sandbox.fatzebra.com.au:443... -opened -starting SSL for gateway.sandbox.fatzebra.com.au:443... -SSL established -<- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" -<- "{\"card_number\":\"[FILTERED]\",\"card_expiry\":\"5/2017\",\"cvv\":\"[FILTERED]\",\"card_holder\":\"Foo Bar\"}" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: application/json; charset=utf-8\r\n" --> "Connection: close\r\n" --> "Status: 200 OK\r\n" --> "Cache-control: no-store\r\n" --> "Pragma: no-cache\r\n" --> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" --> "X-Runtime: 0.142463\r\n" --> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" --> "X-Rack-Cache: invalidate, pass\r\n" --> "X-Sandbox: true\r\n" --> "X-Backend-Server: app-3\r\n" --> "\r\n" -reading all... --> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"[FILTERED]\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" -read 214 bytes -Conn close + <<~'POST_SCRUBBED' + opening connection to gateway.sandbox.fatzebra.com.au:443... + opened + starting SSL for gateway.sandbox.fatzebra.com.au:443... + SSL established + <- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" + <- "{\"card_number\":\"[FILTERED]\",\"card_expiry\":\"5/2017\",\"cvv\":\"[FILTERED]\",\"card_holder\":\"Foo Bar\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Connection: close\r\n" + -> "Status: 200 OK\r\n" + -> "Cache-control: no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" + -> "X-Runtime: 0.142463\r\n" + -> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" + -> "X-Rack-Cache: invalidate, pass\r\n" + -> "X-Sandbox: true\r\n" + -> "X-Backend-Server: app-3\r\n" + -> "\r\n" + reading all... + -> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"[FILTERED]\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" + read 214 bytes + Conn close POST_SCRUBBED end # Place raw successful response from gateway here def successful_purchase_response { - :successful => true, - :response => { - :authorization => '55355', - :id => '001-P-12345AA', - :card_number => 'XXXXXXXXXXXX1111', - :card_holder => 'John Smith', - :card_expiry => '10/2011', - :card_token => 'a1bhj98j', - :amount => 349, - :successful => true, - :reference => 'ABC123', - :message => 'Approved', + successful: true, + response: { + authorization: 55355, + id: '001-P-12345AA', + card_number: 'XXXXXXXXXXXX1111', + card_holder: 'John Smith', + card_expiry: '10/2011', + card_token: 'a1bhj98j', + amount: 349, + decimal_amount: 3.49, + successful: true, + message: 'Approved', + reference: 'ABC123', + currency: 'AUD', + transaction_id: '001-P-12345AA', + settlement_date: '2011-07-01', + transaction_date: '2011-07-01T12:00:00+11:00', + response_code: '08', + captured: true, + captured_amount: 349, + rrn: '000000000000', + cvv_match: 'U', + metadata: { + } }, - :test => true, - :errors => [] + test: true, + errors: [] + }.to_json + end + + def successful_purchase_response_with_metadata + { + successful: true, + response: { + authorization: 55355, + id: '001-P-12345AA', + card_number: 'XXXXXXXXXXXX1111', + card_holder: 'John Smith', + card_expiry: '2011-10-31', + card_token: 'a1bhj98j', + amount: 349, + decimal_amount: 3.49, + successful: true, + message: 'Approved', + reference: 'ABC123', + currency: 'AUD', + transaction_id: '001-P-12345AA', + settlement_date: '2011-07-01', + transaction_date: '2011-07-01T12:00:00+11:00', + response_code: '08', + captured: true, + captured_amount: 349, + rrn: '000000000000', + cvv_match: 'U', + metadata: { + 'foo' => 'bar' + } + }, + test: true, + errors: [] }.to_json end def declined_purchase_response { - :successful => true, - :response => { - :authorization_id => nil, - :id => nil, - :card_number => 'XXXXXXXXXXXX1111', - :card_holder => 'John Smith', - :card_expiry => '10/2011', - :amount => 100, - :authorized => false, - :reference => 'ABC123', - :message => 'Card Declined - check with issuer', + successful: true, + response: { + authorization: 0, + id: '001-P-12345AB', + card_number: 'XXXXXXXXXXXX1111', + card_holder: 'John Smith', + card_expiry: '10/2011', + amount: 100, + authorized: false, + reference: 'ABC123', + decimal_amount: 1.0, + successful: false, + message: 'Card Declined - check with issuer', + currency: 'AUD', + transaction_id: '001-P-12345AB', + settlement_date: nil, + transaction_date: '2011-07-01T12:00:00+11:00', + response_code: '01', + captured: false, + captured_amount: 0, + rrn: '000000000001', + cvv_match: 'U', + metadata: { + } }, - :test => true, - :errors => [] + test: true, + errors: [] }.to_json end def successful_refund_response { - :successful => true, - :response => { - :authorization => '1339973263', - :id => '003-R-7MNIUMY6', - :amount => -10, - :refunded => 'Approved', - :message => '08 Approved', - :card_holder => 'Harry Smith', - :card_number => 'XXXXXXXXXXXX4444', - :card_expiry => '2013-05-31', - :card_type => 'MasterCard', - :transaction_id => '003-R-7MNIUMY6', - :successful => true + successful: true, + response: { + authorization: 1339973263, + id: '003-R-7MNIUMY6', + amount: 10, + refunded: 'Approved', + message: 'Approved', + card_holder: 'Harry Smith', + card_number: 'XXXXXXXXXXXX4444', + card_expiry: '2013-05-31', + card_type: 'MasterCard', + transaction_id: '003-R-7MNIUMY6', + reference: '18280', + currency: 'USD', + successful: true, + transaction_date: '2013-07-01T12:00:00+11:00', + response_code: '08', + settlement_date: '2013-07-01', + metadata: { + }, + standalone: false, + rrn: '000000000002' }, - :errors => [ - - ], - :test => true + errors: [], + test: true }.to_json end def unsuccessful_refund_response { - :successful => false, - :response => { - :authorization => nil, - :id => nil, - :amount => nil, - :refunded => nil, - :message => nil, - :card_holder => 'Matthew Savage', - :card_number => 'XXXXXXXXXXXX4444', - :card_expiry => '2013-05-31', - :card_type => 'MasterCard', - :transaction_id => nil, - :successful => false + successful: false, + response: { + authorization: nil, + id: nil, + amount: nil, + refunded: nil, + message: nil, + card_holder: 'Matthew Savage', + card_number: 'XXXXXXXXXXXX4444', + card_expiry: '2013-05-31', + card_type: 'MasterCard', + transaction_id: nil, + successful: false }, - :errors => [ + errors: [ "Reference can't be blank" ], - :test => true + test: true }.to_json end def successful_tokenize_response { - :successful => true, - :response => { - :token => 'e1q7dbj2', - :card_holder => 'Bob Smith', - :card_number => 'XXXXXXXXXXXX2346', - :card_expiry => '2013-05-31T23:59:59+10:00', - :authorized => true, - :transaction_count => 0 + successful: true, + response: { + token: 'e1q7dbj2', + card_holder: 'Bob Smith', + card_number: 'XXXXXXXXXXXX2346', + card_expiry: '2013-05-31T23:59:59+10:00', + authorized: true, + transaction_count: 0 }, - :errors => [], - :test => true + errors: [], + test: true }.to_json end def failed_tokenize_response { - :successful => false, - :response => { - :token => nil, - :card_holder => 'Bob ', - :card_number => '512345XXXXXX2346', - :card_expiry => nil, - :authorized => false, - :transaction_count => 10 + successful: false, + response: { + token: nil, + card_holder: 'Bob ', + card_number: '512345XXXXXX2346', + card_expiry: nil, + authorized: false, + transaction_count: 10 }, - :errors => [ + errors: [ "Expiry date can't be blank" ], - :test => false + test: false + }.to_json + end + + def successful_no_cvv_tokenize_response + { + successful: true, + response: { + token: 'ep3c05nzsqvft15wsf1z', + card_holder: 'Bob ', + card_number: '512345XXXXXX2346', + card_expiry: nil, + authorized: true, + transaction_count: 0 + }, + errors: [], + test: false + }.to_json + end + + def failed_no_cvv_tokenize_response + { + successful: false, + response: { + token: nil, + card_holder: 'Bob ', + card_number: '512345XXXXXX2346', + card_expiry: nil, + authorized: false, + transaction_count: 0 + }, + errors: [ + 'CVV is required' + ], + test: false }.to_json end # Place raw failed response from gateway here def failed_purchase_response { - :successful => false, - :response => {}, - :test => true, - :errors => ['Invalid Card Number'] + successful: false, + response: {}, + test: true, + errors: ['Invalid Card Number'] }.to_json end def missing_data_response { - :successful => false, - :response => {}, - :test => true, - :errors => ['Card Number is required'] + successful: false, + response: {}, + test: true, + errors: ['Card Number is required'] }.to_json end end diff --git a/test/unit/gateways/federated_canada_test.rb b/test/unit/gateways/federated_canada_test.rb index d3ca5956ae8..c54a6c7e006 100644 --- a/test/unit/gateways/federated_canada_test.rb +++ b/test/unit/gateways/federated_canada_test.rb @@ -3,24 +3,24 @@ class FederatedCanadaTest < Test::Unit::TestCase def setup @gateway = FederatedCanadaGateway.new( - :login => 'demo', - :password => 'password' - ) + login: 'demo', + password: 'password' + ) @credit_card = credit_card('4111111111111111') @credit_card.verification_value = '999' @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - options = {:billing_address => {:address1 => '888', :address2 => 'apt 13', :country => 'CA', :state => 'SK', :city => 'Big Beaver', :zip => '77777'}} + options = { billing_address: { address1: '888', address2: 'apt 13', country: 'CA', state: 'SK', city: 'Big Beaver', zip: '77777' } } assert response = @gateway.authorize(@amount, @credit_card, options) assert_instance_of Response, response assert_success response @@ -47,8 +47,8 @@ def test_unsuccessful_request def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Happy Town Road', :address2 => 'apt 13', :country => 'CA', :state => 'SK', :phone => '1234567890'}) - assert_equal ['address1', 'address2', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, billing_address: { address1: '123 Happy Town Road', address2: 'apt 13', country: 'CA', state: 'SK', phone: '1234567890' }) + assert_equal %w[address1 address2 city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'SK', result[:state] assert_equal '123 Happy Town Road', result[:address1] assert_equal 'apt 13', result[:address2] @@ -57,13 +57,13 @@ def test_add_address def test_add_invoice result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001', :description => 'This is a great order') + @gateway.send(:add_invoice, result, order_id: '#1001', description: 'This is a great order') assert_equal '#1001', result[:orderid] assert_equal 'This is a great order', result[:orderdescription] end def test_purchase_is_valid_csv - params = {:amount => @amount} + params = { amount: @amount } @gateway.send(:add_creditcard, params, @credit_card) assert data = @gateway.send(:post_data, 'auth', params) @@ -71,7 +71,7 @@ def test_purchase_is_valid_csv end def test_purchase_meets_minimum_requirements - params = {:amount => @amount} + params = { amount: @amount } @gateway.send(:add_creditcard, params, @credit_card) assert data = @gateway.send(:post_data, 'auth', params) minimum_requirements.each do |key| @@ -80,8 +80,8 @@ def test_purchase_meets_minimum_requirements end def test_expdate_formatting - assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) - assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', :month => '7', :year => '2011')) + assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', month: '9', year: '2009')) + assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', month: '7', year: '2011')) end def test_supported_countries @@ -89,7 +89,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal @gateway.supported_cardtypes, [:visa, :master, :american_express, :discover] + assert_equal @gateway.supported_cardtypes, %i[visa master american_express discover] end def test_avs_result diff --git a/test/unit/gateways/finansbank_test.rb b/test/unit/gateways/finansbank_test.rb index 9cb280aac30..2f7e1cfabbd 100644 --- a/test/unit/gateways/finansbank_test.rb +++ b/test/unit/gateways/finansbank_test.rb @@ -7,18 +7,18 @@ def setup @original_kcode = nil @gateway = FinansbankGateway.new( - :login => 'login', - :password => 'password', - :client_id => 'client_id' + login: 'login', + password: 'password', + client_id: 'client_id' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -122,228 +122,228 @@ def test_failed_credit private def successful_purchase_response - <<-EOF - - 1 - 1 - Approved - 123456 - 123456 - 00 - 123456 - - - EOF + <<~XML + + 1 + 1 + Approved + 123456 + 123456 + 00 + 123456 + + + XML end def failed_purchase_response - <<-EOF - - 1 - 2 - Declined - - 123456 - 12 - 123456 - Not enough credit - - EOF + <<~XML + + 1 + 2 + Declined + + 123456 + 12 + 123456 + Not enough credit + + XML end def successful_authorize_response - <<-EOF - - 1 - 1 - Approved - 794573 - 305219419620 - 00 - 13052TpOI06012476 - - - 411 - 20130221 19:41:14 - - ISLEMINIZ ONAYLANDI - 00 - 000 - N - - - EOF + <<~XML + + 1 + 1 + Approved + 794573 + 305219419620 + 00 + 13052TpOI06012476 + + + 411 + 20130221 19:41:14 + + ISLEMINIZ ONAYLANDI + 00 + 000 + N + + + XML end def successful_capture_response - <<-EOF - - 1 - 1 - Approved - 794573 - 305219419622 - 00 - 13052TpPH06012485 - - - 411 - 20130221 19:41:15 - - 00 - - - EOF + <<~XML + + 1 + 1 + Approved + 794573 + 305219419622 + 00 + 13052TpPH06012485 + + + 411 + 20130221 19:41:15 + + 00 + + + XML end def capture_without_authorize_response - <<-EOF - - - - Error - - - 99 - 13052TtZF06012712 - PostAuth sadece iliskili bir PreAuth icin yapilabilir. - - - 20130221 19:45:25 - CORE-2115 - 992115 - - - EOF + <<~XML + + + + Error + + + 99 + 13052TtZF06012712 + PostAuth sadece iliskili bir PreAuth icin yapilabilir. + + + 20130221 19:45:25 + CORE-2115 + 992115 + + + XML end def successful_void_response - <<-EOF - - 1 - 1 - Approved - 794573 - 402310197597 - 00 - 14023KVGD18549 - - - 1363 - 20140123 10:21:05 - - 00 - - - EOF + <<~XML + + 1 + 1 + Approved + 794573 + 402310197597 + 00 + 14023KVGD18549 + + + 1363 + 20140123 10:21:05 + + 00 + + + XML end def failed_void_response - <<-EOF - - - - Error - - - 99 - 14023KvNI18702 - İptal edilmeye uygun satış işlemi bulunamadı. - - - 20140123 10:47:13 - CORE-2008 - 992008 - - - EOF + <<~XML + + + + Error + + + 99 + 14023KvNI18702 + İptal edilmeye uygun satış işlemi bulunamadı. + + + 20140123 10:47:13 + CORE-2008 + 992008 + + + XML end def success_refund_response - <<-EOF - - 1 - 1 - Approved - 811778 - 402410197809 - 00 - 14024KACE13836 - - - 1364 - 20140124 10:00:02 - - 000000001634 - 000000001634 - 00 - 3 - - - EOF + <<~XML + + 1 + 1 + Approved + 811778 + 402410197809 + 00 + 14024KACE13836 + + + 1364 + 20140124 10:00:02 + + 000000001634 + 000000001634 + 00 + 3 + + + XML end def failed_refund_response - <<-EOF - - - - Error - - - 99 - 14024KEwH13882 - Iade yapilamaz, siparis gunsonuna girmemis. - - - 20140124 10:04:48 - CORE-2508 - 992508 - - - EOF + <<~XML + + + + Error + + + 99 + 14024KEwH13882 + Iade yapilamaz, siparis gunsonuna girmemis. + + + 20140124 10:04:48 + CORE-2508 + 992508 + + + XML end def success_credit_response - <<-EOF - - ORDER-14024KUGB13953 - ORDER-14024KUGB13953 - Approved - 718160 - 402410197818 - 00 - 14024KUGD13955 - - - 1364 - 20140124 10:20:06 - - 00 - 3 - - - EOF + <<~XML + + ORDER-14024KUGB13953 + ORDER-14024KUGB13953 + Approved + 718160 + 402410197818 + 00 + 14024KUGD13955 + + + 1364 + 20140124 10:20:06 + + 00 + 3 + + + XML end def failed_credit_response - <<-EOF - - - - Error - - - 99 - 14024KUtG13966 - Kredi karti numarasi gecerli formatta degil. - - - 20140124 10:20:45 - CORE-2012 - 992012 - - - EOF + <<~XML + + + + Error + + + 99 + 14024KUtG13966 + Kredi karti numarasi gecerli formatta degil. + + + 20140124 10:20:45 + CORE-2012 + 992012 + + + XML end end diff --git a/test/unit/gateways/first_pay_test.rb b/test/unit/gateways/first_pay_test.rb index b761e884938..0db2ce8680a 100644 --- a/test/unit/gateways/first_pay_test.rb +++ b/test/unit/gateways/first_pay_test.rb @@ -21,7 +21,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/FIELD>/, data) assert_match(/a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) assert_match(/sale<\/FIELD>/, data) @@ -62,7 +62,7 @@ def test_failed_purchase def test_successful_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/FIELD>/, data) assert_match(/a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) assert_match(/auth<\/FIELD>/, data) @@ -101,7 +101,7 @@ def test_failed_authorize def test_successful_capture response = stub_comms do @gateway.capture(@amount, '47920') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/FIELD>/, data) assert_match(/a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) assert_match(/settle<\/FIELD>/, data) @@ -129,7 +129,7 @@ def test_failed_capture def test_successful_refund response = stub_comms do @gateway.refund(@amount, '47925') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/FIELD>/, data) assert_match(/a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) assert_match(/credit<\/FIELD>/, data) @@ -157,7 +157,7 @@ def test_failed_refund def test_successful_void response = stub_comms do @gateway.void('47934') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/FIELD>/, data) assert_match(/a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) assert_match(/void<\/FIELD>/, data) @@ -188,7 +188,7 @@ def test_recurring_payments @options[:recurring_type] = 'monthly' response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{1}, data) assert_match(%r{01/01/1900}, data) assert_match(%r{02/02/1901}, data) diff --git a/test/unit/gateways/firstdata_e4_test.rb b/test/unit/gateways/firstdata_e4_test.rb index e4aa2511096..c18b5941cc9 100755 --- a/test/unit/gateways/firstdata_e4_test.rb +++ b/test/unit/gateways/firstdata_e4_test.rb @@ -7,16 +7,16 @@ class FirstdataE4Test < Test::Unit::TestCase def setup @gateway = FirstdataE4Gateway.new( - :login => 'A00427-01', - :password => 'testus' + login: 'A00427-01', + password: 'testus' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @authorization = 'ET1700;106625152;4738' end @@ -42,7 +42,7 @@ def test_successful_purchase end def test_successful_purchase_with_specified_currency - options_with_specified_currency = @options.merge({currency: 'GBP'}) + options_with_specified_currency = @options.merge({ currency: 'GBP' }) @gateway.expects(:ssl_post).returns(successful_purchase_with_specified_currency_response) assert response = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) assert_success response @@ -61,10 +61,9 @@ def test_successful_purchase_with_token end def test_successful_purchase_with_specified_currency_and_token - options_with_specified_currency = @options.merge({currency: 'GBP'}) + 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 @@ -82,7 +81,7 @@ def test_successful_refund end def test_successful_refund_with_specified_currency - options_with_specified_currency = @options.merge({currency: 'GBP'}) + options_with_specified_currency = @options.merge({ currency: 'GBP' }) @gateway.expects(:ssl_post).returns(successful_refund_with_specified_currency_response) assert response = @gateway.refund(@amount, @authorization, options_with_specified_currency) assert_success response @@ -136,11 +135,11 @@ def test_no_transaction end def test_supported_countries - assert_equal ['CA', 'US'], FirstdataE4Gateway.supported_countries + assert_equal %w[CA US], FirstdataE4Gateway.supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :jcb, :discover], FirstdataE4Gateway.supported_cardtypes + assert_equal %i[visa master american_express jcb discover], FirstdataE4Gateway.supported_cardtypes end def test_avs_result @@ -160,15 +159,24 @@ def test_cvv_result def test_requests_include_verification_string stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '456 My Street|K1C2N6|Ottawa|ON|CA', data end.respond_with(successful_purchase_response) end + def test_requests_scrub_newline_and_return_characters_from_verification_string_components + stub_comms do + options_with_newline_and_return_characters_in_address = @options.merge({ billing_address: address({ address1: "123 My\nStreet", address2: "K1C2N6\r", city: "Ottawa\r\n" }) }) + @gateway.purchase(@amount, @credit_card, options_with_newline_and_return_characters_in_address) + end.check_request do |_endpoint, data, _headers| + assert_match '123 My Street|K1C2N6|Ottawa|ON|CA', data + end.respond_with(successful_purchase_response) + end + def test_tax_fields_are_sent stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(tax1_amount: 830, tax1_number: 'Br59a')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '830', data assert_match 'Br59a', data end.respond_with(successful_purchase_response) @@ -177,7 +185,7 @@ def test_tax_fields_are_sent def test_customer_ref_is_sent stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(customer: '932')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '932', data end.respond_with(successful_purchase_response) end @@ -185,7 +193,7 @@ def test_customer_ref_is_sent def test_eci_default_value stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '07', data end.respond_with(successful_purchase_response) end @@ -196,7 +204,7 @@ def test_eci_numeric_padding stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) @@ -205,7 +213,7 @@ def test_eci_numeric_padding stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) end @@ -213,7 +221,7 @@ def test_eci_numeric_padding def test_eci_option_value stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(eci: '05')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) end @@ -287,7 +295,7 @@ def test_requests_include_card_authentication_data stub_comms do @gateway.purchase(@amount, @credit_card, options_with_authentication_data) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '06', data assert_match 'SAMPLECAVV', data assert_match 'SAMPLEXID', data @@ -309,7 +317,7 @@ def test_add_swipe_data_with_creditcard stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'Track Data', data assert_match 'R', data end.respond_with(successful_purchase_response) @@ -394,780 +402,780 @@ def post_scrub end def successful_purchase_response - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - - 773 - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - 8938737759041111 - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + + 773 + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + 8938737759041111 + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def successful_purchase_with_specified_currency_response - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - - 773 - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - GBP - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - 8938737759041111 - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa £ 47.38 GBP - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + + 773 + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + GBP + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + 8938737759041111 + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa £ 47.38 GBP + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def successful_purchase_response_without_transarmor - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - - 773 - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + + 773 + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def successful_refund_response - <<-RESPONSE - - - AD1234-56 - - 34 - 123 - - ############1111 - 888 - - - - ET112216 - 0913 - Fred Burfle - - - 0 - - - - - - - - - - - - - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000041 - - I - 9176784 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Refund - -ACCT: Visa $ 23.69 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 08:31:23 -REFERENCE # : 000041 M -AUTHOR. # : ET112216 -TRANS. REF. : - - Approved - Thank You 100 - - -Please retain this copy for your records. - -========================================= - + <<~RESPONSE + + + AD1234-56 + + 34 + 123 + + ############1111 + 888 + + + + ET112216 + 0913 + Fred Burfle + + + 0 + + + + + + + + + + + + + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000041 + + I + 9176784 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Refund + + ACCT: Visa $ 23.69 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 08:31:23 + REFERENCE # : 000041 M + AUTHOR. # : ET112216 + TRANS. REF. : + + Approved - Thank You 100 + + + Please retain this copy for your records. + + ========================================= + RESPONSE end def successful_refund_with_specified_currency_response - <<-RESPONSE - - - AD1234-56 - - 34 - 123 - - ############1111 - 888 - - - - ET112216 - 0913 - Fred Burfle - - - 0 - - - - - - - - - - - - - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000041 - - I - 9176784 - - GBP - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Refund - -ACCT: Visa £ 23.69 GBP - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 08:31:23 -REFERENCE # : 000041 M -AUTHOR. # : ET112216 -TRANS. REF. : - - Approved - Thank You 100 - - -Please retain this copy for your records. - -========================================= - + <<~RESPONSE + + + AD1234-56 + + 34 + 123 + + ############1111 + 888 + + + + ET112216 + 0913 + Fred Burfle + + + 0 + + + + + + + + + + + + + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000041 + + I + 9176784 + + GBP + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Refund + + ACCT: Visa £ 23.69 GBP + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 08:31:23 + REFERENCE # : 000041 M + AUTHOR. # : ET112216 + TRANS. REF. : + + Approved - Thank You 100 + + + Please retain this copy for your records. + + ========================================= + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - AD1234-56 - - 00 - 5013.0 - - ############1111 - 555555 - - - - - 0911 - Fred Burfle - - 773 - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - - 0 - - false - false - 00 - Transaction Normal - 605 - Invalid Expiration Date - - 000033 - - - - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase -ACCT: Visa $ 5,013.00 USD -CARD NUMBER : ############1111 -DATE/TIME : 25 Sep 12 07:27:00 -REFERENCE # : 000033 M -AUTHOR. # : -TRANS. REF. : 77 -Transaction not approved 605 -Please retain this copy for your records. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 5013.0 + + ############1111 + 555555 + + + + + 0911 + Fred Burfle + + 773 + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + + 0 + + false + false + 00 + Transaction Normal + 605 + Invalid Expiration Date + + 000033 + + + + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + ACCT: Visa $ 5,013.00 USD + CARD NUMBER : ############1111 + DATE/TIME : 25 Sep 12 07:27:00 + REFERENCE # : 000033 M + AUTHOR. # : + TRANS. REF. : 77 + Transaction not approved 605 + Please retain this copy for your records. + ========================================= + RESPONSE end def successful_verify_response - <<-RESPONSE - - - AD2552-05 - - 05 - 0.0 - - ############4242 - 25101911 - - - - ET184931 - 0915 - Longbob Longsen - 1234 My Street|K1C2N6|Ottawa|ON|CA - 123 - 0 - - - - - - - - - - - - 1 - - Store Purchase - - 75.182.123.244 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - 1 - M - 7228838 - - USD - - false - FriendlyInc - 123 Main Street - Durham - North Carolina - United States - 27592 - - - Visa - - - - - false - =========== TRANSACTION RECORD ========== -FriendlyInc DEMO0 -123 Main Street -Durham, NC 27592 -United States - - -TYPE: Auth Only - -ACCT: Visa $ 0.00 USD - -CARDHOLDER NAME : Longbob Longsen -CARD NUMBER : ############4242 -DATE/TIME : 04 Jul 14 14:21:52 -REFERENCE # : 000040 M -AUTHOR. # : ET184931 -TRANS. REF. : 1 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD2552-05 + + 05 + 0.0 + + ############4242 + 25101911 + + + + ET184931 + 0915 + Longbob Longsen + 1234 My Street|K1C2N6|Ottawa|ON|CA + 123 + 0 + + + + + + + + + + + + 1 + + Store Purchase + + 75.182.123.244 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + 1 + M + 7228838 + + USD + + false + FriendlyInc + 123 Main Street + Durham + North Carolina + United States + 27592 + + + Visa + + + + + false + =========== TRANSACTION RECORD ========== + FriendlyInc DEMO0 + 123 Main Street + Durham, NC 27592 + United States + + + TYPE: Auth Only + + ACCT: Visa $ 0.00 USD + + CARDHOLDER NAME : Longbob Longsen + CARD NUMBER : ############4242 + DATE/TIME : 04 Jul 14 14:21:52 + REFERENCE # : 000040 M + AUTHOR. # : ET184931 + TRANS. REF. : 1 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def no_transaction_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -message: Failed with 400 Bad Request -message: -response: !ruby/object:Net::HTTPBadRequest - body: "Malformed request: Transaction Type is missing." - body_exist: true - code: "400" - header: - connection: - - Close - content-type: - - text/html; charset=utf-8 - server: - - Apache - date: - - Fri, 28 Sep 2012 18:21:37 GMT - content-length: - - "47" - status: - - "400" - cache-control: - - no-cache - http_version: "1.1" - message: Bad Request - read: true - socket: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + message: Failed with 400 Bad Request + message: + response: !ruby/object:Net::HTTPBadRequest + body: "Malformed request: Transaction Type is missing." + body_exist: true + code: "400" + header: + connection: + - Close + content-type: + - text/html; charset=utf-8 + server: + - Apache + date: + - Fri, 28 Sep 2012 18:21:37 GMT + content-length: + - "47" + status: + - "400" + cache-control: + - no-cache + http_version: "1.1" + message: Bad Request + read: true + socket: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) end def bad_credentials_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -message: -response: !ruby/object:Net::HTTPUnauthorized - code: '401' - message: Authorization Required - body: Unauthorized Request. Bad or missing credentials. - read: true - header: - cache-control: - - no-cache - content-type: - - text/html; charset=utf-8 - date: - - Tue, 30 Dec 2014 23:28:32 GMT - server: - - Apache - status: - - '401' - x-rack-cache: - - invalidate, pass - x-request-id: - - 4157e21cc5620a95ead8d2025b55bdf4 - x-ua-compatible: - - IE=Edge,chrome=1 - content-length: - - '49' - connection: - - Close - body_exist: true - http_version: '1.1' - socket: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + message: + response: !ruby/object:Net::HTTPUnauthorized + code: '401' + message: Authorization Required + body: Unauthorized Request. Bad or missing credentials. + read: true + header: + cache-control: + - no-cache + content-type: + - text/html; charset=utf-8 + date: + - Tue, 30 Dec 2014 23:28:32 GMT + server: + - Apache + status: + - '401' + x-rack-cache: + - invalidate, pass + x-request-id: + - 4157e21cc5620a95ead8d2025b55bdf4 + x-ua-compatible: + - IE=Edge,chrome=1 + content-length: + - '49' + connection: + - Close + body_exist: true + http_version: '1.1' + socket: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) end def successful_void_response - <<-RESPONSE - - - AD1234-56 - - 33 - 11.45 - - ############1111 - 987123 - - - - ET112112 - 0913 - Fred Burfle - - - 0 - - - - - - - - - - - - - - - - 1.1.1.10 - - - 0 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000166 - - I - 2046743 - - USD - - false - FreshBooks DEMO0785 - 35 Golden Ave - Toronto - Ontario - Canada - M6R 2J5 - -=========== TRANSACTION RECORD ========== -FreshBooks DEMO0785 -35 Golden Ave -Toronto, ON M6R 2J5 -Canada - - -TYPE: Void - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 15 Nov 12 08:20:36 -REFERENCE # : 000166 M -AUTHOR. # : ET112112 -TRANS. REF. : - -Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - -RESPONSE + <<~RESPONSE + + + AD1234-56 + + 33 + 11.45 + + ############1111 + 987123 + + + + ET112112 + 0913 + Fred Burfle + + + 0 + + + + + + + + + + + + + + + + 1.1.1.10 + + + 0 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000166 + + I + 2046743 + + USD + + false + FreshBooks DEMO0785 + 35 Golden Ave + Toronto + Ontario + Canada + M6R 2J5 + + =========== TRANSACTION RECORD ========== + FreshBooks DEMO0785 + 35 Golden Ave + Toronto, ON M6R 2J5 + Canada + + + TYPE: Void + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 15 Nov 12 08:20:36 + REFERENCE # : 000166 M + AUTHOR. # : ET112112 + TRANS. REF. : + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + + RESPONSE end end diff --git a/test/unit/gateways/firstdata_e4_v27_test.rb b/test/unit/gateways/firstdata_e4_v27_test.rb index 8e4b4371c8a..a4301598f65 100644 --- a/test/unit/gateways/firstdata_e4_v27_test.rb +++ b/test/unit/gateways/firstdata_e4_v27_test.rb @@ -7,18 +7,18 @@ class FirstdataE4V27Test < Test::Unit::TestCase def setup @gateway = FirstdataE4V27Gateway.new( - :login => 'A00427-01', - :password => 'testus', - :key_id => '12345', - :hmac_key => 'hexkey' + login: 'A00427-01', + password: 'testus', + key_id: '12345', + hmac_key: 'hexkey' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @authorization = 'ET1700;106625152;4738' end @@ -51,8 +51,21 @@ def test_successful_purchase_with_token def test_successful_purchase_with_wallet response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge!({wallet_provider_id: 4})) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge!({ wallet_provider_id: 3 })) + end.check_request do |_endpoint, data, _headers| + assert_match(/WalletProviderID>341UnewC/, data) end.respond_with(successful_purchase_response_with_stored_credentials) assert_success response @@ -138,11 +152,11 @@ def test_no_transaction end def test_supported_countries - assert_equal ['CA', 'US'], FirstdataE4V27Gateway.supported_countries + assert_equal %w[CA US], FirstdataE4V27Gateway.supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :jcb, :discover], FirstdataE4V27Gateway.supported_cardtypes + assert_equal %i[visa master american_express jcb discover], FirstdataE4V27Gateway.supported_cardtypes end def test_avs_result @@ -162,15 +176,24 @@ def test_cvv_result def test_request_includes_address stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '
456 My StreetApt 1OttawaONK1C2N6CA
', data end.respond_with(successful_purchase_response) end + def test_requests_scrub_newline_and_return_characters_from_verification_string_components + stub_comms do + options_with_newline_and_return_characters_in_address = @options.merge({ billing_address: address({ address1: "456 My\nStreet", address2: nil, city: "Ottawa\r\n", state: 'ON', country: 'CA', zip: 'K1C2N6' }) }) + @gateway.purchase(@amount, @credit_card, options_with_newline_and_return_characters_in_address) + end.check_request do |_endpoint, data, _headers| + assert_match '
456 My StreetOttawaONK1C2N6CA
', data + end.respond_with(successful_purchase_response) + end + def test_tax_fields_are_sent stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(tax1_amount: 830, tax1_number: 'Br59a')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '830', data assert_match 'Br59a', data end.respond_with(successful_purchase_response) @@ -179,7 +202,7 @@ def test_tax_fields_are_sent def test_customer_ref_is_sent stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(customer: '932')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '932', data end.respond_with(successful_purchase_response) end @@ -187,7 +210,7 @@ def test_customer_ref_is_sent def test_eci_default_value stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '07', data end.respond_with(successful_purchase_response) end @@ -198,7 +221,7 @@ def test_eci_numeric_padding stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) @@ -207,7 +230,7 @@ def test_eci_numeric_padding stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) end @@ -215,7 +238,7 @@ def test_eci_numeric_padding def test_eci_option_value stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(eci: '05')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '05', data end.respond_with(successful_purchase_response) end @@ -245,13 +268,13 @@ def test_network_tokenization_requests_with_discover '6011111111111117', brand: 'discover', transaction_id: '123', - eci: '05', + eci: '04', payment_cryptogram: 'whatever_the_cryptogram_is' ) @gateway.purchase(@amount, credit_card, @options) end.check_request do |_, data, _| - assert_match '04', data + assert_match '05', data assert_match '123', data assert_match 'whatever_the_cryptogram_is', data assert_xml_valid_to_wsdl(data) @@ -289,7 +312,7 @@ def test_requests_include_card_authentication_data stub_comms do @gateway.purchase(@amount, @credit_card, options_with_authentication_data) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '06', data assert_match 'SAMPLECAVV', data assert_match 'SAMPLEXID', data @@ -310,7 +333,7 @@ def test_add_swipe_data_with_creditcard stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'Track Data', data assert_match 'R', data end.respond_with(successful_purchase_response) @@ -409,701 +432,701 @@ def post_scrub end def successful_purchase_response - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - 8938737759041111 - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= -
- 456 My Street - Apt 1 - Ottawa - ON - K1C2N6 - CA -
-
+ <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + 8938737759041111 + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= +
+ 456 My Street + Apt 1 + Ottawa + ON + K1C2N6 + CA +
+
RESPONSE end def successful_purchase_response_with_stored_credentials - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - 8938737759041111 - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= -
- 456 My Street - Apt 1 - Ottawa - ON - K1C2N6 - CA -
- - 1 - U - 732602247202501 - -
+ <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + 8938737759041111 + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= +
+ 456 My Street + Apt 1 + Ottawa + ON + K1C2N6 + CA +
+ + 1 + U + 732602247202501 + +
RESPONSE end def successful_purchase_response_without_transarmor - <<-RESPONSE - - - AD1234-56 - - 00 - 47.38 - - ############1111 - 106625152 - - - - ET1700 - 0913 - Fred Burfle - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - U - M - 3146117 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 07:54:48 -REFERENCE # : 000040 M -AUTHOR. # : ET120454 -TRANS. REF. : 77 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 47.38 + + ############1111 + 106625152 + + + + ET1700 + 0913 + Fred Burfle + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + U + M + 3146117 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 07:54:48 + REFERENCE # : 000040 M + AUTHOR. # : ET120454 + TRANS. REF. : 77 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def successful_refund_response - <<-RESPONSE - - - AD1234-56 - - 34 - 123 - - ############1111 - 888 - - - - ET112216 - 0913 - Fred Burfle - 0 - - - - - - - - - - - - - - - - 1.1.1.10 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000041 - - I - 9176784 - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Refund - -ACCT: Visa $ 23.69 USD - -CARD NUMBER : ############1111 -DATE/TIME : 28 Sep 12 08:31:23 -REFERENCE # : 000041 M -AUTHOR. # : ET112216 -TRANS. REF. : - - Approved - Thank You 100 - - -Please retain this copy for your records. - -========================================= - + <<~RESPONSE + + + AD1234-56 + + 34 + 123 + + ############1111 + 888 + + + + ET112216 + 0913 + Fred Burfle + 0 + + + + + + + + + + + + + + + + 1.1.1.10 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000041 + + I + 9176784 + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Refund + + ACCT: Visa $ 23.69 USD + + CARD NUMBER : ############1111 + DATE/TIME : 28 Sep 12 08:31:23 + REFERENCE # : 000041 M + AUTHOR. # : ET112216 + TRANS. REF. : + + Approved - Thank You 100 + + + Please retain this copy for your records. + + ========================================= + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - AD1234-56 - - 00 - 5013.0 - - ############1111 - 555555 - - - - - 0911 - Fred Burfle - 0 - - - - - - - - - - - - 77 - - - - 1.1.1.10 - - - 0 - - false - false - 00 - Transaction Normal - 605 - Invalid Expiration Date - - 000033 - - - - - USD - - false - Friendly Inc DEMO0983 - 123 King St - Toronto - Ontario - Canada - L7Z 3K8 - - =========== TRANSACTION RECORD ========== -Friendly Inc DEMO0983 -123 King St -Toronto, ON L7Z 3K8 -Canada - - -TYPE: Purchase -ACCT: Visa $ 5,013.00 USD -CARD NUMBER : ############1111 -DATE/TIME : 25 Sep 12 07:27:00 -REFERENCE # : 000033 M -AUTHOR. # : -TRANS. REF. : 77 -Transaction not approved 605 -Please retain this copy for your records. -========================================= - + <<~RESPONSE + + + AD1234-56 + + 00 + 5013.0 + + ############1111 + 555555 + + + + + 0911 + Fred Burfle + 0 + + + + + + + + + + + + 77 + + + + 1.1.1.10 + + + 0 + + false + false + 00 + Transaction Normal + 605 + Invalid Expiration Date + + 000033 + + + + + USD + + false + Friendly Inc DEMO0983 + 123 King St + Toronto + Ontario + Canada + L7Z 3K8 + + =========== TRANSACTION RECORD ========== + Friendly Inc DEMO0983 + 123 King St + Toronto, ON L7Z 3K8 + Canada + + + TYPE: Purchase + ACCT: Visa $ 5,013.00 USD + CARD NUMBER : ############1111 + DATE/TIME : 25 Sep 12 07:27:00 + REFERENCE # : 000033 M + AUTHOR. # : + TRANS. REF. : 77 + Transaction not approved 605 + Please retain this copy for your records. + ========================================= + RESPONSE end def successful_verify_response - <<-RESPONSE - - - AD2552-05 - - 05 - 0.0 - - ############4242 - 25101911 - - - - ET184931 - 0915 - Longbob Longsen - 0 - - - - - - - - - - - - 1 - - Store Purchase - - 75.182.123.244 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000040 - 1 - M - 7228838 - - USD - - false - FriendlyInc - 123 Main Street - Durham - North Carolina - United States - 27592 - - - Visa - - - - - false - =========== TRANSACTION RECORD ========== -FriendlyInc DEMO0 -123 Main Street -Durham, NC 27592 -United States - - -TYPE: Auth Only - -ACCT: Visa $ 0.00 USD - -CARDHOLDER NAME : Longbob Longsen -CARD NUMBER : ############4242 -DATE/TIME : 04 Jul 14 14:21:52 -REFERENCE # : 000040 M -AUTHOR. # : ET184931 -TRANS. REF. : 1 - - Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - + <<~RESPONSE + + + AD2552-05 + + 05 + 0.0 + + ############4242 + 25101911 + + + + ET184931 + 0915 + Longbob Longsen + 0 + + + + + + + + + + + + 1 + + Store Purchase + + 75.182.123.244 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000040 + 1 + M + 7228838 + + USD + + false + FriendlyInc + 123 Main Street + Durham + North Carolina + United States + 27592 + + + Visa + + + + + false + =========== TRANSACTION RECORD ========== + FriendlyInc DEMO0 + 123 Main Street + Durham, NC 27592 + United States + + + TYPE: Auth Only + + ACCT: Visa $ 0.00 USD + + CARDHOLDER NAME : Longbob Longsen + CARD NUMBER : ############4242 + DATE/TIME : 04 Jul 14 14:21:52 + REFERENCE # : 000040 M + AUTHOR. # : ET184931 + TRANS. REF. : 1 + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + RESPONSE end def no_transaction_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -message: Failed with 400 Bad Request -message: -response: !ruby/object:Net::HTTPBadRequest - body: "Malformed request: Transaction Type is missing." - body_exist: true - code: "400" - header: - connection: - - Close - content-type: - - text/html; charset=utf-8 - server: - - Apache - date: - - Fri, 28 Sep 2012 18:21:37 GMT - content-length: - - "47" - status: - - "400" - cache-control: - - no-cache - http_version: "1.1" - message: Bad Request - read: true - socket: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + message: Failed with 400 Bad Request + message: + response: !ruby/object:Net::HTTPBadRequest + body: "Malformed request: Transaction Type is missing." + body_exist: true + code: "400" + header: + connection: + - Close + content-type: + - text/html; charset=utf-8 + server: + - Apache + date: + - Fri, 28 Sep 2012 18:21:37 GMT + content-length: + - "47" + status: + - "400" + cache-control: + - no-cache + http_version: "1.1" + message: Bad Request + read: true + socket: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) end def bad_credentials_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -message: -response: !ruby/object:Net::HTTPUnauthorized - code: '401' - message: Authorization Required - body: Unauthorized Request. Bad or missing credentials. - read: true - header: - cache-control: - - no-cache - content-type: - - text/html; charset=utf-8 - date: - - Tue, 30 Dec 2014 23:28:32 GMT - server: - - Apache - status: - - '401' - x-rack-cache: - - invalidate, pass - x-request-id: - - 4157e21cc5620a95ead8d2025b55bdf4 - x-ua-compatible: - - IE=Edge,chrome=1 - content-length: - - '49' - connection: - - Close - body_exist: true - http_version: '1.1' - socket: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + message: + response: !ruby/object:Net::HTTPUnauthorized + code: '401' + message: Authorization Required + body: Unauthorized Request. Bad or missing credentials. + read: true + header: + cache-control: + - no-cache + content-type: + - text/html; charset=utf-8 + date: + - Tue, 30 Dec 2014 23:28:32 GMT + server: + - Apache + status: + - '401' + x-rack-cache: + - invalidate, pass + x-request-id: + - 4157e21cc5620a95ead8d2025b55bdf4 + x-ua-compatible: + - IE=Edge,chrome=1 + content-length: + - '49' + connection: + - Close + body_exist: true + http_version: '1.1' + socket: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) end def successful_void_response - <<-RESPONSE - - - AD1234-56 - - 33 - 11.45 - - ############1111 - 987123 - - - - ET112112 - 0913 - Fred Burfle - 0 - - - - - - - - - - - - - - - - 1.1.1.10 - - - 0 - - false - true - 00 - Transaction Normal - 100 - Approved - - 000166 - - I - 2046743 - - USD - - false - FreshBooks DEMO0785 - 35 Golden Ave - Toronto - Ontario - Canada - M6R 2J5 - -=========== TRANSACTION RECORD ========== -FreshBooks DEMO0785 -35 Golden Ave -Toronto, ON M6R 2J5 -Canada - - -TYPE: Void - -ACCT: Visa $ 47.38 USD - -CARD NUMBER : ############1111 -DATE/TIME : 15 Nov 12 08:20:36 -REFERENCE # : 000166 M -AUTHOR. # : ET112112 -TRANS. REF. : - -Approved - Thank You 100 - - -Please retain this copy for your records. - -Cardholder will pay above amount to card -issuer pursuant to cardholder agreement. -========================================= - -RESPONSE + <<~RESPONSE + + + AD1234-56 + + 33 + 11.45 + + ############1111 + 987123 + + + + ET112112 + 0913 + Fred Burfle + 0 + + + + + + + + + + + + + + + + 1.1.1.10 + + + 0 + + false + true + 00 + Transaction Normal + 100 + Approved + + 000166 + + I + 2046743 + + USD + + false + FreshBooks DEMO0785 + 35 Golden Ave + Toronto + Ontario + Canada + M6R 2J5 + + =========== TRANSACTION RECORD ========== + FreshBooks DEMO0785 + 35 Golden Ave + Toronto, ON M6R 2J5 + Canada + + + TYPE: Void + + ACCT: Visa $ 47.38 USD + + CARD NUMBER : ############1111 + DATE/TIME : 15 Nov 12 08:20:36 + REFERENCE # : 000166 M + AUTHOR. # : ET112112 + TRANS. REF. : + + Approved - Thank You 100 + + + Please retain this copy for your records. + + Cardholder will pay above amount to card + issuer pursuant to cardholder agreement. + ========================================= + + RESPONSE end end diff --git a/test/unit/gateways/flo2cash_simple_test.rb b/test/unit/gateways/flo2cash_simple_test.rb index 627f6782e29..d6d2b580d6c 100644 --- a/test/unit/gateways/flo2cash_simple_test.rb +++ b/test/unit/gateways/flo2cash_simple_test.rb @@ -7,9 +7,9 @@ def setup Base.mode = :test @gateway = Flo2cashSimpleGateway.new( - :username => 'username', - :password => 'password', - :account_id => 'account_id' + username: 'username', + password: 'password', + account_id: 'account_id' ) @credit_card = credit_card @@ -19,7 +19,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, order_id: 'boom') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{boom}, data) end.respond_with(successful_purchase_response) @@ -50,7 +50,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/P150200005007600/, data) end.respond_with(successful_refund_response) @@ -67,7 +67,7 @@ def test_empty_response_fails end def test_transcript_scrubbing - transcript = @gateway.scrub(successful_purchase_response) + transcript = @gateway.scrub(successful_purchase_response) assert_scrubbed(@credit_card.number, transcript) assert_scrubbed(@credit_card.verification_value, transcript) diff --git a/test/unit/gateways/flo2cash_test.rb b/test/unit/gateways/flo2cash_test.rb index 4564114db64..664a329739c 100644 --- a/test/unit/gateways/flo2cash_test.rb +++ b/test/unit/gateways/flo2cash_test.rb @@ -7,9 +7,9 @@ def setup Base.mode = :test @gateway = Flo2cashGateway.new( - :username => 'username', - :password => 'password', - :account_id => 'account_id' + username: 'username', + password: 'password', + account_id: 'account_id' ) @credit_card = credit_card @@ -19,7 +19,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, order_id: 'boom') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{boom}, data) end.respond_with(successful_authorize_response, successful_capture_response) @@ -50,7 +50,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/P150100005006789/, data) end.respond_with(successful_capture_response) @@ -78,7 +78,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/P150100005006789/, data) end.respond_with(successful_refund_response) @@ -95,7 +95,7 @@ def test_empty_response_fails end def test_transcript_scrubbing - transcript = @gateway.scrub(successful_authorize_response) + transcript = @gateway.scrub(successful_authorize_response) assert_scrubbed(@credit_card.number, transcript) assert_scrubbed(@credit_card.verification_value, transcript) diff --git a/test/unit/gateways/forte_test.rb b/test/unit/gateways/forte_test.rb index c7ca564cc65..2d3254244db 100644 --- a/test/unit/gateways/forte_test.rb +++ b/test/unit/gateways/forte_test.rb @@ -28,7 +28,7 @@ def test_successful_purchase def test_purchase_passes_options options = { order_id: '1' } - @gateway.expects(:commit).with(anything, has_entries(:order_number => '1')) + @gateway.expects(:commit).with(anything, has_entries(order_number: '1')) stub_comms(@gateway, :raw_ssl_request) do @gateway.purchase(@amount, @credit_card, options) @@ -61,6 +61,26 @@ def test_failed_purchase_with_echeck assert_equal 'INVALID CREDIT CARD NUMBER', response.message end + def test_successful_purchase_with_service_fee + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(successful_purchase_with_service_fee_response)) + assert_success response + + assert_equal '.5', response.params['service_fee_amount'] + assert response.test? + end + + def test_successful_purchase_with_xdata + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(successful_purchase_with_xdata_response)) + + assert_success response + (1..9).each { |n| assert_equal 'some customer metadata', response.params['xdata']["xdata_#{n}"] } + assert response.test? + end + def test_successful_authorize response = stub_comms(@gateway, :raw_ssl_request) do @gateway.authorize(@amount, @credit_card, @options) @@ -157,7 +177,7 @@ def test_handles_improper_padding @gateway = ForteGateway.new(location_id: ' improperly-padded ', account_id: ' account_id ', api_key: 'api_key', secret: 'secret') response = stub_comms(@gateway, :raw_ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |type, url, parameters, headers| + end.check_request do |_type, url, _parameters, _headers| URI.parse(url) end.respond_with(MockedResponse.new(successful_purchase_response)) assert_success response @@ -171,7 +191,7 @@ def test_scrub private class MockedResponse - attr :code, :body + attr_reader :code, :body def initialize(body, code = 200) @code = code @body = body @@ -351,6 +371,97 @@ def failed_echeck_purchase_response ' end + def successful_purchase_with_service_fee_response + ' + { + "transaction_id":"trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount": 1.0, + "service_fee_amount": ".5", + "subtotal_amount": ".5", + "authorization_code":"123456", + "billing_address":{ + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249/settlements" + } + } + ' + end + + def successful_purchase_with_xdata_response + ' + { + "transaction_id":"trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount": 1.0, + "service_fee_amount": ".5", + "subtotal_amount": ".5", + "authorization_code":"123456", + "billing_address":{ + "first_name":"Jim", + "last_name":"Smith" + }, + "xdata": { + "xdata_1": "some customer metadata", + "xdata_2": "some customer metadata", + "xdata_3": "some customer metadata", + "xdata_4": "some customer metadata", + "xdata_5": "some customer metadata", + "xdata_6": "some customer metadata", + "xdata_7": "some customer metadata", + "xdata_8": "some customer metadata", + "xdata_9": "some customer metadata" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249/settlements" + } + } + ' + end + def successful_authorize_response ' { diff --git a/test/unit/gateways/garanti_test.rb b/test/unit/gateways/garanti_test.rb index 46e7ce79e87..ad8ed645b9c 100644 --- a/test/unit/gateways/garanti_test.rb +++ b/test/unit/gateways/garanti_test.rb @@ -7,15 +7,15 @@ def setup @original_kcode = nil Base.mode = :test - @gateway = GarantiGateway.new(:login => 'a', :password => 'b', :terminal_id => 'c', :merchant_id => 'd') + @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 = { - :order_id => 'db4af18c5222503d845180350fbda516', - :billing_address => address, - :description => 'Store Purchase' + order_id: 'db4af18c5222503d845180350fbda516', + billing_address: address, + description: 'Store Purchase' } end @@ -58,11 +58,11 @@ def test_nil_normalization end def test_strip_invalid_xml_chars - xml = < Parse the First & but not this ˜ &x002a; -EOF +XML parsed_xml = @gateway.send(:strip_invalid_xml_chars, xml) assert REXML::Document.new(parsed_xml) @@ -75,71 +75,71 @@ def test_strip_invalid_xml_chars # Place raw successful response from gateway here def successful_purchase_response - <<-EOF - - - - db4af18c5222503d845180350fbda516 - - - - - HOST - 00 - 00 - Approved - - - - 035208609374 - 784260 - 000089 - 000008 - 20101218 08:56:39 - - Company Name & Another Name - - - - - - - - EOF + <<~XML + + + + db4af18c5222503d845180350fbda516 + + + + + HOST + 00 + 00 + Approved + + + + 035208609374 + 784260 + 000089 + 000008 + 20101218 08:56:39 + + Company Name & Another Name + + + + + + + + XML end # Place raw failed response from gateway here def failed_purchase_response - <<-EOF - - - - db4af18c5222503d845180350fbda516 - - - - - GVPS - 92 - 0651 - Declined - - ErrorId: 0651 - - - - - - 20101220 01:58:41 - - - - - - - - - - EOF + <<~XML + + + + db4af18c5222503d845180350fbda516 + + + + + GVPS + 92 + 0651 + Declined + + ErrorId: 0651 + + + + + + 20101220 01:58:41 + + + + + + + + + + XML end end diff --git a/test/unit/gateways/gateway_test.rb b/test/unit/gateways/gateway_test.rb index 3372ee5de13..27ccbd14215 100644 --- a/test/unit/gateways/gateway_test.rb +++ b/test/unit/gateways/gateway_test.rb @@ -10,11 +10,11 @@ def teardown end def test_should_detect_if_a_card_is_supported - Gateway.supported_cardtypes = [:visa, :bogus] - assert([:visa, :bogus].all? { |supported_cardtype| Gateway.supports?(supported_cardtype) }) + Gateway.supported_cardtypes = %i[visa bogus] + assert(%i[visa bogus].all? { |supported_cardtype| Gateway.supports?(supported_cardtype) }) Gateway.supported_cardtypes = [] - assert_false([:visa, :bogus].all? { |invalid_cardtype| Gateway.supports?(invalid_cardtype) }) + assert_false(%i[visa bogus].all? { |invalid_cardtype| Gateway.supports?(invalid_cardtype) }) end def test_should_validate_supported_countries @@ -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 @@ -54,12 +53,12 @@ def test_amount_style end def test_card_brand - credit_card = stub(:brand => 'visa') + credit_card = stub(brand: 'visa') assert_equal 'visa', Gateway.card_brand(credit_card) end def test_card_brand_using_type - credit_card = stub(:type => 'String') + credit_card = stub(type: 'String') assert_equal 'string', Gateway.card_brand(credit_card) end @@ -103,7 +102,7 @@ def test_localized_amount_returns_three_decimal_places_for_three_decimal_currenc end def test_split_names - assert_equal ['Longbob', 'Longsen'], @gateway.send(:split_names, 'Longbob Longsen') + assert_equal %w[Longbob Longsen], @gateway.send(:split_names, 'Longbob Longsen') end def test_split_names_with_single_name @@ -131,11 +130,11 @@ def test_should_not_allow_scrubbing_if_unsupported end def test_strip_invalid_xml_chars - xml = < Parse the First & but not this ˜ &x002a; -EOF + XML parsed_xml = @gateway.send(:strip_invalid_xml_chars, xml) assert REXML::Document.new(parsed_xml) @@ -143,4 +142,30 @@ def test_strip_invalid_xml_chars REXML::Document.new(xml) end end + + def test_add_field_to_post_if_present + order_id = 'abc123' + + post = {} + options = { order_id: order_id, do_not_add: 24 } + + @gateway.add_field_to_post_if_present(post, options, :order_id) + + assert_equal post[:order_id], order_id + assert_false post.key?(:do_not_add) + end + + def test_add_fields_to_post_if_present + order_id = 'abc123' + transaction_number = 500 + + post = {} + options = { order_id: order_id, transaction_number: transaction_number, do_not_add: 24 } + + @gateway.add_fields_to_post_if_present(post, options, %i[order_id transaction_number]) + + assert_equal post[:order_id], order_id + assert_equal post[:transaction_number], transaction_number + assert_false post.key?(:do_not_add) + end end diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb index 318f4ea8e38..e4c96bc3e8a 100644 --- a/test/unit/gateways/global_collect_test.rb +++ b/test/unit/gateways/global_collect_test.rb @@ -9,6 +9,33 @@ def setup secret_api_key: '109H/288H*50Y18W4/0G8571F245KA=') @credit_card = credit_card('4567350000427977') + @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 + ) + + @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_pan_only = credit_card( + '4444333322221111', + month: '01', + year: Time.new.year + 2 + ) + @declined_card = credit_card('5424180279791732') @accepted_amount = 4005 @rejected_amount = 2997 @@ -16,27 +43,303 @@ def setup billing_address: address, description: 'Store Purchase' } + @options_3ds2 = @options.merge( + 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', + flow: 'frictionless' + } + ) end def test_successful_authorize_and_capture - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, @credit_card, @options) end.respond_with(successful_authorize_response) assert_success response assert_equal '000000142800000000920000100001', response.authorization - capture = stub_comms do + capture = stub_comms(@gateway, :ssl_request) do @gateway.capture(@accepted_amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_match(/000000142800000000920000100001/, endpoint) end.respond_with(successful_capture_response) assert_success capture end + def test_successful_preproduction_url + @gateway = GlobalCollectGateway.new( + merchant_id: '1234', + api_key_id: '39u4193urng12', + secret_api_key: '109H/288H*50Y18W4/0G8571F245KA=', + url_override: 'preproduction' + ) + + 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) + end.respond_with(successful_authorize_response) + end + + # When requires_approval is true (or not present), + # a `purchase` makes two calls (`auth` and `capture`). + def test_successful_purchase_with_requires_approval_true + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, @options.merge(requires_approval: true)) + end.check_request do |_method, _endpoint, _data, _headers| + end.respond_with(successful_authorize_response, successful_capture_response) + end + + def test_purchase_request_with_google_pay + stub_comms(@gateway, :ssl_request) do + @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 + end + + def test_purchase_request_with_google_pay_pan_only + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @google_pay_pan_only, @options.merge(customer: 'GP1234ID', google_pay_pan_only: true)) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + assert_equal '320', JSON.parse(data)['mobilePaymentMethodSpecificInput']['paymentProductId'] + end + end + + def test_add_payment_for_credit_card + post = {} + options = {} + payment = @credit_card + @gateway.send('add_payment', post, payment, options) + assert_includes post.keys, 'cardPaymentMethodSpecificInput' + assert_equal post['cardPaymentMethodSpecificInput']['paymentProductId'], '1' + assert_equal post['cardPaymentMethodSpecificInput']['authorizationMode'], 'FINAL_AUTHORIZATION' + assert_includes post['cardPaymentMethodSpecificInput'].keys, 'card' + assert_equal post['cardPaymentMethodSpecificInput']['card']['cvv'], '123' + assert_equal post['cardPaymentMethodSpecificInput']['card']['cardNumber'], '4567350000427977' + end + + def test_add_payment_for_google_pay + post = {} + options = {} + payment = @google_pay_network_token + @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']['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'] + end + + def test_add_payment_for_apple_pay + post = {} + options = {} + payment = @apple_pay_network_token + @gateway.send('add_payment', post, payment, options) + assert_includes post.keys, 'mobilePaymentMethodSpecificInput' + assert_equal post['mobilePaymentMethodSpecificInput']['paymentProductId'], '302' + 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'], '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) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + assert_equal '302', JSON.parse(data)['mobilePaymentMethodSpecificInput']['paymentProductId'] + end + end + + # When requires_approval is false, a `purchase` makes one call (`auth`). + def test_successful_purchase_with_requires_approval_false + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, @options.merge(requires_approval: false)) + end.check_request do |_method, _endpoint, data, _headers| + assert_equal false, JSON.parse(data)['cardPaymentMethodSpecificInput']['requiresApproval'] + end.respond_with(successful_authorize_response) + end + + def test_successful_purchase_airline_fields + options = @options.merge( + airline_data: { + code: 111, + name: 'Spreedly Airlines', + flight_date: '20190810', + passenger_name: 'Randi Smith', + agent_numeric_code: '12345', + flight_legs: [ + { arrival_airport: 'BDL', + origin_airport: 'RDU', + date: '20190810', + carrier_code: 'SA', + number: 596, + airline_class: 'ZZ' }, + { arrival_airport: 'RDU', + origin_airport: 'BDL', + date: '20190817', + carrier_code: 'SA', + number: 597, + airline_class: 'ZZ' } + ] + } + ) + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_equal 111, JSON.parse(data)['order']['additionalInput']['airlineData']['code'] + assert_equal '20190810', JSON.parse(data)['order']['additionalInput']['airlineData']['flightDate'] + assert_equal 2, JSON.parse(data)['order']['additionalInput']['airlineData']['flightLegs'].length + end.respond_with(successful_authorize_response, successful_capture_response) + end + + def test_successful_purchase_lodging_fields + options = @options.merge( + lodging_data: { + charges: [ + { charge_amount: '1000', + charge_amount_currency_code: 'USD', + charge_type: 'giftshop' } + ], + check_in_date: '20211223', + check_out_date: '20211227', + folio_number: 'randAssortmentofChars', + is_confirmed_reservation: 'true', + is_facility_fire_safety_conform: 'true', + is_no_show: 'false', + is_preference_smoking_room: 'false', + number_of_adults: '2', + number_of_nights: '1', + number_of_rooms: '1', + program_code: 'advancedDeposit', + property_customer_service_phone_number: '5555555555', + property_phone_number: '5555555555', + renter_name: 'Guy', + rooms: [ + { daily_room_rate: '25000', + daily_room_rate_currency_code: 'USD', + daily_room_tax_amount: '5', + daily_room_tax_amount_currency_code: 'USD', + number_of_nights_at_room_rate: '1', + room_location: 'Courtyard', + type_of_bed: 'Queen', + type_of_room: 'Walled' } + ] + } + ) + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_equal 'advancedDeposit', JSON.parse(data)['order']['additionalInput']['lodgingData']['programCode'] + assert_equal '20211223', JSON.parse(data)['order']['additionalInput']['lodgingData']['checkInDate'] + assert_equal '1000', JSON.parse(data)['order']['additionalInput']['lodgingData']['charges'][0]['chargeAmount'] + end.respond_with(successful_authorize_response, successful_capture_response) + end + + def test_successful_purchase_passenger_fields + options = @options.merge( + airline_data: { + passengers: [ + { first_name: 'Randi', + surname: 'Smith', + surname_prefix: 'S', + title: 'Mr' }, + { first_name: 'Julia', + surname: 'Smith', + surname_prefix: 'S', + title: 'Mrs' } + ] + } + ) + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_equal 'Julia', JSON.parse(data)['order']['additionalInput']['airlineData']['passengers'][1]['firstName'] + assert_equal 2, JSON.parse(data)['order']['additionalInput']['airlineData']['passengers'].length + end.respond_with(successful_authorize_response, successful_capture_response) + end + + def test_purchase_passes_installments + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, @options.merge(number_of_installments: '3')) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"numberOfInstallments\":\"3\"/, data) + end.respond_with(successful_authorize_response, successful_capture_response) + end + def test_purchase_does_not_run_capture_if_authorize_auto_captured - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@accepted_amount, @credit_card, @options) end.respond_with(successful_capture_response) @@ -46,19 +349,19 @@ def test_purchase_does_not_run_capture_if_authorize_auto_captured end def test_authorize_with_pre_authorization_flag - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, @credit_card, @options.merge(pre_authorization: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/PRE_AUTHORIZATION/, data) - end.respond_with(successful_authorize_response) + end.respond_with(successful_authorize_response_with_pre_authorization_flag) assert_success response end def test_authorize_without_pre_authorization_flag - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/FINAL_AUTHORIZATION/, data) end.respond_with(successful_authorize_response) @@ -76,27 +379,76 @@ def test_successful_authorization_with_extra_options { 'website' => 'www.example.com', 'giftMessage' => 'Happy Day!' - } + }, + payment_product_id: '123ABC' } ) - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + 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("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) assert_success response end - def test_trucates_first_name_to_15_chars + 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/, data) + assert_match(/externalCardholderAuthenticationData/, data) + assert_match(/"eci\":\"05\"/, data) + assert_match(/"cavv\":\"jJ81HADVRtXfCBATEp01CJUAAAA=\"/, data) + assert_match(/"xid\":\"BwABBJQ1AgAAAAAgJDUCAAAAAAA=\"/, data) + assert_match(/"threeDSecureVersion\":\"2.1.0\"/, data) + assert_match(/"directoryServerTransactionId\":\"97267598-FAE6-48F2-8083-C23433990FBC\"/, data) + assert_match(/"acsTransactionId\":\"13c701a3-5a88-4c45-89e9-ef65e50a8bf9\"/, data) + assert_match(/"cavvAlgorithm\":1/, data) + assert_match(/"validationResult\":\"Y\"/, data) + end.respond_with(successful_authorize_with_3ds2_data_response) + + assert_success response + end + + def test_does_not_send_3ds_auth_when_empty + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@accepted_amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_not_match(/threeDSecure/, data) + assert_not_match(/externalCardholderAuthenticationData/, data) + assert_not_match(/cavv/, data) + assert_not_match(/xid/, data) + assert_not_match(/threeDSecureVersion/, data) + assert_not_match(/directoryServerTransactionId/, data) + assert_not_match(/acsTransactionId/, data) + assert_not_match(/cavvAlgorithm/, data) + assert_not_match(/validationResult/, data) + end.respond_with(successful_authorize_response) + + 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' }) - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/thisisaverylong/, data) end.respond_with(successful_authorize_response) @@ -104,8 +456,37 @@ def test_trucates_first_name_to_15_chars assert_equal '000000142800000000920000100001', response.authorization end + def test_handles_blank_names + credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil }) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@accepted_amount, credit_card, @options) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_truncates_split_address_fields + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, { + billing_address: { + address1: '1234 Supercalifragilisticexpialidociousthiscantbemorethanfiftycharacters', + address2: 'Unit 6', + city: '‎Portland', + state: 'ME', + zip: '09901', + country: 'US' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + 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 + def test_failed_authorize - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@rejected_amount, @declined_card, @options) end.respond_with(failed_authorize_response) @@ -114,72 +495,99 @@ def test_failed_authorize end def test_failed_capture - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.capture(100, '', @options) end.respond_with(failed_capture_response) assert_failure response end + def test_successful_inquire + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@accepted_amount, @credit_card, @options) + end.respond_with(successful_capture_response) + + assert_success response + + response = stub_comms(@gateway, :ssl_request) do + @gateway.inquire(response.authorization) + end.respond_with(successful_inquire_response) + + assert_success response + end + def test_successful_void - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@accepted_amount, @credit_card, @options) end.respond_with(successful_capture_response) assert_success response - assert_equal '000000142800000000920000100001', response.authorization - void = stub_comms do + void = stub_comms(@gateway, :ssl_request) do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| - assert_match(/000000142800000000920000100001/, endpoint) end.respond_with(successful_void_response) assert_success void end def test_failed_void - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.void('5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, endpoint) end.respond_with(failed_void_response) assert_failure response end + def test_successful_provider_unresponsive_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.respond_with(successful_provider_unresponsive_void_response) + + assert_success response + end + + def test_failed_provider_unresponsive_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.respond_with(failed_provider_unresponsive_void_response) + + assert_failure response + end + def test_successful_verify - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.verify(@credit_card, @options) - end.respond_with(successful_verify_response) + end.respond_with(successful_authorize_response, successful_void_response) assert_equal '000000142800000000920000100001', response.authorization assert_success response end def test_failed_verify - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.verify(@credit_card, @options) - end.respond_with(failed_verify_response) - assert_equal 'cee09c50-5d9d-41b8-b740-8c7bf06d2c66', response.authorization + end.respond_with(failed_authorize_response) + assert_equal '000000142800000000640000100001', response.authorization assert_failure response end def test_successful_refund - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@accepted_amount, @credit_card, @options) end.respond_with(successful_authorize_response) assert_equal '000000142800000000920000100001', response.authorization - capture = stub_comms do + capture = stub_comms(@gateway, :ssl_request) do @gateway.capture(@accepted_amount, response.authorization) end.respond_with(successful_capture_response) - refund = stub_comms do + refund = stub_comms(@gateway, :ssl_request) do @gateway.refund(@accepted_amount, capture.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_method, endpoint, _data, _headers| assert_match(/000000142800000000920000100001/, endpoint) end.respond_with(successful_refund_response) @@ -187,15 +595,15 @@ def test_successful_refund end def test_refund_passes_currency_code - stub_comms do - @gateway.refund(@accepted_amount, '000000142800000000920000100001', {currency: 'COP'}) - end.check_request do |endpoint, data, headers| + stub_comms(@gateway, :ssl_request) do + @gateway.refund(@accepted_amount, '000000142800000000920000100001', { currency: 'COP' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/"currencyCode\":\"COP\"/, data) end.respond_with(failed_refund_response) 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) @@ -203,7 +611,7 @@ def test_failed_refund end def test_rejected_refund - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.refund(@accepted_amount, '000000142800000000920000100001') end.respond_with(rejected_refund_response) @@ -213,7 +621,7 @@ def test_rejected_refund end def test_invalid_raw_response - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@accepted_amount, @credit_card, @options) end.respond_with(invalid_json_response) @@ -227,13 +635,23 @@ def test_scrub end def test_scrub_invalid_response - response = stub_comms do + response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@accepted_amount, @credit_card, @options) end.respond_with(invalid_json_plus_card_data).message assert_equal @gateway.scrub(response), scrubbed_invalid_json_plus end + def test_authorize_with_optional_idempotency_key_header + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@accepted_amount, @credit_card, @options.merge(idempotency_key: 'test123')) + end.check_request do |_method, _endpoint, _data, headers| + assert_equal headers['X-GCS-Idempotence-Key'], 'test123' + end.respond_with(successful_authorize_response) + + assert_success response + end + private def pre_scrubbed @@ -349,15 +767,23 @@ def post_scrubbed end def successful_authorize_response - %({\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000092\",\n \"externalReference\" : \"000000142800000000920000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20160316205952\",\n \"isAuthorized\" : true\n }\n }\n}) + "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000092\",\n \"externalReference\" : \"000000142800000000920000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 4005,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0920\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20191203162910\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}" + end + + def successful_authorize_with_3ds2_data_response + %({\"creationOutput\":{\"additionalReference\":\"00000021960000002279\",\"externalReference\":\"000000219600000022790000100001\"},\"payment\":{\"id\":\"000000219600000022790000100001\",\"paymentOutput\":{\"amountOfMoney\":{\"amount\":100,\"currencyCode\":\"USD\"},\"references\":{\"paymentReference\":\"0\"},\"paymentMethod\":\"card\",\"cardPaymentMethodSpecificOutput\":{\"paymentProductId\":1,\"authorisationCode\":\"OK1131\",\"fraudResults\":{\"fraudServiceResult\":\"no-advice\",\"avsResult\":\"0\",\"cvvResult\":\"0\"},\"threeDSecureResults\":{\"cavv\":\"jJ81HADVRtXfCBATEp01CJUAAAA=\",\"directoryServerTransactionId\":\"97267598-FAE6-48F2-8083-C23433990FBC\",\"eci\":\"5\",\"threeDSecureVersion\":\"2.1.0\"},\"card\":{\"cardNumber\":\"************7977\",\"expiryDate\":\"0921\"}}},\"status\":\"PENDING_APPROVAL\",\"statusOutput\":{\"isCancellable\":true,\"statusCategory\":\"PENDING_MERCHANT\",\"statusCode\":600,\"statusCodeChangeDateTime\":\"20201029212921\",\"isAuthorized\":true,\"isRefundable\":false}}}) end def failed_authorize_response %({\n \"errorId\" : \"460ec7ed-f8be-4bd7-bf09-a4cbe07f774e\",\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"message\" : \"Not authorised\"\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000064\",\n \"externalReference\" : \"000000142800000000640000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000640000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"requestId\" : \"55635\",\n \"message\" : \"Not authorised\"\n } ],\n \"isCancellable\" : false,\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20160316154235\",\n \"isAuthorized\" : false\n }\n }\n }\n}) end + def successful_authorize_response_with_pre_authorization_flag + %({\n \"creationOutput\" : {\n \"additionalReference\" : \"00000021960000000968\",\n \"externalReference\" : \"000000219600000009680000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000219600000009680000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 4005,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0920\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_MERCHANT\",\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20191017153833\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}) + end + def successful_capture_response - %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20160317191047\",\n \"isAuthorized\" : true\n }\n }\n}) + "{\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 4005,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0920\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCategory\" : \"PENDING_CONNECT_OR_3RD_PARTY\",\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20191203163030\",\n \"isAuthorized\" : true,\n \"isRefundable\" : false\n }\n }\n}" end def failed_capture_response @@ -376,20 +802,24 @@ def rejected_refund_response %({\n \"id\" : \"00000022184000047564000-100001\",\n \"refundOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 627000,\n \"currencyCode\" : \"COP\"\n },\n \"references\" : {\n \"merchantReference\" : \"17091GTgZmcC\",\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardRefundMethodSpecificOutput\" : {\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 1850,\n \"statusCodeChangeDateTime\" : \"20170313230631\"\n }\n}) end + def successful_inquire_response + %({\n \"payment\" : {\n \"id\" : \"000001263340000255950000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 126000,\n \"currencyCode\" : \"ARS\"\n },\n \"references\" : {\n \"merchantReference\" : \"10032994586\",\n \"paymentReference\" : \"0\",\n \"providerId\" : \"88\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"002792\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"493768******8095\",\n \"expiryDate\" : \"0824\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20220214193408\",\n \"isAuthorized\" : true\n }\n }\n }) + end + def successful_void_response - %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20160317191526\"\n }\n }\n}) + %({\n \"payment\" : {\n \"id\" : \"000001263340000255950000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 126000,\n \"currencyCode\" : \"ARS\"\n },\n \"references\" : {\n \"merchantReference\" : \"10032994586\",\n \"paymentReference\" : \"0\",\n \"providerId\" : \"88\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"002792\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"493768******8095\",\n \"expiryDate\" : \"0824\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20220214193408\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n },\n \"cardPaymentMethodSpecificOutput\" : {\n \"voidResponseId\" : \"00\"\n }\n}) end def failed_void_response %({\n \"errorId\" : \"9e38736e-15f3-4d6b-8517-aad3029619b9\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"INVALID_PAYMENT_ID\"\n } ]\n}) end - def successful_verify_response - %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20160318170240\"\n }\n }\n}) + def successful_provider_unresponsive_void_response + %({\n \"payment\" : {\n \"id\" : \"000001263340000255950000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 126000,\n \"currencyCode\" : \"ARS\"\n },\n \"references\" : {\n \"merchantReference\" : \"10032994586\",\n \"paymentReference\" : \"0\",\n \"providerId\" : \"88\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"002792\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"493768******8095\",\n \"expiryDate\" : \"0824\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20220214193408\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n },\n \"cardPaymentMethodSpecificOutput\" : {\n \"voidResponseId\" : \"98\"\n }\n}) end - def failed_verify_response - %({\n \"errorId\" : \"cee09c50-5d9d-41b8-b740-8c7bf06d2c66\",\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"message\" : \"Not authorised\"\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000134\",\n \"externalReference\" : \"000000142800000000920000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"requestId\" : \"64357\",\n \"message\" : \"Not authorised\"\n } ],\n \"isCancellable\" : false,\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20160318170253\",\n \"isAuthorized\" : false\n }\n }\n }\n}) + def failed_provider_unresponsive_void_response + %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"ARS\"\n },\n \"references\" : {\n \"merchantReference\" : \"0\",\n \"paymentReference\" : \"0\",\n \"providerId\" : \"88\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 3,\n \"authorisationCode\" : \"123456\",\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n },\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0125\"\n }\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20191011201122\",\n \"isAuthorized\" : false,\n \"isRefundable\" : false\n }\n },\n \"cardPaymentMethodSpecificOutput\" : {\n \"voidResponseId\" : \"98\"\n }\n}) end def invalid_json_response diff --git a/test/unit/gateways/global_transport_test.rb b/test/unit/gateways/global_transport_test.rb index b64fd765323..637ace0ed49 100644 --- a/test/unit/gateways/global_transport_test.rb +++ b/test/unit/gateways/global_transport_test.rb @@ -9,7 +9,7 @@ def setup @options = { order_id: '1', - billing_address: address, + billing_address: address } end @@ -21,7 +21,7 @@ def test_successful_purchase assert_equal '3648838', response.authorization assert response.test? assert_equal 'CVV matches', response.cvv_result['message'] - assert_equal 'Street address and postal code do not match.', response.avs_result['message'] + assert_equal 'Street address and postal code do not match. For American Express: Card member\'s name, street address and postal code do not match.', response.avs_result['message'] end def test_failed_purchase @@ -52,7 +52,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(100, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/PNRef=3648890/, data) end.respond_with(successful_capture_response) @@ -70,7 +70,7 @@ def test_successful_partial_authorize_and_capture capture = stub_comms do @gateway.capture(150, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/PNRef=8869269/, data) end.respond_with(successful_partial_capture_response) @@ -103,7 +103,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(100, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/PNRef=3648838/, data) end.respond_with(successful_refund_response) @@ -127,7 +127,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/PNRef=3648838/, data) end.respond_with(successful_void_response) @@ -162,7 +162,7 @@ def test_failed_verify def test_truncation stub_comms do @gateway.purchase(100, credit_card, order_id: 'a' * 17) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/&InvNum=a{16}&/, data) end.respond_with(successful_purchase_response) end diff --git a/test/unit/gateways/hdfc_test.rb b/test/unit/gateways/hdfc_test.rb index 9913adedf19..935c0adf433 100644 --- a/test/unit/gateways/hdfc_test.rb +++ b/test/unit/gateways/hdfc_test.rb @@ -7,8 +7,8 @@ def setup Base.mode = :test @gateway = HdfcGateway.new( - :login => 'login', - :password => 'password' + login: 'login', + password: 'password' ) @credit_card = credit_card @@ -47,7 +47,7 @@ def test_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/2441955352022771/, data) end.respond_with(successful_capture_response) @@ -64,7 +64,7 @@ def test_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/849768440022761/, data) end.respond_with(successful_refund_response) @@ -74,45 +74,45 @@ def test_refund def test_passing_cvv stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_purchase_response) end def test_passing_currency stub_comms do - @gateway.purchase(@amount, @credit_card, :currency => 'INR') - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, currency: 'INR') + end.check_request do |_endpoint, data, _headers| assert_match(/currencycode>356 'AOA') + @gateway.purchase(@amount, @credit_card, currency: 'AOA') end end def test_passing_order_id stub_comms do - @gateway.purchase(@amount, @credit_card, :order_id => '932823723') - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, order_id: '932823723') + end.check_request do |_endpoint, data, _headers| assert_match(/932823723/, data) end.respond_with(successful_purchase_response) end def test_passing_description stub_comms do - @gateway.purchase(@amount, @credit_card, :description => 'Awesome Services By Us') - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, description: 'Awesome Services By Us') + end.check_request do |_endpoint, data, _headers| assert_match(/Awesome Services By Us/, data) end.respond_with(successful_purchase_response) end def test_escaping stub_comms do - @gateway.purchase(@amount, @credit_card, :order_id => 'a' * 41, :description => "This has 'Hack Characters' ~`!\#$%^=+|\\:'\",;<>{}[]() and non-Hack Characters -_@.") - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, order_id: 'a' * 41, description: "This has 'Hack Characters' ~`!\#$%^=+|\\:'\",;<>{}[]() and non-Hack Characters -_@.") + end.check_request do |_endpoint, data, _headers| assert_match(/>This has Hack Characters and non-Hack Characters -_@.#{"a" * 40} address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/udf4>Jim Smith\nWidgets Inc\n456 My Street\nApt 1\nOttawa ON K1C2N6\nCA/, data) end.respond_with(successful_purchase_response) end def test_passing_phone_number stub_comms do - @gateway.purchase(@amount, @credit_card, :billing_address => address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/udf3>555555-5555 address(:phone => nil)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address(phone: nil)) + end.check_request do |_endpoint, data, _headers| assert_no_match(/udf3/, data) end.respond_with(successful_purchase_response) end def test_passing_eci stub_comms do - @gateway.purchase(@amount, @credit_card, :eci => 22) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, eci: 22) + end.check_request do |_endpoint, data, _headers| assert_match(/eci>22 '12'}) + @gateway = HpsGateway.new({ secret_api_key: '12' }) @credit_card = credit_card @amount = 100 + @check = check(account_number: '1357902468', routing_number: '122000030', number: '1234', account_type: 'SAVINGS') + @check_amount = 2000 @options = { order_id: '1', @@ -34,6 +38,52 @@ def test_successful_purchase_no_address assert_success response end + def test_successful_zip_formatting + @options[:billing_address][:zip] = '12345-1234 ' + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/123451234<\/hps:CardHolderZip>/, data) + end.respond_with(successful_swipe_purchase_response) + + assert_success response + end + + def test_successful_check_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@check_amount, @check, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/SALE<\/hps:CheckAction>/, data) + end.respond_with(successful_check_purchase_response) + + assert_success response + end + + def test_check_purchase_does_not_raise_no_method_error_when_account_type_missing + check = @check.dup + check.account_type = nil + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@check_amount, check, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/SALE<\/hps:CheckAction>/, data) + end.respond_with(successful_check_purchase_response) + + assert_success response + end + + def test_check_purchase_does_not_raise_no_method_error_when_account_holder_type_missing + check = @check.dup + check.account_holder_type = nil + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@check_amount, check, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/SALE<\/hps:CheckAction>/, data) + end.respond_with(successful_check_purchase_response) + + assert_success response + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_charge_response) @@ -104,6 +154,20 @@ def test_failed_refund assert_failure refund end + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_refund_response) + + credit = @gateway.credit(@amount, @credit_card) + assert_success credit + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_refund_response) + + credit = @gateway.refund(@amount, @credit_card) + assert_failure credit + end + def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) @@ -112,6 +176,16 @@ def test_successful_void assert_success void end + def test_successful_check_void + void = stub_comms(@gateway, :ssl_request) do + @gateway.void('169054', check_void: true) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(//, data) + end.respond_with(successful_check_void_response) + + assert_success void + end + def test_failed_void @gateway.expects(:ssl_post).returns(failed_refund_response) @@ -120,6 +194,18 @@ def test_failed_void assert_failure void end + def test_successful_recurring_purchase + stored_credential_params = { + reason_type: 'recurring' + } + + @gateway.expects(:ssl_post).returns(successful_charge_response) + + response = @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + assert_instance_of Response, response + assert_success response + end + def test_successful_purchase_with_swipe_no_encryption @gateway.expects(:ssl_post).returns(successful_swipe_purchase_response) @@ -195,418 +281,1062 @@ def test_transcript_scrubbing assert_equal @gateway.scrub(pre_scrub), post_scrub end + def test_account_number_scrubbing + assert_equal @gateway.scrub(pre_scrubbed_account_number), post_scrubbed_account_number + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(failed_charge_response_decline) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(failed_charge_response_decline) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_auth_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(failed_authorize_response_decline) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_auth_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(failed_authorize_response_decline) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_purchase_with_android_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_purchase_with_android_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_android_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_android_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :android_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :android_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :google_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :google_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_purchase_with_google_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :google_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :google_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_google_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :google_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :google_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_google_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :google_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + 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', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :google_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_three_d_secure_visa + @credit_card.number = '4012002000060016' + @credit_card.brand = 'visa' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/(.*)<\/hps:SecureECommerce>/, data) + assert_match(/Visa 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/5<\/hps:ECommerceIndicator>/, data) + assert_match(/#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_mastercard + @credit_card.number = '5473500000000014' + @credit_card.brand = 'master' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/(.*)<\/hps:SecureECommerce>/, data) + assert_match(/MasterCard 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/5<\/hps:ECommerceIndicator>/, data) + assert_match(/#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_discover + @credit_card.number = '6011000990156527' + @credit_card.brand = 'discover' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '5', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/(.*)<\/hps:SecureECommerce>/, data) + assert_match(/Discover 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/5<\/hps:ECommerceIndicator>/, data) + assert_match(/#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_amex + @credit_card.number = '372700699251018' + @credit_card.brand = 'american_express' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '05', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/(.*)<\/hps:SecureECommerce>/, data) + assert_match(/AMEX 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/5<\/hps:ECommerceIndicator>/, data) + assert_match(/#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_jcb + @credit_card.number = '372700699251018' + @credit_card.brand = 'jcb' + + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '5', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + refute_match(/(.*)<\/hps:SecureECommerce>/, data) + refute_match(/(.*)<\/hps:PaymentDataSource>/, data) + refute_match(/3DSecure<\/hps:TypeOfPaymentData>/, data) + refute_match(/#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + refute_match(/5<\/hps:ECommerceIndicator>/, data) + refute_match(/#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_auth_with_stored_credentials + stored_credential_params = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'customer', + network_transaction_id: 12345 + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_params })) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/C<\/hps:CardOnFile>/, data) + assert_match(/12345<\/hps:CardBrandTxnId>/, data) + assert_match(/N<\/hps:OneTime>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_truncates_long_invoicenbr + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@check_amount, @check, @options.merge(order_id: '04863692e6b56aaed85760b3d0879afd18b980da0521f6454c007a838435e561')) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/04863692e6b56aaed85760b3d0879afd18b980da0521f6454c007a838435<\/hps:InvoiceNbr>/, data) + end.respond_with(successful_check_purchase_response) + + assert_success response + assert_equal 'Success', response.message + end + private def successful_charge_response - <<-RESPONSE - - - - - -
- 95878 - 95881 - 2409000 - 15927453 - 0 - Success - 2014-03-14T15:40:25.4686202 -
- - - 00 - APPROVAL - 36987A - 0 - M - 407313649105 - ACCEPT - ACCEPT - Visa - AVS Not Requested. - Match. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 95878 + 95881 + 2409000 + 15927453 + 0 + Success + 2014-03-14T15:40:25.4686202 +
+ + + 00 + APPROVAL + 36987A + 0 + M + 407313649105 + ACCEPT + ACCEPT + Visa + AVS Not Requested. + Match. + + +
+
+
+
+ XML + end + + def successful_check_purchase_response + <<~XML + + + + + +
+ 144379 + 144474 + 6407594 + 1284694345 + 0 + Success + 2020-01-13T15:11:24.735047 +
+ + + 0 + Transaction Approved. BatchID:31796 + + +
+
+
+
+ XML end def failed_charge_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 16099851 - 0 - Success - 2014-03-17T13:01:55.851307 -
- - - 02 - CALL - - 0 - 407613674802 - Visa - AVS Not Requested. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16099851 + 0 + Success + 2014-03-17T13:01:55.851307 +
+ + + 02 + CALL + + 0 + 407613674802 + Visa + AVS Not Requested. + + +
+
+
+
+ XML + end + + def failed_charge_response_decline + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16099851 + 0 + Success + 2014-03-17T13:01:55.851307 +
+ + + 05 + DECLINE + + 0 + 407613674802 + Visa + AVS Not Requested. + + +
+
+
+
+ XML end def successful_authorize_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 16072891 - 0 - Success - 2014-03-17T13:05:34.5819712 -
- - - 00 - APPROVAL - 43204A - 0 - M - 407613674895 - ACCEPT - ACCEPT - Visa - AVS Not Requested. - Match. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16072891 + 0 + Success + 2014-03-17T13:05:34.5819712 +
+ + + 00 + APPROVAL + 43204A + 0 + M + 407613674895 + ACCEPT + ACCEPT + Visa + AVS Not Requested. + Match. + + +
+
+
+
+ XML end def failed_authorize_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 16088893 - 0 - Success - 2014-03-17T13:06:45.449707 -
- - - 54 - EXPIRED CARD - - 0 - 407613674811 - Visa - AVS Not Requested. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16088893 + 0 + Success + 2014-03-17T13:06:45.449707 +
+ + + 54 + EXPIRED CARD + + 0 + 407613674811 + Visa + AVS Not Requested. + + +
+
+
+
+ XML + end + + def failed_authorize_response_decline + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16088893 + 0 + Success + 2014-03-17T13:06:45.449707 +
+ + + 05 + DECLINE + + 0 + 407613674811 + Visa + AVS Not Requested. + + +
+
+
+
+ XML end def successful_capture_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 17213037 - 0 - Success - 2014-05-16T14:45:48.9906929 -
- - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 17213037 + 0 + Success + 2014-05-16T14:45:48.9906929 +
+ + + +
+
+
+
+ XML end def failed_capture_response - <<-Response - - - - - -
- 21229 - 21232 - 1525997 - 16104055 - 3 - Transaction rejected because the referenced original transaction is invalid. Subject '216072899'. Original transaction not found. - 2014-03-17T14:20:32.355307 -
-
-
-
-
- Response + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16104055 + 3 + Transaction rejected because the referenced original transaction is invalid. Subject '216072899'. Original transaction not found. + 2014-03-17T14:20:32.355307 +
+
+
+
+
+ XML end def successful_refund_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - - 16092738 - 0 - Success - 2014-03-17T13:31:42.0231712 -
- - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + + 16092738 + 0 + Success + 2014-03-17T13:31:42.0231712 +
+ + + +
+
+
+
+ XML end def failed_refund_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - - 16092766 - 3 - Transaction rejected because the referenced original transaction is invalid. - 2014-03-17T13:48:55.3203712 -
-
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + + 16092766 + 3 + Transaction rejected because the referenced original transaction is invalid. + 2014-03-17T13:48:55.3203712 +
+
+
+
+
+ XML end def successful_void_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 16092767 - 0 - Success - 2014-03-17T13:53:43.6863712 -
- - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16092767 + 0 + Success + 2014-03-17T13:53:43.6863712 +
+ + + +
+
+
+
+ XML + end + + def successful_check_void_response + <<~XML + + + + + +
+ 144379 + 144474 + 6407594 + 1284696436 + 0 + Success + 2020-01-13T15:44:24.3568038 +
+ + + 0 + Transaction Approved. + + +
+
+
+
+ XML end def failed_void_response - <<-RESPONSE - - - - - -
- 21229 - 21232 - 1525997 - 16103858 - 3 - Transaction rejected because the referenced original transaction is invalid. Subject '169054'. Original transaction not found. - 2014-03-17T13:55:56.8947712 -
-
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 21229 + 21232 + 1525997 + 16103858 + 3 + Transaction rejected because the referenced original transaction is invalid. Subject '169054'. Original transaction not found. + 2014-03-17T13:55:56.8947712 +
+
+
+
+
+ XML end def successful_swipe_purchase_response - <<-RESPONSE - - - - - -
- 95878 - 95881 - 2409000 - 17596558 - 0 - Success - 2014-05-26T10:27:30.4211513 -
- - - 00 - APPROVAL - 037677 - 0 - 414614470800 - ACCEPT - MC - AVS Not Requested. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 95878 + 95881 + 2409000 + 17596558 + 0 + Success + 2014-05-26T10:27:30.4211513 +
+ + + 00 + APPROVAL + 037677 + 0 + 414614470800 + ACCEPT + MC + AVS Not Requested. + + +
+
+
+
+ XML end def failed_swipe_purchase_response - <<-RESPONSE - - - - - -
- 95878 - 95881 - 2409000 - 17602711 - 8 - Transaction was rejected because the track data could not be read. - 2014-05-26T10:42:44.5031513 -
-
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 95878 + 95881 + 2409000 + 17602711 + 8 + Transaction was rejected because the track data could not be read. + 2014-05-26T10:42:44.5031513 +
+
+
+
+
+ XML end def successful_verify_response - <<-RESPONSE - - - - - -
- 95878 - 95881 - 2409000 - - 20153225 - 0 - Success - 2014-09-04T14:43:49.6015895 -
- - - 85 - CARD OK - 65557A - 0 - M - 424715929580 - Visa - AVS Not Requested. - Match. - - -
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 95878 + 95881 + 2409000 + + 20153225 + 0 + Success + 2014-09-04T14:43:49.6015895 +
+ + + 85 + CARD OK + 65557A + 0 + M + 424715929580 + Visa + AVS Not Requested. + Match. + + +
+
+
+
+ XML end def failed_verify_response - <<-RESPONSE - - - - - -
- 95878 - 95881 - 2409000 - - 20155097 - 14 - Transaction rejected because the manually entered card number is invalid. - 2014-09-04T15:42:47.983634 -
-
-
-
-
- RESPONSE + <<~XML + + + + + +
+ 95878 + 95881 + 2409000 + + 20155097 + 14 + Transaction rejected because the manually entered card number is invalid. + 2014-09-04T15:42:47.983634 +
+
+
+
+
+ XML end def pre_scrub @@ -616,7 +1346,7 @@ def pre_scrub starting SSL for posgateway.cert.secureexchange.net:443... SSL established <- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: posgateway.cert.secureexchange.net\r\nContent-Length: 1295\r\n\r\n" -<- "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ1.00YLongbobLongsen456 My StreetOttawaONK1C2N6Store Purchase1400010001111222492019123NNN" +<- "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ1.00YLongbobLongsen456 My StreetOttawaONK1C2N6Store Purchase1400010001111222492019123NNNApplePay3DSecureEHuWW9PiBkWvqE5juRwDzAUFBAk5abc123" -> "HTTP/1.1 200 OK\r\n" -> "Cache-Control: private, max-age=0\r\n" -> "Content-Type: text/xml; charset=utf-8\r\n" @@ -646,7 +1376,7 @@ def post_scrub starting SSL for posgateway.cert.secureexchange.net:443... SSL established <- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: posgateway.cert.secureexchange.net\r\nContent-Length: 1295\r\n\r\n" -<- "[FILTERED]1.00YLongbobLongsen456 My StreetOttawaONK1C2N6Store Purchase1[FILTERED]92019[FILTERED]NNN" +<- "[FILTERED]1.00YLongbobLongsen456 My StreetOttawaONK1C2N6Store Purchase1[FILTERED]92019[FILTERED]NNNApplePay3DSecure[FILTERED]5abc123" -> "HTTP/1.1 200 OK\r\n" -> "Cache-Control: private, max-age=0\r\n" -> "Content-Type: text/xml; charset=utf-8\r\n" @@ -669,4 +1399,57 @@ def post_scrub } end + def pre_scrubbed_account_number + <<~PRE_SCRUBBED + opening connection to posgateway.secureexchange.net:443... + opened + starting SSL for posgateway.secureexchange.net:443... + SSL established, protocol: TLSv1.2, cipher: DHE-RSA-AES256-SHA256 + <- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: posgateway.secureexchange.net\r\nContent-Length: 1029\r\n\r\n" + <- "SALE12200003013579024681234SAVINGSPERSONAL20.00WEBJimSmithHot Buttered Toast IncorporatedStore Purchase1" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache,no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Expires: -1\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains;\r\n" + -> "Date: Tue, 12 Oct 2021 15:17:29 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 543\r\n" + -> "\r\n" + reading 543 bytes... + -> "
1589181322-2Authentication Error
" + read 543 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed_account_number + <<~POST_SCRUBBED + opening connection to posgateway.secureexchange.net:443... + opened + starting SSL for posgateway.secureexchange.net:443... + SSL established, protocol: TLSv1.2, cipher: DHE-RSA-AES256-SHA256 + <- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: posgateway.secureexchange.net\r\nContent-Length: 1029\r\n\r\n" + <- "SALE[FILTERED][FILTERED]1234SAVINGSPERSONAL20.00WEBJimSmithHot Buttered Toast IncorporatedStore Purchase1" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache,no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Expires: -1\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains;\r\n" + -> "Date: Tue, 12 Oct 2021 15:17:29 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 543\r\n" + -> "\r\n" + reading 543 bytes... + -> "
1589181322-2Authentication Error
" + read 543 bytes + Conn close + POST_SCRUBBED + end end diff --git a/test/unit/gateways/iats_payments_test.rb b/test/unit/gateways/iats_payments_test.rb index ac94ec90912..5cb3aa396d1 100644 --- a/test/unit/gateways/iats_payments_test.rb +++ b/test/unit/gateways/iats_payments_test.rb @@ -5,18 +5,31 @@ class IatsPaymentsTest < Test::Unit::TestCase def setup @gateway = IatsPaymentsGateway.new( - :agent_code => 'login', - :password => 'password', - :region => 'uk' + agent_code: 'login', + password: 'password', + region: 'uk' ) @amount = 100 @credit_card = credit_card @check = check + @address = { + name: 'Jim Smith', + address1: '456 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '555-555-5555', + fax: '(555)555-6666', + email: 'jimsmith@example.com' + } @options = { - :ip => '71.65.249.145', - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase' + ip: '71.65.249.145', + order_id: generate_unique_id, + billing_address: @address, + description: 'Store purchase' } end @@ -38,9 +51,12 @@ def test_successful_purchase assert_match(/#{@options[:billing_address][:city]}<\/city>/, data) assert_match(/#{@options[:billing_address][:state]}<\/state>/, data) assert_match(/#{@options[:billing_address][:zip]}<\/zipCode>/, data) + assert_match(/#{@options[:billing_address][:phone]}<\/phone>/, data) + assert_match(/#{@options[:billing_address][:country]}<\/country>/, data) assert_match(/1.00<\/total>/, data) assert_match(/#{@options[:description]}<\/comment>/, data) - assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + assert_match(/#{@options[:billing_address][:email]}<\/email>/, data) + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLinkv3.asmx?op=ProcessCreditCard' assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' end.respond_with(successful_purchase_response) @@ -66,7 +82,7 @@ def test_successful_check_purchase response = stub_comms do @gateway.purchase(@amount, @check, @options) end.check_request do |endpoint, data, headers| - assert_match(/login<\/agentCode>/, data) assert_match(/password<\/password>/, data) assert_match(/#{@options[:ip]}<\/customerIPAddress>/, data) @@ -79,9 +95,12 @@ def test_successful_check_purchase assert_match(/#{@options[:billing_address][:city]}<\/city>/, data) assert_match(/#{@options[:billing_address][:state]}<\/state>/, data) assert_match(/#{@options[:billing_address][:zip]}<\/zipCode>/, data) + assert_match(/#{@options[:billing_address][:phone]}<\/phone>/, data) + assert_match(/#{@options[:billing_address][:country]}<\/country>/, data) assert_match(/1.00<\/total>/, data) + assert_match(/#{@options[:billing_address][:email]}<\/email>/, data) assert_match(/#{@options[:description]}<\/comment>/, data) - assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessACHEFTV1' + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLinkv3.asmx?op=ProcessACHEFT' assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' end.respond_with(successful_check_purchase_response) @@ -92,6 +111,17 @@ def test_successful_check_purchase assert_equal 'Success', response.message end + def test_successful_purchase_with_customer_details + @options[:email] = 'jsmith2@example.com' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/#{@options[:email]}<\/email>/, data) + end.respond_with(successful_purchase_response) + + assert response + end + def test_failed_check_purchase response = stub_comms do @gateway.purchase(@amount, @check, @options) @@ -107,7 +137,7 @@ def test_failed_check_purchase def test_successful_refund response = stub_comms do @gateway.refund(@amount, '1234', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/transactionId>/, data) assert_match(/-1.00<\/total>/, data) end.respond_with(successful_refund_response) @@ -121,13 +151,13 @@ def test_successful_check_refund response = stub_comms do @gateway.refund(@amount, 'ref|check', @options) end.check_request do |endpoint, data, headers| - assert_match(/login<\/agentCode>/, data) assert_match(/password<\/password>/, data) assert_match(/#{@options[:ip]}<\/customerIPAddress>/, data) assert_match(/-1.00<\/total>/, data) assert_match(/#{@options[:description]}<\/comment>/, data) - assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessACHEFTRefundWithTransactionIdV1' + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLinkv3.asmx?op=ProcessACHEFTRefundWithTransactionId' assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' end.respond_with(successful_check_refund_response) @@ -153,7 +183,7 @@ def test_failed_check_refund def test_failed_refund response = stub_comms do @gateway.refund(@amount, '1234', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1234<\/transactionId>/, data) assert_match(/-1.00<\/total>/, data) end.respond_with(failed_refund_response) @@ -166,7 +196,7 @@ def test_failed_refund def test_successful_store response = stub_comms do @gateway.store(@credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/beginDate/, data) assert_match(/endDate/, data) assert_match(%r{#{@credit_card.number}}, data) @@ -180,6 +210,18 @@ def test_successful_store assert_equal 'Success', response.message end + def test_successful_purchase_with_customer_code + response = stub_comms do + @gateway.purchase(@amount, 'CustomerCode', @options) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{CustomerCode}, data) + end.respond_with(successful_purchase_response) + + assert response + assert_success response + assert_equal 'Success', response.message + end + def test_failed_store response = stub_comms do @gateway.store(@credit_card, @options) @@ -193,7 +235,7 @@ def test_failed_store def test_successful_unstore response = stub_comms do @gateway.unstore('TheAuthorization', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{TheAuthorization}, data) end.respond_with(successful_unstore_response) @@ -205,17 +247,17 @@ def test_successful_unstore def test_deprecated_options assert_deprecation_warning("The 'login' option is deprecated in favor of 'agent_code' and will be removed in a future version.") do @gateway = IatsPaymentsGateway.new( - :login => 'login', - :password => 'password' + login: 'login', + password: 'password' ) end response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| assert_match(/login<\/agentCode>/, data) assert_match(/password<\/password>/, data) - assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLinkv3.asmx?op=ProcessCreditCard' end.respond_with(successful_purchase_response) assert_success response @@ -223,15 +265,15 @@ def test_deprecated_options def test_region_urls @gateway = IatsPaymentsGateway.new( - :agent_code => 'code', - :password => 'password', - :region => 'na' # North america + agent_code: 'code', + password: 'password', + region: 'na' # North america ) response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + end.check_request do |endpoint, _data, _headers| + assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLinkv3.asmx?op=ProcessCreditCard' end.respond_with(successful_purchase_response) assert_success response @@ -264,196 +306,196 @@ def test_supports_scrubbing? private def successful_purchase_response - <<-XML - - - - - - - Success - - - OK - - 04/22/2014 - 04/23/2014 - A6DE6F24 - - - - - - + <<~XML + + + + + + + Success + + + OK + + 04/22/2014 + 04/23/2014 + A6DE6F24 + + + + + + XML end def failed_purchase_response - <<-XML - - - - - - - Success - - - REJECT: 15 - - 04/22/2014 - 04/23/2014 - A6DE6F24 - - - - - - + <<~XML + + + + + + + Success + + + REJECT: 15 + + 04/22/2014 + 04/23/2014 + A6DE6F24 + + + + + + XML end def successful_check_purchase_response - <<-XML - - - - - - - Success - - - OK: 555555 - - A7F8B8B3 - - - - - - + <<~XML + + + + + + + Success + + + OK: 555555 + + A7F8B8B3 + + + + + + XML end def failed_check_purchase_response - <<-XML - - - - - - - Success - - - REJECT: 40 - - - - - - - - + <<~XML + + + + + + + Success + + + REJECT: 40 + + + + + + + + XML end def successful_refund_response - <<-XML - - - - - - - Success - - - OK: 678594: - - 04/22/2014 - 04/23/2014 - A6DEA654 - - - - - - + <<~XML + + + + + + + Success + + + OK: 678594: + + 04/22/2014 + 04/23/2014 + A6DEA654 + + + + + + XML end def successful_check_refund_response - <<-XML - - - - - - - Success - - - OK: 555555 - - A7F8B8B3 - - - - - - + <<~XML + + + + + + + Success + + + OK: 555555 + + A7F8B8B3 + + + + + + XML end def failed_check_refund_response - <<-XML - - - - - - - Success - - - REJECT: 39 - - 06/11/2015 - 06/12/2015 - - - - - - - + <<~XML + + + + + + + Success + + + REJECT: 39 + + 06/11/2015 + 06/12/2015 + + + + + + + XML end def failed_refund_response - <<-XML - - - - - - - Success - - - REJECT: 15 - - 04/22/2014 - 04/23/2014 - A6DEA654 - - - - - - + <<~XML + + + + + + + Success + + + REJECT: 15 + + 04/22/2014 + 04/23/2014 + A6DEA654 + + + + + + XML end @@ -462,8 +504,8 @@ def successful_store_response - - + + Success @@ -472,8 +514,8 @@ def successful_store_response A12181132 - - + + XML @@ -484,8 +526,8 @@ def failed_store_response - - + + Success @@ -494,8 +536,8 @@ def failed_store_response - - + + XML @@ -506,8 +548,8 @@ def successful_unstore_response - - + + Success @@ -516,8 +558,8 @@ def successful_unstore_response "A12181132" is deleted - - + + XML @@ -528,16 +570,16 @@ def failed_connection_response - - + + Failure Server Error - - + + XML @@ -549,8 +591,8 @@ def pre_scrub opened starting SSL for www.iatspayments.com:443... SSL established - <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCardV1 HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" - <- "TEST88TEST8863b5dd7098e8e3a9ff9a6f0992fdb6d51.00LongbobLongsen422222222222222009/17123VISA
456 My Street
OttawaONK1C2N6Store purchase
" + <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCard HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" + <- "TEST88TEST8863b5dd7098e8e3a9ff9a6f0992fdb6d51.00LongbobLongsen422222222222222009/17123VISA
456 My Street
OttawaONK1C2N6Store purchase
" -> "HTTP/1.1 200 OK\r\n" -> "Cache-Control: private, max-age=0\r\n" -> "Content-Type: application/soap+xml; charset=utf-8\r\n" @@ -562,7 +604,7 @@ def pre_scrub -> "Via: 1.1 sjc1-10\r\n" -> "\r\n" reading 719 bytes... - -> "Success OK: 678594:\n 09/28/2016\n 09/29/2016\nA92E3B72\n" + -> "Success OK: 678594:\n 09/28/2016\n 09/29/2016\nA92E3B72\n" read 719 bytes Conn close XML @@ -574,8 +616,8 @@ def post_scrub opened starting SSL for www.iatspayments.com:443... SSL established - <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCardV1 HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" - <- "[FILTERED][FILTERED]63b5dd7098e8e3a9ff9a6f0992fdb6d51.00LongbobLongsen[FILTERED]09/17[FILTERED]VISA
456 My Street
OttawaONK1C2N6Store purchase
" + <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCard HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" + <- "[FILTERED][FILTERED]63b5dd7098e8e3a9ff9a6f0992fdb6d51.00LongbobLongsen[FILTERED]09/17[FILTERED]VISA
456 My Street
OttawaONK1C2N6Store purchase
" -> "HTTP/1.1 200 OK\r\n" -> "Cache-Control: private, max-age=0\r\n" -> "Content-Type: application/soap+xml; charset=utf-8\r\n" @@ -587,7 +629,7 @@ def post_scrub -> "Via: 1.1 sjc1-10\r\n" -> "\r\n" reading 719 bytes... - -> "Success OK: 678594:\n 09/28/2016\n 09/29/2016\nA92E3B72\n" + -> "Success OK: 678594:\n 09/28/2016\n 09/29/2016\nA92E3B72\n" read 719 bytes Conn close XML diff --git a/test/unit/gateways/in_context_paypal_express_test.rb b/test/unit/gateways/in_context_paypal_express_test.rb index 4e92dc3032a..30bf8efffcc 100644 --- a/test/unit/gateways/in_context_paypal_express_test.rb +++ b/test/unit/gateways/in_context_paypal_express_test.rb @@ -8,9 +8,9 @@ class InContextPaypalExpressTest < Test::Unit::TestCase def setup @gateway = InContextPaypalExpressGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' + login: 'cody', + password: 'test', + pem: 'PEM' ) Base.mode = :test diff --git a/test/unit/gateways/inspire_test.rb b/test/unit/gateways/inspire_test.rb index b92b13ef34c..230db39427c 100644 --- a/test/unit/gateways/inspire_test.rb +++ b/test/unit/gateways/inspire_test.rb @@ -5,12 +5,12 @@ class InspireTest < Test::Unit::TestCase def setup @gateway = InspireGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @credit_card = credit_card('4242424242424242') @amount = 100 - @options = { :billing_address => address } + @options = { billing_address: address } end def test_successful_purchase @@ -60,8 +60,8 @@ def test_failed_refund def test_add_address result = {} - @gateway.send(:add_address, result, nil, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) - assert_equal ['address1', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, nil, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO' }) + assert_equal %w[address1 city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'CO', result[:state] assert_equal '164 Waverley Street', result[:address1] assert_equal 'US', result[:country] @@ -72,14 +72,14 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express], InspireGateway.supported_cardtypes + assert_equal %i[visa master american_express], InspireGateway.supported_cardtypes end def test_adding_store_adds_vault_id_flag result = {} - @gateway.send(:add_creditcard, result, @credit_card, :store => true) - assert_equal ['ccexp', 'ccnumber', 'customer_vault', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort + @gateway.send(:add_creditcard, result, @credit_card, store: true) + assert_equal %w[ccexp ccnumber customer_vault cvv firstname lastname], result.stringify_keys.keys.sort assert_equal 'add_customer', result[:customer_vault] end @@ -87,17 +87,17 @@ def test_blank_store_doesnt_add_vault_flag result = {} @gateway.send(:add_creditcard, result, @credit_card, {}) - assert_equal ['ccexp', 'ccnumber', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort + assert_equal %w[ccexp ccnumber cvv firstname lastname], result.stringify_keys.keys.sort assert_nil result[:customer_vault] end def test_accept_check post = {} - check = Check.new(:name => 'Fred Bloggs', - :routing_number => '111000025', - :account_number => '123456789012', - :account_holder_type => 'personal', - :account_type => 'checking') + check = Check.new(name: 'Fred Bloggs', + routing_number: '111000025', + account_number: '123456789012', + account_holder_type: 'personal', + account_type: 'checking') @gateway.send(:add_check, post, check) assert_equal %w[account_holder_type account_type checkaba checkaccount checkname payment], post.stringify_keys.keys.sort end diff --git a/test/unit/gateways/instapay_test.rb b/test/unit/gateways/instapay_test.rb index d70c9c52f72..bc58c48efea 100644 --- a/test/unit/gateways/instapay_test.rb +++ b/test/unit/gateways/instapay_test.rb @@ -2,7 +2,7 @@ class InstapayTest < Test::Unit::TestCase def setup - @gateway = InstapayGateway.new(:login => 'TEST0') + @gateway = InstapayGateway.new(login: 'TEST0') @credit_card = credit_card @amount = 100 end diff --git a/test/unit/gateways/ipg_test.rb b/test/unit/gateways/ipg_test.rb new file mode 100644 index 00000000000..255fbea4dce --- /dev/null +++ b/test/unit/gateways/ipg_test.rb @@ -0,0 +1,800 @@ +require 'test_helper' + +class IpgTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = IpgGateway.new(fixtures(:ipg)) + @credit_card = credit_card + @amount = 100 + + @options = { + currency: 'ARS' + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('sale', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_transaction_with_single_digit_card + @credit_card.month = 3 + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('03', REXML::XPath.first(doc, '//v1:CreditCardData//v1:ExpMonth').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_stored_credentials + stored_credential = { + initial_transaction: true, + reason_type: '', + initiator: 'merchant', + network_transaction_id: nil + } + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential, order_id: '123' })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('FIRST', REXML::XPath.first(doc, '//v1:recurringType').text) + end.respond_with(successful_purchase_response) + + stored_credential = { + initial_transaction: false, + reason_type: '', + initiator: 'merchant', + network_transaction_id: response.params['IpgTransactionId'] + } + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential, order_id: '123' })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('REPEAT', REXML::XPath.first(doc, '//v1:recurringType').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_submerchant + submerchant = { + mcc: '6513', + legal_name: 'KINDERLAND', + address: { + address1: 'ALVARADO 494', + address2: 'Street 2', + zip: '1704', + city: 'BUENOS AIRES', + state: 'BUENOS AIRES', + country: 'ARG' + }, + document: { + type: 'SINGLE_CODE_OF_LABOR_IDENTIFICATION', + number: '30710655479' + }, + merchant_id: '12345678' + } + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ submerchant: submerchant })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match(submerchant[:mcc], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Mcc').text) + assert_match(submerchant[:legal_name], REXML::XPath.first(doc, '//v1:SubMerchant//v1:LegalName').text) + assert_match(submerchant[:address][:address1], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:Address1').text) + assert_match(submerchant[:address][:address2], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:Address2').text) + assert_match(submerchant[:address][:zip], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:Zip').text) + assert_match(submerchant[:address][:city], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:City').text) + assert_match(submerchant[:address][:state], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:State').text) + assert_match(submerchant[:address][:country], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Address//v1:Country').text) + assert_match(submerchant[:document][:type], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Document//v1:Type').text) + assert_match(submerchant[:document][:number], REXML::XPath.first(doc, '//v1:SubMerchant//v1:Document//v1:Number').text) + assert_match(submerchant[:merchant_id], REXML::XPath.first(doc, '//v1:SubMerchant//v1:MerchantID').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_recurring_type + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ recurring_type: 'FIRST' })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('FIRST', REXML::XPath.first(doc, '//v1:recurringType').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_store_id + response = stub_comms do + @gateway.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_successful_purchase_with_payment_token + payment_token = 'ABC123' + + response = stub_comms do + @gateway.purchase(@amount, payment_token, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match(payment_token, REXML::XPath.first(doc, '//v1:Payment//v1:HostedDataID').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_3ds2 + three_d_options = { + three_d_secure: { + version: '2.1.0', + cavv: 'jEET5Odser3oCRAyNTY5BVgAAAA=', + xid: 'jHDMyjJJF9bLBCFT/YUbqMhoQ0s=', + directory_response_status: 'Y', + authentication_response_status: 'Y', + ds_transaction_id: '925a0317-9143-5130-8000-0000000f8742' + } + } + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_options)) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match(three_d_options[:three_d_secure][:cavv], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:AuthenticationValue').text) + assert_match(three_d_options[:three_d_secure][:version], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:Secure3DProtocolVersion').text) + assert_match(three_d_options[:three_d_secure][:xid], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:XID').text) + assert_match(three_d_options[:three_d_secure][:authentication_response_status], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:Secure3D2AuthenticationResponse').text) + assert_match(three_d_options[:three_d_secure][:ds_transaction_id], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:DirectoryServerTransactionId').text) + assert_match(three_d_options[:three_d_secure][:directory_response_status], REXML::XPath.first(doc, '//v1:CreditCard3DSecure//v1:Secure3D2TransactionStatus').text) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'DECLINED', response.message + end + + def test_successful_authorize + order_id = generate_unique_id + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: order_id })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('preAuth', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text) + assert_match(order_id, REXML::XPath.first(doc, '//v1:TransactionDetails//v1:OrderId').text) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options.merge!({ order_id: 'ORD03' })) + assert_failure response + assert_match 'FAILED', response.message + end + + def test_successful_capture + order_id = generate_unique_id + response = stub_comms do + @gateway.capture(@amount, order_id, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('postAuth', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text) + assert_match(order_id, REXML::XPath.first(doc, '//v1:TransactionDetails//v1:OrderId').text) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '123', @options) + assert_failure response + assert_match 'FAILED', response.message + end + + def test_successful_refund + order_id = generate_unique_id + response = stub_comms do + @gateway.refund(@amount, order_id, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('return', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text) + assert_match(order_id, REXML::XPath.first(doc, '//v1:TransactionDetails//v1:OrderId').text) + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '123', @options) + assert_failure response + assert_match 'FAILED', response.message + end + + def test_successful_void + order_id = generate_unique_id + response = stub_comms do + @gateway.void(order_id, @options) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('void', REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type').text) + assert_match(order_id, REXML::XPath.first(doc, '//v1:TransactionDetails//v1:OrderId').text) + end.respond_with(successful_void_response) + + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('', @options) + assert_failure response + assert_match 'FAILED', response.message + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('032', REXML::XPath.first(doc, '//v1:Payment//v1:Currency').text) if REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type')&.text == 'preAuth' + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_currency_code + response = stub_comms do + @gateway.verify(@credit_card, { currency: 'ARS' }) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match('032', REXML::XPath.first(doc, '//v1:Payment//v1:Currency').text) if REXML::XPath.first(doc, '//v1:CreditCardTxType//v1:Type')&.text == 'preAuth' + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + end + + def test_successful_store + payment_token = generate_unique_id + response = stub_comms do + @gateway.store(@credit_card, @options.merge!({ hosted_data_id: payment_token })) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match(payment_token, REXML::XPath.first(doc, '//ns2:HostedDataID').text) + end.respond_with(successful_store_response) + + assert_success response + end + + def test_successful_unstore + payment_token = generate_unique_id + response = stub_comms do + @gateway.unstore(payment_token) + end.check_request do |_endpoint, data, _headers| + doc = REXML::Document.new(data) + assert_match(payment_token, REXML::XPath.first(doc, '//ns2:HostedDataID').text) + end.respond_with(successful_store_response) + + assert_success response + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + response = @gateway.store(@credit_card, @options.merge!({ hosted_data_id: '123' })) + assert_failure response + assert_equal response.params['tpv_error_code'], 'SGSDAS-020300' + assert !response.params['tpv_error_msg'].nil? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_store_and_user_id_from_with_complete_credentials + test_combined_user_id = 'WS5921102002._.1' + split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id) + + assert_equal '5921102002', split_credentials[:store_id] + assert_equal '1', split_credentials[:user_id] + end + + def test_store_and_user_id_from_missing_store_id_prefix + test_combined_user_id = '5921102002._.1' + split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id) + + assert_equal '5921102002', split_credentials[:store_id] + assert_equal '1', split_credentials[:user_id] + end + + def test_store_and_user_id_misplaced_store_id_prefix + test_combined_user_id = '5921102002WS._.1' + split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id) + + assert_equal '5921102002WS', split_credentials[:store_id] + assert_equal '1', split_credentials[:user_id] + end + + def test_store_and_user_id_from_missing_delimiter + test_combined_user_id = 'WS59211020021' + split_credentials = @gateway.send(:store_and_user_id_from, test_combined_user_id) + + assert_equal '59211020021', split_credentials[:store_id] + assert_equal nil, split_credentials[:user_id] + 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 + + private + + def successful_purchase_response + <<~RESPONSE + + + + + Y:019349:4578600880:PPXX:0193497665 + PPX + MASTERCARD + ARG + FDCS + 5921102002 + A-5e3d7dc2-0454-4d60-aae8-7edf35eb28c7 + 84578600880 + CREDITCARD + 019349 + 7665 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 019349019349 + 00 + Function performed error-free + 019349 + 1635149370 + 2021.10.25 10:09:30 (CEST) + 98000000 + APPROVED + 1635149370 + + + + RESPONSE + end + + def failed_purchase_response + <<~RESPONSE + + + + + SOAP-ENV:Client + ProcessingException + + + N:05:Do not honour + PPX + VISA + FDCS + SGS-050005: Do not honour + 5921102002 + A-5c70b8fc-43d8-40f4-93de-46590dbf6d01 + 84578606308 + CREDITCARD + 7668 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 05 + Do not honour + 034209 + 1635152461 + 2021.10.25 11:01:01 (CEST) + 98000000 + DECLINED + 1635152461 + + + + + + RESPONSE + end + + def successful_authorize_response + <<~RESPONSE + + + + + Y:014593:4578595466:PPXX:0145937641 + PPX + MASTERCARD + ARG + FDCS + 5921102002 + ORD02 + 84578595466 + CREDITCARD + 014593 + 7641 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 014593014593 + 00 + Function performed error-free + 014593 + 1635146125 + 2021.10.25 09:15:25 (CEST) + 98000000 + APPROVED + 1635146125 + + + + RESPONSE + end + + def failed_authorize_response + <<~RESPONSE + + + + + SOAP-ENV:Client + ProcessingException + + + N:-5003:The order already exists in the database. + SGS-005003: The order already exists in the database. + ORD03 + 1635156782 + 2021.10.25 12:13:02 (CEST) + FAILED + 1635156782 + + + + + + + RESPONSE + end + + def successful_capture_response + <<~RESPONSE + + + + + Y:034747:4578608047:PPXX:0347477672 + PPX + MASTERCARD + ARG + FDCS + 5921102002 + ORD04 + 84578608047 + CREDITCARD + 034747 + 7672 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 034747034747 + 00 + Function performed error-free + 034747 + 1635157266 + 1635157275 + 2021.10.25 12:21:15 (CEST) + 98000000 + APPROVED + 1635157275 + + + + RESPONSE + end + + def failed_capture_response + <<~RESPONSE + + + + + SOAP-ENV:Client + ProcessingException + + + N:-5008:Order does not exist. + FDCS + SGS-005008: Order does not exist. + ORD090 + 84578608161 + CREDITCARD + FAILED + 1635157307 + + + + + + RESPONSE + end + + def successful_refund_response + <<~RESPONSE + + + + + Y:034889:4578608244:PPXX:0348897676 + PPX + MASTERCARD + ARG + FDCS + 5921102002 + A-8b75ffc2-95dd-4861-a91b-9c3816075f82 + 84578608244 + CREDITCARD + 034889 + 7676 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 034889034889 + 00 + Function performed error-free + 034889 + 1635157480 + 1635157594 + 2021.10.25 12:26:34 (CEST) + 98000000 + APPROVED + 1635157594 + + + + RESPONSE + end + + def failed_refund_response + <<~RESPONSE + + + + + SOAP-ENV:Client + ProcessingException + + + N:-5008:Order does not exist. + FDCS + SGS-005008: Order does not exist. + 182 + 84578608249 + CREDITCARD + FAILED + 1635157647 + + + + + + RESPONSE + end + + def successful_void_response + <<~RESPONSE + + + + + Y:035631:4578609369:PPXX:0356317745 + PPX + MASTERCARD + ARG + FDCS + 5921102002 + ORD07 + 84578609369 + CREDITCARD + 035631 + 7745 + 090 + TXSP ARGENTINA VIA CAFEX VISA + X + 035631035631 + 00 + Function performed error-free + 035631 + 1635158863 + 1635158884 + 2021.10.25 12:48:04 (CEST) + 98000000 + APPROVED + 1635158884 + + + + RESPONSE + end + + def failed_void_response + <<~RESPONSE + + + + + SOAP-ENV:Client + ProcessingException + + + N:-5019:Transaction not voidable + FDCS + SGS-005019: The transaction to be voided is not voidable + ORD07 + 84578609426 + CREDITCARD + 1635158863 + 2021.10.25 12:47:43 (CEST) + FAILED + 1635158863 + + + + + + RESPONSE + end + + def successful_store_response + <<~RESPONSE + + + + + true + + + + RESPONSE + end + + def failed_store_response + <<~RESPONSE + + true + + + Could not store the hosted data id: + 691c7cb3-a752-4d6d-abde-83cad63de258. + Reason: An internal error has occured while + processing your request + + + + RESPONSE + end + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to test.ipg-online.com:443... + opened + 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 V1M1OTIxMTAyMDAyLl8uMTpuOU1DXTJzO25m\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 5921102002\n sale\n \n\n 5165850000000008\n 12\n 22\n 123\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" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Cache-Control: no-cache, no-store, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Security-Policy: default-src 'self' *.googleapis.com *.klarna.com *.masterpass.com *.mastercard.com *.npci.org.in 'unsafe-eval' 'unsafe-inline'; frame-ancestors 'self'\r\n" + -> "Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" + -> "SOAPAction: \"\"\r\n" + -> "Expires: 0\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Content-Length: 1808\r\n" + -> "Set-Cookie: JSESSIONID=08B9B3093F010FFB653B645616E0A258.dc; Path=/ipgapi; Secure; HttpOnly;HttpOnly;Secure;SameSite=Lax\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS0108ee57=0167ad6846753d9e71cb1e6ee74e68d3fd44879a5754a362817ba3e6f52bd01c4c794c29e5cd962b66ea0104c43957e17bc40d819c; Path=/\r\n" + -> "Set-Cookie: TS01c97684=0167ad6846d1db53410992975f8e679ecc1ec0624e54a362817ba3e6f52bd01c4c794c29e5a3f3b525308fafc99af65129fab2b19ce5715c3f475bc6c349b8428ffd87beac; path=/ipgapi\r\n" + -> "\r\n" + reading 1808 bytes... + -> "" + -> "Y:334849:4579259603:PPXX:3348490741PPXMASTERCARDARGFDCS5921102002A-2e68e140-6024-41bb-b49c-a92d4984ae0184579259603CREDITCARD3348490741090TXSP ARGENTINA VIA CAFEX VISAX33484933484900Function performed error-free33484916355358832021.10.29 21:31:23 (CEST)98000000APPROVED1635535883" + read 1808 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to test.ipg-online.com:443... + opened + 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" + -> "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" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Cache-Control: no-cache, no-store, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Security-Policy: default-src 'self' *.googleapis.com *.klarna.com *.masterpass.com *.mastercard.com *.npci.org.in 'unsafe-eval' 'unsafe-inline'; frame-ancestors 'self'\r\n" + -> "Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" + -> "SOAPAction: \"\"\r\n" + -> "Expires: 0\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Content-Length: 1808\r\n" + -> "Set-Cookie: JSESSIONID=08B9B3093F010FFB653B645616E0A258.dc; Path=/ipgapi; Secure; HttpOnly;HttpOnly;Secure;SameSite=Lax\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS0108ee57=0167ad6846753d9e71cb1e6ee74e68d3fd44879a5754a362817ba3e6f52bd01c4c794c29e5cd962b66ea0104c43957e17bc40d819c; Path=/\r\n" + -> "Set-Cookie: TS01c97684=0167ad6846d1db53410992975f8e679ecc1ec0624e54a362817ba3e6f52bd01c4c794c29e5a3f3b525308fafc99af65129fab2b19ce5715c3f475bc6c349b8428ffd87beac; path=/ipgapi\r\n" + -> "\r\n" + reading 1808 bytes... + -> "" + -> "Y:334849:4579259603:PPXX:3348490741PPXMASTERCARDARGFDCS5921102002A-2e68e140-6024-41bb-b49c-a92d4984ae0184579259603CREDITCARD3348490741090TXSP ARGENTINA VIA CAFEX VISAX33484933484900Function performed error-free33484916355358832021.10.29 21:31:23 (CEST)98000000APPROVED1635535883" + read 1808 bytes + Conn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/ipp_test.rb b/test/unit/gateways/ipp_test.rb index 40b8035b37b..0174a7b7a93 100644 --- a/test/unit/gateways/ipp_test.rb +++ b/test/unit/gateways/ipp_test.rb @@ -16,7 +16,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, order_id: 1) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{username<}, data) assert_match(%r{password<}, data) @@ -48,7 +48,7 @@ def test_failed_purchase def test_successful_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, order_id: 1) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{1<}, data) assert_match(%r{2<}, data) @@ -61,7 +61,7 @@ def test_successful_authorize def test_successful_capture response = stub_comms do @gateway.capture(@amount, 'receipt') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{receipt<}, data) assert_match(%r{100<}, data) @@ -73,7 +73,7 @@ def test_successful_capture def test_successful_refund response = stub_comms do @gateway.refund(@amount, 'receipt') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{receipt<}, data) assert_match(%r{100<}, data) @@ -90,122 +90,122 @@ def test_scrub private def pre_scrubbed - <<-'PRE_SCRUBBED' -opening connection to demo.ippayments.com.au:443... -opened -starting SSL for demo.ippayments.com.au:443... -SSL established -<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" -<- "\n\n \n \n \n \n 1\n \n 1\n \n 4005550000000001\n 09\n 2015\n 123\n Longbob Longsen\n \n \n nmi.api\n qwerty123\n \n \n\n]]>\n \n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Server: Microsoft-IIS/6.0\r\n" --> "X-Robots-Tag: noindex\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Cache-Control: private, max-age=0\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Length: 767\r\n" --> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 767 bytes... --> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" -read 767 bytes -Conn close + <<~'PRE_SCRUBBED' + opening connection to demo.ippayments.com.au:443... + opened + starting SSL for demo.ippayments.com.au:443... + SSL established + <- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" + <- "\n\n \n \n \n \n 1\n \n 1\n \n 4005550000000001\n 09\n 2015\n 123\n Longbob Longsen\n \n \n nmi.api\n qwerty123\n \n \n\n]]>\n \n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Microsoft-IIS/6.0\r\n" + -> "X-Robots-Tag: noindex\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Length: 767\r\n" + -> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 767 bytes... + -> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" + read 767 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-'POST_SCRUBBED' -opening connection to demo.ippayments.com.au:443... -opened -starting SSL for demo.ippayments.com.au:443... -SSL established -<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" -<- "\n\n \n \n \n \n 1\n \n 1\n \n [FILTERED]\n 09\n 2015\n [FILTERED]\n Longbob Longsen\n \n \n nmi.api\n [FILTERED]\n \n \n\n]]>\n \n \n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Server: Microsoft-IIS/6.0\r\n" --> "X-Robots-Tag: noindex\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Cache-Control: private, max-age=0\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Length: 767\r\n" --> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 767 bytes... --> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" -read 767 bytes -Conn close + <<~'POST_SCRUBBED' + opening connection to demo.ippayments.com.au:443... + opened + starting SSL for demo.ippayments.com.au:443... + SSL established + <- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" + <- "\n\n \n \n \n \n 1\n \n 1\n \n [FILTERED]\n 09\n 2015\n [FILTERED]\n Longbob Longsen\n \n \n nmi.api\n [FILTERED]\n \n \n\n]]>\n \n \n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Microsoft-IIS/6.0\r\n" + -> "X-Robots-Tag: noindex\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Length: 767\r\n" + -> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 767 bytes... + -> "<Response>\r\n\t<ResponseCode>1</ResponseCode>\r\n\t<Timestamp>20-Dec-2014 06:55:17</Timestamp>\r\n\t<Receipt></Receipt>\r\n\t<SettlementDate></SettlementDate>\r\n\t<DeclinedCode>183</DeclinedCode>\r\n\t<DeclinedMessage>Exception parsing transaction XML</DeclinedMessage>\r\n</Response>\r\n" + read 767 bytes + Conn close POST_SCRUBBED end def successful_purchase_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:07:39</Timestamp> - <Receipt>89435577</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:07:39</Timestamp> + <Receipt>89435577</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def failed_purchase_response - <<-XML -<Response> - <ResponseCode>1</ResponseCode> - <Timestamp>20-Dec-2014 04:14:56</Timestamp> - <Receipt></Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode>05</DeclinedCode> - <DeclinedMessage>Do Not Honour</DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>1</ResponseCode> + <Timestamp>20-Dec-2014 04:14:56</Timestamp> + <Receipt></Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode>05</DeclinedCode> + <DeclinedMessage>Do Not Honour</DeclinedMessage> + </Response> + XML end def successful_authorize_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:18:13</Timestamp> - <Receipt>89435583</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:18:13</Timestamp> + <Receipt>89435583</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def successful_capture_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:18:15</Timestamp> - <Receipt>89435584</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:18:15</Timestamp> + <Receipt>89435584</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end def successful_refund_response - <<-XML -<Response> - <ResponseCode>0</ResponseCode> - <Timestamp>20-Dec-2014 04:24:51</Timestamp> - <Receipt>89435596</Receipt> - <SettlementDate>22-Dec-2014</SettlementDate> - <DeclinedCode></DeclinedCode> - <DeclinedMessage></DeclinedMessage> -</Response> - + <<~XML + <Response> + <ResponseCode>0</ResponseCode> + <Timestamp>20-Dec-2014 04:24:51</Timestamp> + <Receipt>89435596</Receipt> + <SettlementDate>22-Dec-2014</SettlementDate> + <DeclinedCode></DeclinedCode> + <DeclinedMessage></DeclinedMessage> + </Response> + XML end end diff --git a/test/unit/gateways/iridium_test.rb b/test/unit/gateways/iridium_test.rb index 055afb50653..865b183acac 100644 --- a/test/unit/gateways/iridium_test.rb +++ b/test/unit/gateways/iridium_test.rb @@ -1,18 +1,20 @@ require 'test_helper' class IridiumTest < Test::Unit::TestCase + include CommStub + def setup Base.mode = :test - @gateway = IridiumGateway.new(:login => 'login', :password => 'password') + @gateway = IridiumGateway.new(login: 'login', password: 'password') @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -89,7 +91,7 @@ def test_override_currency @gateway.expects(:ssl_post). with(anything, all_of(regexp_matches(/Amount="400"/), regexp_matches(/CurrencyCode="484"/)), anything). returns(successful_purchase_response) - assert_success @gateway.purchase(400, @credit_card, @options.merge(:currency => 'MXN')) + assert_success @gateway.purchase(400, @credit_card, @options.merge(currency: 'MXN')) end def test_do_not_depend_on_expiry_date_class @@ -102,13 +104,21 @@ def test_do_not_depend_on_expiry_date_class def test_use_ducktyping_for_credit_card @gateway.expects(:ssl_post).returns(successful_purchase_response) - credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => 'Hans Tester', :year => 2012, :month => 1) + credit_card = stub(number: '4242424242424242', verification_value: '123', name: 'Hans Tester', year: 2012, month: 1) assert_nothing_raised do assert_success @gateway.purchase(@amount, credit_card, @options) end end + def test_nonfractional_currency_handling + stub_comms do + @gateway.authorize(14200, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |_endpoint, data, _headers| + assert_match(/0AuthCode: 608724608724PASSEDPASSEDPASSED POST_SCRUBBED end - end diff --git a/test/unit/gateways/itransact_test.rb b/test/unit/gateways/itransact_test.rb index 5f43bfc702f..46e019ebc95 100644 --- a/test/unit/gateways/itransact_test.rb +++ b/test/unit/gateways/itransact_test.rb @@ -3,21 +3,21 @@ class ItransactTest < Test::Unit::TestCase def setup @gateway = ItransactGateway.new( - :login => 'login', - :password => 'password', - :gateway_id => '09999' - ) + login: 'login', + password: 'password', + gateway_id: '09999' + ) @credit_card = credit_card @check = check @amount = 1014 # = $10.14 @options = { - :email => 'name@domain.com', - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :email_text => ['line1', 'line2', 'line3'] + email: 'name@domain.com', + order_id: '1', + billing_address: address, + description: 'Store Purchase', + email_text: %w[line1 line2 line3] } end @@ -67,5 +67,4 @@ def successful_check_purchase_response " ok20081216141214TRUE1.099999999991234 My StreetOttawaLongbobLongsenONK1C2N6CA(555)555-5555" end - end diff --git a/test/unit/gateways/iveri_test.rb b/test/unit/gateways/iveri_test.rb index 1269cd1ee53..c193f6c05d7 100644 --- a/test/unit/gateways/iveri_test.rb +++ b/test/unit/gateways/iveri_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class IveriTest < Test::Unit::TestCase + include CommStub + def setup @gateway = IveriGateway.new(app_id: '123', cert_id: '321') @credit_card = credit_card('4242424242424242') @@ -23,6 +25,17 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_iveri_url + @gateway = IveriGateway.new(app_id: '123', cert_id: '321', url_override: 'iveri') + + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '{F0568958-D10B-4093-A3BF-663168B06140}|{5CEF96FD-960E-4EA5-811F-D02CE6E36A96}|48b63446223ce91451fc3c1641a9ec03', response.authorization + assert response.test? + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -100,20 +113,19 @@ def test_failed_void end def test_successful_verify - @gateway.expects(:ssl_post).returns(successful_verify_response) - - response = @gateway.verify(@credit_card, @options) + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) assert_success response - assert_equal '{F4337D04-B526-4A7E-A400-2A6DEADDCF57}|{5D5F8BF7-2D9D-42C3-AF32-08C5E62CD45E}|c0006d1d739905afc9e70beaf4194ea3', response.authorization - assert response.test? + assert_equal 'Succeeded', response.message end def test_failed_verify - @gateway.expects(:ssl_post).returns(failed_verify_response) - - response = @gateway.verify(credit_card('2121212121212121'), @options) + response = stub_comms do + @gateway.verify(credit_card('2121212121212121'), @options) + end.respond_with(failed_authorize_response, successful_void_response) assert_failure response - assert_equal '4', response.error_code + assert_equal 'Denied', response.message end def test_successful_verify_credentials @@ -184,370 +196,332 @@ def post_scrubbed end def successful_purchase_response - <<-XML -<V_XML Version="2.0" Direction="Response"> -<Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{F0568958-D10B-4093-A3BF-663168B06140}"> - <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> - <Amount>100</Amount> - <AuthorisationCode>537473</AuthorisationCode> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>48b63446223ce91451fc3c1641a9ec03</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{5CEF96FD-960E-4EA5-811F-D02CE6E36A96}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04077982</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>190433</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>4</BIN> - <Association>VISA</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>International</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04077982</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>4242........4242</CCNumber> - <PAN>4242........4242</PAN> -</Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{F0568958-D10B-4093-A3BF-663168B06140}"> + <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> + <Amount>100</Amount> + <AuthorisationCode>537473</AuthorisationCode> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>48b63446223ce91451fc3c1641a9ec03</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{5CEF96FD-960E-4EA5-811F-D02CE6E36A96}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04077982</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>190433</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>4</BIN> + <Association>VISA</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>International</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04077982</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>4242........4242</CCNumber> + <PAN>4242........4242</PAN> + </Transaction> + </V_XML> XML end def failed_purchase_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{B14C3834-72B9-4ACA-B362-B3C9EC96E8C0}"> - <Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /> - <Amount>100</Amount> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>435a5d60b5fe874840c34e2e0504626b</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{B35872A9-39C7-4DB8-9774-A5E34FFA519E}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04077988</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>192038</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>2</BIN> - <Association>Unknown Association</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>Local</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04077988</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>2121........2121</CCNumber> - <PAN>2121........2121</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{B14C3834-72B9-4ACA-B362-B3C9EC96E8C0}"> + <Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /> + <Amount>100</Amount> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>435a5d60b5fe874840c34e2e0504626b</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{B35872A9-39C7-4DB8-9774-A5E34FFA519E}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04077988</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>192038</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>2</BIN> + <Association>Unknown Association</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>Local</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04077988</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>2121........2121</CCNumber> + <PAN>2121........2121</PAN> + </Transaction> + </V_XML> XML end def successful_authorize_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{B90D7CDB-C8E8-4477-BDF2-695F28137874}"> - <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> - <Amount>100</Amount> - <AuthorisationCode>541267</AuthorisationCode> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>23b4125c3b8e2777bffee52e196a863b</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04078057</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>200747</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>4</BIN> - <Association>VISA</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>International</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04078057</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>4242........4242</CCNumber> - <PAN>4242........4242</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{B90D7CDB-C8E8-4477-BDF2-695F28137874}"> + <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> + <Amount>100</Amount> + <AuthorisationCode>541267</AuthorisationCode> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>23b4125c3b8e2777bffee52e196a863b</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04078057</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>200747</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>4</BIN> + <Association>VISA</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>International</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04078057</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>4242........4242</CCNumber> + <PAN>4242........4242</PAN> + </Transaction> + </V_XML> XML end def failed_authorize_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{3A1A29BE-288F-4FEE-8C15-B3BB8A207544}"> - <Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /> - <Amount>100</Amount> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>3d12442ea042e78fd33057b7b50c76f7</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{8AC33FB1-0D2E-42C7-A0DB-CF8B20279825}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04078062</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>202648</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>2</BIN> - <Association>Unknown Association</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>Local</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04078062</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>2121........2121</CCNumber> - <PAN>2121........2121</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{3A1A29BE-288F-4FEE-8C15-B3BB8A207544}"> + <Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /> + <Amount>100</Amount> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>3d12442ea042e78fd33057b7b50c76f7</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{8AC33FB1-0D2E-42C7-A0DB-CF8B20279825}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04078062</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>202648</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>2</BIN> + <Association>Unknown Association</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>Local</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04078062</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>2121........2121</CCNumber> + <PAN>2121........2121</PAN> + </Transaction> + </V_XML> XML end def successful_capture_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{7C91245F-607D-44AE-8958-C26E447BAEB7}"> - <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /> - <Amount>100</Amount> - <AuthorisationCode>541268</AuthorisationCode> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>23b4125c3b8e2777bffee52e196a863b</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04078057</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>200748</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>4</BIN> - <Association>VISA</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>International</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04078057</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>4242........4242</CCNumber> - <PAN>4242........4242</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{7C91245F-607D-44AE-8958-C26E447BAEB7}"> + <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /> + <Amount>100</Amount> + <AuthorisationCode>541268</AuthorisationCode> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>23b4125c3b8e2777bffee52e196a863b</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04078057</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>200748</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>4</BIN> + <Association>VISA</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>International</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04078057</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>4242........4242</CCNumber> + <PAN>4242........4242</PAN> + </Transaction> + </V_XML> XML end def failed_capture_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{9DAAA002-0EF9-46DC-A440-8DCD9E78B36F}"> - <Result Status="-1" Code="14" Description="Missing PAN" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{9DAAA002-0EF9-46DC-A440-8DCD9E78B36F}"> + <Result Status="-1" Code="14" Description="Missing PAN" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> + </Transaction> + </V_XML> XML end def successful_refund_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{097C55B5-D020-40AD-8949-F9F5E4102F1D}"> - <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /> - <Amount>100</Amount> - <AuthorisationCode>541996</AuthorisationCode> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>5be2c040bd46b7eebc70274659779acf</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{D50DB1B4-B6EC-4AF1-AFF7-71C2AA4A957B}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70417:04078059</AcquirerReference> - <AcquirerDate>20170417</AcquirerDate> - <AcquirerTime>201956</AcquirerTime> - <DisplayAmount>R 1.00</DisplayAmount> - <BIN>4</BIN> - <Association>VISA</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>International</Jurisdiction> - <PANMode /> - <ReconReference>04078059</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>4242........4242</CCNumber> - <PAN>4242........4242</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{097C55B5-D020-40AD-8949-F9F5E4102F1D}"> + <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /> + <Amount>100</Amount> + <AuthorisationCode>541996</AuthorisationCode> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>5be2c040bd46b7eebc70274659779acf</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{D50DB1B4-B6EC-4AF1-AFF7-71C2AA4A957B}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70417:04078059</AcquirerReference> + <AcquirerDate>20170417</AcquirerDate> + <AcquirerTime>201956</AcquirerTime> + <DisplayAmount>R 1.00</DisplayAmount> + <BIN>4</BIN> + <Association>VISA</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>International</Jurisdiction> + <PANMode /> + <ReconReference>04078059</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>4242........4242</CCNumber> + <PAN>4242........4242</PAN> + </Transaction> + </V_XML> XML end def failed_refund_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{5097A60A-A112-42F1-9490-FA17A859E7A3}"> - <Result Status="-1" Code="255" Description="Credit is not supported for ApplicationID (D10A603D-4ADE-405B-93F1-826DFC0181E8)" Source="PortalService" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{5097A60A-A112-42F1-9490-FA17A859E7A3}"> + <Result Status="-1" Code="255" Description="Credit is not supported for ApplicationID (D10A603D-4ADE-405B-93F1-826DFC0181E8)" Source="PortalService" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> + </Transaction> + </V_XML> XML end def successful_void_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{0A1A3FFF-C2A3-4B91-85FD-10D1C25B765B}"> - <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" /> - <OriginalRequestID>{230390C8-4A9E-4426-BDD3-15D072F135FE}</OriginalRequestID> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{0A1A3FFF-C2A3-4B91-85FD-10D1C25B765B}"> + <Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" /> + <OriginalRequestID>{230390C8-4A9E-4426-BDD3-15D072F135FE}</OriginalRequestID> + </Transaction> + </V_XML> XML end def failed_void_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{AE97CCE4-0631-4F08-AB47-9C2698ABEC75}"> - <Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{AE97CCE4-0631-4F08-AB47-9C2698ABEC75}"> + <Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> + </Transaction> + </V_XML> XML end def successful_verify_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{F4337D04-B526-4A7E-A400-2A6DEADDCF57}"> - <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> - <Amount>0</Amount> - <AuthorisationCode>613755</AuthorisationCode> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>c0006d1d739905afc9e70beaf4194ea3</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{5D5F8BF7-2D9D-42C3-AF32-08C5E62CD45E}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70418:04078335</AcquirerReference> - <AcquirerDate>20170418</AcquirerDate> - <AcquirerTime>161555</AcquirerTime> - <DisplayAmount>R 0.00</DisplayAmount> - <BIN>4</BIN> - <Association>VISA</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>International</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04078335</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>4242........4242</CCNumber> - <PAN>4242........4242</PAN> - </Transaction> -</V_XML> - XML - end - - def failed_verify_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{A700FAE2-2A76-407D-A540-B41668E2B703}"> - <Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /> - <Amount>0</Amount> - <Currency>ZAR</Currency> - <ExpiryDate>092018</ExpiryDate> - <MerchantReference>e955afb03f224284b09ad6ae7e9b4683</MerchantReference> - <Terminal>Default</Terminal> - <TransactionIndex>{2A378547-AEA4-48E1-8A3E-29F9BBEA954D}</TransactionIndex> - <MerchantName>iVeri Payment Technology</MerchantName> - <MerchantUSN>7771777</MerchantUSN> - <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> - <AcquirerReference>70418:04078337</AcquirerReference> - <AcquirerDate>20170418</AcquirerDate> - <AcquirerTime>161716</AcquirerTime> - <DisplayAmount>R 0.00</DisplayAmount> - <BIN>2</BIN> - <Association>Unknown Association</Association> - <CardType>Unknown CardType</CardType> - <Issuer>Unknown</Issuer> - <Jurisdiction>Local</Jurisdiction> - <PANMode>Keyed,CVV</PANMode> - <ReconReference>04078337</ReconReference> - <CardHolderPresence>CardNotPresent</CardHolderPresence> - <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> - <MerchantCity>Sandton</MerchantCity> - <MerchantCountryCode>ZA</MerchantCountryCode> - <MerchantCountry>South Africa</MerchantCountry> - <DistributorName>Nedbank</DistributorName> - <CCNumber>2121........2121</CCNumber> - <PAN>2121........2121</PAN> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{F4337D04-B526-4A7E-A400-2A6DEADDCF57}"> + <Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /> + <Amount>0</Amount> + <AuthorisationCode>613755</AuthorisationCode> + <Currency>ZAR</Currency> + <ExpiryDate>092018</ExpiryDate> + <MerchantReference>c0006d1d739905afc9e70beaf4194ea3</MerchantReference> + <Terminal>Default</Terminal> + <TransactionIndex>{5D5F8BF7-2D9D-42C3-AF32-08C5E62CD45E}</TransactionIndex> + <MerchantName>iVeri Payment Technology</MerchantName> + <MerchantUSN>7771777</MerchantUSN> + <Acquirer>NBPostilionBICISONBSouthAfrica</Acquirer> + <AcquirerReference>70418:04078335</AcquirerReference> + <AcquirerDate>20170418</AcquirerDate> + <AcquirerTime>161555</AcquirerTime> + <DisplayAmount>R 0.00</DisplayAmount> + <BIN>4</BIN> + <Association>VISA</Association> + <CardType>Unknown CardType</CardType> + <Issuer>Unknown</Issuer> + <Jurisdiction>International</Jurisdiction> + <PANMode>Keyed,CVV</PANMode> + <ReconReference>04078335</ReconReference> + <CardHolderPresence>CardNotPresent</CardHolderPresence> + <MerchantAddress>MERCHANT ADDRESS</MerchantAddress> + <MerchantCity>Sandton</MerchantCity> + <MerchantCountryCode>ZA</MerchantCountryCode> + <MerchantCountry>South Africa</MerchantCountry> + <DistributorName>Nedbank</DistributorName> + <CCNumber>4242........4242</CCNumber> + <PAN>4242........4242</PAN> + </Transaction> + </V_XML> XML end def successful_verify_credentials_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{5ED922D0-92AD-40DF-9019-320591A4BA59}"> - <Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> - </Transaction> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{5ED922D0-92AD-40DF-9019-320591A4BA59}"> + <Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> + </Transaction> + </V_XML> XML end def failed_verify_credentials_response - <<-XML -<V_XML Version="2.0" Direction="Response"> - <Result Status="-1" Code="255" Description="The ApplicationID {11111111-1111-1111-1111-111111111111} is not valid for the current CertificateID {11111111-1111-1111-1111-111111111111}" Source="RequestHandler" RequestID="{EE6E5B39-63AD-402C-8331-F25082AD8564}" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> -</V_XML> + <<~XML + <V_XML Version="2.0" Direction="Response"> + <Result Status="-1" Code="255" Description="The ApplicationID {11111111-1111-1111-1111-111111111111} is not valid for the current CertificateID {11111111-1111-1111-1111-111111111111}" Source="RequestHandler" RequestID="{EE6E5B39-63AD-402C-8331-F25082AD8564}" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /> + </V_XML> XML end end diff --git a/test/unit/gateways/ixopay_test.rb b/test/unit/gateways/ixopay_test.rb new file mode 100644 index 00000000000..a7f251c0d36 --- /dev/null +++ b/test/unit/gateways/ixopay_test.rb @@ -0,0 +1,673 @@ +require 'test_helper' + +class IxopayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = IxopayGateway.new( + username: 'username', + password: 'password', + secret: 'secret', + api_key: 'api_key' + ) + + @declined_card = credit_card('4000300011112220') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + ip: '192.168.1.1' + } + + @extra_data = { extra_data: { customData1: 'some data', customData2: 'Can be anything really' } } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/.+<\/description>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal 'b2bef23a30b537b90fbe|20191016-b2bef23a30b537b90fbe', response.authorization + assert response.test? + end + + def test_successful_purchase_with_extra_data + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _headers| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal 'b2bef23a30b537b90fbe|20191016-b2bef23a30b537b90fbe', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @declined_card, @options) + + assert_failure response + assert_equal 'The transaction was declined', response.message + assert_equal '2003', response.error_code + end + + def test_failed_authentication + @gateway.expects(:ssl_post).raises(mock_response_error) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert 'Invalid Signature', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert match(/.+<\/description>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal '00eb44f8f0382443cce5|20191028-00eb44f8f0382443cce5', response.authorization + assert response.test? + end + + def test_successful_authorize_with_extra_data + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _headers| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal '00eb44f8f0382443cce5|20191028-00eb44f8f0382443cce5', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.authorize(@amount, @declined_card, @options) + + assert_failure response + assert_equal 'The transaction was declined', response.message + assert_equal '2003', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '00eb44f8f0382443cce5|20191028-00eb44f8f0382443cce5') + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal '17dd1e0b09221e9db038|20191031-17dd1e0b09221e9db038', response.authorization + assert response.test? + end + + def test_successful_capture_with_extra_data + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = stub_comms do + @gateway.capture(@amount, '00eb44f8f0382443cce5|20191028-00eb44f8f0382443cce5', @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _header| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal 'FINISHED', response.message + assert_equal '17dd1e0b09221e9db038|20191031-17dd1e0b09221e9db038', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, nil) + + assert_failure response + assert_equal 'Transaction of type "capture" requires a referenceTransactionId', response.message + assert_equal '9999', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, 'eb2bef23a30b537b90fb|20191016-b2bef23a30b537b90fbe') + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_successful_refund_with_extra_data + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = stub_comms do + @gateway.refund(@amount, 'eb2bef23a30b537b90fb|20191016-b2bef23a30b537b90fbe', @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _header| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_refund_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_refund_includes_currency_option + options = { currency: 'USD' } + + stub_comms do + @gateway.refund(@amount, 'eb2bef23a30b537b90fb|20191016-b2bef23a30b537b90fbe', options) + end.check_request do |_endpoint, data, _headers| + assert_match(/USD<\/currency>/, data) + end.respond_with(successful_refund_response) + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + response = @gateway.refund(@amount, nil) + + assert_failure response + assert_equal 'Transaction of type "refund" requires a referenceTransactionId', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void('eb2bef23a30b537b90fb|20191016-b2bef23a30b537b90fbe') + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_successful_void_with_extra_data + @gateway.expects(:ssl_post).returns(successful_void_response) + response = stub_comms do + @gateway.void('eb2bef23a30b537b90fb|20191016-b2bef23a30b537b90fbe', @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _header| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_void_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void(nil) + + assert_failure response + assert_equal 'Transaction of type "void" requires a referenceTransactionId', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + response = @gateway.verify(credit_card('4111111111111111'), @options) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_successful_verify_with_extra_data + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + response = stub_comms do + @gateway.verify(credit_card('4111111111111111'), @options.merge(@extra_data)) + end.check_request do |_endpoint, data, _header| + assert_match(/some data<\/extraData>/, data) + assert_match(/Can be anything really<\/extraData>/, data) + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(credit_card('4111111111111111'), @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + # Stored Credential Tests + # Ixopay does not pass any parameters for cardholder/merchant initiated. + # Ixopay also doesn't support installment transactions, only recurring + # ("RECURRING") and unscheduled ("CARDONFILE"). + # + # Furthermore, Ixopay is slightly unusual in its application of stored + # credentials in that the gateway does not return a true + # network_transaction_id that can be sent on subsequent transactions. + def test_purchase_stored_credentials_initial + options = @options.merge( + stored_credential: stored_credential(:initial, :recurring) + ) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/INITIAL<\/transactionIndicator>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_authorize_stored_credentials_initial + options = @options.merge( + stored_credential: stored_credential(:initial, :unscheduled) + ) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/INITIAL<\/transactionIndicator>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_purchase_stored_credentials_recurring + options = @options.merge( + stored_credential: stored_credential(:recurring) + ) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/RECURRING<\/transactionIndicator>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_authorize_stored_credentials_recurring + options = @options.merge( + stored_credential: stored_credential(:recurring) + ) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/RECURRING<\/transactionIndicator>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_purchase_stored_credentials_unscheduled + options = @options.merge( + stored_credential: stored_credential(:unscheduled) + ) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/CARDONFILE<\/transactionIndicator>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_authorize_stored_credentials_unscheduled + options = @options.merge( + stored_credential: stored_credential(:unscheduled) + ) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/CARDONFILE<\/transactionIndicator>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + def test_three_decimal_currency_handling + response = stub_comms do + @gateway.authorize(14200, @credit_card, @options.merge(currency: 'KWD')) + end.check_request do |_endpoint, data, _headers| + assert_match(/14.200<\/amount>/, data) + assert_match(/KWD<\/currency>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'FINISHED', response.message + end + + private + + def mock_response_error + mock_response = Net::HTTPUnprocessableEntity.new('1.1', '401', 'Unauthorized') + mock_response.stubs(:body).returns(failed_authentication_response) + + ActiveMerchant::ResponseError.new(mock_response) + end + + def pre_scrubbed + <<-TRANSCRIPT + opening connection to secure.ixopay.com:443... + opened + starting SSL for secure.ixopay.com:443... + SSL established + <- "POST /transaction HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nAuthorization: Gateway spreedly-integration-1:i8CtuPyY820sX8hvJuRbygSnotj+VibBxqFl9MoFLYdrwC91zxymCv3h72DZBkOYT05P/L1Ig5aQrPf8SdOWtw==\r\nDate: Fri, 18 Oct 2019 19:24:53 GMT\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.ixopay.com\r\nContent-Length: 1717\r\n\r\n" + <- "\n\n spreedly-dev-api\n 834ab26f399def0fea3e444d6cecbf6c61230e09\n \n Longbob Longsen\n 4111111111111111\n 123\n 09\n 2020\n \n \n 13454623-e012-4f77-b9e7-c9536964f186\n \n Jim\n Smith\n 456 My Street\n Apt 1\n Ottawa\n K1C2N6\n ON\n CA\n (555)555-5555\n Jim\n Smith\n Widgets Inc\n 456 My Street\n Apt 1\n Ottawa\n K1C2N6\n ON\n CA\n (555)555-5555\n Widgets Inc\n test@example.com\n 192.168.1.1\n \n 100\n EUR\n Store Purchase\n http://example.com\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 18 Oct 2019 19:24:55 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=db8efa44225d95d93942c576b8f53feb31571426693; expires=Sat, 17-Oct-20 19:24:53 GMT; path=/; domain=.ixopay.com; HttpOnly\r\n" + -> "5: Content-Type: text/xml; charset=UTF-8\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Strict-Transport-Security: max-age=15552000; includeSubDomains; preload\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Server: vau-prod-webfe-esh-02\r\n" + -> "CF-Cache-Status: DYNAMIC\r\n" + -> "Expect-CT: max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"\r\n" + -> "Server: cloudflare\r\n" + -> "CF-RAY: 527ce522ab3b9f7c-IAD\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "18c\r\n" + reading 396 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03l\x92Mo\xDB0\f\x86\xEF\xF9\x15\x82\xEF\x8D\xFC\x91\xA6\xC9 \xAB\x87eE\x03\xAC=4\xC3\x80\x1De\x89\x89\x85\xD9\x92AI\x85\xFD\xEF\v\xD9q\x96\xB4\xD3E\xE0\xCB\x87/EB\xEC\xB1o\e\xF2\x0E\xE8\xB45e\x92-\xD3\x84\x80\x91Vis*\x93\xE0\x8Fw\x9B\xE4\x91/\x18\x82\v\x8D'}\xDB\x18W&\xB5\xF7\xDD7J\x1D\xC8\x80\xB0\xD4\xBD\xED\xC4\xB0\x94\xB6\xA5\aYC+\xE8\xEF\x9C\xBE\x8D\x05\t_\x10\xC2\\\x90\x12\x9C\xE3\x1E\x030:G1\x83p\x04\x04#a\xAF\xF8\xAA(\xF2j\x9D\x17b\xBD\x11\x0F\xD5}Q\xACW\x0F\x8C^\x13\xB1\xA2\v(k\xE1b\x98\xA7\xD96K\xB3\xCD\xDD\xFF+\xAF\xC8\xA9\x95\x0Fh~\r\x1D\xF0\xA7\xFD\xEB\xFE\xF0\xFCc\x17\xDD/\xE2h.\x86\x16\x8C\x7F\x01_[\xC5\xBF#(\xED\xA5@\xC5\xE8m\xE6\x9F\xDFNxA\xFC\xD0A\x99\xC8\v\x1E\xC5qrB\xD8\xAD:\x89\x84\xB0X\xC2\xDF\xB5\x13\x8C\xFAs\xF7\t\x17\xA8\x9Em\xA3\x00\xF9OkN\x95\xADH\xBC\x1D\x18F\xAFr3\x0E}\xA7qx\xB1\xC6\xD7<\xDD2z\x1D\xDF2\x7F@ \xCF\xD3<\x9D\xA1Q\x98\x99\xA3F\xE7\x0F\xBA\xDF\xE9\x93\xF6\x8E\xAF\xB2x\x18\xFD$\xCFt#\x9C\x7F\xB2\x01\xCF\xF2\xC4~\x12\xA7\xE9\xE9\xD7\xF1\xE7\xA5_b\xE8=\x8Aq\x8F\x7Fa(\x13):\x1F\x10\xF6*\xE1\xF7J\x88,\xDB\xAC\xB7\xC5\x16\xAA\xF8\xEE3\xC8\x17\xD1$\xFE/\xBE\xF8\x00\x00\x00\xFF\xFF\x03\x00\x0F\x10\x82\b\xC1\x02\x00\x00" + read 396 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT + end + + def post_scrubbed + transcript = <<-TRANSCRIPT + opening connection to secure.ixopay.com:443... + opened + starting SSL for secure.ixopay.com:443... + SSL established + <- "POST /transaction HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nAuthorization: Gateway [FILTERED]:i8CtuPyY820sX8hvJuRbygSnotj+VibBxqFl9MoFLYdrwC91zxymCv3h72DZBkOYT05P/L1Ig5aQrPf8SdOWtw==\r\nDate: Fri, 18 Oct 2019 19:24:53 GMT\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.ixopay.com\r\nContent-Length: 1717\r\n\r\n" + <- "\n\n spreedly-dev-api\n [FILTERED]\n \n Longbob Longsen\n [FILTERED]\n [FILTERED]\n 09\n 2020\n \n \n 13454623-e012-4f77-b9e7-c9536964f186\n \n Jim\n Smith\n 456 My Street\n Apt 1\n Ottawa\n K1C2N6\n ON\n CA\n (555)555-5555\n Jim\n Smith\n Widgets Inc\n 456 My Street\n Apt 1\n Ottawa\n K1C2N6\n ON\n CA\n (555)555-5555\n Widgets Inc\n test@example.com\n 192.168.1.1\n \n 100\n EUR\n Store Purchase\n http://example.com\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 18 Oct 2019 19:24:55 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=db8efa44225d95d93942c576b8f53feb31571426693; expires=Sat, 17-Oct-20 19:24:53 GMT; path=/; domain=.ixopay.com; HttpOnly\r\n" + -> "5: Content-Type: text/xml; charset=UTF-8\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Strict-Transport-Security: max-age=15552000; includeSubDomains; preload\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Server: vau-prod-webfe-esh-02\r\n" + -> "CF-Cache-Status: DYNAMIC\r\n" + -> "Expect-CT: max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"\r\n" + -> "Server: cloudflare\r\n" + -> "CF-RAY: 527ce522ab3b9f7c-IAD\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "18c\r\n" + reading 396 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03l\x92Mo\xDB0\f\x86\xEF\xF9\x15\x82\xEF\x8D\xFC\x91\xA6\xC9 \xAB\x87eE\x03\xAC=4\xC3\x80\x1De\x89\x89\x85\xD9\x92AI\x85\xFD\xEF\v\xD9q\x96\xB4\xD3E\xE0\xCB\x87/EB\xEC\xB1o\e\xF2\x0E\xE8\xB45e\x92-\xD3\x84\x80\x91Vis*\x93\xE0\x8Fw\x9B\xE4\x91/\x18\x82\v\x8D'}\xDB\x18W&\xB5\xF7\xDD7J\x1D\xC8\x80\xB0\xD4\xBD\xED\xC4\xB0\x94\xB6\xA5\aYC+\xE8\xEF\x9C\xBE\x8D\x05\t_\x10\xC2\\\x90\x12\x9C\xE3\x1E\x030:G1\x83p\x04\x04#a\xAF\xF8\xAA(\xF2j\x9D\x17b\xBD\x11\x0F\xD5}Q\xACW\x0F\x8C^\x13\xB1\xA2\v(k\xE1b\x98\xA7\xD96K\xB3\xCD\xDD\xFF+\xAF\xC8\xA9\x95\x0Fh~\r\x1D\xF0\xA7\xFD\xEB\xFE\xF0\xFCc\x17\xDD/\xE2h.\x86\x16\x8C\x7F\x01_[\xC5\xBF#(\xED\xA5@\xC5\xE8m\xE6\x9F\xDFNxA\xFC\xD0A\x99\xC8\v\x1E\xC5qrB\xD8\xAD:\x89\x84\xB0X\xC2\xDF\xB5\x13\x8C\xFAs\xF7\t\x17\xA8\x9Em\xA3\x00\xF9OkN\x95\xADH\xBC\x1D\x18F\xAFr3\x0E}\xA7qx\xB1\xC6\xD7<\xDD2z\x1D\xDF2\x7F@ \xCF\xD3<\x9D\xA1Q\x98\x99\xA3F\xE7\x0F\xBA\xDF\xE9\x93\xF6\x8E\xAF\xB2x\x18\xFD$\xCFt#\x9C\x7F\xB2\x01\xCF\xF2\xC4~\x12\xA7\xE9\xE9\xD7\xF1\xE7\xA5_b\xE8=\x8Aq\x8F\x7Fa(\x13):\x1F\x10\xF6*\xE1\xF7J\x88,\xDB\xAC\xB7\xC5\x16\xAA\xF8\xEE3\xC8\x17\xD1$\xFE/\xBE\xF8\x00\x00\x00\xFF\xFF\x03\x00\x0F\x10\x82\b\xC1\x02\x00\x00" + read 396 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT + + remove_invalid_utf_8_byte_sequences(transcript) + end + + def remove_invalid_utf_8_byte_sequences(text) + text.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + end + + def successful_purchase_response + <<-XML + + + true + b2bef23a30b537b90fbe + 20191016-b2bef23a30b537b90fbe + FINISHED + Creditcard + + + visa + Longbob Longsen + 09 + 2020 + 411111 + 1111 + + + 5da76cc5ce84b + + XML + end + + def failed_purchase_response + <<-XML + + + false + d74211aa7d0ba8294b4d + 20191016-d74211aa7d0ba8294b4d + ERROR + Creditcard + + + visa + Longbob Longsen + 09 + 2020 + 400030 + 2220 + + + + + The transaction was declined + 2003 + Test decline + transaction_declined + + + + XML + end + + def failed_authentication_response + <<-XML + + + false + ERROR + + + Invalid Signature: Invalid authorization header + 1004 + + + + XML + end + + def successful_authorize_response + <<-XML + + + true + 00eb44f8f0382443cce5 + 20191028-00eb44f8f0382443cce5 + FINISHED + Creditcard + + + visa + Longbob Longsen + 09 + 2020 + 411111 + 1111 + + + + XML + end + + def failed_authorize_response + <<-XML + + + false + 91278c76405116378b85 + 20191028-91278c76405116378b85 + ERROR + Creditcard + + + visa + Longbob Longsen + 09 + 2020 + 400030 + 2220 + + + + + The transaction was declined + 2003 + Test decline + transaction_declined + + + + XML + end + + def successful_capture_response + <<-XML + + + true + 17dd1e0b09221e9db038 + 20191031-17dd1e0b09221e9db038 + FINISHED + Creditcard + + + visa + Longbob Longsen + 09 + 2020 + 411111 + 1111 + + + + XML + end + + def failed_capture_response + <<-XML + + + false + ERROR + + + Transaction of type "capture" requires a referenceTransactionId + 9999 + + + + XML + end + + def successful_refund_response + <<-XML + + true + 21c47c977476d5a3b682 + 20191028-c9e173c255d14f90816b + FINISHED + Creditcard + + XML + end + + def failed_refund_response + <<-XML + + false + ERROR + + + Transaction of type "refund" requires a referenceTransactionId + 9999 + + + + XML + end + + def successful_void_response + <<-XML + + true + cb656bd5286e77501b2e + 20191031-b1f9f7991766cf933659 + FINISHED + Creditcard + + XML + end + + def failed_void_response + <<-XML + + false + ERROR + + + Transaction of type "void" requires a referenceTransactionId + 9999 + + + + XML + end +end diff --git a/test/unit/gateways/jetpay_test.rb b/test/unit/gateways/jetpay_test.rb index 91069432c3e..88a58cea6c5 100644 --- a/test/unit/gateways/jetpay_test.rb +++ b/test/unit/gateways/jetpay_test.rb @@ -6,18 +6,18 @@ class JetpayTest < Test::Unit::TestCase def setup Base.mode = :test - @gateway = JetpayGateway.new(:login => 'login') + @gateway = JetpayGateway.new(login: 'login') @credit_card = credit_card @amount = 100 @options = { - :billing_address => address(:country => 'US'), - :shipping_address => address(:country => 'US'), - :email => 'test@test.com', - :ip => '127.0.0.1', - :order_id => '12345', - :tax => 7 + billing_address: address(country: 'US'), + shipping_address: address(country: 'US'), + email: 'test@test.com', + ip: '127.0.0.1', + order_id: '12345', + tax: 7 } end @@ -76,7 +76,7 @@ def test_successful_void def test_successful_credit # no need for csv - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) @gateway.expects(:ssl_post).returns(successful_credit_response) @@ -137,13 +137,13 @@ def test_transcript_scrubbing def test_purchase_sends_order_origin @gateway.expects(:ssl_post).with(anything, regexp_matches(/RECURRING<\/Origin>/)).returns(successful_purchase_response) - @gateway.purchase(@amount, @credit_card, {:origin => 'RECURRING'}) + @gateway.purchase(@amount, @credit_card, { origin: 'RECURRING' }) end private def successful_purchase_response - <<-EOF + <<-XML 8afa688fd002821362 000 @@ -155,21 +155,21 @@ def successful_purchase_response Y Y - EOF + XML end def failed_purchase_response - <<-EOF + <<-XML 7605f7c5d6e8f74deb 005 DECLINED - EOF + XML end def successful_authorize_response - <<-EOF + <<-XML cbf902091334a0b1aa 000 @@ -181,44 +181,44 @@ def successful_authorize_response Y Y - EOF + XML end def successful_capture_response - <<-EOF + <<-XML 010327153017T10018 000 502F6B APPROVED - EOF + XML end def successful_void_response - <<-EOF + <<-XML 010327153x17T10418 000 502F7B VOID PROCESSED - EOF + XML end def successful_credit_response - <<-EOF + <<-XML 010327153017T10017 000 002F6B APPROVED - EOF + XML end def transcript - <<-EOF + <<-XML TESTTERMINAL SALE e23c963a1247fd7aad @@ -227,11 +227,11 @@ def transcript 16 Longbob Longsen 123 - EOF + XML end def scrubbed_transcript - <<-EOF + <<-XML TESTTERMINAL SALE e23c963a1247fd7aad @@ -240,6 +240,6 @@ def scrubbed_transcript 16 Longbob Longsen [FILTERED] - EOF + XML end end diff --git a/test/unit/gateways/jetpay_v2_test.rb b/test/unit/gateways/jetpay_v2_test.rb index 8129bf5eb11..c7533d18e98 100644 --- a/test/unit/gateways/jetpay_v2_test.rb +++ b/test/unit/gateways/jetpay_v2_test.rb @@ -1,22 +1,21 @@ require 'test_helper' class JetpayV2Test < Test::Unit::TestCase - def setup - @gateway = JetpayV2Gateway.new(:login => 'login') + @gateway = JetpayV2Gateway.new(login: 'login') @credit_card = credit_card @amount = 100 @options = { - :device => 'spreedly', - :application => 'spreedly', - :developer_id => 'GenkID', - :billing_address => address(:country => 'US'), - :shipping_address => address(:country => 'US'), - :email => 'test@test.com', - :ip => '127.0.0.1', - :order_id => '12345' + device: 'spreedly', + application: 'spreedly', + developer_id: 'GenkID', + billing_address: address(country: 'US'), + shipping_address: address(country: 'US'), + email: 'test@test.com', + ip: '127.0.0.1', + order_id: '12345' } end @@ -89,7 +88,7 @@ def test_failed_void end def test_successful_credit - card = credit_card('4242424242424242', :verification_value => nil) + card = credit_card('4242424242424242', verification_value: nil) @gateway.expects(:ssl_post).returns(successful_credit_response) @@ -98,7 +97,7 @@ def test_successful_credit end def test_failed_credit - card = credit_card('2424242424242424', :verification_value => nil) + card = credit_card('2424242424242424', verification_value: nil) @gateway.expects(:ssl_post).returns(failed_credit_response) @@ -133,7 +132,7 @@ def test_successful_verify end def test_failed_verify - card = credit_card('2424242424242424', :verification_value => nil) + card = credit_card('2424242424242424', verification_value: nil) @gateway.expects(:ssl_post).returns(failed_credit_response) @@ -169,13 +168,13 @@ def test_purchase_sends_additional_options with(anything, regexp_matches(/Value3<\/UDField3>/)). returns(successful_purchase_response) - @gateway.purchase(@amount, @credit_card, {:tax => '777', :ud_field_1 => 'Value1', :ud_field_2 => 'Value2', :ud_field_3 => 'Value3'}) + @gateway.purchase(@amount, @credit_card, { tax: '777', ud_field_1: 'Value1', ud_field_2: 'Value2', ud_field_3: 'Value3' }) end private def successful_purchase_response - <<-EOF + <<-XML 8afa688fd002821362 000 @@ -187,21 +186,21 @@ def successful_purchase_response Y D - EOF + XML end def failed_purchase_response - <<-EOF + <<-XML 7605f7c5d6e8f74deb 005 DECLINED - EOF + XML end def successful_authorize_response - <<-EOF + <<-XML cbf902091334a0b1aa 000 @@ -213,75 +212,75 @@ def successful_authorize_response Y D - EOF + XML end def successful_capture_response - <<-EOF + <<-XML 010327153017T10018 000 502F6B APPROVED - EOF + XML end def failed_capture_response - <<-EOF + <<-XML 010327153017T10018 025 REJECT ED - EOF + XML end def successful_void_response - <<-EOF + <<-XML 010327153x17T10418 000 502F7B VOID PROCESSED - EOF + XML end def failed_void_response - <<-EOF + <<-XML 010327153x17T10418 900 INVALID MESSAGE TYPE - EOF + XML end def successful_credit_response - <<-EOF + <<-XML 010327153017T10017 000 002F6B APPROVED - EOF + XML end def failed_credit_response - <<-EOF + <<-XML 010327153017T10017 912 INVALID CARD NUMBER - EOF + XML end def transcript - <<-EOF + <<-XML TESTMCC3136X SALE e23c963a1247fd7aad @@ -290,11 +289,11 @@ def transcript 16 Longbob Longsen 123 - EOF + XML end def scrubbed_transcript - <<-EOF + <<-XML TESTMCC3136X SALE e23c963a1247fd7aad @@ -303,6 +302,6 @@ def scrubbed_transcript 16 Longbob Longsen [FILTERED] - EOF + XML end end diff --git a/test/unit/gateways/komoju_test.rb b/test/unit/gateways/komoju_test.rb index dc67d18686c..deaf75df6f4 100644 --- a/test/unit/gateways/komoju_test.rb +++ b/test/unit/gateways/komoju_test.rb @@ -2,19 +2,19 @@ class KomojuTest < Test::Unit::TestCase def setup - @gateway = KomojuGateway.new(:login => 'login') + @gateway = KomojuGateway.new(login: 'login') @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :description => 'Store Purchase', - :tax => '10', - :ip => '192.168.0.1', - :email => 'valid@email.com', - :browser_language => 'en', - :browser_user_agent => 'user_agent' + order_id: '1', + description: 'Store Purchase', + tax: '10', + ip: '192.168.0.1', + email: 'valid@email.com', + browser_language: 'en', + browser_user_agent: 'user_agent' } end @@ -59,7 +59,7 @@ def test_successful_credit_card_refund successful_response = successful_credit_card_refund_response @gateway.expects(:ssl_post).returns(JSON.generate(successful_response)) - response = @gateway.refund(@amount, '7e8c55a54256ce23e387f2838c', @options) + response = @gateway.refund(@amount, '7e8c55a54256ce23e387f2838c', @options) assert_success response assert_equal successful_response['id'], response.authorization diff --git a/test/unit/gateways/kushki_test.rb b/test/unit/gateways/kushki_test.rb index 861d82f8354..34f2aab927e 100644 --- a/test/unit/gateways/kushki_test.rb +++ b/test/unit/gateways/kushki_test.rb @@ -28,7 +28,24 @@ def test_successful_purchase_with_options subtotal_iva: '10', iva: '1.54', ice: '3.50' - } + }, + contact_details: { + document_type: 'CC', + document_number: '123456', + email: 'who_dis@monkeys.tv', + first_name: 'Who', + last_name: 'Dis', + second_last_name: 'Buscemi', + phone_number: '+13125556789' + }, + metadata: { + productos: 'bananas', + nombre_apellido: 'Kirk' + }, + months: 2, + deferred_grace_months: '05', + deferred_credit_type: '01', + deferred_months: 3 } amount = 100 * ( @@ -41,7 +58,16 @@ def test_successful_purchase_with_options @gateway.expects(:ssl_post).returns(successful_charge_response) @gateway.expects(:ssl_post).returns(successful_token_response) - response = @gateway.purchase(amount, @credit_card, options) + response = stub_comms do + @gateway.purchase(amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_includes data, 'metadata' + assert_includes data, 'months' + assert_includes data, 'deferred_grace_month' + assert_includes data, 'deferred_credit_type' + assert_includes data, 'deferred_months' + end.respond_with(successful_token_response, successful_charge_response) + assert_success response assert_equal 'Succeeded', response.message assert_match %r(^\d+$), response.authorization @@ -68,10 +94,8 @@ def test_taxes_are_excluded_when_not_provided response = stub_comms do @gateway.purchase(amount, @credit_card, options) - end.check_request do |endpoint, data, headers| - if /charges/ =~ endpoint - assert_no_match %r{extraTaxes}, data - end + end.check_request do |endpoint, data, _headers| + assert_no_match %r{extraTaxes}, data if /charges/.match?(endpoint) end.respond_with(successful_charge_response, successful_token_response) assert_success response @@ -101,8 +125,8 @@ def test_partial_taxes_do_not_error response = stub_comms do @gateway.purchase(amount, @credit_card, options) - end.check_request do |endpoint, data, headers| - if /charges/ =~ endpoint + end.check_request do |endpoint, data, _headers| + if /charges/.match?(endpoint) assert_match %r{extraTaxes}, data assert_no_match %r{propina}, data assert_match %r{iac}, data @@ -112,7 +136,7 @@ def test_partial_taxes_do_not_error assert_success response end - def test_taxes_are_included_when_provided + def test_cop_taxes_are_included_when_provided options = { currency: 'COP', amount: { @@ -138,8 +162,44 @@ def test_taxes_are_included_when_provided response = stub_comms do @gateway.purchase(amount, @credit_card, options) - end.check_request do |endpoint, data, headers| - if /charges/ =~ endpoint + end.check_request do |endpoint, data, _headers| + if /charges/.match?(endpoint) + assert_match %r{extraTaxes}, data + assert_match %r{propina}, data + end + end.respond_with(successful_charge_response, successful_token_response) + + assert_success response + end + + def test_usd_taxes_are_included_when_provided + options = { + currency: 'USD', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50', + extra_taxes: { + propina: 0.1, + tasa_aeroportuaria: 0.2, + agencia_de_viaje: 0.3, + iac: 0.4 + } + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + response = stub_comms do + @gateway.purchase(amount, @credit_card, options) + end.check_request do |endpoint, data, _headers| + if /charges/.match?(endpoint) assert_match %r{extraTaxes}, data assert_match %r{propina}, data end @@ -164,6 +224,33 @@ def test_failed_purchase assert_equal '220', response.error_code end + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = @gateway.authorize(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + assert response.test? + end + + def test_failed_authorize + options = { + amount: { + subtotal_iva: '200' + } + } + + @gateway.expects(:ssl_post).returns(failed_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message + assert_equal '220', response.error_code + end + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_charge_response) @gateway.expects(:ssl_post).returns(successful_token_response) @@ -193,6 +280,78 @@ 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) + + auth = @gateway.authorize(@amount, @credit_card) + assert_success auth + + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + auth = @gateway.authorize(@amount, @credit_card) + assert_success auth + + @gateway.expects(:ssl_post).returns(failed_capture_response) + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_failure capture + assert_equal 'Monto de captura inválido.', capture.message + assert_equal 'K012', capture.error_code + end + def test_successful_void @gateway.expects(:ssl_post).returns(successful_charge_response) @gateway.expects(:ssl_post).returns(successful_token_response) @@ -216,6 +375,23 @@ def test_failed_void assert_equal '205', response.error_code end + def test_successful_purchase_with_full_response_flag + options = { + full_response: 'true' + } + + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_includes data, 'fullResponse' + end.respond_with(successful_token_response, successful_charge_response) + + assert_success response + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -346,6 +522,39 @@ def failed_charge_response ) end + def successful_authorize_response + %( + { + "ticketNumber":"676185788080292214" + } + ) + end + + def failed_authorize_response + %( + { + "code":"220", + "message":"Monto de la transacción es diferente al monto de la venta inicial" + } + ) + end + + def successful_capture_response + %( + { + "ticketNumber":"911597984059374763" + } + ) + end + + def failed_capture_response + %( + { + "code":"K012","message":"Monto de captura inválido." + } + ) + end + def successful_refund_response %( { diff --git a/test/unit/gateways/latitude19_test.rb b/test/unit/gateways/latitude19_test.rb index 75f7b75cba8..afb7de4214f 100644 --- a/test/unit/gateways/latitude19_test.rb +++ b/test/unit/gateways/latitude19_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class Latitude19Test < Test::Unit::TestCase + include CommStub + def setup @gateway = Latitude19Gateway.new(fixtures(:latitude19)) @@ -29,18 +31,18 @@ def test_successful_purchase # end def test_successful_authorize_and_capture - @gateway.expects(:ssl_post).returns(successful_authorize_response) - @gateway.expects(:ssl_post).returns(successful_token_response) - @gateway.expects(:ssl_post).returns(successful_session_response) - - response = @gateway.authorize(@amount, @credit_card, @options) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(successful_session_response, successful_token_response, successful_authorize_response) assert_success response assert_equal 'Approved', response.message assert_match %r(^auth\|\w+$), response.authorization - @gateway.expects(:ssl_post).returns(successful_capture_response) - - capture = @gateway.capture(@amount, response.authorization, @options) + capture = stub_comms do + @gateway.capture(@amount, response.authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"amount\":\"1.00\"/, data) + end.respond_with(successful_capture_response) assert_success capture assert_equal 'Approved', capture.message end diff --git a/test/unit/gateways/linkpoint_test.rb b/test/unit/gateways/linkpoint_test.rb index 50639eb460a..9d14398a1bb 100644 --- a/test/unit/gateways/linkpoint_test.rb +++ b/test/unit/gateways/linkpoint_test.rb @@ -5,13 +5,13 @@ def setup Base.mode = :test @gateway = LinkpointGateway.new( - :login => 123123, - :pem => 'PEM' + login: 123123, + pem: 'PEM' ) @amount = 100 @credit_card = credit_card('4111111111111111') - @options = { :order_id => 1000, :billing_address => address } + @options = { order_id: 1000, billing_address: address } end def test_instantiating_without_credential_raises @@ -65,7 +65,7 @@ def test_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(2400, @credit_card, :order_id => 1003, :installments => 12, :startdate => 'immediate', :periodicity => :monthly) + @gateway.recurring(2400, @credit_card, order_id: 1003, installments: 12, startdate: 'immediate', periodicity: :monthly) end assert_success response end @@ -81,13 +81,13 @@ def test_amount_style def test_purchase_is_valid_xml @gateway.send( :parameters, 1000, @credit_card, - :ordertype => 'SALE', - :order_id => 1004, - :billing_address => { - :address1 => '1313 lucky lane', - :city => 'Lost Angeles', - :state => 'CA', - :zip => '90210' + ordertype: 'SALE', + order_id: 1004, + billing_address: { + address1: '1313 lucky lane', + city: 'Lost Angeles', + state: 'CA', + zip: '90210' } ) @@ -98,17 +98,17 @@ def test_purchase_is_valid_xml def test_recurring_is_valid_xml @gateway.send( :parameters, 1000, @credit_card, - :ordertype => 'SALE', - :action => 'SUBMIT', - :installments => 12, - :startdate => 'immediate', - :periodicity => 'monthly', - :order_id => 1006, - :billing_address => { - :address1 => '1313 lucky lane', - :city => 'Lost Angeles', - :state => 'CA', - :zip => '90210' + ordertype: 'SALE', + action: 'SUBMIT', + installments: 12, + startdate: 'immediate', + periodicity: 'monthly', + order_id: 1006, + billing_address: { + address1: '1313 lucky lane', + city: 'Lost Angeles', + state: 'CA', + zip: '90210' } ) assert data = @gateway.send(:post_data, @amount, @credit_card, @options) @@ -117,38 +117,40 @@ def test_recurring_is_valid_xml def test_line_items_are_valid_xml options = { - :ordertype => 'SALE', - :action => 'SUBMIT', - :installments => 12, - :startdate => 'immediate', - :periodicity => 'monthly', - :order_id => 1006, - :billing_address => { - :address1 => '1313 lucky lane', - :city => 'Lost Angeles', - :state => 'CA', - :zip => '90210' - }, - :line_items => [ + ordertype: 'SALE', + action: 'SUBMIT', + installments: 12, + startdate: 'immediate', + periodicity: 'monthly', + order_id: 1006, + billing_address: { + address1: '1313 lucky lane', + city: 'Lost Angeles', + state: 'CA', + zip: '90210' + }, + line_items: [ { - :id => '123456', - :description => 'Logo T-Shirt', - :price => '12.00', - :quantity => '1', - :options => [ + id: '123456', + description: 'Logo T-Shirt', + price: '12.00', + quantity: '1', + options: [ { - :name => 'Color', - :value => 'Red'}, + name: 'Color', + value: 'Red' + }, { - :name => 'Size', - :value => 'XL'} + name: 'Size', + value: 'XL' + } ] }, { - :id => '111', - :description => 'keychain', - :price => '3.00', - :quantity => '1' + id: '111', + description: 'keychain', + price: '3.00', + quantity: '1' } ] } @@ -158,17 +160,17 @@ def test_line_items_are_valid_xml end def test_declined_purchase_is_valid_xml - @gateway = LinkpointGateway.new(:login => 123123, :pem => 'PEM') + @gateway = LinkpointGateway.new(login: 123123, pem: 'PEM') @gateway.send( :parameters, 1000, @credit_card, - :ordertype => 'SALE', - :order_id => 1005, - :billing_address => { - :address1 => '1313 lucky lane', - :city => 'Lost Angeles', - :state => 'CA', - :zip => '90210' + ordertype: 'SALE', + order_id: 1005, + billing_address: { + address1: '1313 lucky lane', + city: 'Lost Angeles', + state: 'CA', + zip: '90210' } ) @@ -180,9 +182,9 @@ def test_overriding_test_mode Base.mode = :production gateway = LinkpointGateway.new( - :login => 'LOGIN', - :pem => 'PEM', - :test => true + login: 'LOGIN', + pem: 'PEM', + test: true ) assert gateway.test? @@ -192,8 +194,8 @@ def test_using_production_mode Base.mode = :production gateway = LinkpointGateway.new( - :login => 'LOGIN', - :pem => 'PEM' + login: 'LOGIN', + pem: 'PEM' ) assert !gateway.test? @@ -204,7 +206,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club], LinkpointGateway.supported_cardtypes + assert_equal %i[visa master american_express discover jcb diners_club], LinkpointGateway.supported_cardtypes end def test_avs_result @@ -254,5 +256,4 @@ def transcript def scrubbed_transcript '[FILTERED]916[FILTERED]providedJim Smith' end - end diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb index 2eafd178f35..4c2314894aa 100644 --- a/test/unit/gateways/litle_test.rb +++ b/test/unit/gateways/litle_test.rb @@ -20,7 +20,8 @@ def setup brand: 'visa', number: '44444444400009', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) @decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( { source: :android_pay, @@ -29,7 +30,18 @@ def setup brand: 'visa', number: '4457000300000007', payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' - }) + } + ) + @decrypted_google_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + source: :google_pay, + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + } + ) @amount = 100 @options = {} @check = check( @@ -44,11 +56,71 @@ def setup account_number: '1099999999', account_type: 'checking' ) + + @long_address = { + address1: '1234 Supercalifragilisticexpialidocious', + address2: 'Unit 6', + city: '‎Lake Chargoggagoggmanchauggagoggchaubunagungamaugg', + state: 'ME', + zip: '09901', + country: 'US' + } end def test_successful_purchase 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) + + assert_success response + assert_equal 'Approved', response.message + assert_equal '100000000000000006;sale;100', response.authorization + assert response.test? + 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 + + def test_successful_postlive_url + @gateway = LitleGateway.new( + login: 'login', + password: 'password', + merchant_id: 'merchant_id', + url_override: 'postlive' + ) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, _data, _headers| + assert_match(/payments\.vantivpostlive\.com/, endpoint) end.respond_with(successful_purchase_response) assert_success response @@ -68,6 +140,22 @@ def test_successful_purchase_with_echeck assert response.test? end + def test_sale_response_duplicate_attribute + dup_response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(duplicate_purchase_response) + + assert_success dup_response + assert_true dup_response.params['duplicate'] + + non_dup_response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success non_dup_response + assert_false non_dup_response.params['duplicate'] + end + def test_failed_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) @@ -87,7 +175,7 @@ def test_passing_merchant_data ) stub_comms do @gateway.purchase(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(some-affiliate), data) assert_match(%r(super-awesome-campaign), data) assert_match(%r(brilliant-group), data) @@ -97,7 +185,7 @@ def test_passing_merchant_data def test_passing_name_on_card stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(\s*Longbob Longsen<), data) end.respond_with(successful_purchase_response) end @@ -105,15 +193,39 @@ def test_passing_name_on_card def test_passing_order_id stub_comms do @gateway.purchase(@amount, @credit_card, order_id: '774488') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/774488/, data) end.respond_with(successful_purchase_response) end + def test_passing_customer_id_on_purchase + stub_comms do + @gateway.purchase(@amount, @credit_card, customer_id: '8675309') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(customerId=\"8675309\">\n), data) + end.respond_with(successful_purchase_response) + end + + def test_passing_customer_id_on_capture + stub_comms do + @gateway.capture(@amount, @credit_card, customer_id: '8675309') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(customerId=\"8675309\">\n), data) + end.respond_with(successful_capture_response) + end + + def test_passing_customer_id_on_refund + stub_comms do + @gateway.credit(@amount, @credit_card, customer_id: '8675309') + end.check_request do |_endpoint, data, _headers| + assert_match(%r(customerId=\"8675309\">\n), data) + end.respond_with(successful_credit_response) + end + def test_passing_billing_address stub_comms do @gateway.purchase(@amount, @credit_card, billing_address: address) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/.*Widgets.*456.*Apt 1.*Otta.*ON.*K1C.*CA.*555-5/m, data) end.respond_with(successful_purchase_response) end @@ -121,17 +233,25 @@ def test_passing_billing_address def test_passing_shipping_address stub_comms do @gateway.purchase(@amount, @credit_card, shipping_address: address) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/.*Widgets.*456.*Apt 1.*Otta.*ON.*K1C.*CA.*555-5/m, data) end.respond_with(successful_purchase_response) end + def test_truncating_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, billing_address: @long_address) + end.check_request do |_endpoint, data, _headers| + refute_match(/Supercalifragilisticexpialidocious/m, data) + end.respond_with(successful_purchase_response) + end + def test_passing_descriptor stub_comms do @gateway.authorize(@amount, @credit_card, { descriptor_name: 'Name', descriptor_phone: 'Phone' }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(.*Name<)m, data) assert_match(%r(.*Phone<)m, data) end.respond_with(successful_authorize_response) @@ -140,23 +260,54 @@ def test_passing_descriptor def test_passing_debt_repayment stub_comms do @gateway.authorize(@amount, @credit_card, { debt_repayment: true }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(true), data) end.respond_with(successful_authorize_response) end + def test_fraud_filter_override + stub_comms do + @gateway.authorize(@amount, @credit_card, { fraud_filter_override: true }) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(true), data) + end.respond_with(successful_authorize_response) + end + def test_passing_payment_cryptogram stub_comms do @gateway.purchase(@amount, @decrypted_apple_pay) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/BwABBJQ1AgAAAAAgJDUCAAAAAAA=/, data) end.respond_with(successful_purchase_response) end + def test_passing_basis_date + stub_comms do + @gateway.purchase(@amount, 'token', { basis_expiration_month: '04', basis_expiration_year: '2027' }) + end.check_request do |_endpoint, data, _headers| + assert_match(/0427<\/expDate>/, data) + end.respond_with(successful_purchase_response) + end + + def test_does_not_pass_empty_checknum + check = check( + name: 'Tom Black', + routing_number: '011075150', + account_number: '4099999992', + number: nil, + account_type: 'checking' + ) + stub_comms do + @gateway.purchase(@amount, check) + end.check_request do |_endpoint, data, _headers| + assert_not_match(//m, data) + end.respond_with(successful_purchase_response) + end + def test_add_applepay_order_source stub_comms do @gateway.purchase(@amount, @decrypted_apple_pay) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'applepay', data end.respond_with(successful_purchase_response) end @@ -164,7 +315,15 @@ def test_add_applepay_order_source def test_add_android_pay_order_source stub_comms do @gateway.purchase(@amount, @decrypted_android_pay) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| + assert_match 'androidpay', data + end.respond_with(successful_purchase_response) + end + + def test_add_google_pay_order_source + stub_comms do + @gateway.purchase(@amount, @decrypted_google_pay) + end.check_request do |_endpoint, data, _headers| assert_match 'androidpay', data end.respond_with(successful_purchase_response) end @@ -181,7 +340,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/100000000000000001/, data) end.respond_with(successful_capture_response) @@ -217,7 +376,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/100000000000000006/, data) end.respond_with(successful_refund_response) @@ -234,6 +393,23 @@ def test_failed_refund assert_equal '360', response.params['response'] end + def test_successful_credit + credit = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(successful_credit_response) + + assert_success credit + assert_equal 'Approved', credit.message + end + + def test_failed_credit + credit = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure credit + end + def test_successful_void_of_authorization response = stub_comms do @gateway.authorize(@amount, @credit_card) @@ -244,7 +420,7 @@ def test_successful_void_of_authorization void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/1000000000000000011000000000000000034242424242424242Track Data', data assert_match 'retail', data assert_match %r{.+<\/pos>}m, data @@ -364,7 +540,7 @@ def test_add_swipe_data_with_creditcard def test_order_source_with_creditcard_no_track_data stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'ecommerce', data assert %r{.+<\/pos>}m !~ data end.respond_with(successful_purchase_response) @@ -373,7 +549,7 @@ def test_order_source_with_creditcard_no_track_data def test_order_source_override stub_comms do @gateway.purchase(@amount, @credit_card, order_source: 'recurring') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'recurring', data end.respond_with(successful_purchase_response) end @@ -400,7 +576,7 @@ def test_stored_credential_cit_card_on_file_initial response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(initialCOF), data) end.respond_with(successful_authorize_stored_credentials) @@ -419,7 +595,7 @@ def test_stored_credential_cit_card_on_file_used response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(cardholderInitiatedCOF), data) assert_match(%r(#{network_transaction_id}), data) assert_match(%r(ecommerce), data) @@ -443,7 +619,7 @@ def test_stored_credential_cit_cof_doesnt_override_order_source response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(cardholderInitiatedCOF), data) assert_match(%r(#{network_transaction_id}), data) assert_match(%r(3dsAuthenticated), data) @@ -464,7 +640,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| + end.check_request do |_endpoint, data, _headers| assert_match(%r(initialCOF), data) end.respond_with(successful_authorize_stored_credentials) @@ -483,7 +659,7 @@ def test_stored_credential_mit_card_on_file_used response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(merchantInitiatedCOF), data) assert_match(%r(#{network_transaction_id}), data) assert_match(%r(ecommerce), data) @@ -504,7 +680,7 @@ def test_stored_credential_installment_initial response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(initialInstallment), data) end.respond_with(successful_authorize_stored_credentials) @@ -523,7 +699,7 @@ def test_stored_credential_installment_used response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(#{network_transaction_id}), data) assert_match(%r(installment), data) end.respond_with(successful_authorize_stored_credentials) @@ -543,7 +719,7 @@ def test_stored_credential_recurring_initial response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(initialRecurring), data) end.respond_with(successful_authorize_stored_credentials) @@ -562,7 +738,7 @@ def test_stored_credential_recurring_used response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(#{network_transaction_id}), data) assert_match(%r(recurring), data) end.respond_with(successful_authorize_stored_credentials) @@ -584,10 +760,29 @@ def network_transaction_id '63225578415568556365452427825' end - def successful_purchase_response + def successful_purchase_response(code = '000', message = 'Approved') %( + 100000000000000006 + 1 + #{code} + 2014-03-31T11:34:39 + #{message} + 11111 + + 01 + M + + + + ) + end + + def duplicate_purchase_response + %( + + 100000000000000006 1 000 @@ -740,6 +935,28 @@ def failed_refund_response ) end + def successful_credit_response + %( + + + + 908410935514139173 + 1 + 000 + 2020-10-30T19:19:38.935 + Approved + + + ) + end + + def failed_credit_response + %( + + + ) + end + def successful_void_of_auth_response %( @@ -864,7 +1081,7 @@ def unsuccessful_xml_schema_validation_response end def pre_scrub - <<-pre_scrub + <<-REQUEST opening connection to www.testlitle.com:443... opened starting SSL for www.testlitle.com:443... @@ -890,11 +1107,11 @@ def pre_scrub -> "0\r\n" -> "\r\n" Conn close - pre_scrub + REQUEST end def post_scrub - <<-post_scrub + <<-REQUEST opening connection to www.testlitle.com:443... opened starting SSL for www.testlitle.com:443... @@ -920,7 +1137,6 @@ def post_scrub -> "0\r\n" -> "\r\n" Conn close - post_scrub + REQUEST end - end diff --git a/test/unit/gateways/maxipago_test.rb b/test/unit/gateways/maxipago_test.rb index b437bcda00a..afac8fc8912 100644 --- a/test/unit/gateways/maxipago_test.rb +++ b/test/unit/gateways/maxipago_test.rb @@ -3,18 +3,18 @@ class MaxipagoTest < Test::Unit::TestCase def setup @gateway = MaxipagoGateway.new( - :login => 'login', - :password => 'password' + login: 'login', + password: 'password' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :installments => 3 + order_id: '1', + billing_address: address, + description: 'Store Purchase', + installments: 3 } end diff --git a/test/unit/gateways/mercado_pago_test.rb b/test/unit/gateways/mercado_pago_test.rb index 3690b766f46..a25401c202d 100644 --- a/test/unit/gateways/mercado_pago_test.rb +++ b/test/unit/gateways/mercado_pago_test.rb @@ -6,12 +6,29 @@ class MercadoPagoTest < Test::Unit::TestCase def setup @gateway = MercadoPagoGateway.new(access_token: 'access_token') @credit_card = credit_card - @elo_credit_card = credit_card('5067268650517446', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737' + @elo_credit_card = credit_card( + '5067268650517446', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737' + ) + @cabal_credit_card = credit_card( + '6035227716427021', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737' + ) + @naranja_credit_card = credit_card( + '5895627823453005', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '123' ) @amount = 100 @@ -44,6 +61,28 @@ def test_successful_purchase_with_elo assert response.test? end + def test_successful_purchase_with_cabal + @gateway.expects(:ssl_post).at_most(2).returns(successful_purchase_with_cabal_response) + + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + + assert_equal '20728968|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_purchase_with_naranja + @gateway.expects(:ssl_post).at_most(2).returns(successful_purchase_with_naranja_response) + + response = @gateway.purchase(@amount, @naranja_credit_card, @options) + assert_success response + + assert_equal '20728968|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + def test_failed_purchase @gateway.expects(:ssl_post).at_most(2).returns(failed_purchase_response) @@ -64,6 +103,17 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_with_capture_option + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_capture_option_response) + + response = @gateway.authorize(@amount, @credit_card, @options.merge(capture: true)) + assert_success response + + assert_equal '4261941|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + def test_successful_authorize_with_elo @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_elo_response) @@ -75,6 +125,28 @@ def test_successful_authorize_with_elo assert response.test? end + def test_successful_authorize_with_cabal + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_cabal_response) + + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + + def test_successful_authorize_with_naranja + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_naranja_response) + + response = @gateway.authorize(@amount, @naranja_credit_card, @options) + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + def test_failed_authorize @gateway.expects(:ssl_post).at_most(2).returns(failed_authorize_response) @@ -106,6 +178,28 @@ def test_successful_capture_with_elo assert response.test? end + def test_successful_capture_with_cabal + @gateway.expects(:ssl_request).returns(successful_capture_with_cabal_response) + + response = @gateway.capture(@amount, 'authorization|amount') + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_capture_with_naranja + @gateway.expects(:ssl_request).returns(successful_capture_with_naranja_response) + + response = @gateway.capture(@amount, 'authorization|amount') + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + def test_failed_capture @gateway.expects(:ssl_request).returns(failed_capture_response) @@ -164,6 +258,17 @@ def test_successful_verify assert response.test? end + def test_successful_verify_with_custom_amount + response = stub_comms do + @gateway.verify(@credit_card, @options.merge({ amount: 200 })) + end.check_request do |endpoint, data, _headers| + params = JSON.parse(data) + assert_equal 2.0, params['transaction_amount'] if endpoint.include? 'payment' + end.respond_with(successful_authorize_response) + + assert_success response + end + def test_successful_verify_with_failed_void @gateway.expects(:ssl_request).at_most(3).returns(failed_void_response) @@ -184,6 +289,29 @@ def test_failed_verify assert response.test? end + def test_successful_inquire_with_id + @gateway.expects(:ssl_get).returns(successful_authorize_response) + + response = @gateway.inquire('authorization|amount') + assert_success response + + assert_equal '4261941|', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + + def test_successful_inquire_with_external_reference + @gateway.expects(:ssl_get).returns(successful_search_payments_response) + + response = @gateway.inquire(nil, { external_reference: '1234' }) + assert_success response + + assert_equal '1234', response.params['external_reference'] + assert_equal '1|', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -194,10 +322,8 @@ def test_does_not_send_brand response = stub_comms do @gateway.purchase(@amount, credit_card, @options) - end.check_request do |endpoint, data, headers| - if endpoint =~ /payments/ - assert_not_match(%r("payment_method_id":"amex"), data) - end + end.check_request do |endpoint, data, _headers| + assert_not_match(%r("payment_method_id":"amex"), data) if endpoint =~ /payments/ end.respond_with(successful_purchase_response) assert_success response @@ -209,31 +335,39 @@ def test_sends_payment_method_id response = stub_comms do @gateway.purchase(@amount, credit_card, @options.merge(payment_method_id: 'diners')) - end.check_request do |endpoint, data, headers| - if endpoint =~ /payments/ - assert_match(%r("payment_method_id":"diners"), data) - end + end.check_request do |endpoint, data, _headers| + assert_match(%r("payment_method_id":"diners"), data) if endpoint =~ /payments/ end.respond_with(successful_purchase_response) assert_success response assert_equal '4141491|1.0', response.authorization end + def test_successful_purchase_with_notification_url + response = stub_comms do + @gateway.purchase(@amount, credit_card, @options.merge(notification_url: 'www.mercado-pago.com')) + end.check_request do |endpoint, data, _headers| + assert_match(%r("notification_url":"www.mercado-pago.com"), data) if endpoint =~ /payments/ + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_includes_deviceid_header @options[:device_id] = '1a2b3c' - @gateway.expects(:ssl_post).with(anything, anything, {'Content-Type' => 'application/json'}).returns(successful_purchase_response) - @gateway.expects(:ssl_post).with(anything, anything, {'Content-Type' => 'application/json', 'X-Device-Session-ID' => '1a2b3c'}).returns(successful_purchase_response) + @gateway.expects(:ssl_post).with(anything, anything, { 'Content-Type' => 'application/json' }).returns(successful_purchase_response) + @gateway.expects(:ssl_post).with(anything, anything, { 'Content-Type' => 'application/json', 'X-meli-session-id' => '1a2b3c' }).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) assert_success response end def test_includes_additional_data - @options[:additional_info] = {'foo' => 'bar', 'baz' => 'quux'} + @options[:additional_info] = { 'foo' => 'bar', 'baz' => 'quux' } response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - if data =~ /payment_method_id/ + end.check_request do |_endpoint, data, _headers| + if /payment_method_id/.match?(data) assert_match(/"foo":"bar"/, data) assert_match(/"baz":"quux"/, data) end @@ -245,16 +379,126 @@ def test_includes_additional_data def test_includes_issuer_id response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '1a2b3c4d')) - end.check_request do |endpoint, data, headers| - if endpoint =~ /payments/ - assert_match(%r("issuer_id":"1a2b3c4d"), data) - end + end.check_request do |endpoint, data, _headers| + assert_match(%r("issuer_id":"1a2b3c4d"), data) if endpoint =~ /payments/ end.respond_with(successful_purchase_response) assert_success response assert_equal '4141491|1.0', response.authorization end + def test_purchase_includes_taxes_array + taxes_type = 'IVA' + taxes_value = 500.0 + taxes_object = { value: taxes_value, type: taxes_type } + taxes_array = [taxes_object, taxes_object] + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(taxes: taxes_array)) + end.check_request do |endpoint, data, _headers| + single_pattern = "{\"value\":#{taxes_value},\"type\":\"#{taxes_type}\"}" + pattern = "\"taxes\":[#{single_pattern},#{single_pattern}]" + assert_match(pattern, data) if endpoint =~ /payments/ + end.respond_with(successful_purchase_response) + end + + def test_purchase_includes_taxes_hash + taxes_type = 'IVA' + taxes_value = 500.0 + taxes_object = { value: taxes_value, type: taxes_type } + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(taxes: taxes_object)) + end.check_request do |endpoint, data, _headers| + pattern = "\"taxes\":[{\"value\":#{taxes_value},\"type\":\"#{taxes_type}\"}]" + assert_match(pattern, data) if endpoint =~ /payments/ + end.respond_with(successful_purchase_response) + end + + def test_purchase_includes_net_amount + net_amount = 9500 + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(net_amount: net_amount)) + end.check_request do |endpoint, data, _headers| + assert_match("\"net_amount\":#{net_amount}", data) if endpoint =~ /payments/ + end.respond_with(successful_purchase_response) + end + + def test_purchase_includes_metadata + key1 = 'value_1' + key2 = 'value_2' + metadata_object = { key_1: key1, key_2: key2 } + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(metadata: metadata_object)) + end.check_request do |endpoint, data, _headers| + assert_match("\"metadata\":{\"key_1\":\"#{key1}\",\"key_2\":\"#{key2}\"}", data) if endpoint =~ /payments/ + end.respond_with(successful_purchase_with_metadata_response) + end + + def test_authorize_includes_taxes_array + taxes_type = 'IVA' + taxes_value = 500.0 + taxes_object = { value: taxes_value, type: taxes_type } + taxes_array = [taxes_object, taxes_object] + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(taxes: taxes_array)) + end.check_request do |endpoint, data, _headers| + single_pattern = "{\"value\":#{taxes_value},\"type\":\"#{taxes_type}\"}" + pattern = "\"taxes\":[#{single_pattern},#{single_pattern}]" + assert_match(pattern, data) if endpoint =~ /payments/ + end.respond_with(successful_authorize_response) + end + + def test_authorize_includes_taxes_hash + taxes_type = 'IVA' + taxes_value = 500.0 + taxes_object = { value: taxes_value, type: taxes_type } + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(taxes: taxes_object)) + end.check_request do |endpoint, data, _headers| + pattern = "\"taxes\":[{\"value\":#{taxes_value},\"type\":\"#{taxes_type}\"}]" + assert_match(pattern, data) if endpoint =~ /payments/ + end.respond_with(successful_authorize_response) + end + + def test_authorize_includes_net_amount + net_amount = 9500 + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(net_amount: net_amount)) + end.check_request do |endpoint, data, _headers| + assert_match("\"net_amount\":#{net_amount}", data) if endpoint =~ /payments/ + end.respond_with(successful_authorize_response) + end + + def test_invalid_taxes_type + assert_raises(ArgumentError) do + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(taxes: 10)) + end.respond_with(successful_purchase_response) + end + end + + def test_invalid_taxes_embedded_type + assert_raises(ArgumentError) do + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(taxes: [{ value: 500, type: 'IVA' }, 10])) + end.respond_with(successful_purchase_response) + end + end + + def test_invalid_taxes_shape + assert_raises(ArgumentError) do + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(taxes: [{ amount: 500, type: 'IVA' }])) + end.respond_with(successful_purchase_response) + end + end + private def pre_scrubbed @@ -379,6 +623,18 @@ def successful_purchase_with_elo_response ) end + def successful_purchase_with_cabal_response + %( + {"id":20728968,"date_created":"2019-08-06T15:38:12.000-04:00","date_approved":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:38:12.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"ab86ce493d1cc1e447877720843812e9","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_purchase_with_naranja_response + %( + {"id":20728968,"date_created":"2019-08-06T15:38:12.000-04:00","date_approved":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:38:12.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"naranja","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"ab86ce493d1cc1e447877720843812e9","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + def failed_purchase_response %( {"id":4142297,"date_created":"2017-07-06T10:13:32.000-04:00","date_approved":null,"date_last_updated":"2017-07-06T10:13:32.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"166","payment_method_id":"visa","payment_type_id":"credit_card","status":"rejected","status_detail":"cc_rejected_other_reason","currency_id":"MXN","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":261735089,"payer":{"type":"guest","id":null,"email":"user@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":""},"first_name":"First User","last_name":"User","entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"830943860538524456"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":true,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"400030","last_four_digits":"2220","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-06T10:13:32.000-04:00","date_last_updated":"2017-07-06T10:13:32.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":null,"merchant_account_id":null,"acquirer":null,"merchant_number":null} @@ -391,12 +647,30 @@ def successful_authorize_response ) end + def successful_authorize_with_capture_option_response + %( + {"id":4261941,"date_created":"2017-07-13T14:24:46.000-04:00","date_approved":null,"date_last_updated":"2017-07-13T14:24:46.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"authorized","status_detail":"accredited","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"2294029672081601730"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:24:46.000-04:00","date_last_updated":"2017-07-13T14:24:46.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + def successful_authorize_with_elo_response %( {"id":18044850,"date_created":"2019-03-04T09:44:37.000-04:00","date_approved":null,"date_last_updated":"2019-03-04T09:44:40.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"687","payment_method_id":"elo","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":263489584,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"413001901"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"24cd4658b9ea6dbf164f9fb9f67e5e78","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"MERCADOPAGO","installments":1,"card":{"id":null,"first_six_digits":"506726","last_four_digits":"7446","expiration_month":10,"expiration_year":2020,"date_created":"2019-03-04T09:44:37.000-04:00","date_last_updated":"2019-03-04T09:44:37.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} ) end + def successful_authorize_with_cabal_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":null,"date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_authorize_with_naranja_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":null,"date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"688","payment_method_id":"naranja","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + def failed_authorize_response %( {"id":4261953,"date_created":"2017-07-13T14:25:33.000-04:00","date_approved":null,"date_last_updated":"2017-07-13T14:25:33.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"rejected","status_detail":"cc_rejected_other_reason","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"7528376941458928221"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"400030","last_four_digits":"2220","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:25:33.000-04:00","date_last_updated":"2017-07-13T14:25:33.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} @@ -415,6 +689,18 @@ def successful_capture_with_elo_response ) end + def successful_capture_with_cabal_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":"2019-08-06T15:57:49.000-04:00","date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:57:49.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_capture_with_naranja_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":"2019-08-06T15:57:49.000-04:00","date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:57:49.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"naranja","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + def failed_capture_response %( {"message":"Method not allowed","error":"method_not_allowed","status":405,"cause":[{"code":"Method not allowed","description":"Method not allowed","data":null}]} @@ -444,4 +730,16 @@ def failed_void_response {"message":"Method not allowed","error":"method_not_allowed","status":405,"cause":[{"code":"Method not allowed","description":"Method not allowed","data":null}]} ) end + + def successful_purchase_with_metadata_response + %( + {"id":4141491,"date_created":"2017-07-06T09:49:35.000-04:00","date_approved":"2017-07-06T09:49:35.000-04:00","date_last_updated":"2017-07-06T09:49:35.000-04:00","date_of_expiration":null,"money_release_date":"2017-07-18T09:49:35.000-04:00","operation_type":"regular_payment","issuer_id":"166","payment_method_id":"visa","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"MXN","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":261735089,"payer":{"type":"guest","id":null,"email":"user@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":""},"first_name":"First User","last_name":"User","entity_type":null},"metadata":{"key_1":"value_1","key_2":"value_2","key_3":{"nested_key_1":"value_3"}},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"2326513804447055222"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0.14,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":4.86,"fee_payer":"collector"}],"captured":true,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-06T09:49:35.000-04:00","date_last_updated":"2017-07-06T09:49:35.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":null,"merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_search_payments_response + %( + [{"paging":{"total":17493,"limit":30,"offset":0},"results":[{"id":1,"date_created":"2017-08-31T11:26:38.000Z","date_approved":"2017-08-31T11:26:38.000Z","date_last_updated":"2017-08-31T11:26:38.000Z","money_release_date":"2017-09-14T11:26:38.000Z","payment_method_id":"account_money","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"BRL","description":"PagoPizza","collector_id":2,"payer":{"id":123,"email":"afriend@gmail.com","identification":{"type":"DNI","number":12345678},"type":"customer"},"metadata":{},"additional_info":{},"external_reference":"1234","transaction_amount":250,"transaction_amount_refunded":0,"coupon_amount":0,"transaction_details":{"net_received_amount":250,"total_paid_amount":250,"overpaid_amount":0,"installment_amount":250},"installments":1,"card":{}}]}] + ) + end end diff --git a/test/unit/gateways/merchant_e_solutions_test.rb b/test/unit/gateways/merchant_e_solutions_test.rb index 9e073026c41..228ac4e1305 100644 --- a/test/unit/gateways/merchant_e_solutions_test.rb +++ b/test/unit/gateways/merchant_e_solutions_test.rb @@ -7,17 +7,27 @@ def setup Base.mode = :test @gateway = MerchantESolutionsGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @stored_credential_options = { + moto_ecommerce_ind: '7', + client_reference_number: '345892', + recurring_pmt_num: 11, + recurring_pmt_count: 10, + card_on_file: 'Y', + cit_mit_indicator: 'C101', + account_data_source: 'Y' } end @@ -30,6 +40,20 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_stored_credentials + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @stored_credential_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/moto_ecommerce_ind=7/, data) + assert_match(/client_reference_number=345892/, data) + assert_match(/recurring_pmt_num=11/, data) + assert_match(/recurring_pmt_count=10/, data) + assert_match(/card_on_file=Y/, data) + assert_match(/cit_mit_indicator=C101/, data) + assert_match(/account_data_source=Y/, data) + end.respond_with(successful_purchase_response) + end + def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -38,7 +62,7 @@ def test_unsuccessful_purchase end def test_purchase_with_long_order_id_truncates_id - options = {order_id: 'thisislongerthan17characters'} + options = { order_id: 'thisislongerthan17characters' } @gateway.expects(:ssl_post).with( anything, all_of( @@ -84,14 +108,6 @@ def test_void assert response.test? end - def test_store - @gateway.expects(:ssl_post).returns(successful_store_response) - assert response = @gateway.store(@credit_card) - assert response.success? - assert_equal 'ae641b57b19b3bb89faab44191479872', response.authorization - assert response.test? - end - def test_unstore @gateway.expects(:ssl_post).returns(successful_unstore_response) assert response = @gateway.unstore('ae641b57b19b3bb89faab44191479872') @@ -100,6 +116,18 @@ def test_unstore assert response.test? end + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, { store_card: 'y' }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/transaction_type=A/, data) + assert_match(/store_card=y/, data) + assert_match(/card_number=#{@credit_card.number}/, data) + end.respond_with(successful_verify_response) + assert_success response + assert_equal 'Card Ok', response.message + end + def test_successful_avs_check @gateway.expects(:ssl_post).returns(successful_purchase_response + '&avs_result=Y') assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -122,7 +150,7 @@ def test_unsuccessful_avs_check_with_bad_zip @gateway.expects(:ssl_post).returns(successful_purchase_response + '&avs_result=A') assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal response.avs_result['code'], 'A' - assert_equal response.avs_result['message'], 'Street address matches, but 5-digit and 9-digit postal code do not match.' + assert_equal response.avs_result['message'], 'Street address matches, but postal code does not match.' assert_equal response.avs_result['street_match'], 'Y' assert_equal response.avs_result['postal_match'], 'N' end @@ -143,8 +171,8 @@ def test_unsuccessful_cvv_check def test_visa_3dsecure_params_submitted stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:xid => '1', :cavv => '2'})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ xid: '1', cavv: '2' })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/xid=1/, data) assert_match(/cavv=2/, data) end.respond_with(successful_purchase_response) @@ -152,8 +180,8 @@ def test_visa_3dsecure_params_submitted def test_mastercard_3dsecure_params_submitted stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:ucaf_collection_ind => '1', :ucaf_auth_data => '2'})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ ucaf_collection_ind: '1', ucaf_auth_data: '2' })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ucaf_collection_ind=1/, data) assert_match(/ucaf_auth_data=2/, data) end.respond_with(successful_purchase_response) @@ -164,7 +192,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :jcb], MerchantESolutionsGateway.supported_cardtypes + assert_equal %i[visa master american_express discover jcb], MerchantESolutionsGateway.supported_cardtypes end def test_scrub @@ -202,6 +230,10 @@ def successful_unstore_response 'transaction_id=d79410c91b4b31ba99f5a90558565df9&error_code=000&auth_response_text=Stored Card Data Deleted' end + def successful_verify_response + 'transaction_id=a5ef059bff7a3f75ac2398eea4cc73cd&error_code=085&auth_response_text=Card Ok&avs_result=0&cvv2_result=M&auth_code=T1933H' + end + def failed_purchase_response 'transaction_id=error&error_code=101&auth_response_text=Invalid%20I%20or%20Key%20Incomplete%20Request' end diff --git a/test/unit/gateways/merchant_one_test.rb b/test/unit/gateways/merchant_one_test.rb index 16e3b879473..78189ec91cb 100644 --- a/test/unit/gateways/merchant_one_test.rb +++ b/test/unit/gateways/merchant_one_test.rb @@ -1,23 +1,22 @@ require 'test_helper' class MerchantOneTest < Test::Unit::TestCase - def setup @gateway = MerchantOneGateway.new(fixtures(:merchant_one)) @credit_card = credit_card @amount = 1000 @options = { - :order_id => '1', - :description => 'Store Purchase', - :billing_address => { - :name =>'Jim Smith', - :address1 =>'1234 My Street', - :address2 =>'Apt 1', - :city =>'Tampa', - :state =>'FL', - :zip =>'33603', - :country =>'US', - :phone =>'(813)421-4331' + order_id: '1', + description: 'Store Purchase', + billing_address: { + name: 'Jim Smith', + address1: '1234 My Street', + address2: 'Apt 1', + city: 'Tampa', + state: 'FL', + zip: '33603', + country: 'US', + phone: '(813)421-4331' } } end diff --git a/test/unit/gateways/merchant_partners_test.rb b/test/unit/gateways/merchant_partners_test.rb index 3307fa37d61..d86a9697e0e 100644 --- a/test/unit/gateways/merchant_partners_test.rb +++ b/test/unit/gateways/merchant_partners_test.rb @@ -19,7 +19,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -51,7 +51,7 @@ def test_failed_purchase def test_successful_authorize_and_capture response = stub_comms do @gateway.authorize(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -70,7 +70,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -115,7 +115,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -145,7 +145,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -170,7 +170,7 @@ def test_failed_refund def test_successful_credit response = stub_comms do @gateway.credit(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -217,7 +217,7 @@ def test_failed_verify def test_successful_store response = stub_comms do @gateway.store(@credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -247,7 +247,7 @@ def test_successful_stored_purchase purchase = stub_comms do @gateway.purchase(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content @@ -275,7 +275,7 @@ def test_successful_stored_credit credit = stub_comms do @gateway.credit(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| parse(data) do |doc| assert_not_nil root = doc.at_xpath(@request_root) assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content diff --git a/test/unit/gateways/merchant_ware_test.rb b/test/unit/gateways/merchant_ware_test.rb index bfaf52f2462..a63e8259543 100644 --- a/test/unit/gateways/merchant_ware_test.rb +++ b/test/unit/gateways/merchant_ware_test.rb @@ -5,17 +5,17 @@ class MerchantWareTest < Test::Unit::TestCase def setup @gateway = MerchantWareGateway.new( - :login => 'login', - :password => 'password', - :name => 'name' - ) + login: 'login', + password: 'password', + name: 'name' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address + order_id: '1', + billing_address: address } end @@ -32,8 +32,8 @@ def test_successful_authorization end def test_soap_fault_during_authorization - response_500 = stub(:code => '500', :message => 'Internal Server Error', :body => fault_authorization_response) - @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response_500)) + response500 = stub(code: '500', message: 'Internal Server Error', body: fault_authorization_response) + @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response500)) assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response @@ -42,8 +42,8 @@ def test_soap_fault_during_authorization assert_nil response.authorization assert_equal 'Server was unable to process request. ---> strPAN should be at least 13 to at most 19 characters in size. Parameter name: strPAN', response.message - assert_equal response_500.code, response.params['http_code'] - assert_equal response_500.message, response.params['http_message'] + assert_equal response500.code, response.params['http_code'] + assert_equal response500.message, response.params['http_message'] end def test_failed_authorization @@ -110,10 +110,10 @@ def test_cvv_result def test_add_swipe_data_with_creditcard @credit_card.track_data = 'Track Data' - options = {:order_id => '1'} + options = { order_id: '1' } stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'Track Data', data end.respond_with(successful_authorization_response) end @@ -121,100 +121,99 @@ def test_add_swipe_data_with_creditcard private def successful_authorization_response - <<-XML - - - - - - 4706382 - 1 - 7/3/2009 2:05:04 AM - APPROVED - VI0100 - Longbob Longsen - 1.00 - 5 - ************4242 - 0 - N - M - 1 - - - - + <<~XML + + + + + + 4706382 + 1 + 7/3/2009 2:05:04 AM + APPROVED + VI0100 + Longbob Longsen + 1.00 + 5 + ************4242 + 0 + N + M + 1 + + + + XML end def fault_authorization_response - <<-XML - - - - - soap:Server - Server was unable to process request. ---> strPAN should be at least 13 to at most 19 characters in size. -Parameter name: strPAN - - - - + <<~XML + + + + + soap:Server + Server was unable to process request. ---> strPAN should be at least 13 to at most 19 characters in size. + Parameter name: strPAN + + + + XML end def failed_authorization_response - <<-XML - - - - - - - 1 - 7/3/2009 3:04:33 AM - FAILED;1014;transaction type not supported by version - - Longbob Longsen - 1.00 - 5 - *********0123 - 0 - - - 1 - - - - + <<~XML + + + + + + + 1 + 7/3/2009 3:04:33 AM + FAILED;1014;transaction type not supported by version + + Longbob Longsen + 1.00 + 5 + *********0123 + 0 + + + 1 + + + + XML end def failed_void_response - <<-XML - - - - - - 4707277 - - 7/3/2009 3:28:38 AM - DECLINED;1012;decline - - - - 3 - - 0 - - - 0 - - - - + <<~XML + + + + + + 4707277 + + 7/3/2009 3:28:38 AM + DECLINED;1012;decline + + + + 3 + + 0 + + + 0 + + + + XML end - end diff --git a/test/unit/gateways/merchant_ware_version_four_test.rb b/test/unit/gateways/merchant_ware_version_four_test.rb index 4119f8f63d9..b51f07a34f7 100644 --- a/test/unit/gateways/merchant_ware_version_four_test.rb +++ b/test/unit/gateways/merchant_ware_version_four_test.rb @@ -3,18 +3,18 @@ class MerchantWareVersionFourTest < Test::Unit::TestCase def setup @gateway = MerchantWareVersionFourGateway.new( - :login => 'login', - :password => 'password', - :name => 'name' - ) + login: 'login', + password: 'password', + name: 'name' + ) @credit_card = credit_card @authorization = '1236564' @amount = 100 @options = { - :order_id => '1', - :billing_address => address + order_id: '1', + billing_address: address } end @@ -31,8 +31,8 @@ def test_successful_authorization end def test_soap_fault_during_authorization - response_400 = stub(:code => '400', :message => 'Bad Request', :body => failed_authorize_response) - @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response_400)) + response400 = stub(code: '400', message: 'Bad Request', body: failed_authorize_response) + @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response400)) assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response @@ -41,8 +41,8 @@ def test_soap_fault_during_authorization assert_nil response.authorization assert_equal 'amount cannot be null. Parameter name: amount', response.message - assert_equal response_400.code, response.params['http_code'] - assert_equal response_400.message, response.params['http_message'] + assert_equal response400.code, response.params['http_code'] + assert_equal response400.message, response.params['http_message'] end def test_failed_authorization @@ -161,212 +161,212 @@ def post_scrubbed end def successful_authorize_response - <<-XML - - - - - - 1.00 - APPROVED - MC0110 - N - - - 0 - M - 0 - - - - 1236564 - 10/10/2008 1:13:55 PM - 7 - - - - + <<~XML + + + + + + 1.00 + APPROVED + MC0110 + N + + + 0 + M + 0 + + + + 1236564 + 10/10/2008 1:13:55 PM + 7 + + + + XML end def failed_authorize_response - <<-XML - - - - - - - - - - - - 0 - - 0 - amount cannot be null. Parameter name: amount - - - - - 0 - - - - + <<~XML + + + + + + + + + + + + 0 + + 0 + amount cannot be null. Parameter name: amount + + + + + 0 + + + + XML end def failed_authorization_response - <<-XML - - - - - - 1.00 - DECLINED;1024;invalid exp date - - - Visa Test Card - ************0019 - 4 - - 1 - - - TT0017 - - 5/15/2013 8:47:14 AM - 5 - - - - + <<~XML + + + + + + 1.00 + DECLINED;1024;invalid exp date + + + Visa Test Card + ************0019 + 4 + + 1 + + + TT0017 + + 5/15/2013 8:47:14 AM + 5 + + + + XML end def successful_void_response - <<-XML - - - - - - - APPROVED - VOID - - - - 0 - - 0 - - - - 266783537 - 7/9/2015 3:13:51 PM - 3 - - - - + <<~XML + + + + + + + APPROVED + VOID + + + + 0 + + 0 + + + + 266783537 + 7/9/2015 3:13:51 PM + 3 + + + + XML end def failed_void_response - <<-XML - - - - - - DECLINED;1019;original transaction id not found - - - - - 0 - - 0 - - - - - 5/15/2013 9:37:04 AM - 3 - - - - + <<~XML + + + + + + DECLINED;1019;original transaction id not found + + + + + 0 + + 0 + + + + + 5/15/2013 9:37:04 AM + 3 + + + + XML end def successful_purchase_using_prior_transaction_response - <<-XML - - - - - - 5.00 - APPROVED - MC0110 - - - - 0 - - 0 - - - - 1236564 - 10/10/2008 1:13:55 PM - 7 - - - - + <<~XML + + + + + + 5.00 + APPROVED + MC0110 + + + + 0 + + 0 + + + + 1236564 + 10/10/2008 1:13:55 PM + 7 + + + + XML end def invalid_credit_card_number_response - <<-XML - - - - - - - - - - - - 0 - - 0 - Invalid card number. - - - - - 0 - - - - + <<~XML + + + + + + + + + + + + 0 + + 0 + Invalid card number. + + + + + 0 + + + + XML end end diff --git a/test/unit/gateways/merchant_warrior_test.rb b/test/unit/gateways/merchant_warrior_test.rb index 2d718e7da74..7838382d861 100644 --- a/test/unit/gateways/merchant_warrior_test.rb +++ b/test/unit/gateways/merchant_warrior_test.rb @@ -5,10 +5,10 @@ class MerchantWarriorTest < Test::Unit::TestCase def setup @gateway = MerchantWarriorGateway.new( - :merchant_uuid => '4e922de8c2a4c', - :api_key => 'g6jrxa9o', - :api_passphrase => 'vp4ujoem' - ) + merchant_uuid: '4e922de8c2a4c', + api_key: 'g6jrxa9o', + api_passphrase: 'vp4ujoem' + ) @credit_card = credit_card @success_amount = 10000 @@ -16,11 +16,30 @@ def setup @failure_amount = 10033 @options = { - :address => address, - :transaction_product => 'TestProduct' + address: address, + transaction_product: 'TestProduct' } end + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert response = @gateway.authorize(@success_amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction approved', response.message + assert response.test? + assert_equal '1336-20be3569-b600-11e6-b9c3-005056b209e0', response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(nil) + + assert response = @gateway.authorize(@success_amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid gateway response', response.message + assert response.test? + end + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -61,13 +80,33 @@ def test_failed_refund assert_nil response.authorization end + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert response = @gateway.void(@transaction_id, amount: @success_amount) + assert_success response + assert_equal 'Transaction approved', response.message + assert response.test? + assert_equal '30-d4d19f4-db17-11df-9322-0022198101cd', response.authorization + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert response = @gateway.void(@transaction_id, amount: @success_amount) + assert_failure response + assert_equal 'MW -016:transactionID has already been reversed', response.message + assert response.test? + assert_nil response.authorization + end + def test_successful_store @credit_card.month = '2' @credit_card.year = '2005' store = stub_comms do @gateway.store(@credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/cardExpiryMonth=02\b/, data) assert_match(/cardExpiryYear=05\b/, data) end.respond_with(successful_store_response) @@ -84,7 +123,7 @@ def test_scrub_name stub_comms do @gateway.purchase(@success_amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/customerName=Ren\+\+Stimpy/, data) assert_match(/paymentCardName=Chars\+Merchant-Warrior\+Dont\+Like\+\+More\.\+\+Here/, data) end.respond_with(successful_purchase_response) @@ -105,7 +144,7 @@ def test_address stub_comms do @gateway.purchase(@success_amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/customerName=Bat\+Man/, data) assert_match(/customerCountry=US/, data) assert_match(/customerState=NY/, data) @@ -126,7 +165,7 @@ def test_address_without_state stub_comms do @gateway.purchase(@success_amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/customerState=N%2FA/, data) end.respond_with(successful_purchase_response) end @@ -134,11 +173,127 @@ def test_address_without_state def test_orderid_truncated stub_comms do @gateway.purchase(@success_amount, @credit_card, order_id: 'ThisIsQuiteALongDescriptionWithLotsOfChars') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/transactionProduct=ThisIsQuiteALongDescriptionWithLot&/, data) end.respond_with(successful_purchase_response) end + def test_authorize_recurring_flag_absent + stub_comms do + @gateway.authorize(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/recurringFlag&/, data) + end.respond_with(successful_authorize_response) + end + + def test_authorize_recurring_flag_present + recurring_flag = 1 + + stub_comms do + @gateway.authorize(@success_amount, @credit_card, recurring_flag: recurring_flag) + end.check_request do |_endpoint, data, _headers| + assert_match(/recurringFlag=#{recurring_flag}&/, data) + end.respond_with(successful_authorize_response) + end + + def test_purchase_recurring_flag_absent + stub_comms do + @gateway.purchase(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/recurringFlag&/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_recurring_flag_present + recurring_flag = 1 + + stub_comms do + @gateway.purchase(@success_amount, @credit_card, recurring_flag: recurring_flag) + end.check_request do |_endpoint, data, _headers| + assert_match(/recurringFlag=#{recurring_flag}&/, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_with_soft_descriptor_absent + stub_comms do + @gateway.authorize(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/descriptorName&/, data) + assert_not_match(/descriptorCity&/, data) + assert_not_match(/descriptorState&/, data) + end.respond_with(successful_authorize_response) + end + + def test_authorize_with_soft_descriptor_present + stub_comms do + @gateway.authorize(@success_amount, @credit_card, soft_descriptor_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/descriptorName=FOO%2ATest&/, data) + assert_match(/descriptorCity=Melbourne&/, data) + assert_match(/descriptorState=VIC&/, data) + end.respond_with(successful_authorize_response) + end + + def test_purchase_with_soft_descriptor_absent + stub_comms do + @gateway.purchase(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/descriptorName&/, data) + assert_not_match(/descriptorCity&/, data) + assert_not_match(/descriptorState&/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_with_soft_descriptor_present + stub_comms do + @gateway.purchase(@success_amount, @credit_card, soft_descriptor_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/descriptorName=FOO%2ATest&/, data) + assert_match(/descriptorCity=Melbourne&/, data) + assert_match(/descriptorState=VIC&/, data) + end.respond_with(successful_purchase_response) + end + + def test_capture_with_soft_descriptor_absent + stub_comms do + @gateway.capture(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/descriptorName&/, data) + assert_not_match(/descriptorCity&/, data) + assert_not_match(/descriptorState&/, data) + end.respond_with(successful_capture_response) + end + + def test_capture_with_soft_descriptor_present + stub_comms do + @gateway.capture(@success_amount, @credit_card, soft_descriptor_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/descriptorName=FOO%2ATest&/, data) + assert_match(/descriptorCity=Melbourne&/, data) + assert_match(/descriptorState=VIC&/, data) + end.respond_with(successful_capture_response) + end + + def test_refund_with_soft_descriptor_absent + stub_comms do + @gateway.refund(@success_amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_not_match(/descriptorName&/, data) + assert_not_match(/descriptorCity&/, data) + assert_not_match(/descriptorState&/, data) + end.respond_with(successful_refund_response) + end + + def test_refund_with_soft_descriptor_present + stub_comms do + @gateway.refund(@success_amount, @credit_card, soft_descriptor_options) + end.check_request do |_endpoint, data, _headers| + assert_match(/descriptorName=FOO%2ATest&/, data) + assert_match(/descriptorCity=Melbourne&/, data) + assert_match(/descriptorState=VIC&/, data) + end.respond_with(successful_refund_response) + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -147,78 +302,120 @@ def test_scrub private def successful_purchase_response - <<-XML - - - 0 - Transaction approved - 30-98a79008-dae8-11df-9322-0022198101cd - 44639 - Approved - 0 - 2010-10-19 - - - - c0aca5a0d9573322c79cc323d6cc8050 - + <<~XML + + + 0 + Transaction approved + 30-98a79008-dae8-11df-9322-0022198101cd + 44639 + Approved + 0 + 2010-10-19 + + + + c0aca5a0d9573322c79cc323d6cc8050 + XML end def failed_purchase_response - <<-XML - - - 4 - Card has expired - 30-69433444-af1-11df-9322-0022198101cd - 44657 - Expired+Card - 4 - 2010-10-19 - - - - c0aca5a0d9573322c79cc323d6cc8050 - + <<~XML + + + 4 + Card has expired + 30-69433444-af1-11df-9322-0022198101cd + 44657 + Expired+Card + 4 + 2010-10-19 + + + + c0aca5a0d9573322c79cc323d6cc8050 + XML end def successful_refund_response - <<-XML - - - 0 - Transaction approved - 30-d4d19f4-db17-11df-9322-0022198101cd - 44751 - Approved - 0 - 2010-10-19 - + <<~XML + + + 0 + Transaction approved + 30-d4d19f4-db17-11df-9322-0022198101cd + 44751 + Approved + 0 + 2010-10-19 + XML end def failed_refund_response - <<-XML - - - -2 - MW -016:transactionID has already been reversed - + <<~XML + + + -2 + MW -016:transactionID has already been reversed + XML end def successful_store_response - <<-XML - - - 0 - Operation successful - KOCI10023982 - s5KQIxsZuiyvs3Sc - 10023982 - + <<~XML + + + 0 + Operation successful + KOCI10023982 + s5KQIxsZuiyvs3Sc + 10023982 + + XML + end + + def successful_authorize_response + <<~XML + + + 0 + Transaction approved + 1336-20be3569-b600-11e6-b9c3-005056b209e0 + 12345 + 731357421 + 731357421 + Honour with identification + 08 + 2016-11-29 + 512345XXXXXX2346 + 1.00 + mc + 05 + 21 + + + + 65b172551b7d3a0706c0ce5330c98470 + + XML + end + + def successful_capture_response + <<~XML + + + 0 + Transaction approved + 1336-fe4d3be6-b604-11e6-b9c3-005056b209e0 + 731357526 + 731357526 + Approved or completed successfully + 00 + 2016-11-30 + XML end @@ -229,4 +426,12 @@ def pre_scrubbed def post_scrubbed 'transactionAmount=1.00&transactionCurrency=AUD&hash=adb50f6ff360f861e6f525e8daae76b5&transactionProduct=98fc25d40a47f3d24da460c0ca307c&customerName=Longbob+Longsen&customerCountry=AU&customerState=Queensland&customerCity=Brisbane&customerAddress=123+test+st&customerPostCode=4000&customerIP=&customerPhone=&customerEmail=&paymentCardNumber=[FILTERED]&paymentCardName=Longbob+Longsen&paymentCardExpiry=0520&paymentCardCSC=[FILTERED]&merchantUUID=51f7da294af8f&apiKey=[FILTERED]&method=processCard' end + + def soft_descriptor_options + { + descriptor_name: 'FOO*Test', + descriptor_city: 'Melbourne', + descriptor_state: 'VIC' + } + end end diff --git a/test/unit/gateways/mercury_test.rb b/test/unit/gateways/mercury_test.rb index e7d298f52b4..d9b2e8d9cd0 100644 --- a/test/unit/gateways/mercury_test.rb +++ b/test/unit/gateways/mercury_test.rb @@ -9,18 +9,18 @@ def setup @gateway = MercuryGateway.new(fixtures(:mercury)) @amount = 100 - @credit_card = credit_card('5499990123456781', :brand => 'master') + @credit_card = credit_card('5499990123456781', brand: 'master') @declined_card = credit_card('4000300011112220') @options = { - :order_id => 'c111111111.1' + order_id: 'c111111111.1' } end def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/InvoiceNo>c111111111.1OneTime/, data) assert_match(/RecordNo>RecordNumberRequested/, data) @@ -36,7 +36,7 @@ def test_successful_purchase def test_successful_purchase_with_allow_partial_auth response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(allow_partial_auth: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/PartialAuth>Allow#{Regexp.escape(track_data)}<\/Track1>/, data) end.respond_with(successful_purchase_response) @@ -82,7 +82,7 @@ def test_card_present_with_track_2_data @credit_card.track_data = track_data response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{Regexp.escape(stripped_track_data)}<\/Track2>/, data) end.respond_with(successful_purchase_response) @@ -92,11 +92,11 @@ def test_card_present_with_track_2_data def test_card_present_with_max_length_track_1_data track_data = '%B373953192351004^CARDUSER/JOHN^200910100000019301000000877000000930001234567?' - stripped_data = 'B373953192351004^CARDUSER/JOHN^200910100000019301000000877000000930001234567' + stripped_data = 'B373953192351004^CARDUSER/JOHN^200910100000019301000000877000000930001234567' @credit_card.track_data = track_data response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{Regexp.escape(stripped_data)}<\/Track1>/, data) end.respond_with(successful_purchase_response) @@ -109,7 +109,7 @@ def test_card_present_with_invalid_data @credit_card.track_data = track_data response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{Regexp.escape(track_data)}<\/Track1>/, data) end.respond_with(successful_purchase_response) @@ -125,88 +125,88 @@ def test_transcript_scrubbing private def successful_purchase_response - <<-RESPONSE - - - - Processor - 000000 - Approved - AP* - - - - 595901 - 5499990123456781 - 0813 - M/C - Sale - 000011 - Captured - 0194 - 1 - Y - M - 999 - LM Integration (Ruby) - - 1.00 - 1.00 - - KbMCC0742510421 - |17|410100700000 - - - + <<~RESPONSE + + + + Processor + 000000 + Approved + AP* + + + + 595901 + 5499990123456781 + 0813 + M/C + Sale + 000011 + Captured + 0194 + 1 + Y + M + 999 + LM Integration (Ruby) + + 1.00 + 1.00 + + KbMCC0742510421 + |17|410100700000 + + + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - - Server - 000000 - Error - No Live Cards on Test Merchant ID Allowed. - - - - + <<~RESPONSE + + + + Server + 000000 + Error + No Live Cards on Test Merchant ID Allowed. + + + + RESPONSE end def successful_refund_response - <<-RESPONSE - - - - Processor - 000000 - Approved - AP - - - - 595901 - 5499990123456781 - 0813 - M/C - VoidSale - VOIDED - Captured - 0568 - 123 - 999 - - 1.00 - 1.00 - - K - - - + <<~RESPONSE + + + + Processor + 000000 + Approved + AP + + + + 595901 + 5499990123456781 + 0813 + M/C + VoidSale + VOIDED + Captured + 0568 + 123 + 999 + + 1.00 + 1.00 + + K + + + RESPONSE end diff --git a/test/unit/gateways/metrics_global_test.rb b/test/unit/gateways/metrics_global_test.rb index 0184ddfbeff..b8c2d7072bc 100644 --- a/test/unit/gateways/metrics_global_test.rb +++ b/test/unit/gateways/metrics_global_test.rb @@ -1,15 +1,16 @@ require 'test_helper' +require 'active_support/core_ext/kernel/singleton_class' class MetricsGlobalTest < Test::Unit::TestCase include CommStub def setup @gateway = ActiveMerchant::Billing::MetricsGlobalGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @amount = 100 - @credit_card = credit_card('4111111111111111', :verification_value => '999') + @credit_card = credit_card('4111111111111111', verification_value: '999') @subscription_id = '100748' @subscription_status = 'active' end @@ -44,9 +45,9 @@ def test_failed_authorization def test_add_address_outsite_north_america result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => ''}) + @gateway.send(:add_address, result, billing_address: { address1: '164 Waverley Street', country: 'DE', state: '' }) - assert_equal ['address', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + assert_equal %w[address city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'n/a', result[:state] assert_equal '164 Waverley Street', result[:address] assert_equal 'DE', result[:country] @@ -55,9 +56,9 @@ def test_add_address_outsite_north_america def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) + @gateway.send(:add_address, result, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO' }) - assert_equal ['address', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + assert_equal %w[address city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'CO', result[:state] assert_equal '164 Waverley Street', result[:address] assert_equal 'US', result[:country] @@ -65,13 +66,13 @@ def test_add_address def test_add_invoice result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001') + @gateway.send(:add_invoice, result, order_id: '#1001') assert_equal '#1001', result[:invoice_num] end def test_add_description result = {} - @gateway.send(:add_invoice, result, :description => 'My Purchase is great') + @gateway.send(:add_invoice, result, description: 'My Purchase is great') assert_equal 'My Purchase is great', result[:description] end @@ -90,7 +91,7 @@ def test_add_duplicate_window_with_duplicate_window end def test_purchase_is_valid_csv - params = { :amount => '1.01' } + params = { amount: '1.01' } @gateway.send(:add_creditcard, params, @credit_card) @@ -100,7 +101,7 @@ def test_purchase_is_valid_csv def test_purchase_meets_minimum_requirements params = { - :amount => '1.01', + amount: '1.01' } @gateway.send(:add_creditcard, params, @credit_card) @@ -113,15 +114,15 @@ def test_purchase_meets_minimum_requirements def test_successful_refund @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) + assert response = @gateway.refund(@amount, '123456789', card_number: @credit_card.number) assert_success response assert_equal 'This transaction has been approved', response.message end def test_refund_passing_extra_info response = stub_comms do - @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => 'Bob', :last_name => 'Smith', :zip => '12345') - end.check_request do |endpoint, data, headers| + @gateway.refund(50, '123456789', card_number: @credit_card.number, first_name: 'Bob', last_name: 'Smith', zip: '12345') + end.check_request do |_endpoint, data, _headers| assert_match(/x_first_name=Bob/, data) assert_match(/x_last_name=Smith/, data) assert_match(/x_zip=12345/, data) @@ -132,7 +133,7 @@ def test_refund_passing_extra_info def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) - assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) + assert response = @gateway.refund(@amount, '123456789', card_number: @credit_card.number) assert_failure response assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message end @@ -140,7 +141,7 @@ def test_failed_refund def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_purchase_response) assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do - assert response = @gateway.credit(@amount, '123456789', :card_number => @credit_card.number) + assert response = @gateway.credit(@amount, '123456789', card_number: @credit_card.number) assert_success response assert_equal 'This transaction has been approved', response.message end @@ -151,7 +152,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], MetricsGlobalGateway.supported_cardtypes + assert_equal %i[visa master american_express discover diners_club jcb], MetricsGlobalGateway.supported_cardtypes end def test_failure_without_response_reason_text @@ -188,16 +189,16 @@ def test_message_from public :message_from } result = { - :response_code => 2, - :card_code => 'N', - :avs_result_code => 'A', - :response_reason_code => '27', - :response_reason_text => 'Failure.', + response_code: 2, + card_code: 'N', + avs_result_code: 'A', + response_reason_code: '27', + response_reason_text: 'Failure.' } assert_equal 'CVV does not match', @gateway.message_from(result) result[:card_code] = 'M' - assert_equal 'Street address matches, but 5-digit and 9-digit postal code do not match.', @gateway.message_from(result) + assert_equal 'Street address matches, but postal code does not match.', @gateway.message_from(result) result[:response_reason_code] = '22' assert_equal 'Failure', @gateway.message_from(result) diff --git a/test/unit/gateways/micropayment_test.rb b/test/unit/gateways/micropayment_test.rb index 5e63d8f0fa3..ca773b448ec 100644 --- a/test/unit/gateways/micropayment_test.rb +++ b/test/unit/gateways/micropayment_test.rb @@ -15,7 +15,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/accessKey=key/, data) assert_match(/number=#{@credit_card.number}/, data) assert_match(/cvc2=#{@credit_card.verification_value}/, data) @@ -40,7 +40,7 @@ def test_failed_purchase def test_successful_authorize_and_capture response = stub_comms do @gateway.authorize(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/accessKey=key/, data) assert_match(/number=#{@credit_card.number}/, data) assert_match(/cvc2=#{@credit_card.verification_value}/, data) @@ -52,7 +52,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/accessKey=key/, data) assert_match(/transactionId=www.spreedly.com-IDhngtaj81a1/, data) assert_match(/sessionId=CC747358d9598614c3ba1e9a7b82a28318cd81bc/, data) @@ -89,7 +89,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/accessKey=key/, data) assert_match(/transactionId=www.spreedly.com-IDhngtaj81a1/, data) assert_match(/sessionId=CC747358d9598614c3ba1e9a7b82a28318cd81bc/, data) @@ -115,7 +115,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/accessKey=key/, data) assert_match(/transactionId=www.spreedly.com-IDhm7nyju168/, data) assert_match(/sessionId=CCadc2b593ca98bfd730c383582de00faed995b0/, data) diff --git a/test/unit/gateways/migs_test.rb b/test/unit/gateways/migs_test.rb index f6ba3282202..8f5eb3dbe6e 100644 --- a/test/unit/gateways/migs_test.rb +++ b/test/unit/gateways/migs_test.rb @@ -3,18 +3,21 @@ class MigsTest < Test::Unit::TestCase def setup @gateway = MigsGateway.new( - :login => 'login', - :password => 'password', - :secure_hash => '76AF3392002D202A60D0AB5F9D81653C' - ) - + login: 'login', + password: 'password', + secure_hash: '76AF3392002D202A60D0AB5F9D81653C', + advanced_login: 'advlogin', + advanced_password: 'advpass' + ) @credit_card = credit_card @amount = 100 + @authorization = '2070000742' + @tx_source = 'MOTO' @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -41,9 +44,9 @@ def test_unsuccessful_request def test_secure_hash params = { - :MerchantId => 'MER123', - :OrderInfo => 'A48cvE28', - :Amount => 2995 + MerchantId: 'MER123', + OrderInfo: 'A48cvE28', + Amount: 2995 } ordered_values = 'vpc_Amount=2995&vpc_MerchantId=MER123&vpc_OrderInfo=A48cvE28' @gateway.send(:add_secure_hash, params) @@ -74,21 +77,41 @@ def test_scrub assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end + def test_purchase_passes_tx_source + expect_commit_with_tx_source + @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @tx_source)) + end + + def test_capture_passes_tx_source + expect_commit_with_tx_source + @gateway.capture(@amount, @authorization, @options.merge(tx_source: @tx_source)) + end + + def test_refund_passes_tx_source + expect_commit_with_tx_source + @gateway.refund(@amount, @authorization, @options.merge(tx_source: @tx_source)) + end + + def test_void_passes_tx_source + expect_commit_with_tx_source + @gateway.void(@authorization, @options.merge(tx_source: @tx_source)) + end + private # Place raw successful response from gateway here def successful_purchase_response build_response( - :TxnResponseCode => '0', - :TransactionNo => '123456' + TxnResponseCode: '0', + TransactionNo: '123456' ) end # Place raw failed response from gateway here def failed_purchase_response build_response( - :TxnResponseCode => '3', - :TransactionNo => '654321' + TxnResponseCode: '3', + TransactionNo: '654321' ) end @@ -96,55 +119,59 @@ def build_response(options) options.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join('&') end + def expect_commit_with_tx_source + @gateway.expects(:commit).with(has_entries(TxSource: @tx_source)) + end + def pre_scrubbed - <<-EOS -opening connection to migs.mastercard.com.au:443... -opened -starting SSL for migs.mastercard.com.au:443... -SSL established -<- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" -<- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=4987654321098769&vpc_CardSecurityCode=123&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=F1CE6F32&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" --> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" --> "Pragma: no-cache\r\n" --> "Cache-Control: no-cache\r\n" --> "Content-Length: 595\r\n" --> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" --> "Content-Type: text/plain;charset=iso-8859-1\r\n" --> "Connection: close\r\n" --> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" --> "\r\n" -reading 595 bytes... --> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" -read 595 bytes -Conn close - EOS + <<~REQUEST + opening connection to migs.mastercard.com.au:443... + opened + starting SSL for migs.mastercard.com.au:443... + SSL established + <- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" + <- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=4987654321098769&vpc_CardSecurityCode=123&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=F1CE6F32&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" + -> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" + -> "Pragma: no-cache\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Content-Length: 595\r\n" + -> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" + -> "Content-Type: text/plain;charset=iso-8859-1\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" + -> "\r\n" + reading 595 bytes... + -> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" + read 595 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to migs.mastercard.com.au:443... -opened -starting SSL for migs.mastercard.com.au:443... -SSL established -<- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" -<- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=[FILTERED]&vpc_CardSecurityCode=[FILTERED]&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=[FILTERED]&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" --> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" --> "Pragma: no-cache\r\n" --> "Cache-Control: no-cache\r\n" --> "Content-Length: 595\r\n" --> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" --> "Content-Type: text/plain;charset=iso-8859-1\r\n" --> "Connection: close\r\n" --> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" --> "\r\n" -reading 595 bytes... --> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" -read 595 bytes -Conn close - EOS + <<~REQUEST + opening connection to migs.mastercard.com.au:443... + opened + starting SSL for migs.mastercard.com.au:443... + SSL established + <- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" + <- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=[FILTERED]&vpc_CardSecurityCode=[FILTERED]&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=[FILTERED]&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" + -> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" + -> "Pragma: no-cache\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Content-Length: 595\r\n" + -> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" + -> "Content-Type: text/plain;charset=iso-8859-1\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" + -> "\r\n" + reading 595 bytes... + -> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" + read 595 bytes + Conn close + REQUEST end end diff --git a/test/unit/gateways/mit_test.rb b/test/unit/gateways/mit_test.rb new file mode 100644 index 00000000000..4ae7b4932be --- /dev/null +++ b/test/unit/gateways/mit_test.rb @@ -0,0 +1,268 @@ +require 'test_helper' + +class MitTest < Test::Unit::TestCase + def setup + @credentials = { + commerce_id: '147', + user: 'IVCA33721', + api_key: 'IGECPJ0QOJJCEHUI', + key_session: 'CB0DC4887DD1D5CEA205E66EE934E430' + } + @gateway = MitGateway.new(@credentials) + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: '4000000000000002', + verification_value: '183', + month: '01', + year: '2024', + first_name: 'Pedro', + last_name: 'Flores Valdes' + ) + + @amount = 100 + + @options = { + order_id: '7111', + transaction_id: '7111', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_authorize_response) + auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + assert_equal 'approved', auth_response.message + + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, @credit_card, @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(@amount, @credit_card, @options) + + assert_not_equal 'approved', response.message + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_not_equal 'approved', response.message + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, @credit_card, @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(@amount, @credit_card, @options) + + assert_not_equal 'approved', response.message + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, 'testauthorization', @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + response = @gateway.refund(@amount, 'authorizationtest', @options) + assert_failure response + assert_not_equal 'approved', response.message + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + private + + def successful_purchase_response + %( + 2Nvuvo/fotnYnFVFHpNVPAW+U9oJVZ0eiB6jFFPixkAJi9rciTa1YKd2qPl+YybJHQaLgdITH3L+3M1N3xYObd03vZnKdTin3fXFE6B+0jcrjhMuYq1h8TP7tgfYheFVHQY6Kur/N606pCG9NAwQZ2WbpZ3Vf6byfVo7euVCRF8B95zx9ZyAbsohxrXEQpWHqd09z6SduCG2CTQG+ZfXveoJfAroOLpiRoF6KqOsprnxXP6ikhE454PAvAz4WROY51AGtPi35egb80OF69fiHg== + ) + end + + def failed_purchase_response + %{ + VXlw5DmHlP8LGpDmxSsdabtCw5yycFl3Jq3QYcRKbdYWYXKZR4Rv+gEZDqJH1e9Uk9FW43CtJKu4et+x8Wskc3VTOsD1BrNrOgv8EguZ+MhUQsnWeUWwqEGZd9rO4B51qS7Pb69SJb4PWsyOB0fMUBctiduyGF5kaxA2ieoLA9eGfxmoIsfBptpax37PdsaxTTHbHNQXiRkg1c9f9nyAbBzPQFD/Xuf7OOjhbECXq5Ev1OIxT97PqxVh5RQX+KIQ6gZUFVkwWDaiQh/c7KGIgI3UtXCEFxZtxTvNP81l9p6FgZRDfAcYRZfEOLE8LcqtMpW6p6GTsW6EfnvEaZxzy89xFv+RXuYdns1suxYOPb0= + } + end + + def successful_authorize_response + %( + SQOZIaVhhKGAdneX1QpUuA7ZfKNkx1vq39s8o+yLsl2kbMznbkA32/Z5ovA3leZNMHShEqJPAh4AK3BC0qiL7xETKNFv1BozHaLtZlvaPhPKMCrNeWkAdqesNpD0SvSvT7XZRarWRjcMnwGP9zSvuHqz3kaASZt7Oagh+FCssjZvXUoic7XV7owZEkEAvYiXlTfmd6sv0WYbUknMI9igr2MSe6rNBarIAscnhGJF/yW+ng0wR1pGnvtXJqlYbaTYx7urZEPP6GDfO2BeHkkMT46graqjNnQhsPLr2/Nfe6g= + ) + end + + def failed_authorize_response + %{ + VXlw5DmHlP8LGpDmxSsdabtCw5yycFl3Jq3QYcRKbdYWYXKZR4Rv+gEZDqJH1e9Uk9FW43CtJKu4et+x8Wskc3VTOsD1BrNrOgv8EguZ+MhUQsnWeUWwqEGZd9rO4B51qS7Pb69SJb4PWsyOB0fMUBctiduyGF5kaxA2ieoLA9eGfxmoIsfBptpax37PdsaxTTHbHNQXiRkg1c9f9nyAbBzPQFD/Xuf7OOjhbECXq5Ev1OIxT97PqxVh5RQX+KIQ6gZUFVkwWDaiQh/c7KGIgI3UtXCEFxZtxTvNP81l9p6FgZRDfAcYRZfEOLE8LcqtMpW6p6GTsW6EfnvEaZxzy89xFv+RXuYdns1suxYOPb0= + } + end + + def successful_capture_response + %( + 2Nvuvo/fotnYnFVFHpNVPAW+U9oJVZ0eiB6jFFPixkAJi9rciTa1YKd2qPl+YybJHQaLgdITH3L+3M1N3xYObd03vZnKdTin3fXFE6B+0jcrjhMuYq1h8TP7tgfYheFVHQY6Kur/N606pCG9NAwQZ2WbpZ3Vf6byfVo7euVCRF8B95zx9ZyAbsohxrXEQpWHqd09z6SduCG2CTQG+ZfXveoJfAroOLpiRoF6KqOsprnxXP6ikhE454PAvAz4WROY51AGtPi35egb80OF69fiHg== + ) + end + + def failed_capture_response + %{ + VXlw5DmHlP8LGpDmxSsdabtCw5yycFl3Jq3QYcRKbdYWYXKZR4Rv+gEZDqJH1e9Uk9FW43CtJKu4et+x8Wskc3VTOsD1BrNrOgv8EguZ+MhUQsnWeUWwqEGZd9rO4B51qS7Pb69SJb4PWsyOB0fMUBctiduyGF5kaxA2ieoLA9eGfxmoIsfBptpax37PdsaxTTHbHNQXiRkg1c9f9nyAbBzPQFD/Xuf7OOjhbECXq5Ev1OIxT97PqxVh5RQX+KIQ6gZUFVkwWDaiQh/c7KGIgI3UtXCEFxZtxTvNP81l9p6FgZRDfAcYRZfEOLE8LcqtMpW6p6GTsW6EfnvEaZxzy89xFv+RXuYdns1suxYOPb0= + } + end + + def successful_refund_response + %{ + yn3RJK3KwXedXShm/1DaCED1QA6lpFzVGORcTfHCviFTwSUxGduuhCZEWPTaiksvCpTMwMFBrdQO/2THtJ/+GH2+1vIdV5QYFbLU4QCD5G33Le1x1WAU72e8o7arPdBYapZqhiIjz1NwEPOnir2XGV1AXNAuj8OjDj+YQ42cH+iUxWYU6ROaVUhApWqgVhAWz9pZqPTeDssj3dzO/iAM9z7mlhxYnqDlHdWpPpNFdk34jPb//+xCfg13HLdplqBaeDPVuWaRiEG/pc3ttETjYw== + } + end + + def failed_refund_response + %{ + i+HTzdwnXqLEh9EPAyP54p6DyHOeKlt0lZqdbNy5paxwAAUSTciZzUGgFb8t8eXakdlZXWtlFHLBIRuiUFUyLZSB/btqldzuQPc+I8dEsz5F5yL4DdI/FFtAChYEHoumAvrth9uiBeEyGoAKL9etHOTPed2RFCcZYpsA8Gc3P1LterAeZwWX91LS0PzL6mKcsSUkkLCeT2UBJCg+N7a7ipop+U9jGsGBzKMhpZH6DyjZleBfh7j8ICbwMNClI8ixSYDQvmE5/fP7AZtL4oszdVAnlALhjL0Ld1MBLmeiTIiGkycZB0dKbrN5fAS0/mpbOm64wSF3ZAM/geKaXA6jmQ== + } + end + + def successful_void_response + %{ + yn3RJK3KwXedXShm/1DaCED1QA6lpFzVGORcTfHCviFTwSUxGduuhCZEWPTaiksvCpTMwMFBrdQO/2THtJ/+GH2+1vIdV5QYFbLU4QCD5G33Le1x1WAU72e8o7arPdBYapZqhiIjz1NwEPOnir2XGV1AXNAuj8OjDj+YQ42cH+iUxWYU6ROaVUhApWqgVhAWz9pZqPTeDssj3dzO/iAM9z7mlhxYnqDlHdWpPpNFdk34jPb//+xCfg13HLdplqBaeDPVuWaRiEG/pc3ttETjYw== + } + end + + def failed_void_response + %{ + i+HTzdwnXqLEh9EPAyP54p6DyHOeKlt0lZqdbNy5paxwAAUSTciZzUGgFb8t8eXakdlZXWtlFHLBIRuiUFUyLZSB/btqldzuQPc+I8dEsz5F5yL4DdI/FFtAChYEHoumAvrth9uiBeEyGoAKL9etHOTPed2RFCcZYpsA8Gc3P1LterAeZwWX91LS0PzL6mKcsSUkkLCeT2UBJCg+N7a7ipop+U9jGsGBzKMhpZH6DyjZleBfh7j8ICbwMNClI8ixSYDQvmE5/fP7AZtL4oszdVAnlALhjL0Ld1MBLmeiTIiGkycZB0dKbrN5fAS0/mpbOm64wSF3ZAM/geKaXA6jmQ== + } + 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 + 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" + 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/modern_payments_cim_test.rb b/test/unit/gateways/modern_payments_cim_test.rb index 0e6aa87a998..e126c3a4a54 100644 --- a/test/unit/gateways/modern_payments_cim_test.rb +++ b/test/unit/gateways/modern_payments_cim_test.rb @@ -5,17 +5,17 @@ def setup Base.mode = :test @gateway = ModernPaymentsCimGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -83,80 +83,80 @@ def test_soap_fault_response private def successful_create_customer_response - <<-XML - - - - - 6677348 - - - + <<~XML + + + + + 6677348 + + + XML end def successful_modify_customer_credit_card_response - <<-XML - - - - - 6677757 - - - + <<~XML + + + + + 6677757 + + + XML end def unsuccessful_credit_card_authorization_response - <<-XML - - - - - - 999 - - - - RESPONSECODE=D,AUTHCODE=,DECLINEREASON.1.TAG=,DECLINEREASON.1.ERRORCLASS=card declined,DECLINEREASON.1.PARAM1=05:DECLINE,DECLINEREASON.1.PARAM2=The authorization is declined,DECLINEREASON.1.MESSAGE=Card was declined: The authorization is declined,AVSDATA - RESPONSECODE=D,AUTHCODE=,DECLINEREASON.1.TAG=,DECLINEREASON.1.ERRORCLASS=card declined,DECLINEREASON.1.PARAM1=05:DECLINE,DECLINEREASON.1.PARAM2=The authorization is declined,DECLINEREASON.1.MESSAGE=Card was declined: The authorization is declined,AVSDATA - false - - - - + <<~XML + + + + + + 999 + + + + RESPONSECODE=D,AUTHCODE=,DECLINEREASON.1.TAG=,DECLINEREASON.1.ERRORCLASS=card declined,DECLINEREASON.1.PARAM1=05:DECLINE,DECLINEREASON.1.PARAM2=The authorization is declined,DECLINEREASON.1.MESSAGE=Card was declined: The authorization is declined,AVSDATA + RESPONSECODE=D,AUTHCODE=,DECLINEREASON.1.TAG=,DECLINEREASON.1.ERRORCLASS=card declined,DECLINEREASON.1.PARAM1=05:DECLINE,DECLINEREASON.1.PARAM2=The authorization is declined,DECLINEREASON.1.MESSAGE=Card was declined: The authorization is declined,AVSDATA + false + + + + XML end def soap_fault_response - <<-XML - - - - - soap:Client - System.Web.Services.Protocols.SoapException: Server did not recognize the value of HTTP Header SOAPAction: h heheheh http://secure.modpay.com:81/ws/CreateCustomer. - at System.Web.Services.Protocols.Soap11ServerProtocolHelper.RouteRequest() - at System.Web.Services.Protocols.SoapServerProtocol.RouteRequest(SoapServerMessage message) - at System.Web.Services.Protocols.SoapServerProtocol.Initialize() - at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) - - - - + <<~XML + + + + + soap:Client + System.Web.Services.Protocols.SoapException: Server did not recognize the value of HTTP Header SOAPAction: h heheheh http://secure.modpay.com:81/ws/CreateCustomer. + at System.Web.Services.Protocols.Soap11ServerProtocolHelper.RouteRequest() + at System.Web.Services.Protocols.SoapServerProtocol.RouteRequest(SoapServerMessage message) + at System.Web.Services.Protocols.SoapServerProtocol.Initialize() + at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) + + + + XML end def successful_authorization_response - <<-XML -18713505020411ZC00 17093294 -RESPONSECODE=A -AUTHCODE=020411 -DECLINEREASON= -AVSDATA=Z -TRANSID=C00 17093294 -Approvedtrue + <<~XML + 18713505020411ZC00 17093294 + RESPONSECODE=A + AUTHCODE=020411 + DECLINEREASON= + AVSDATA=Z + TRANSID=C00 17093294 + Approvedtrue XML end end diff --git a/test/unit/gateways/moka_test.rb b/test/unit/gateways/moka_test.rb new file mode 100644 index 00000000000..dc5bbc04fb7 --- /dev/null +++ b/test/unit/gateways/moka_test.rb @@ -0,0 +1,356 @@ +require 'test_helper' + +class MokaTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = MokaGateway.new(dealer_code: '123', username: 'username', password: 'password') + @credit_card = credit_card + @amount = 100 + + @options = { + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c', response.authorization + assert response.test? + end + + def test_failed_purchase_with_top_level_error + @gateway.expects(:ssl_post).returns(failed_response_with_top_level_error) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'PaymentDealer.DoDirectPayment.InvalidRequest', response.error_code + assert_equal 'PaymentDealer.DoDirectPayment.InvalidRequest', response.message + end + + def test_failed_purchase_with_nested_error + @gateway.expects(:ssl_post).returns(failed_response_with_nested_error) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal 'General error', response.error_code + assert_equal 'Genel Hata(Geçersiz kart numarası)', 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_equal 1, JSON.parse(data)['PaymentDealerRequest']['IsPreAuth'] + end.respond_with(successful_response) + assert_success response + + assert_equal 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_response_with_top_level_error) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_response) + + response = @gateway.capture(@amount, 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c', @options) + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'wrong-authorization', @options) + assert_failure response + assert_equal 'PaymentDealer.DoCapture.PaymentNotFound', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(0, 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c') + assert_success response + end + + def test_successful_partial_refund + stub_comms do + @gateway.refund(50, 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c') + end.check_request do |_endpoint, data, _headers| + assert_equal '0.50', JSON.parse(data)['PaymentDealerRequest']['Amount'] + end.respond_with(successful_refund_response) + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(0, '') + assert_failure response + assert_equal 'PaymentDealer.DoCreateRefundRequest.OtherTrxCodeOrVirtualPosOrderIdMustGiven', response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_response) + + response = @gateway.void('Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c') + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'PaymentDealer.DoVoid.InvalidRequest', response.error_code + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_response) + assert_success response + assert_equal 'Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_response_with_top_level_error) + assert_failure response + assert_equal 'PaymentDealer.DoDirectPayment.InvalidRequest', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_buyer_information_is_passed + options = @options.merge({ + billing_address: address, + email: 'safiye.ali@example.com' + }) + + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + buyer_info = JSON.parse(data)['PaymentDealerRequest']['BuyerInformation'] + assert_equal buyer_info['BuyerFullName'], [@credit_card.first_name, @credit_card.last_name].join(' ') + assert_equal buyer_info['BuyerEmail'], 'safiye.ali@example.com' + assert_equal buyer_info['BuyerAddress'], options[:billing_address][:address1] + assert_equal buyer_info['BuyerGsmNumber'], options[:billing_address][:phone] + end.respond_with(successful_response) + end + + def test_basket_product_is_passed + options = @options.merge({ + basket_product: [ + { + product_id: 333, + product_code: '0173', + unit_price: 19900, + quantity: 1 + }, + { + product_id: 281, + product_code: '38', + unit_price: 5000, + quantity: 1 + } + ] + }) + + stub_comms do + @gateway.authorize(24900, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + basket = JSON.parse(data)['PaymentDealerRequest']['BasketProduct'] + basket.each_with_index do |product, i| + assert_equal product['ProductId'], options[:basket_product][i][:product_id] + assert_equal product['ProductCode'], options[:basket_product][i][:product_code] + assert_equal product['UnitPrice'], (sprintf '%.2f', options[:basket_product][i][:unit_price] / 100) + assert_equal product['Quantity'], options[:basket_product][i][:quantity] + end + end.respond_with(successful_response) + end + + def test_additional_auth_purchase_fields_are_passed + options = @options.merge({ + description: 'custom purchase', + installment_number: 12, + sub_merchant_name: 'testco', + is_pool_payment: 1 + }) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + response = JSON.parse(data) + assert_equal response['PaymentDealerRequest']['Description'], 'custom purchase' + assert_equal response['PaymentDealerRequest']['InstallmentNumber'], 12 + assert_equal response['SubMerchantName'], 'testco' + assert_equal response['IsPoolPayment'], 1 + end.respond_with(successful_response) + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to service.testmoka.com:443... + opened + starting SSL for service.testmoka.com:443... + SSL established + <- "POST /PaymentDealer/DoDirectPayment 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: service.testmoka.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"PaymentDealerRequest\":{\"Amount\":\"1.00\",\"Currency\":\"TL\",\"CardHolderFullName\":\"Longbob Longsen\",\"CardNumber\":\"5269111122223332\",\"ExpMonth\":10,\"ExpYear\":2024,\"CvcNumber\":\"123\",\"IsPreAuth\":0,\"BuyerInformation\":{\"BuyerFullName\":\"Longbob Longsen\"}},\"PaymentDealerAuthentication\":{\"DealerCode\":\"1731\",\"Username\":\"TestMoka2\",\"Password\":\"HYSYHDS8DU8HU\",\"CheckKey\":\"1c1cccfe19b782415c207f1d66f97889cf11ed6d1e1ad6f585e5fe70b6f5da90\"},\"IsPoolPayment\":0}" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Expires: -1\r\n" + -> "Server: Microsoft-IIS/10.0\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Headers: *\r\n" + -> "Date: Mon, 16 Aug 2021 20:33:17 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 188\r\n" + -> "\r\n" + reading 188 bytes... + -> "{\"Data\":{\"IsSuccessful\":true,\"ResultCode\":\"\",\"ResultMessage\":\"\",\"VirtualPosOrderId\":\"Test-e8345c66-b614-4490-83ce-7be510f22312\"},\"ResultCode\":\"Success\",\"ResultMessage\":\"\",\"Exception\":null}" + read 188 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to service.testmoka.com:443... + opened + starting SSL for service.testmoka.com:443... + SSL established + <- "POST /PaymentDealer/DoDirectPayment 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: service.testmoka.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"PaymentDealerRequest\":{\"Amount\":\"1.00\",\"Currency\":\"TL\",\"CardHolderFullName\":\"Longbob Longsen\",\"CardNumber\":\"[FILTERED]\",\"ExpMonth\":10,\"ExpYear\":2024,\"CvcNumber\":\"[FILTERED]\",\"IsPreAuth\":0,\"BuyerInformation\":{\"BuyerFullName\":\"Longbob Longsen\"}},\"PaymentDealerAuthentication\":{\"DealerCode\":\"[FILTERED]\",\"Username\":\"[FILTERED]\",\"Password\":\"[FILTERED]\",\"CheckKey\":\"[FILTERED]\"},\"IsPoolPayment\":0}" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Expires: -1\r\n" + -> "Server: Microsoft-IIS/10.0\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Headers: *\r\n" + -> "Date: Mon, 16 Aug 2021 20:33:17 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 188\r\n" + -> "\r\n" + reading 188 bytes... + -> "{\"Data\":{\"IsSuccessful\":true,\"ResultCode\":\"\",\"ResultMessage\":\"\",\"VirtualPosOrderId\":\"Test-e8345c66-b614-4490-83ce-7be510f22312\"},\"ResultCode\":\"Success\",\"ResultMessage\":\"\",\"Exception\":null}" + read 188 bytes + Conn close + POST_SCRUBBED + end + + def successful_response + <<-RESPONSE + { + "Data": { + "IsSuccessful": true, + "ResultCode": "", + "ResultMessage": "", + "VirtualPosOrderId": "Test-9732c2ce-08d9-4ff6-a89f-bd3fa345811c" + }, + "ResultCode": "Success", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "Data": { + "IsSuccessful": true, + "ResultCode": "", + "ResultMessage": "", + "RefundRequestId": 2320 + }, + "ResultCode": "Success", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def failed_response_with_top_level_error + <<-RESPONSE + { + "Data": null, + "ResultCode": "PaymentDealer.DoDirectPayment.InvalidRequest", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def failed_response_with_nested_error + <<-RESPONSE + { + "Data": { + "IsSuccessful": false, + "ResultCode": "000", + "ResultMessage": "Genel Hata(Geçersiz kart numarası)", + "VirtualPosOrderId": "" + }, + "ResultCode": "Success", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "Data": null, + "ResultCode": "PaymentDealer.DoCapture.PaymentNotFound", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "Data": null, + "ResultCode": "PaymentDealer.DoCreateRefundRequest.OtherTrxCodeOrVirtualPosOrderIdMustGiven", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "Data": null, + "ResultCode": "PaymentDealer.DoVoid.InvalidRequest", + "ResultMessage": "", + "Exception": null + } + RESPONSE + end +end diff --git a/test/unit/gateways/monei_test.rb b/test/unit/gateways/monei_test.rb index 27b70aff1bd..005880124f1 100755 --- a/test/unit/gateways/monei_test.rb +++ b/test/unit/gateways/monei_test.rb @@ -5,10 +5,7 @@ class MoneiTest < Test::Unit::TestCase def setup @gateway = MoneiGateway.new( - :sender_id => 'mother', - :channel_id => 'there is no other', - :login => 'like mother', - :pwd => 'so treat Her right' + fixtures(:monei) ) @credit_card = credit_card @@ -27,7 +24,7 @@ def test_successful_purchase response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal '8a829449488d79090148996c441551fb', response.authorization + assert_equal '067574158f1f42499c31404752d52d06', response.authorization assert response.test? end @@ -130,290 +127,292 @@ def test_failed_verify assert_failure response end + def test_3ds_request + authentication_eci = '05' + authentication_cavv = 'AAACAgSRBklmQCFgMpEGAAAAAAA=' + authentication_xid = 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + + three_d_secure_options = { + eci: authentication_eci, + cavv: authentication_cavv, + xid: authentication_xid + } + options = @options.merge!({ + three_d_secure: three_d_secure_options + }) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"eci\":\"#{authentication_eci}\"/, data) + assert_match(/\"cavv\":\"#{authentication_cavv}\"/, data) + assert_match(/\"xid\":\"#{authentication_xid}\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_sending_cardholder_name + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal @credit_card.name, JSON.parse(data)['paymentMethod']['card']['cardholderName'] + end.respond_with(successful_purchase_response) + end + + def test_sending_browser_info + ip = '77.110.174.153' + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' + lang = 'en' + + @options.merge!({ + ip: ip, + user_agent: user_agent, + lang: lang + }) + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + parsed = JSON.parse(data)['sessionDetails'] + assert_equal ip, parsed['ip'] + assert_equal user_agent, parsed['userAgent'] + assert_equal lang, parsed['lang'] + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrubs_auth_data + assert_equal @gateway.scrub(pre_scrubbed_with_auth), post_scrubbed_with_auth + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + private def successful_purchase_response - return <<-XML - - - - - 7621.0198.1858 - 8a829449488d79090148996c441551fb - 1 - - - - 1.00 - EUR - 7621.0198.1858 DEFAULT Store Purchase - 1.0 - INTERN - 2014-09-21 18:14:42 - - - - 2014-09-21 18:14:42 - ACK - NEW - Successful Processing - Request successfully processed in 'Merchant in Connector Test Mode' - - - - - XML + <<-RESPONSE + { + "id": "067574158f1f42499c31404752d52d06", + "amount": 110, + "currency": "EUR", + "orderId": "1", + "accountId": "00000000-aaaa-bbbb-cccc-dddd123456789", + "status": "SUCCEEDED", + "statusMessage": "Transaction Approved", + "signature": "3dc52e4dbcc15cee5bb03cb7e3ab90708bf8b8a21818c0262ac05ec0c01780d0" + } + RESPONSE end def failed_purchase_response - <<-XML - - - - - 9086.6774.0834 - 8a82944a488d36c101489972b0ee6ace - 1 - - - - 2014-09-21 18:21:43 - NOK - REJECTED_VALIDATION - Account Validation - invalid cc number/brand combination - - - - XML + <<-RESPONSE + { + "status": "error", + "message": "Card number declined by processor" + } + RESPONSE end def successful_authorize_response - <<-XML - - - - - 6853.2944.1442 - 8a82944a488d36c101489976f0cc6b1c - 1 - - - - 1.00 - EUR - 6853.2944.1442 DEFAULT Store Purchase - 1.0 - INTERN - 2014-09-21 18:26:22 - - - - 2014-09-21 18:26:22 - ACK - NEW - Successful Processing - Request successfully processed in 'Merchant in Connector Test Mode' - - - - - XML + <<-RESPONSE + { + "id": "067574158f1f42499c31404752d52d06", + "amount": 110, + "currency": "EUR", + "orderId": "1", + "accountId": "00000000-aaaa-bbbb-cccc-dddd123456789", + "status": "AUTHORIZED", + "statusMessage": "Transaction Approved", + "signature": "3dc52e4dbcc15cee5bb03cb7e3ab90708bf8b8a21818c0262ac05ec0c01780d0" + } + RESPONSE end def failed_authorize_response - <<-XML - - - - - 4727.2856.0290 - 8a829449488d79090148998943a853f6 - 1 - - - - 2014-09-21 18:46:22 - NOK - REJECTED_VALIDATION - Account Validation - invalid cc number/brand combination - - - - XML + <<-RESPONSE + { + "status": "error", + "message": "Card number declined by processor" + } + RESPONSE end def successful_capture_response - <<-XML - - - - - 1269.8369.2962 - 8a82944a488d36c10148998d9b316cc6 - - 8a829449488d79090148998d97f05439 - - - - 1.00 - EUR - 1269.8369.2962 DEFAULT Store Purchase - 1.0 - INTERN - 2014-09-21 18:51:07 - - - - 2014-09-21 18:51:07 - ACK - NEW - Successful Processing - Request successfully processed in 'Merchant in Connector Test Mode' - - - - - XML + <<-RESPONSE + { + "id": "067574158f1f42499c31404752d52d06", + "amount": 110, + "currency": "EUR", + "orderId": "1", + "accountId": "00000000-aaaa-bbbb-cccc-dddd123456789", + "status": "SUCCEEDED", + "statusMessage": "Transaction Approved", + "signature": "3dc52e4dbcc15cee5bb03cb7e3ab90708bf8b8a21818c0262ac05ec0c01780d0" + } + RESPONSE end def failed_capture_response - <<-XML - - - - - 0239.0447.7858 - 8a82944a488d36c10148998fc4b66cfc - - - - - - 2014-09-21 18:53:29 - NOK - REJECTED_VALIDATION - Format Error - invalid Request/Transaction/Payment/Presentation tag (not present or [partially] empty) - - - - XML + <<-RESPONSE + { + "status": "error", + "message": "Card number declined by processor" + } + RESPONSE end def successful_refund_response - <<-XML - - - - - 3009.2986.8450 - 8a829449488d790901489992a493546f - - 8a82944a488d36c101489992a10f6d21 - - - - 1.00 - EUR - 3009.2986.8450 DEFAULT Store Purchase - 1.0 - INTERN - 2014-09-21 18:56:37 - - - - 2014-09-21 18:56:37 - ACK - NEW - Successful Processing - Request successfully processed in 'Merchant in Connector Test Mode' - - - - XML + <<-RESPONSE + { + "id": "067574158f1f42499c31404752d52d06", + "amount": 110, + "currency": "EUR", + "orderId": "1", + "accountId": "00000000-aaaa-bbbb-cccc-dddd123456789", + "status": "REFUNDED", + "statusMessage": "Transaction Approved", + "signature": "3dc52e4dbcc15cee5bb03cb7e3ab90708bf8b8a21818c0262ac05ec0c01780d0" + } + RESPONSE end def failed_refund_response - <<-XML - - - - - 5070.8829.8658 - 8a829449488d790901489994b2c65481 - - - - - - 2014-09-21 18:58:52 - NOK - REJECTED_VALIDATION - Format Error - invalid Request/Transaction/Payment/Presentation tag (not present or [partially] empty) - - - - XML + <<-RESPONSE + { + "status": "error", + "message": "Card number declined by processor" + } + RESPONSE end def successful_void_response - <<-XML - - - - - 4587.6991.6578 - 8a82944a488d36c1014899957fff6d49 - - 8a829449488d7909014899957cb45486 - - - - 1.00 - EUR - 4587.6991.6578 DEFAULT Store Purchase - 1.0 - INTERN - 2014-09-21 18:59:44 - - - - 2014-09-21 18:59:44 - ACK - NEW - Successful Processing - Request successfully processed in 'Merchant in Connector Test Mode' - - - - - XML + <<-RESPONSE + { + "id": "067574158f1f42499c31404752d52d06", + "amount": 110, + "currency": "EUR", + "orderId": "1", + "accountId": "00000000-aaaa-bbbb-cccc-dddd123456789", + "status": "CANCELED", + "statusMessage": "Transaction Approved", + "signature": "3dc52e4dbcc15cee5bb03cb7e3ab90708bf8b8a21818c0262ac05ec0c01780d0" + } + RESPONSE end def failed_void_response - <<-XML - - - - 5843.9770.9986 - 8a829449488d7909014899965cd354b6 - - - - - - 2014-09-21 19:00:41 - NOK - REJECTED_VALIDATION - Reference Error - reversal needs at least one successful transaction of type (CP or DB or RB or PA) - - - - - XML + <<-RESPONSE + { + "status": "error", + "message": "Card number declined by processor" + } + RESPONSE + end + + def pre_scrubbed + <<-PRE_SCRUBBED + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: pk_test_3cb2d54b7ee145fa92d683c01816ad15\r\nUser-Agent: MONEI/Shopify/0.1.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.monei.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"livemode\":\"false\",\"orderId\":\"66e0d04361fb7b401bec3b078744c21e\",\"transactionType\":\"AUTH\",\"description\":\"Store Purchase\",\"amount\":100,\"currency\":\"EUR\",\"paymentMethod\":{\"card\":{\"number\":\"5453010000059675\",\"expMonth\":\"12\",\"expYear\":\"34\",\"cvc\":\"123\"}},\"customer\":{\"email\":\"support@monei.com\",\"name\":\"Jim Smith\"},\"billingDetails\":{\"address\":{\"line1\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"CA\"}},\"sessionDetails\":{}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1069\r\n" + -> "Connection: close\r\n" + -> "Date: Mon, 05 Jul 2021 15:59:36 GMT\r\n" + -> "x-amzn-RequestId: 75b637ff-f230-4522-b6c5-bc5b95495a55\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "x-amz-apigw-id: CAPf6EPXjoEFdxA=\r\n" + -> "X-Amzn-Trace-Id: Root=1-60e32c65-625fbe465afdff1666fa4da9;Sampled=0\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "X-Cache: Miss from cloudfront\r\n" + -> "Via: 1.1 508a0f3451a34ff5e6a3963c94ef304d.cloudfront.net (CloudFront)\r\n" + -> "X-Amz-Cf-Pop: MAD51-C3\r\n" + -> "X-Amz-Cf-Id: SH_5SGGltcCwOgNwn4cnuZAYCa8__JZuUe5lj_Dnvkhigu2yB8M-SQ==\r\n" + -> "\r\n" + reading 1069 bytes... + -> "{\"id\":\"cdc503654e76e29051bce6054e4b4d47dfb63edc\",\"amount\":100,\"currency\":\"EUR\",\"orderId\":\"66e0d04361fb7b401bec3b078744c21e\",\"description\":\"Store Purchase\",\"accountId\":\"00000000-aaaa-bbbb-cccc-dddd123456789\",\"authorizationCode\":\"++++++\",\"livemode\":false,\"status\":\"FAILED\",\"statusCode\":\"E501\",\"statusMessage\":\"Card rejected: invalid card number\",\"customer\":{\"name\":\"Jim Smith\",\"email\":\"support@monei.com\"},\"billingDetails\":{\"address\":{\"zip\":\"K1C2N6\",\"country\":\"CA\",\"state\":\"ON\",\"city\":\"Ottawa\",\"line1\":\"456 My Street\"}},\"sessionDetails\":{\"deviceType\":\"desktop\"},\"traceDetails\":{\"deviceType\":\"desktop\",\"sourceVersion\":\"0.1.0\",\"countryCode\":\"ES\",\"ip\":\"217.61.227.107\",\"userAgent\":\"MONEI/Shopify/0.1.0\",\"source\":\"MONEI/Shopify\",\"lang\":\"en\"},\"createdAt\":1625500773,\"updatedAt\":1625500776,\"paymentMethod\":{\"method\":\"card\",\"card\":{\"country\":\"US\",\"last4\":\"9675\",\"threeDSecure\":false,\"expiration\":2048544000,\"type\":\"credit\",\"brand\":\"mastercard\"}},\"nextAction\":{\"type\":\"COMPLETE\",\"redirectUrl\":\"https://secure.monei.com/payments/cdc503654e76e29051bce6054e4b4d47dfb63edc/receipt\"}}" + read 1069 bytes + Conn close + PRE_SCRUBBED + end + + def pre_scrubbed_with_auth + <<-PRE_SCRUBBED_WITH_AUTH + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: pk_test_3cb2d54b7ee145fa92d683c01816ad15\r\nUser-Agent: MONEI/Shopify/0.1.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.monei.com\r\nContent-Length: 1063\r\n\r\n" + <- "{\"livemode\":\"false\",\"orderId\":\"851925032d391d67e3fbf70b06aa182d\",\"transactionType\":\"SALE\",\"description\":\"Store Purchase\",\"amount\":100,\"currency\":\"EUR\",\"paymentMethod\":{\"card\":{\"number\":\"4444444444444406\",\"expMonth\":\"12\",\"expYear\":\"34\",\"cvc\":\"123\",\"auth\":{\"threeDSVersion\":null,\"eci\":\"05\",\"cavv\":\"AAACAgSRBklmQCFgMpEGAAAAAAA=\",\"dsTransID\":\"7eac9571-3533-4c38-addd-00cf34af6a52\",\"directoryResponse\":null,\"authenticationResponse\":null,\"notificationUrl\":\"https://example.com/notification\"}}},\"customer\":{\"email\":\"support@monei.com\",\"name\":\"Jim Smith\"},\"billingDetails\":{\"address\":{\"line1\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"CA\"}},\"sessionDetails\":{\"ip\":\"77.110.174.153\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\",\"browserAccept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json\",\"browserColorDepth\":\"100\",\"lang\":\"US\",\"browserScreenHeight\":\"1000\",\"browserScreenWidth\":\"500\",\"browserTimezoneOffset\":\"-120\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 253\r\n" + -> "Connection: close\r\n" + -> "Date: Mon, 05 Jul 2021 15:59:59 GMT\r\n" + -> "x-amzn-RequestId: ac5a5ec8-6dd4-4254-a28a-8e9fa652ba90\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "x-amz-apigw-id: CAPj7FFfDoEFXOQ=\r\n" + -> "X-Amzn-Trace-Id: Root=1-60e32c7f-690a46280dc8da307f679795;Sampled=0\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "X-Cache: Miss from cloudfront\r\n" + -> "Via: 1.1 1868e2f5b79bbf25cd21cd4b652be313.cloudfront.net (CloudFront)\r\n" + -> "X-Amz-Cf-Pop: MAD51-C3\r\n" + -> "X-Amz-Cf-Id: RVunC63Qvaswh2fcVB5n0p0BB_1zxbMOx68nuq5m6GKhWUFPpfAgVQ==\r\n" + -> "\r\n" + reading 253 bytes... + -> "{\"id\":\"e1310ab50f7cf1dcf87f1ae75b2ed0fbd2a4d05f\",\"amount\":100,\"currency\":\"EUR\",\"orderId\":\"851925032d391d67e3fbf70b06aa182d\",\"accountId\":\"00000000-aaaa-bbbb-cccc-dddd123456789\",\"liveMode\":false,\"status\":\"SUCCEEDED\",\"statusMessage\":\"Transaction Approved\"}" + read 253 bytes + Conn close + PRE_SCRUBBED_WITH_AUTH + end + + def post_scrubbed + <<-POST_SCRUBBED + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\n\Authorization: [FILTERED]\r\nUser-Agent: MONEI/Shopify/0.1.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.monei.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"livemode\":\"false\",\"orderId\":\"66e0d04361fb7b401bec3b078744c21e\",\"transactionType\":\"AUTH\",\"description\":\"Store Purchase\",\"amount\":100,\"currency\":\"EUR\",\"paymentMethod\":{\"card\":{\"number\":\"[FILTERED]\",\"expMonth\":\"12\",\"expYear\":\"34\",\"cvc\":\"[FILTERED]\"}},\"customer\":{\"email\":\"support@monei.com\",\"name\":\"Jim Smith\"},\"billingDetails\":{\"address\":{\"line1\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"CA\"}},\"sessionDetails\":{}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1069\r\n" + -> "Connection: close\r\n" + -> "Date: Mon, 05 Jul 2021 15:59:36 GMT\r\n" + -> "x-amzn-RequestId: 75b637ff-f230-4522-b6c5-bc5b95495a55\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "x-amz-apigw-id: CAPf6EPXjoEFdxA=\r\n" + -> "X-Amzn-Trace-Id: Root=1-60e32c65-625fbe465afdff1666fa4da9;Sampled=0\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "X-Cache: Miss from cloudfront\r\n" + -> "Via: 1.1 508a0f3451a34ff5e6a3963c94ef304d.cloudfront.net (CloudFront)\r\n" + -> "X-Amz-Cf-Pop: MAD51-C3\r\n" + -> "X-Amz-Cf-Id: SH_5SGGltcCwOgNwn4cnuZAYCa8__JZuUe5lj_Dnvkhigu2yB8M-SQ==\r\n" + -> "\r\n" + reading 1069 bytes... + -> "{\"id\":\"cdc503654e76e29051bce6054e4b4d47dfb63edc\",\"amount\":100,\"currency\":\"EUR\",\"orderId\":\"66e0d04361fb7b401bec3b078744c21e\",\"description\":\"Store Purchase\",\"accountId\":\"00000000-aaaa-bbbb-cccc-dddd123456789\",\"authorizationCode\":\"++++++\",\"livemode\":false,\"status\":\"FAILED\",\"statusCode\":\"E501\",\"statusMessage\":\"Card rejected: invalid card number\",\"customer\":{\"name\":\"Jim Smith\",\"email\":\"support@monei.com\"},\"billingDetails\":{\"address\":{\"zip\":\"K1C2N6\",\"country\":\"CA\",\"state\":\"ON\",\"city\":\"Ottawa\",\"line1\":\"456 My Street\"}},\"sessionDetails\":{\"deviceType\":\"desktop\"},\"traceDetails\":{\"deviceType\":\"desktop\",\"sourceVersion\":\"0.1.0\",\"countryCode\":\"ES\",\"ip\":\"217.61.227.107\",\"userAgent\":\"MONEI/Shopify/0.1.0\",\"source\":\"MONEI/Shopify\",\"lang\":\"en\"},\"createdAt\":1625500773,\"updatedAt\":1625500776,\"paymentMethod\":{\"method\":\"card\",\"card\":{\"country\":\"US\",\"last4\":\"9675\",\"threeDSecure\":false,\"expiration\":2048544000,\"type\":\"credit\",\"brand\":\"mastercard\"}},\"nextAction\":{\"type\":\"COMPLETE\",\"redirectUrl\":\"https://secure.monei.com/payments/cdc503654e76e29051bce6054e4b4d47dfb63edc/receipt\"}}" + read 1069 bytes + Conn close + POST_SCRUBBED + end + + def post_scrubbed_with_auth + <<-POST_SCRUBBED_WITH_AUTH + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: [FILTERED]\r\nUser-Agent: MONEI/Shopify/0.1.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.monei.com\r\nContent-Length: 1063\r\n\r\n" + <- "{\"livemode\":\"false\",\"orderId\":\"851925032d391d67e3fbf70b06aa182d\",\"transactionType\":\"SALE\",\"description\":\"Store Purchase\",\"amount\":100,\"currency\":\"EUR\",\"paymentMethod\":{\"card\":{\"number\":\"[FILTERED]\",\"expMonth\":\"12\",\"expYear\":\"34\",\"cvc\":\"[FILTERED]\",\"auth\":{\"threeDSVersion\":null,\"eci\":\"05\",\"cavv\":\"[FILTERED]\",\"dsTransID\":\"7eac9571-3533-4c38-addd-00cf34af6a52\",\"directoryResponse\":null,\"authenticationResponse\":null,\"notificationUrl\":\"https://example.com/notification\"}}},\"customer\":{\"email\":\"support@monei.com\",\"name\":\"Jim Smith\"},\"billingDetails\":{\"address\":{\"line1\":\"456 My Street\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"CA\"}},\"sessionDetails\":{\"ip\":\"77.110.174.153\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\",\"browserAccept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json\",\"browserColorDepth\":\"100\",\"lang\":\"US\",\"browserScreenHeight\":\"1000\",\"browserScreenWidth\":\"500\",\"browserTimezoneOffset\":\"-120\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 253\r\n" + -> "Connection: close\r\n" + -> "Date: Mon, 05 Jul 2021 15:59:59 GMT\r\n" + -> "x-amzn-RequestId: ac5a5ec8-6dd4-4254-a28a-8e9fa652ba90\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "x-amz-apigw-id: CAPj7FFfDoEFXOQ=\r\n" + -> "X-Amzn-Trace-Id: Root=1-60e32c7f-690a46280dc8da307f679795;Sampled=0\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "X-Cache: Miss from cloudfront\r\n" + -> "Via: 1.1 1868e2f5b79bbf25cd21cd4b652be313.cloudfront.net (CloudFront)\r\n" + -> "X-Amz-Cf-Pop: MAD51-C3\r\n" + -> "X-Amz-Cf-Id: RVunC63Qvaswh2fcVB5n0p0BB_1zxbMOx68nuq5m6GKhWUFPpfAgVQ==\r\n" + -> "\r\n" + reading 253 bytes... + -> "{\"id\":\"e1310ab50f7cf1dcf87f1ae75b2ed0fbd2a4d05f\",\"amount\":100,\"currency\":\"EUR\",\"orderId\":\"851925032d391d67e3fbf70b06aa182d\",\"accountId\":\"00000000-aaaa-bbbb-cccc-dddd123456789\",\"liveMode\":false,\"status\":\"SUCCEEDED\",\"statusMessage\":\"Transaction Approved\"}" + read 253 bytes + Conn close + POST_SCRUBBED_WITH_AUTH end end diff --git a/test/unit/gateways/moneris_test.rb b/test/unit/gateways/moneris_test.rb index cf1ece63e0b..4ab51538b20 100644 --- a/test/unit/gateways/moneris_test.rb +++ b/test/unit/gateways/moneris_test.rb @@ -7,13 +7,18 @@ def setup Base.mode = :test @gateway = MonerisGateway.new( - :login => 'store3', - :password => 'yesguy' + login: 'store3', + password: 'yesguy' ) @amount = 100 @credit_card = credit_card('4242424242424242') - @options = { :order_id => '1', :customer => '1', :billing_address => address} + + # https://developer.moneris.com/livedemo/3ds2/reference/guide/php + @fully_authenticated_eci = 5 + @no_liability_shift_eci = 7 + + @options = { order_id: '1', customer: '1', billing_address: address } end def test_default_options @@ -30,10 +35,56 @@ def test_successful_purchase assert_equal '58-0_3;1026.1', response.authorization end + def test_successful_mpi_cavv_purchase + 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_failed_mpi_cavv_purchase + 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_failure response + assert_equal '69785-0_98;a131684dbecc1d89d9927c539ed3791b', response.authorization + end + def test_successful_first_purchase_with_credential_on_file gateway = MonerisGateway.new( - :login => 'store3', - :password => 'yesguy' + login: 'store3', + password: 'yesguy' ) gateway.expects(:ssl_post).returns(successful_first_cof_purchase_response) assert response = gateway.purchase( @@ -53,8 +104,8 @@ def test_successful_first_purchase_with_credential_on_file def test_successful_subsequent_purchase_with_credential_on_file gateway = MonerisGateway.new( - :login => 'store3', - :password => 'yesguy' + login: 'store3', + password: 'yesguy' ) gateway.expects(:ssl_post).returns(successful_first_cof_authorize_response) assert response = gateway.authorize( @@ -89,7 +140,8 @@ 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 ) @@ -129,11 +181,11 @@ def test_amount_style def test_preauth_is_valid_xml params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, + order_id: 'order1', + amount: '1.01', + pan: '4242424242424242', + expdate: '0303', + crypt_type: 7 } assert data = @gateway.send(:post_data, 'preauth', params) @@ -143,11 +195,11 @@ def test_preauth_is_valid_xml def test_purchase_is_valid_xml params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, + order_id: 'order1', + amount: '1.01', + pan: '4242424242424242', + expdate: '0303', + crypt_type: 7 } assert data = @gateway.send(:post_data, 'purchase', params) @@ -157,11 +209,11 @@ def test_purchase_is_valid_xml def test_capture_is_valid_xml params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, + order_id: 'order1', + amount: '1.01', + pan: '4242424242424242', + expdate: '0303', + crypt_type: 7 } assert data = @gateway.send(:post_data, 'preauth', params) @@ -170,10 +222,11 @@ def test_capture_is_valid_xml end def test_successful_verify - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(successful_authorize_response, failed_void_response) + @gateway.expects(:ssl_post).returns(successful_verify_response) + + assert response = @gateway.verify(@credit_card, @options) assert_success response + assert_equal '125-0_14;93565164-01571', response.authorization assert_equal 'Approved', response.message end @@ -182,7 +235,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :discover], MonerisGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club discover], MonerisGateway.supported_cardtypes end def test_should_raise_error_if_transaction_param_empty_on_credit_request @@ -200,6 +253,15 @@ def test_successful_store @data_key = response.params['data_key'] end + def test_successful_store_with_duration + @gateway.expects(:ssl_post).returns(successful_store_with_duration_response) + assert response = @gateway.store(@credit_card, duration: 600) + assert_success response + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + @data_key = response.params['data_key'] + end + def test_successful_unstore @gateway.expects(:ssl_post).returns(successful_unstore_response) test_successful_store @@ -221,7 +283,7 @@ def test_update def test_successful_purchase_with_vault @gateway.expects(:ssl_post).returns(successful_purchase_response) test_successful_store - assert response = @gateway.purchase(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) + assert response = @gateway.purchase(100, @data_key, { order_id: generate_unique_id, customer: generate_unique_id }) assert_success response assert_equal 'Approved', response.message assert response.authorization.present? @@ -229,7 +291,8 @@ 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 ) @@ -241,7 +304,7 @@ def test_successful_authorize_with_network_tokenization def test_successful_authorization_with_vault @gateway.expects(:ssl_post).returns(successful_purchase_response) test_successful_store - assert response = @gateway.authorize(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) + assert response = @gateway.authorize(100, @data_key, { order_id: generate_unique_id, customer: generate_unique_id }) assert_success response assert_equal 'Approved', response.message assert response.authorization.present? @@ -260,7 +323,7 @@ def test_cvv_enabled_and_provided @credit_card.verification_value = '452' stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{cvd_indicator>1<}, data) assert_match(%r{cvd_value>452<}, data) end.respond_with(successful_purchase_response) @@ -272,7 +335,7 @@ def test_cvv_enabled_but_not_provided @credit_card.verification_value = '' stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{cvd_indicator>0<}, data) assert_no_match(%r{cvd_value>}, data) end.respond_with(successful_purchase_response) @@ -282,7 +345,7 @@ def test_cvv_disabled_and_provided @credit_card.verification_value = '452' stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{cvd_value>}, data) assert_no_match(%r{cvd_indicator>}, data) end.respond_with(successful_purchase_response) @@ -292,7 +355,7 @@ def test_cvv_disabled_but_not_provided @credit_card.verification_value = '' stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{cvd_value>}, data) assert_no_match(%r{cvd_indicator>}, data) end.respond_with(successful_purchase_response) @@ -304,7 +367,7 @@ def test_avs_enabled_and_provided billing_address = address(address1: '1234 Anystreet', address2: '') stub_comms(gateway) do gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{avs_street_number>1234<}, data) assert_match(%r{avs_street_name>Anystreet<}, data) assert_match(%r{avs_zipcode>#{billing_address[:zip]}<}, data) @@ -316,7 +379,7 @@ def test_avs_enabled_but_not_provided stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{avs_street_number>}, data) assert_no_match(%r{avs_street_name>}, data) assert_no_match(%r{avs_zipcode>}, data) @@ -327,7 +390,7 @@ def test_avs_disabled_and_provided billing_address = address(address1: '1234 Anystreet', address2: '') stub_comms do @gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{avs_street_number>}, data) assert_no_match(%r{avs_street_name>}, data) assert_no_match(%r{avs_zipcode>}, data) @@ -337,7 +400,7 @@ def test_avs_disabled_and_provided def test_avs_disabled_and_not_provided stub_comms do @gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{avs_street_number>}, data) assert_no_match(%r{avs_street_name>}, data) assert_no_match(%r{avs_zipcode>}, data) @@ -349,7 +412,7 @@ def test_avs_result_valid_with_address assert response = @gateway.purchase(100, @credit_card, @options) assert_equal(response.avs_result, { 'code' => 'A', - 'message' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', + 'message' => 'Street address matches, but postal code does not match.', 'street_match' => 'Y', 'postal_match' => 'N' }) @@ -358,7 +421,7 @@ def test_avs_result_valid_with_address def test_customer_can_be_specified stub_comms do @gateway.purchase(@amount, @credit_card, order_id: '3', customer: 'Joe Jones') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{cust_id>Joe Jones}, data) end.respond_with(successful_purchase_response) end @@ -366,7 +429,7 @@ def test_customer_can_be_specified def test_customer_not_specified_card_name_used stub_comms do @gateway.purchase(@amount, @credit_card, order_id: '3') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{cust_id>Longbob Longsen}, data) end.respond_with(successful_purchase_response) end @@ -376,7 +439,7 @@ def test_add_swipe_data_with_creditcard stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match '00', data assert_match 'Track Data', data end.respond_with(successful_purchase_response) @@ -390,144 +453,384 @@ def test_supports_scrubbing? assert @gateway.supports_scrubbing? end + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/C<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_cit_used + options = stored_credential_options(:cardholder, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/Z<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_initial + options = stored_credential_options(:merchant, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/R<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_used + options = stored_credential_options(:merchant, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/R<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_cit_initial + options = stored_credential_options(:cardholder, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/C<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_cit_used + options = stored_credential_options(:cardholder, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/Z<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_mit_initial + options = stored_credential_options(:merchant, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/R<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_installment_mit_used + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/R<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_initial + options = stored_credential_options(:cardholder, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/C<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_used + options = stored_credential_options(:cardholder, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/Z<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_initial + options = stored_credential_options(:merchant, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/<\/issuer_id>/, data) + assert_match(/C<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_used + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/abc123<\/issuer_id>/, data) + assert_match(/U<\/payment_indicator>/, data) + assert_match(/2<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + + def test_add_cof_overrides_stored_credential_option + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123').merge(issuer_id: 'xyz987', payment_indicator: 'R', payment_information: '0') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/xyz987<\/issuer_id>/, data) + assert_match(/R<\/payment_indicator>/, data) + assert_match(/0<\/payment_information>/, data) + end.respond_with(successful_first_cof_authorize_response) + + assert_success response + end + private + def stored_credential_options(*args, id: nil) + { + order_id: '#1001', + description: 'AM test', + currency: 'CAD', + customer: '123', + stored_credential: stored_credential(*args, id: id), + issuer_id: '' + } + end + def successful_purchase_response - <<-RESPONSE - - - - 1026.1 - 661221050010170010 - 027 - 01 - 013511 - 18:41:13 - 2008-01-05 - 00 - true - APPROVED * = - 1.00 - V - 58-0_3 - false - - + <<~RESPONSE + + + + 1026.1 + 661221050010170010 + 027 + 01 + 013511 + 18:41:13 + 2008-01-05 + 00 + true + APPROVED * = + 1.00 + V + 58-0_3 + false + + + + RESPONSE + end + + def successful_cavv_purchase_response + <<~RESPONSE + + + + a131684dbecc1d89d9927c539ed3791b + 660148420010137130 + 027 + 01 + 655371 + 18:26:32 + 2022-03-25 + 00 + true + APPROVED * = + 1.00 + V + 69785-0_98 + false + null + null + false + 2 + false + + + + RESPONSE + end + + def failed_cavv_purchase_response + # this response is exactly the same as successful_cavv_purchase_response MINUS the CavvResultCode + <<~RESPONSE + + + + a131684dbecc1d89d9927c539ed3791b + 660148420010137130 + 027 + 01 + 655371 + 18:26:32 + 2022-03-25 + 00 + true + APPROVED * = + 1.00 + V + 69785-0_98 + false + null + null + false + false + + RESPONSE end def successful_first_cof_purchase_response - <<-RESPONSE - - - - - a33ba7edd448b91ef8d2f85fea614b8d - 660114080015099160 - 027 - 01 - 822665 - 07:43:28 - 2018-11-11 - 00 - true - APPROVED * = - 1.00 - V - 799655-0_11 - false - null - null - 355689484440192 - false - - + <<~RESPONSE + + + + + a33ba7edd448b91ef8d2f85fea614b8d + 660114080015099160 + 027 + 01 + 822665 + 07:43:28 + 2018-11-11 + 00 + true + APPROVED * = + 1.00 + V + 799655-0_11 + false + null + null + 355689484440192 + false + + RESPONSE end def successful_first_cof_authorize_response - <<-RESPONSE - - - - 8dbc28468af2007779bbede7ec1bab6c - 660109300018229130 - 027 - 01 - 718280 - 07:50:53 - 2018-11-11 - 01 - true - APPROVED * = - 1.00 - V - 830724-0_11 - false - null - null - 1A8315282537312 - 550923784451193 - false - - + <<~RESPONSE + + + + 8dbc28468af2007779bbede7ec1bab6c + 660109300018229130 + 027 + 01 + 718280 + 07:50:53 + 2018-11-11 + 01 + true + APPROVED * = + 1.00 + V + 830724-0_11 + false + null + null + 1A8315282537312 + 550923784451193 + false + + RESPONSE end def successful_subsequent_cof_purchase_response - <<-RESPONSE - - - - 830724-0_11;8dbc28468af2007779bbede7ec1bab6c - 660109490014038930 - 027 - 01 - 111234 - 07:50:54 - 2018-11-11 - 00 - true - APPROVED * = - 1.00 - V - 455422-0_11 - false - null - null - 762097792112819 - false - - + <<~RESPONSE + + + + 830724-0_11;8dbc28468af2007779bbede7ec1bab6c + 660109490014038930 + 027 + 01 + 111234 + 07:50:54 + 2018-11-11 + 00 + true + APPROVED * = + 1.00 + V + 455422-0_11 + false + null + null + 762097792112819 + false + + RESPONSE end def successful_purchase_network_tokenization - <<-RESPONSE - - - - 0bbb277b543a17b6781243889a689573 - 660110910011133780 - 027 - 01 - 368269 - 22:54:10 - 2015-07-05 - 00 - true - APPROVED * = - 1.00 - V - 101965-0_10 - false - null - null - false - false - - + <<~RESPONSE + + + + 0bbb277b543a17b6781243889a689573 + 660110910011133780 + 027 + 01 + 368269 + 22:54:10 + 2015-07-05 + 00 + true + APPROVED * = + 1.00 + V + 101965-0_10 + false + null + null + false + false + + RESPONSE end @@ -562,128 +865,163 @@ def successful_authorize_response end def successful_authorization_network_tokenization - <<-RESPONSE - - - - d88d9f5f3472898832c54d6b5572757e - 660110910011139740 - 027 - 01 - 873534 - 09:31:41 - 2015-07-09 - 01 - true - APPROVED * = - 1.00 - V - 109232-0_10 - false - null - null - false - false - - + <<~RESPONSE + + + + d88d9f5f3472898832c54d6b5572757e + 660110910011139740 + 027 + 01 + 873534 + 09:31:41 + 2015-07-09 + 01 + true + APPROVED * = + 1.00 + V + 109232-0_10 + false + null + null + false + false + + RESPONSE end def successful_purchase_response_with_avs_result - <<-RESPONSE - - - - 9c7189ec64b58f541335be1ca6294d09 - 660110910011136190 - 027 - 01 - 115497 - 15:20:51 - 2014-06-18 - 00 - trueAPPROVED * = - 10.10 - V - 491573-0_9 - false - null - null - false - A - null - false - - + <<~RESPONSE + + + + 9c7189ec64b58f541335be1ca6294d09 + 660110910011136190 + 027 + 01 + 115497 + 15:20:51 + 2014-06-18 + 00 + trueAPPROVED * = + 10.10 + V + 491573-0_9 + false + null + null + false + A + null + false + + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - - 1026.1 - 661221050010170010 - 481 - 01 - 013511 - 18:41:13 - 2008-01-05 - 00 - true - DECLINED * = - 1.00 - V - 97-2-0 - false - - + <<~RESPONSE + + + + 1026.1 + 661221050010170010 + 481 + 01 + 013511 + 18:41:13 + 2008-01-05 + 00 + true + DECLINED * = + 1.00 + V + 97-2-0 + false + + RESPONSE end def successful_store_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully registered cc details * = - - + <<~RESPONSE + + + + 1234567890 + 027 + true + Successfully registered cc details * = + + + RESPONSE + end + + def successful_store_with_duration_response + <<~RESPONSE + + + + 1234567890 + null + null + 001 + null + null + Successfully registered CC details. + null + true + null + null + null + false + null + null + null + null + true + cc + null + + + 4242***4242 + 2010 + + + RESPONSE end def successful_unstore_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully deleted cc details * = - - + <<~RESPONSE + + + + 1234567890 + 027 + true + Successfully deleted cc details * = + + RESPONSE end def successful_update_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully updated cc details * = - - + <<~RESPONSE + + + + 1234567890 + 027 + true + Successfully updated cc details * = + + RESPONSE end @@ -714,6 +1052,37 @@ def failed_void_response RESPONSE end + def successful_verify_response + <<-RESPONSE + + + + 93565164-01571 + 660158360010251110 + 027 + 01 + 000000 + 16:06:11 + 2019-11-04 + 06 + true + APPROVED * = + 0.00 + V + 125-0_14 + false + null + null + null + null + 1M + 2 + false + + + RESPONSE + end + def xml_purchase_fixture 'store1yesguy1.01424242424242424203037order1' end @@ -723,7 +1092,7 @@ def xml_capture_fixture end def pre_scrub - <<-pre_scrub + <<-REQUEST opening connection to esqa.moneris.com:443... opened starting SSL for esqa.moneris.com:443... @@ -747,11 +1116,11 @@ def pre_scrub -> "0\r\n" -> "\r\n" Conn close - pre_scrub + REQUEST end def post_scrub - <<-post_scrub + <<-REQUEST opening connection to esqa.moneris.com:443... opened starting SSL for esqa.moneris.com:443... @@ -775,6 +1144,6 @@ def post_scrub -> "0\r\n" -> "\r\n" Conn close - post_scrub + REQUEST end end diff --git a/test/unit/gateways/moneris_us_test.rb b/test/unit/gateways/moneris_us_test.rb deleted file mode 100644 index b2bff66d3d1..00000000000 --- a/test/unit/gateways/moneris_us_test.rb +++ /dev/null @@ -1,649 +0,0 @@ -require 'test_helper' - -class MonerisUsTest < Test::Unit::TestCase - include CommStub - - def setup - Base.mode = :test - - @gateway = MonerisUsGateway.new( - :login => 'monusqa002', - :password => 'qatoken' - ) - - @amount = 100 - @credit_card = credit_card('4242424242424242') - @check = check({ - routing_number: '011000015', - account_number: '1234455', - number: 123 - }) - @options = { :order_id => '1', :billing_address => address } - end - - def test_default_options - assert_equal 7, @gateway.options[:crypt_type] - assert_equal 'monusqa002', @gateway.options[:login] - assert_equal 'qatoken', @gateway.options[:password] - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.authorize(100, @credit_card, @options) - assert_success response - assert_equal '58-0_3;1026.1', response.authorization - end - - def test_failed_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_response) - - assert response = @gateway.authorize(100, @credit_card, @options) - assert_failure response - end - - def test_successful_echeck_purchase - @gateway.expects(:ssl_post).returns(successful_echeck_purchase_response) - - assert response = @gateway.authorize(100, @check, @options) - assert_success response - assert_equal '1522-0_25;cb80f38f44af2168fd9033cdf2d0d4c0', response.authorization - end - - def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') - @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do - @gateway.credit(@amount, '123;456', @options) - end - end - - def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') - @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, '123;456', @options) - end - - def test_successful_verify - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(successful_authorize_response, successful_capture_response) - assert_success response - assert_equal '830337-0_25;d315c7a28623dec77dc136450692d2dd', response.authorization - end - - def test_successful_verify_and_failed_void - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(successful_authorize_response, failed_capture_response) - assert_success response - assert_equal '830337-0_25;d315c7a28623dec77dc136450692d2dd', response.authorization - assert_equal 'Approved', response.message - end - - def test_failed_verify - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(failed_authorize_response, successful_capture_response) - assert_failure response - assert_equal 'Declined', response.message - end - - def test_amount_style - assert_equal '10.34', @gateway.send(:amount, 1034) - - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end - end - - def test_preauth_is_valid_xml - params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size - end - - def test_purchase_is_valid_xml - params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_purchase', params) - assert REXML::Document.new(data) - assert_equal xml_purchase_fixture.size, data.size - end - - def test_capture_is_valid_xml - params = { - :order_id => 'order1', - :amount => '1.01', - :pan => '4242424242424242', - :expdate => '0303', - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size - end - - def test_supported_countries - assert_equal ['US'], MonerisUsGateway.supported_countries - end - - def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :discover], MonerisUsGateway.supported_cardtypes - end - - def test_should_raise_error_if_transaction_param_empty_on_credit_request - [nil, '', '1234'].each do |invalid_transaction_param| - assert_raise(ArgumentError) { @gateway.void(invalid_transaction_param) } - end - end - - def test_successful_store - @gateway.expects(:ssl_post).returns(successful_store_response) - assert response = @gateway.store(@credit_card) - assert_success response - assert_equal 'Successfully registered cc details', response.message - assert response.params['data_key'].present? - @data_key = response.params['data_key'] - end - - def test_successful_unstore - @gateway.expects(:ssl_post).returns(successful_unstore_response) - test_successful_store - assert response = @gateway.unstore(@data_key) - assert_success response - assert_equal 'Successfully deleted cc details', response.message - assert response.params['data_key'].present? - end - - def test_update - @gateway.expects(:ssl_post).returns(successful_update_response) - test_successful_store - assert response = @gateway.update(@data_key, @credit_card) - assert_success response - assert_equal 'Successfully updated cc details', response.message - assert response.params['data_key'].present? - end - - def test_successful_purchase_with_vault - @gateway.expects(:ssl_post).returns(successful_purchase_response) - test_successful_store - assert response = @gateway.purchase(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) - assert_success response - assert_equal 'Approved', response.message - assert response.authorization.present? - end - - def test_successful_authorization_with_vault - @gateway.expects(:ssl_post).returns(successful_purchase_response) - test_successful_store - assert response = @gateway.authorize(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) - assert_success response - assert_equal 'Approved', response.message - assert response.authorization.present? - end - - def test_failed_authorization_with_vault - @gateway.expects(:ssl_post).returns(failed_purchase_response) - test_successful_store - assert response = @gateway.authorize(100, @data_key, @options) - assert_failure response - end - - def test_cvv_enabled_and_provided - gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) - - @credit_card.verification_value = '452' - stub_comms(gateway) do - gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_match(%r{cvd_indicator>1<}, data) - assert_match(%r{cvd_value>452<}, data) - end.respond_with(successful_purchase_response) - end - - def test_cvv_enabled_but_not_provided - gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) - - @credit_card.verification_value = '' - stub_comms(gateway) do - gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_match(%r{cvd_indicator>0<}, data) - assert_no_match(%r{cvd_value>}, data) - end.respond_with(successful_purchase_response) - end - - def test_cvv_disabled_and_provided - @credit_card.verification_value = '452' - stub_comms do - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_no_match(%r{cvd_value>}, data) - assert_no_match(%r{cvd_indicator>}, data) - end.respond_with(successful_purchase_response) - end - - def test_cvv_disabled_but_not_provided - @credit_card.verification_value = '' - stub_comms do - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_no_match(%r{cvd_value>}, data) - assert_no_match(%r{cvd_indicator>}, data) - end.respond_with(successful_purchase_response) - end - - def test_avs_enabled_and_provided - gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) - - billing_address = address(address1: '1234 Anystreet', address2: '') - stub_comms(gateway) do - gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') - end.check_request do |endpoint, data, headers| - assert_match(%r{avs_street_number>1234<}, data) - assert_match(%r{avs_street_name>Anystreet<}, data) - assert_match(%r{avs_zipcode>#{billing_address[:zip]}<}, data) - end.respond_with(successful_purchase_response_with_avs_result) - end - - def test_avs_enabled_but_not_provided - gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) - - stub_comms(gateway) do - gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) - end.check_request do |endpoint, data, headers| - assert_no_match(%r{avs_street_number>}, data) - assert_no_match(%r{avs_street_name>}, data) - assert_no_match(%r{avs_zipcode>}, data) - end.respond_with(successful_purchase_response) - end - - def test_avs_disabled_and_provided - billing_address = address(address1: '1234 Anystreet', address2: '') - stub_comms do - @gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') - end.check_request do |endpoint, data, headers| - assert_no_match(%r{avs_street_number>}, data) - assert_no_match(%r{avs_street_name>}, data) - assert_no_match(%r{avs_zipcode>}, data) - end.respond_with(successful_purchase_response_with_avs_result) - end - - def test_avs_disabled_and_not_provided - stub_comms do - @gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) - end.check_request do |endpoint, data, headers| - assert_no_match(%r{avs_street_number>}, data) - assert_no_match(%r{avs_street_name>}, data) - assert_no_match(%r{avs_zipcode>}, data) - end.respond_with(successful_purchase_response) - end - - def test_avs_result_valid_with_address - @gateway.expects(:ssl_post).returns(successful_purchase_response_with_avs_result) - assert response = @gateway.purchase(100, @credit_card, @options) - assert_equal(response.avs_result, { - 'code' => 'A', - 'message' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', - 'street_match' => 'Y', - 'postal_match' => 'N' - }) - end - - def test_customer_can_be_specified - stub_comms do - @gateway.purchase(@amount, @credit_card, order_id: '3', customer: 'Joe Jones') - end.check_request do |endpoint, data, headers| - assert_match(%r{cust_id>Joe Jones}, data) - end.respond_with(successful_purchase_response) - end - - def test_customer_not_specified_card_name_used - stub_comms do - @gateway.purchase(@amount, @credit_card, order_id: '3') - end.check_request do |endpoint, data, headers| - assert_match(%r{cust_id>Longbob Longsen}, data) - end.respond_with(successful_purchase_response) - end - - def test_transcript_scrubbing - assert @gateway.supports_scrubbing? - assert_equal @gateway.scrub(pre_scrub), post_scrub - end - - private - - def successful_purchase_response - <<-RESPONSE - - - - 1026.1 - 661221050010170010 - 027 - 01 - 013511 - 18:41:13 - 2008-01-05 - 00 - true - APPROVED * = - 1.00 - V - 58-0_3 - false - - - RESPONSE - end - - def successful_purchase_response_with_avs_result - <<-RESPONSE - - - - 9c7189ec64b58f541335be1ca6294d09 - 660110910011136190 - 027 - 01 - 115497 - 15:20:51 - 2014-06-18 - 00 - trueAPPROVED * = - 10.10 - V - 491573-0_9 - false - null - null - false - A - null - false - - - RESPONSE - end - - def failed_purchase_response - <<-RESPONSE - - - - 1026.1 - 661221050010170010 - 481 - 01 - 013511 - 18:41:13 - 2008-01-05 - 00 - true - DECLINED * = - 1.00 - V - 97-2-0 - false - - - RESPONSE - end - - def successful_authorize_response - <<-RESPONSE - - - - d315c7a28623dec77dc136450692d2dd - 640000030011763320 - 001 - 00 - 372611 - 09:08:58 - 2015-04-21 - 01 - true - APPROVED* - 1.00 - V - 830337-0_25 - false - null - null - false - A - - - - RESPONSE - end - - def failed_authorize_response - <<-RESPONSE - - - - 1fa06a83bbd1285ccfa1312835d5aa8d - 640020510015803330 - 481 - 05 - 242724 - 09:12:31 - 2015-04-21 - 01 - true - DECLINED* - 1.05 - V - 118187-0_25 - false - null - null - false - A - - - - RESPONSE - end - - def successful_capture_response - <<-RESPONSE - - - - 3a7150ceb7026fccc380743ea3f47fdf - 640000030011763340 - 001 - 00 - 224958 - 09:13:45 - 2015-04-21 - 02 - true - APPROVED* - 0.00 - V - 830339-1_25 - false - null - null - false - A - - - RESPONSE - end - - def failed_capture_response - <<-RESPONSE - - - - 3a7150ceb7026fccc380743ea3f47fdf - 640000030011763350 - 476 - 12 - 224958 - 09:13:46 - 2015-04-21 - 02 - true - DECLINED* - 0.00 - V - 830340-2_25 - false - null - null - false - - - RESPONSE - end - - def successful_store_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully registered cc details * = - - - RESPONSE - end - - def successful_unstore_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully deleted cc details * = - - - RESPONSE - end - - def successful_update_response - <<-RESPONSE - - - - 1234567890 - 027 - true - Successfully updated cc details * = - - - RESPONSE - end - - def successful_echeck_purchase_response - <<-RESPONSE - - - - cb80f38f44af2168fd9033cdf2d0d4c0 - 001000040010015220 - 005 - 01 - - 08:23:37 - 2018-06-18 - 00 - true - REGISTERED * = - 1.0 - CQ - 1522-0_25 - false - null - null - false - null - true - - - RESPONSE - end - - def xml_purchase_fixture - 'monusqa002qatoken1.01424242424242424203037order1' - end - - def xml_capture_fixture - 'monusqa002qatoken1.01424242424242424203037order1' - end - - def pre_scrub - %q{ -opening connection to esplusqa.moneris.com:443... -opened -starting SSL for esplusqa.moneris.com:443... -SSL established -<- "POST /gateway_us/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esplusqa.moneris.com\r\nContent-Length: 291\r\n\r\n" -<- "monusqa002qatokencec9ca34132f0945446589e36fff9cceLongbob Longsen1.00424242424242424219097" --> "HTTP/1.1 200 OK\r\n" --> "Date: Mon, 08 Jan 2018 19:20:26 GMT\r\n" --> "Content-Length: 659\r\n" --> "X-UA-Compatible: IE=Edge\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "Set-Cookie: TS01d02998=01649737b16d4ca54c296a0a369f4e549e4191e85d8d022d01468e559975e945b419002a42; Path=/\r\n" --> "Set-Cookie: TS01d02998_28=01e24a44e55591744bc115f421ddccd549b1655d75bda586c8ea625670efaa4432f67c8b7e06e7af82c70ef3ac4f46d7435664f2ac; Path=/\r\n" --> "\r\n" -reading 659 bytes... --> "cec9ca34132f0945446589e36fff9cce6400000300136301900010082712513:20:242018-01-0800trueAPPROVED*1.00V113295-0_25falsenullnullfalseA " -read 659 bytes -Conn close - } - end - - def post_scrub - %q{ -opening connection to esplusqa.moneris.com:443... -opened -starting SSL for esplusqa.moneris.com:443... -SSL established -<- "POST /gateway_us/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esplusqa.moneris.com\r\nContent-Length: 291\r\n\r\n" -<- "monusqa002[FILTERED]cec9ca34132f0945446589e36fff9cceLongbob Longsen1.00[FILTERED]19097" --> "HTTP/1.1 200 OK\r\n" --> "Date: Mon, 08 Jan 2018 19:20:26 GMT\r\n" --> "Content-Length: 659\r\n" --> "X-UA-Compatible: IE=Edge\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "Set-Cookie: TS01d02998=01649737b16d4ca54c296a0a369f4e549e4191e85d8d022d01468e559975e945b419002a42; Path=/\r\n" --> "Set-Cookie: TS01d02998_28=01e24a44e55591744bc115f421ddccd549b1655d75bda586c8ea625670efaa4432f67c8b7e06e7af82c70ef3ac4f46d7435664f2ac; Path=/\r\n" --> "\r\n" -reading 659 bytes... --> "cec9ca34132f0945446589e36fff9cce6400000300136301900010082712513:20:242018-01-0800trueAPPROVED*1.00V113295-0_25falsenullnullfalseA " -read 659 bytes -Conn close - } - end - -end diff --git a/test/unit/gateways/money_movers_test.rb b/test/unit/gateways/money_movers_test.rb index c5c3b275baf..6bd903e88c9 100644 --- a/test/unit/gateways/money_movers_test.rb +++ b/test/unit/gateways/money_movers_test.rb @@ -3,8 +3,8 @@ class MoneyMoversTest < Test::Unit::TestCase def setup @gateway = MoneyMoversGateway.new( - :login => 'demo', - :password => 'password' + login: 'demo', + password: 'password' ) @credit_card = credit_card('4111111111111111') @@ -12,9 +12,9 @@ def setup @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -44,8 +44,8 @@ def test_unsuccessful_request def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '1 Main St.', :address2 => 'apt 13', :country => 'US', :state => 'MI', :phone => '1234567890'}) - assert_equal ['address1', 'address2', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, billing_address: { address1: '1 Main St.', address2: 'apt 13', country: 'US', state: 'MI', phone: '1234567890' }) + assert_equal %w[address1 address2 city company country phone state zip], result.stringify_keys.keys.sort assert_equal 'MI', result[:state] assert_equal '1 Main St.', result[:address1] assert_equal 'apt 13', result[:address2] @@ -54,13 +54,13 @@ def test_add_address def test_add_invoice result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001', :description => 'This is a great order') + @gateway.send(:add_invoice, result, order_id: '#1001', description: 'This is a great order') assert_equal '#1001', result[:orderid] assert_equal 'This is a great order', result[:orderdescription] end def test_purchase_is_valid_csv - params = {:amount => @amount} + params = { amount: @amount } @gateway.send(:add_creditcard, params, @credit_card) assert data = @gateway.send(:post_data, 'auth', params) @@ -68,7 +68,7 @@ def test_purchase_is_valid_csv end def test_purchase_meets_minimum_requirements - params = {:amount => @amount} + params = { amount: @amount } @gateway.send(:add_creditcard, params, @credit_card) assert data = @gateway.send(:post_data, 'auth', params) minimum_requirements.each do |key| @@ -77,8 +77,8 @@ def test_purchase_meets_minimum_requirements end def test_expdate_formatting - assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) - assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', :month => '7', :year => '2011')) + assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', month: '9', year: '2009')) + assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', month: '7', year: '2011')) end def test_supported_countries @@ -86,7 +86,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal @gateway.supported_cardtypes, [:visa, :master, :american_express, :discover] + assert_equal @gateway.supported_cardtypes, %i[visa master american_express discover] end def test_avs_result diff --git a/test/unit/gateways/mundipagg_test.rb b/test/unit/gateways/mundipagg_test.rb index 93b023f3f44..7c9f4d923a8 100644 --- a/test/unit/gateways/mundipagg_test.rb +++ b/test/unit/gateways/mundipagg_test.rb @@ -3,32 +3,89 @@ class MundipaggTest < Test::Unit::TestCase include CommStub def setup - @gateway = MundipaggGateway.new(api_key: 'my_api_key') @credit_card = credit_card + + @alelo_card = credit_card( + '5067700000000028', + { + month: 10, + year: 2032, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'alelo' + } + ) + + @alelo_visa_card = credit_card( + '4025880000000010', + { + month: 10, + year: 2032, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'alelo' + } + ) + + @gateway = MundipaggGateway.new(api_key: 'my_api_key') @amount = 100 @options = { + gateway_affiliation_id: 'abc123', order_id: '1', billing_address: address, description: 'Store Purchase' } + + @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' + }, + "address": { + "street": 'Malibu Point', + "number": '10880', + "complement": 'A', + "neighborhood": 'Central Malibu', + "city": 'Malibu', + "state": 'CA', + "country": 'US', + "zip_code": '24210-460' + } + } + } + + @gateway_response_error = 'Esta loja n??o possui um meio de pagamento configurado para a bandeira VR' + @acquirer_message = 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.' end def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) + test_successful_purchase_with(@credit_card) + end - response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response + def test_successful_purchase_with_alelo_card + test_successful_purchase_with(@alelo_card) + end - assert_equal 'ch_90Vjq8TrwfP74XJO', response.authorization - assert response.test? + def test_successful_purchase_with_alelo_number_beginning_with_4 + test_successful_purchase_with(@alelo_visa_card) end def test_successful_purchase_with_holder_document @options[:holder_document] = 'a1b2c3d4' response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/a1b2c3d4/, data) end.respond_with(successful_purchase_response) @@ -36,11 +93,82 @@ def test_successful_purchase_with_holder_document assert response.test? end + def test_successful_purchase_with_authorization_secret_key + options = { + gateway_affiliation_id: 'abc123', + order_id: '1', + billing_address: address, + description: 'Store Purchase', + authorization_secret_key: 'secret_token' + } + basic_token = Base64.strict_encode64("#{options[:authorization_secret_key]}:") + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, _data, headers| + assert_match(basic_token, headers['Authorization']) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + assert response.test? + end + + def test_api_key_in_headers + basic_token = Base64.strict_encode64("#{@gateway.options[:api_key]}:") + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, _data, headers| + assert_match(basic_token, headers['Authorization']) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + assert response.test? + end + + def test_failed_with_voucher + @gateway.expects(:ssl_post).returns(failed_voucher_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'General Failure', response.message + assert_equal '500', response.params['last_transaction']['gateway_response']['code'] + end + + def test_successful_purchase_with_submerchant + options = @options.update(@submerchant_options) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/44444/, data) + assert_match(/5555555/, data) + assert_match(/code2/, data) + assert_match(/Sub Tony Stark/, data) + assert_match(/123456789/, data) + assert_match(/individual/, data) + assert_match(/55/, data) + assert_match(/000000000/, data) + assert_match(/21/, data) + assert_match(/Malibu Point/, data) + assert_match(/10880/, data) + assert_match(/A/, data) + assert_match(/Central Malibu/, data) + assert_match(/Malibu/, data) + assert_match(/CA/, data) + assert_match(/US/, data) + assert_match(/24210-460/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + assert response.test? + end + def test_billing_not_sent @options.delete(:billing_address) stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| refute data['billing_address'] end.respond_with(successful_purchase_response) end @@ -50,7 +178,35 @@ def test_failed_purchase response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + assert_equal @acquirer_message, response.message + assert_equal '92', response.error_code + end + + def test_failed_purchase_with_top_level_errors + @gateway.expects(:ssl_post).raises(mock_response_error) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_invalid_parameter_errors(response) + end + + def test_failed_purchase_with_gateway_response_errors + @gateway.expects(:ssl_post).returns(failed_response_with_gateway_response_errors) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal @gateway_response_error, response.message + end + + def test_failed_purchase_with_acquirer_return_code + @gateway.expects(:ssl_post).returns(failed_response_with_acquirer_return_code) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'VR|', response.message + assert_equal '14', response.error_code end def test_successful_authorize @@ -77,12 +233,41 @@ def test_successful_authorize_with_partially_missing_address assert response.test? end + def test_successful_authorize_with_submerchant + options = @options.update(@submerchant_options) + + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + + assert_equal 'ch_gm5wrlGMI2Fb0x6K', response.authorization + assert response.test? + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(failed_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response - assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + assert_equal @acquirer_message, response.message + assert_equal '92', response.error_code + end + + def test_failed_authorize_with_top_level_errors + @gateway.expects(:ssl_post).raises(mock_response_error) + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_invalid_parameter_errors(response) + end + + def test_failed_authorize_with_gateway_response_errors + @gateway.expects(:ssl_post).returns(failed_response_with_gateway_response_errors) + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal @gateway_response_error, response.message end def test_successful_capture @@ -168,6 +353,37 @@ def test_sucessful_store assert response.test? end + def test_failed_store_with_top_level_errors + @gateway.expects(:ssl_post).times(2).raises(mock_response_error) + + response = @gateway.store(@credit_card, @options) + + assert_invalid_parameter_errors(response) + end + + def test_failed_store_with_gateway_response_errors + @gateway.expects(:ssl_post).times(2).returns(failed_response_with_gateway_response_errors) + + response = @gateway.store(@credit_card, @options) + + assert_success response + assert_equal @gateway_response_error, response.message + end + + def test_gateway_id_fallback + @gateway = MundipaggGateway.new(api_key: 'my_api_key', gateway_id: 'abcd123') + options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"gateway_affiliation_id":"abcd123"/, data) + end.respond_with(successful_purchase_response) + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -175,6 +391,32 @@ def test_scrub private + def test_successful_purchase_with(card) + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, card, @options) + assert_success response + + assert_equal 'ch_90Vjq8TrwfP74XJO', response.authorization + assert response.test? + end + + def mock_response_error + mock_response = Net::HTTPUnprocessableEntity.new('1.1', '422', 'Unprocessable Entity') + mock_response.stubs(:body).returns(failed_response_with_top_level_errors) + + ActiveMerchant::ResponseError.new(mock_response) + end + + def assert_invalid_parameter_errors(response) + assert_failure response + + assert_equal( + 'Invalid parameters; The request is invalid. | The field neighborhood must be a string with a maximum length of 64. | The field line_1 must be a string with a maximum length of 256.', + response.message + ) + end + def pre_scrubbed %q( opening connection to api.mundipagg.com:443... @@ -299,6 +541,82 @@ def successful_purchase_response ) end + def failed_voucher_response + %( + { + "id": "FILTERED", + "code": "FILTERED", + "amount": 300, + "status": "processing", + "currency": "BRL", + "payment_method": "voucher", + "created_at": "2021-09-20T13:40:04Z", + "updated_at": "2021-09-20T13:40:04Z", + "customer": { + "id": "FILTERED", + "name": "FILTERED", + "email": "FILTERED", + "delinquent": false, + "created_at": "2021-09-20T13:40:04Z", + "updated_at": "2021-09-20T13:40:04Z", + "phones": {} + }, + "last_transaction": { + "id": "FILTERED", + "transaction_type": "voucher", + "amount": 300, + "status": "with_error", + "success": false, + "operation_type": "auth_and_capture", + "card": { + "id": "FILTERED", + "first_six_digits": "FILTERED", + "last_four_digits": "FILTERED", + "brand": "Sodexo", + "holder_name": "FILTERED", + "holder_document": "FILTERED", + "exp_month": 5, + "exp_year": 2030, + "status": "active", + "type": "voucher", + "created_at": "2021-09-20T13:40:04Z", + "updated_at": "2021-09-20T13:40:04Z", + "billing_address": { + "street": "FILTERED", + "number": "00", + "zip_code": "FILTERED", + "neighborhood": "FILTERED", + "city": "FILTERED", + "state": "FILTERED", + "country": "FILTERED", + "line_1": "FILTERED" + }, + "customer": { + "id": "FILTERED", + "name": "FILTERED", + "email": "FILTERED", + "delinquent": false, + "created_at": "2021-09-20T13:40:04Z", + "updated_at": "2021-09-20T13:40:04Z", + "phones": {} + } + }, + "created_at": "2021-09-20T13:40:04Z", + "updated_at": "2021-09-20T13:40:04Z", + "gateway_response": { + "code": "500", + "errors": [ + { + "message": "General Failure" + } + ] + }, + "antifraud_response": {} + } + } + ) + end + def failed_purchase_response %( { @@ -366,6 +684,203 @@ def failed_purchase_response ) end + def failed_response_with_top_level_errors + %( + { + "message": "The request is invalid.", + "errors": { + "charge.payment.credit_card.card.billing_address.neighborhood": [ + "The field neighborhood must be a string with a maximum length of 64." + ], + "charge.payment.credit_card.card.billing_address.line_1": [ + "The field line_1 must be a string with a maximum length of 256." + ] + }, + "request": { + "currency": "USD", + "amount": 100, + "customer": { + "name": "Longbob Longsen", + "phones": {}, + "metadata": {} + }, + "payment": { + "gateway_affiliation_id": "d76dffc8-c3e5-4d80-b9ee-dc8fb6c56c83", + "payment_method": "credit_card", + "credit_card": { + "installments": 1, + "capture": true, + "card": { + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2020, + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame StreetSesame Street" + }, + "options": {} + } + }, + "metadata": { + "mundipagg_payment_method_code": "1" + } + } + } + } + ) + end + + def failed_response_with_gateway_response_errors + %( + { + "id": "ch_90Vjq8TrwfP74XJO", + "code": "ME0KIN4A0O", + "gateway_id": "162bead8-23a0-4708-b687-078a69a1aa7c", + "amount": 100, + "paid_amount": 100, + "status": "paid", + "currency": "USD", + "payment_method": "credit_card", + "paid_at": "2018-02-01T18:41:05Z", + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "customer": { + "id": "cus_VxJX2NmTqyUnXgL9", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_JNzjzadcVZHlG8K2", + "transaction_type": "credit_card", + "gateway_id": "c579c8fa-53d7-41a8-b4cc-a03c712ebbb7", + "amount": 100, + "status": "captured", + "success": true, + "installments": 1, + "operation_type": "auth_and_capture", + "card": { + "id": "card_pD02Q6WtOTB7a3kE", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "gateway_response": { + "code": "400", + "errors": [ + { + "message": "Esta loja n??o possui um meio de pagamento configurado para a bandeira VR" + } + ] + } + } + } + ) + end + + def failed_response_with_acquirer_return_code + %( + { + "id": "ch_9qY3lpeCJyTe2Gxz", + "code": "3Y4ZFENCK4", + "gateway_id": "db9a46cb-2c59-4663-a658-e7817302d97c", + "amount": 2946, + "status": "failed", + "currency": "BRL", + "payment_method": "credit_card", + "created_at": "2019-11-15T16:21:58Z", + "updated_at": "2019-11-15T16:21:59Z", + "customer": { + "id": "cus_KD14bY1F51UR1GrX", + "name": "JOSE NETO", + "email": "jose_bar@uol.com.br", + "delinquent": false, + "created_at": "2019-11-15T16:21:58Z", + "updated_at": "2019-11-15T16:21:58Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_P2zwvPztdVCg6pvA", + "transaction_type": "credit_card", + "gateway_id": "174a1d12-cbea-4c09-a27a-23bbad992cc9", + "amount": 2946, + "status": "not_authorized", + "success": false, + "installments": 1, + "acquirer_name": "vr", + "acquirer_affiliation_code": "", + "acquirer_tid": "28128131916", + "acquirer_nsu": "281281", + "acquirer_message": "VR|", + "acquirer_return_code": "14", + "operation_type": "auth_and_capture", + "card": { + "id": "card_V2pQo2IbjtPqaXRZ", + "first_six_digits": "627416", + "last_four_digits": "7116", + "brand": "VR", + "holder_name": "JOSE NETO", + "holder_document": "27207590822", + "exp_month": 8, + "exp_year": 2029, + "status": "active", + "type": "voucher", + "created_at": "2019-11-15T16:21:58Z", + "updated_at": "2019-11-15T16:21:58Z", + "billing_address": { + "street": "R.Dr.Eduardo de Souza Aranha,", + "number": "67", + "zip_code": "04530030", + "neighborhood": "Av Das Nacoes Unidas 6873", + "city": "Sao Paulo", + "state": "SP", + "country": "BR", + "line_1": "67, R.Dr.Eduardo de Souza Aranha,, Av Das Nacoes Unidas 6873" + } + }, + "created_at": "2019-11-15T16:21:58Z", + "updated_at": "2019-11-15T16:21:58Z", + "gateway_response": { + "code": "201", + "errors": [] + }, + "antifraud_response": {}, + "metadata": {} + } + } + ) + end + def successful_authorize_response %( { diff --git a/test/unit/gateways/nab_transact_test.rb b/test/unit/gateways/nab_transact_test.rb index d02ebadce3a..b4afc7c7313 100644 --- a/test/unit/gateways/nab_transact_test.rb +++ b/test/unit/gateways/nab_transact_test.rb @@ -5,16 +5,16 @@ class NabTransactTest < Test::Unit::TestCase def setup @gateway = NabTransactGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 200 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Test NAB Purchase' + order_id: '1', + billing_address: address, + description: 'Test NAB Purchase' } end @@ -33,7 +33,7 @@ def test_successful_purchase_with_merchant_descriptor name, location = 'Active Merchant', 'USA' response = assert_metadata(name, location) do - response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + response = @gateway.purchase(@amount, @credit_card, @options.merge(merchant_name: name, merchant_location: location)) end assert response @@ -52,7 +52,7 @@ def test_successful_authorize_with_merchant_descriptor name, location = 'Active Merchant', 'USA' response = assert_metadata(name, location) do - response = @gateway.authorize(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + response = @gateway.authorize(@amount, @credit_card, @options.merge(merchant_name: name, merchant_location: location)) end assert response @@ -71,7 +71,7 @@ def test_successful_capture_with_merchant_descriptor name, location = 'Active Merchant', 'USA' response = assert_metadata(name, location) do - response = @gateway.capture(@amount, '009887*test*009887*200', @options.merge(:merchant_name => name, :merchant_location => location)) + response = @gateway.capture(@amount, '009887*test*009887*200', @options.merge(merchant_name: name, merchant_location: location)) end assert response @@ -103,19 +103,19 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :jcb], NabTransactGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club jcb], NabTransactGateway.supported_cardtypes end def test_successful_refund @gateway.expects(:ssl_post).with(&check_transaction_type(:refund)).returns(successful_refund_response) - assert_success @gateway.refund(@amount, '009887', {:order_id => '1'}) + assert_success @gateway.refund(@amount, '009887', { order_id: '1' }) end def test_successful_refund_with_merchant_descriptor name, location = 'Active Merchant', 'USA' response = assert_metadata(name, location) do - response = @gateway.refund(@amount, '009887', {:order_id => '1', :merchant_name => name, :merchant_location => location}) + response = @gateway.refund(@amount, '009887', { order_id: '1', merchant_name: name, merchant_location: location }) end assert response @@ -125,13 +125,13 @@ def test_successful_refund_with_merchant_descriptor def test_successful_credit @gateway.expects(:ssl_post).with(&check_transaction_type(:unmatched_refund)).returns(successful_refund_response) - assert_success @gateway.credit(@amount, @credit_card, {:order_id => '1'}) + assert_success @gateway.credit(@amount, @credit_card, { order_id: '1' }) end def test_failed_refund @gateway.expects(:ssl_post).with(&check_transaction_type(:refund)).returns(failed_refund_response) - response = @gateway.refund(@amount, '009887', {:order_id => '1'}) + response = @gateway.refund(@amount, '009887', { order_id: '1' }) assert_failure response assert_equal 'Only $1.00 available for refund', response.message end @@ -139,7 +139,7 @@ def test_failed_refund def test_request_timeout_default stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/60/, data) end.respond_with(successful_purchase_response) end @@ -148,7 +148,7 @@ def test_override_request_timeout gateway = NabTransactGateway.new(login: 'login', password: 'password', request_timeout: 44) stub_comms(gateway, :ssl_request) do gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/44/, data) end.respond_with(successful_purchase_response) end @@ -156,7 +156,7 @@ def test_override_request_timeout def test_nonfractional_currencies stub_comms(@gateway, :ssl_request) do @gateway.authorize(10000, @credit_card, @options.merge(currency: 'JPY')) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/100<\/amount>/, data) end.respond_with(successful_authorize_response) end @@ -169,60 +169,60 @@ def test_scrub private def pre_scrubbed - <<-'PRE_SCRUBBED' -opening connection to transact.nab.com.au:443... -opened -starting SSL for transact.nab.com.au:443... -SSL established -<- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" -<- "6673348a21d79657983ab247b2483e20151212075932886818+00060xml-4.2XYZ0010abcd1234Payment023200AUD444433332222111105/17111" --> "HTTP/1.1 200 OK\r\n" --> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" --> "Server: Apache-Coyote/1.1\r\n" --> "Content-Type: text/xml;charset=ISO-8859-1\r\n" --> "Content-Length: 920\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 920 bytes... --> "" --> "6673348a21d79657983ab247b2483e20151212185934964000+660xml-4.2PaymentXYZ0010000Normal023200AUDNo103Invalid Purchase Order Number444433...11105/176Visa" -read 920 bytes -Conn close + <<~'PRE_SCRUBBED' + opening connection to transact.nab.com.au:443... + opened + starting SSL for transact.nab.com.au:443... + SSL established + <- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" + <- "6673348a21d79657983ab247b2483e20151212075932886818+00060xml-4.2XYZ0010abcd1234Payment023200AUD444433332222111105/17111" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "Content-Type: text/xml;charset=ISO-8859-1\r\n" + -> "Content-Length: 920\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 920 bytes... + -> "" + -> "6673348a21d79657983ab247b2483e20151212185934964000+660xml-4.2PaymentXYZ0010000Normal023200AUDNo103Invalid Purchase Order Number444433...11105/176Visa" + read 920 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-'POST_SCRUBBED' -opening connection to transact.nab.com.au:443... -opened -starting SSL for transact.nab.com.au:443... -SSL established -<- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" -<- "6673348a21d79657983ab247b2483e20151212075932886818+00060xml-4.2XYZ0010[FILTERED]Payment023200AUD[FILTERED]05/17[FILTERED]" --> "HTTP/1.1 200 OK\r\n" --> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" --> "Server: Apache-Coyote/1.1\r\n" --> "Content-Type: text/xml;charset=ISO-8859-1\r\n" --> "Content-Length: 920\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 920 bytes... --> "" --> "6673348a21d79657983ab247b2483e20151212185934964000+660xml-4.2PaymentXYZ0010000Normal023200AUDNo103Invalid Purchase Order Number444433...11105/176Visa" -read 920 bytes -Conn close + <<~'POST_SCRUBBED' + opening connection to transact.nab.com.au:443... + opened + starting SSL for transact.nab.com.au:443... + SSL established + <- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" + <- "6673348a21d79657983ab247b2483e20151212075932886818+00060xml-4.2XYZ0010[FILTERED]Payment023200AUD[FILTERED]05/17[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "Content-Type: text/xml;charset=ISO-8859-1\r\n" + -> "Content-Length: 920\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 920 bytes... + -> "" + -> "6673348a21d79657983ab247b2483e20151212185934964000+660xml-4.2PaymentXYZ0010000Normal023200AUDNo103Invalid Purchase Order Number444433...11105/176Visa" + read 920 bytes + Conn close POST_SCRUBBED end def check_transaction_type(type) - Proc.new do |endpoint, data, headers| + Proc.new do |_endpoint, data, _headers| request_hash = Hash.from_xml(data) request_hash['NABTransactMessage']['Payment']['TxnList']['Txn']['txnType'] == NabTransactGateway::TRANSACTIONS[type].to_s end end def valid_metadata(name, location) - return <<-XML.gsub(/^\s{4}/, '').gsub(/\n/, '') + return <<-XML.gsub(/^\s{4}/, '').delete("\n") XML end @@ -230,7 +230,7 @@ def valid_metadata(name, location) def assert_metadata(name, location, &block) stub_comms(@gateway, :ssl_request) do yield - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| metadata_matcher = Regexp.escape(valid_metadata(name, location)) assert_match %r{#{metadata_matcher}}, data end.respond_with(successful_purchase_response) @@ -456,5 +456,4 @@ def failed_refund_response XML end - end diff --git a/test/unit/gateways/ncr_secure_pay_test.rb b/test/unit/gateways/ncr_secure_pay_test.rb index 4fabb3b8779..6dee2375635 100644 --- a/test/unit/gateways/ncr_secure_pay_test.rb +++ b/test/unit/gateways/ncr_secure_pay_test.rb @@ -18,7 +18,7 @@ def setup def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\login\<\/username\>/, data) assert_match(/\password\<\/password\>/, data) assert_match(/\sale\<\/action\>/, data) @@ -43,7 +43,7 @@ def test_failed_purchase def test_successful_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\login\<\/username\>/, data) assert_match(/\password\<\/password\>/, data) assert_match(/\preauth\<\/action\>/, data) @@ -67,7 +67,7 @@ def test_failed_authorize def test_successful_capture response = stub_comms do @gateway.capture(@amount, '12345', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\login\<\/username\>/, data) assert_match(/\password\<\/password\>/, data) assert_match(/\preauthcomplete\<\/action\>/, data) @@ -90,7 +90,7 @@ def test_failed_capture def test_successful_refund response = stub_comms do @gateway.refund(@amount, '12345', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\login\<\/username\>/, data) assert_match(/\password\<\/password\>/, data) assert_match(/\credit\<\/action\>/, data) @@ -113,7 +113,7 @@ def test_failed_refund def test_successful_void response = stub_comms do @gateway.void('12345', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\login\<\/username\>/, data) assert_match(/\password\<\/password\>/, data) assert_match(/\void\<\/action\>/, data) diff --git a/test/unit/gateways/net_registry_test.rb b/test/unit/gateways/net_registry_test.rb index 0d6c50e2f63..40d0daba5fa 100644 --- a/test/unit/gateways/net_registry_test.rb +++ b/test/unit/gateways/net_registry_test.rb @@ -3,15 +3,15 @@ class NetRegistryTest < Test::Unit::TestCase def setup @gateway = NetRegistryGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @amount = 100 @credit_card = credit_card @options = { - :order_id => '1', - :billing_address => address + order_id: '1', + billing_address: address } end @@ -67,7 +67,7 @@ def test_successful_authorization_and_capture assert_success response assert_match %r{\A\d{6}\z}, response.authorization - response = @gateway.capture(@amount, response.authorization, :credit_card => @credit_card) + response = @gateway.capture(@amount, response.authorization, credit_card: @credit_card) assert_success response end @@ -96,7 +96,7 @@ def test_purchase_with_invalid_month end def test_bad_login - gateway = NetRegistryGateway.new(:login => 'bad-login', :password => 'bad-login') + gateway = NetRegistryGateway.new(login: 'bad-login', password: 'bad-login') gateway.stubs(:ssl_post).returns(bad_login_response) response = gateway.purchase(@amount, @credit_card, @options) @@ -107,319 +107,319 @@ def test_bad_login private def successful_purchase_response - <<-RESPONSE -approved -00015X000000 -Transaction No: 00000000 ------------------------- -MERCHANTNAME -LOCATION AU - -MERCH ID 10000000 -TERM ID Y0TR00 -COUNTRY CODE AU -16/07/07 18:59 -RRN 00015X000000 -VISA -411111-111 -CREDIT A/C 12/10 - -AUTHORISATION NO: 000000 -APPROVED 08 - -PURCHASE $1.00 -TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - -(SUBJECT TO CARDHOLDER'S - ACCEPTANCE) ------------------------- -. -settlement_date=16/07/07 -card_desc=VISA -status=approved -txn_ref=0707161858000000 -refund_mode=0 -transaction_no=000000 -rrn=00015X000000 -response_text=SIGNATURE REQUIRED -pld=0 -total_amount=100 -card_no=4111111111111111 -version=V1.0 -merchant_index=123 -card_expiry=12/10 -training_mode=0 -operator_no=10000 -response_code=08 -card_type=6 -approved=1 -cashout_amount=0 -receipt_array=ARRAY(0x83725cc) -account_type=CREDIT A/C -result=1 + <<~RESPONSE + approved + 00015X000000 + Transaction No: 00000000 + ------------------------ + MERCHANTNAME + LOCATION AU + + MERCH ID 10000000 + TERM ID Y0TR00 + COUNTRY CODE AU + 16/07/07 18:59 + RRN 00015X000000 + VISA + 411111-111 + CREDIT A/C 12/10 + + AUTHORISATION NO: 000000 + APPROVED 08 + + PURCHASE $1.00 + TOTAL AUD $1.00 + + PLEASE RETAIN AS RECORD + OF PURCHASE + + (SUBJECT TO CARDHOLDER'S + ACCEPTANCE) + ------------------------ + . + settlement_date=16/07/07 + card_desc=VISA + status=approved + txn_ref=0707161858000000 + refund_mode=0 + transaction_no=000000 + rrn=00015X000000 + response_text=SIGNATURE REQUIRED + pld=0 + total_amount=100 + card_no=4111111111111111 + version=V1.0 + merchant_index=123 + card_expiry=12/10 + training_mode=0 + operator_no=10000 + response_code=08 + card_type=6 + approved=1 + cashout_amount=0 + receipt_array=ARRAY(0x83725cc) + account_type=CREDIT A/C + result=1 RESPONSE end def successful_credit_response - <<-RESPONSE -approved -00015X000000 -Transaction No: 00000000 ------------------------- -MERCHANTNAME -LOCATION AU - -MERCH ID 10000000 -TERM ID Y0TR00 -COUNTRY CODE AU -16/07/07 19:03 -RRN 00015X000000 -VISA -411111-111 -CREDIT A/C 12/10 - -AUTHORISATION NO: -APPROVED 08 - -** REFUND ** $1.00 -TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF REFUND - -(SUBJECT TO CARDHOLDER'S - ACCEPTANCE) ------------------------- -. -settlement_date=16/07/07 -card_desc=VISA -status=approved -txn_ref=0707161902000000 -refund_mode=1 -transaction_no=000000 -rrn=00015X000000 -response_text=SIGNATURE REQUIRED -pld=0 -total_amount=100 -card_no=4111111111111111 -version=V1.0 -merchant_index=123 -card_expiry=12/10 -training_mode=0 -operator_no=10000 -response_code=08 -card_type=6 -approved=1 -cashout_amount=0 -receipt_array=ARRAY(0x837241c) -account_type=CREDIT A/C -result=1 + <<~RESPONSE + approved + 00015X000000 + Transaction No: 00000000 + ------------------------ + MERCHANTNAME + LOCATION AU + + MERCH ID 10000000 + TERM ID Y0TR00 + COUNTRY CODE AU + 16/07/07 19:03 + RRN 00015X000000 + VISA + 411111-111 + CREDIT A/C 12/10 + + AUTHORISATION NO: + APPROVED 08 + + ** REFUND ** $1.00 + TOTAL AUD $1.00 + + PLEASE RETAIN AS RECORD + OF REFUND + + (SUBJECT TO CARDHOLDER'S + ACCEPTANCE) + ------------------------ + . + settlement_date=16/07/07 + card_desc=VISA + status=approved + txn_ref=0707161902000000 + refund_mode=1 + transaction_no=000000 + rrn=00015X000000 + response_text=SIGNATURE REQUIRED + pld=0 + total_amount=100 + card_no=4111111111111111 + version=V1.0 + merchant_index=123 + card_expiry=12/10 + training_mode=0 + operator_no=10000 + response_code=08 + card_type=6 + approved=1 + cashout_amount=0 + receipt_array=ARRAY(0x837241c) + account_type=CREDIT A/C + result=1 RESPONSE end def successful_authorization_response - <<-RESPONSE -approved -00015X000000 -Transaction No: 00000000 ------------------------- -MERCHANTNAME -LOCATION AU - -MERCH ID 10000000 -TERM ID Y0TR00 -COUNTRY CODE AU -17/07/07 15:22 -RRN 00015X000000 -VISA -411111-111 -CREDIT A/C 12/10 - -AUTHORISATION NO: 000000 -APPROVED 08 - -PURCHASE $1.00 -TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - -(SUBJECT TO CARDHOLDER'S - ACCEPTANCE) ------------------------- -. -settlement_date=17/07/07 -card_desc=VISA -status=approved -txn_ref=0707171521000000 -refund_mode=0 -transaction_no=000000 -rrn=00015X000000 -response_text=SIGNATURE REQUIRED -pld=0 -total_amount=100 -card_no=4111111111111111 -version=V1.0 -merchant_index=123 -card_expiry=12/10 -training_mode=0 -operator_no=10000 -response_code=08 -card_type=6 -approved=1 -cashout_amount=0 -receipt_array=ARRAY(0x836a25c) -account_type=CREDIT A/C -result=1 + <<~RESPONSE + approved + 00015X000000 + Transaction No: 00000000 + ------------------------ + MERCHANTNAME + LOCATION AU + + MERCH ID 10000000 + TERM ID Y0TR00 + COUNTRY CODE AU + 17/07/07 15:22 + RRN 00015X000000 + VISA + 411111-111 + CREDIT A/C 12/10 + + AUTHORISATION NO: 000000 + APPROVED 08 + + PURCHASE $1.00 + TOTAL AUD $1.00 + + PLEASE RETAIN AS RECORD + OF PURCHASE + + (SUBJECT TO CARDHOLDER'S + ACCEPTANCE) + ------------------------ + . + settlement_date=17/07/07 + card_desc=VISA + status=approved + txn_ref=0707171521000000 + refund_mode=0 + transaction_no=000000 + rrn=00015X000000 + response_text=SIGNATURE REQUIRED + pld=0 + total_amount=100 + card_no=4111111111111111 + version=V1.0 + merchant_index=123 + card_expiry=12/10 + training_mode=0 + operator_no=10000 + response_code=08 + card_type=6 + approved=1 + cashout_amount=0 + receipt_array=ARRAY(0x836a25c) + account_type=CREDIT A/C + result=1 RESPONSE end def successful_capture_response - <<-RESPONSE -approved -00015X000000 -Transaction No: 00000000 ------------------------- -MERCHANTNAME -LOCATION AU - -MERCH ID 10000000 -TERM ID Y0TR00 -COUNTRY CODE AU -17/07/07 15:23 -RRN 00015X000000 -VISA -411111-111 -CREDIT A/C 12/10 - -AUTHORISATION NO: 000000 -APPROVED 08 - -PURCHASE $1.00 -TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - -(SUBJECT TO CARDHOLDER'S - ACCEPTANCE) ------------------------- -. -settlement_date=17/07/07 -card_desc=VISA -status=approved -txn_ref=0707171522000000 -refund_mode=0 -transaction_no=000000 -rrn=00015X000000 -response_text=SIGNATURE REQUIRED -pld=0 -total_amount=100 -card_no=4111111111111111 -version=V1.0 -merchant_index=123 -card_expiry=12/10 -training_mode=0 -operator_no=10000 -response_code=08 -card_type=6 -approved=1 -cashout_amount=0 -receipt_array=ARRAY(0x8378200) -account_type=CREDIT A/C -result=1 + <<~RESPONSE + approved + 00015X000000 + Transaction No: 00000000 + ------------------------ + MERCHANTNAME + LOCATION AU + + MERCH ID 10000000 + TERM ID Y0TR00 + COUNTRY CODE AU + 17/07/07 15:23 + RRN 00015X000000 + VISA + 411111-111 + CREDIT A/C 12/10 + + AUTHORISATION NO: 000000 + APPROVED 08 + + PURCHASE $1.00 + TOTAL AUD $1.00 + + PLEASE RETAIN AS RECORD + OF PURCHASE + + (SUBJECT TO CARDHOLDER'S + ACCEPTANCE) + ------------------------ + . + settlement_date=17/07/07 + card_desc=VISA + status=approved + txn_ref=0707171522000000 + refund_mode=0 + transaction_no=000000 + rrn=00015X000000 + response_text=SIGNATURE REQUIRED + pld=0 + total_amount=100 + card_no=4111111111111111 + version=V1.0 + merchant_index=123 + card_expiry=12/10 + training_mode=0 + operator_no=10000 + response_code=08 + card_type=6 + approved=1 + cashout_amount=0 + receipt_array=ARRAY(0x8378200) + account_type=CREDIT A/C + result=1 RESPONSE end def purchase_with_invalid_credit_card_response - <<-RESPONSE -declined -00015X000000 -Transaction No: 00000000 ------------------------- -MERCHANTNAME -LOCATION AU - -MERCH ID 10000000 -TERM ID Y0TR40 -COUNTRY CODE AU -16/07/07 19:20 -RRN 00015X000000 -VISA -411111-111 -CREDIT A/C 12/10 - -AUTHORISATION NO: -DECLINED 31 - -PURCHASE $1.00 -TOTAL AUD $1.00 - -(SUBJECT TO CARDHOLDER'S - ACCEPTANCE) ------------------------- -. -settlement_date=16/07/07 -card_desc=VISA -status=declined -txn_ref=0707161919000000 -refund_mode=0 -transaction_no=000000 -rrn=00015X000000 -response_text=INVALID CARD -pld=0 -total_amount=100 -card_no=4111111111111111 -version=V1.0 -merchant_index=123 -card_expiry=12/10 -training_mode=0 -operator_no=10000 -response_code=31 -card_type=6 -approved=0 -cashout_amount=0 -receipt_array=ARRAY(0x83752d0) -account_type=CREDIT A/C -result=0 -RESPONSE + <<~RESPONSE + declined + 00015X000000 + Transaction No: 00000000 + ------------------------ + MERCHANTNAME + LOCATION AU + + MERCH ID 10000000 + TERM ID Y0TR40 + COUNTRY CODE AU + 16/07/07 19:20 + RRN 00015X000000 + VISA + 411111-111 + CREDIT A/C 12/10 + + AUTHORISATION NO: + DECLINED 31 + + PURCHASE $1.00 + TOTAL AUD $1.00 + + (SUBJECT TO CARDHOLDER'S + ACCEPTANCE) + ------------------------ + . + settlement_date=16/07/07 + card_desc=VISA + status=declined + txn_ref=0707161919000000 + refund_mode=0 + transaction_no=000000 + rrn=00015X000000 + response_text=INVALID CARD + pld=0 + total_amount=100 + card_no=4111111111111111 + version=V1.0 + merchant_index=123 + card_expiry=12/10 + training_mode=0 + operator_no=10000 + response_code=31 + card_type=6 + approved=0 + cashout_amount=0 + receipt_array=ARRAY(0x83752d0) + account_type=CREDIT A/C + result=0 + RESPONSE end def purchase_with_expired_credit_card_response - <<-RESPONSE -failed - - -. -response_text=CARD EXPIRED -approved=0 -status=failed -txn_ref=0707161910000000 -version=V1.0 -pld=0 -response_code=Q816 -result=-1 + <<~RESPONSE + failed + + + . + response_text=CARD EXPIRED + approved=0 + status=failed + txn_ref=0707161910000000 + version=V1.0 + pld=0 + response_code=Q816 + result=-1 RESPONSE end def purchase_with_invalid_month_response - <<-RESPONSE -failed -Invalid month + <<~RESPONSE + failed + Invalid month RESPONSE end def bad_login_response - <<-RESPONSE -failed + <<~RESPONSE + failed -. -status=failed -result=-1 + . + status=failed + result=-1 RESPONSE end end diff --git a/test/unit/gateways/netaxept_test.rb b/test/unit/gateways/netaxept_test.rb index 5dddbde3427..92d60776572 100644 --- a/test/unit/gateways/netaxept_test.rb +++ b/test/unit/gateways/netaxept_test.rb @@ -5,15 +5,15 @@ class NetaxeptTest < Test::Unit::TestCase def setup @gateway = NetaxeptGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1' + order_id: '1' } end @@ -57,7 +57,7 @@ def test_handles_currency_with_money @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[3]).in_sequence(s) - assert_success @gateway.purchase(100, @credit_card, @options.merge(:currency => 'USD')) + assert_success @gateway.purchase(100, @credit_card, @options.merge(currency: 'USD')) end def test_handles_currency_with_option @@ -67,7 +67,7 @@ def test_handles_currency_with_option @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[3]).in_sequence(s) - assert_success @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) + assert_success @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) end def test_handles_setup_transaction_error @@ -91,7 +91,7 @@ def test_handles_query_error end def test_url_escape_password - @gateway = NetaxeptGateway.new(:login => 'login', :password => '1a=W+Yr2') + @gateway = NetaxeptGateway.new(login: 'login', password: '1a=W+Yr2') s = sequence('request') @gateway.expects(:ssl_get).with(regexp_matches(/token=1a%3DW%2BYr2/)).returns(successful_purchase_response[0]).in_sequence(s) diff --git a/test/unit/gateways/netbanx_test.rb b/test/unit/gateways/netbanx_test.rb index a4a424d27f3..359521c8c35 100644 --- a/test/unit/gateways/netbanx_test.rb +++ b/test/unit/gateways/netbanx_test.rb @@ -15,7 +15,7 @@ def setup end def test_successful_purchase - @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).twice.returns(success_verification_response, successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -34,7 +34,7 @@ def test_failed_purchase end def test_successful_authorize - @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).twice.returns(auth_verification_response, successful_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -56,7 +56,7 @@ def test_failed_authorize def test_successful_capture @gateway.expects(:ssl_request).returns(successful_capture_response) - response = @gateway.authorize(@amount, '056ff3a9-5274-4452-92ab-0e3b3e591c3b') + response = @gateway.capture(@amount, '056ff3a9-5274-4452-92ab-0e3b3e591c3b') assert_success response assert_equal '11e0906b-6596-4490-b0e3-825f71a82799', response.authorization @@ -75,7 +75,7 @@ def test_failed_capture end def test_successful_refund - @gateway.expects(:ssl_request).returns(successful_capture_response) + @gateway.expects(:ssl_request).twice.returns(success_verification_response, successful_capture_response) response = @gateway.refund(@amount, '056ff3a9-5274-4452-92ab-0e3b3e591c3b') assert_success response @@ -128,8 +128,8 @@ def test_successful_store assert response.test? end - def test_successful_purchase_with_token - @gateway.expects(:ssl_request).returns(successful_purchase_with_token_response) + def test_successful_purchase_token + @gateway.expects(:ssl_request).twice.returns(success_verification_response, purchase_with_token_response) response = @gateway.purchase(@amount, 'CL0RCSnrkREnfwA', @options) assert_success response @@ -311,7 +311,7 @@ def failed_purchase_response RESPONSE end - def successful_purchase_with_token_response + def purchase_with_token_response <<-RESPONSE { "links": [ @@ -416,6 +416,26 @@ def successful_authorize_response RESPONSE end + def auth_verification_response + <<-RESPONSE + { + "id": "b8c53059-9da3-4054-8caf-3769161a3cdc", + "status": "COMPLETED", + "message": "OK" + } + RESPONSE + end + + def success_verification_response + <<-RESPONSE + { + "id": "11e0906b-6596-4490-b0e3-825f71a82799", + "status": "COMPLETED", + "message": "OK" + } + RESPONSE + end + def failed_authorize_response <<-RESPONSE { diff --git a/test/unit/gateways/netbilling_test.rb b/test/unit/gateways/netbilling_test.rb index 724e3037542..5b0b7609013 100644 --- a/test/unit/gateways/netbilling_test.rb +++ b/test/unit/gateways/netbilling_test.rb @@ -4,11 +4,11 @@ class NetbillingTest < Test::Unit::TestCase include CommStub def setup - @gateway = NetbillingGateway.new(:login => 'login') + @gateway = NetbillingGateway.new(login: 'login') @credit_card = credit_card('4242424242424242') @amount = 100 - @options = { :billing_address => address } + @options = { billing_address: address } end def test_successful_request @@ -43,11 +43,11 @@ def test_cvv_result end def test_site_tag_sent_if_provided - @gateway = NetbillingGateway.new(:login => 'login', :site_tag => 'dummy-site-tag') + @gateway = NetbillingGateway.new(login: 'login', site_tag: 'dummy-site-tag') response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/site_tag=dummy-site-tag/, data) end.respond_with(successful_purchase_response) @@ -57,7 +57,7 @@ def test_site_tag_sent_if_provided def test_site_tag_not_sent_if_not_provided response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/site_tag/, data) end.respond_with(successful_purchase_response) diff --git a/test/unit/gateways/netpay_test.rb b/test/unit/gateways/netpay_test.rb index 6b107d2e9f5..92cd1ff0452 100644 --- a/test/unit/gateways/netpay_test.rb +++ b/test/unit/gateways/netpay_test.rb @@ -3,10 +3,10 @@ class NetpayTest < Test::Unit::TestCase def setup @gateway = NetpayGateway.new( - :store_id => '12345', - :login => 'login', - :password => 'password' - ) + store_id: '12345', + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 1000 @@ -14,7 +14,7 @@ def setup @order_id = 'C3836048-631F-112B-001E-7C08C0406975' @options = { - :description => 'Store Purchase' + description: 'Store Purchase' } end @@ -64,7 +64,7 @@ def test_successful_purchase_with_ip ) ).returns(successful_response) - assert response = @gateway.purchase(@amount, @credit_card, :ip => '127.0.0.1') + assert response = @gateway.purchase(@amount, @credit_card, ip: '127.0.0.1') assert_success response end @@ -150,7 +150,7 @@ def test_supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :diners_club], NetpayGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club], NetpayGateway.supported_cardtypes end private diff --git a/test/unit/gateways/network_merchants_test.rb b/test/unit/gateways/network_merchants_test.rb index 57fbb837167..5ea99c7da9e 100644 --- a/test/unit/gateways/network_merchants_test.rb +++ b/test/unit/gateways/network_merchants_test.rb @@ -3,18 +3,18 @@ class NetworkMerchantsTest < Test::Unit::TestCase def setup @gateway = NetworkMerchantsGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @check = check @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -60,7 +60,7 @@ def test_successful_check_purchase def test_purchase_and_store @gateway.expects(:ssl_post).returns(successful_purchase_and_store) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:store => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true)) assert_success response assert_equal 'SUCCESS', response.message assert_equal '1378262091', response.params['customer_vault_id'] @@ -156,7 +156,7 @@ def test_purchase_on_stored_card end def test_invalid_login - gateway = NetworkMerchantsGateway.new(:login => '', :password => '') + gateway = NetworkMerchantsGateway.new(login: '', password: '') gateway.expects(:ssl_post).returns(failed_login) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb index 5c0ee07d25c..badce95ff5c 100644 --- a/test/unit/gateways/nmi_test.rb +++ b/test/unit/gateways/nmi_test.rb @@ -5,17 +5,96 @@ class NmiTest < Test::Unit::TestCase def setup @gateway = NmiGateway.new(fixtures(:nmi)) + @gateway_secure = NmiGateway.new(fixtures(:nmi_secure)) @amount = 100 @credit_card = credit_card @check = check - @options = {} + + @merchant_defined_fields = { merchant_defined_field_8: 'value8' } + + @transaction_options = { + recurring: true, order_id: '#1001', description: 'AM test', currency: 'GBP', dup_seconds: 15, + customer: '123', tax: 5.25, shipping: 10.51, ponumber: 1002 + } + @descriptor_options = { + descriptor: 'test', + descriptor_phone: '123', + descriptor_address: 'address', + descriptor_city: 'city', + descriptor_state: 'state', + descriptor_postal: 'postal', + descriptor_country: 'country', + descriptor_mcc: 'mcc', + descriptor_merchant_id: '120', + descriptor_url: 'url' + } + end + + def test_successful_authorize_and_capture_using_security_key + @credit_card.number = '4111111111111111' + response = stub_comms do + @gateway_secure.authorize(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_match(/security_key=#{@gateway_secure.options[:security_key]}/, data) + assert_match(/type=auth/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + end.respond_with(successful_authorization_response) + assert_success response + capture = stub_comms do + @gateway_secure.capture(@amount, response.authorization) + end.check_request do |_endpoint, data, _headers| + assert_match(/security_key=#{@gateway_secure.options[:security_key]}/, data) + assert_match(/type=capture/, data) + assert_match(/amount=1.00/, data) + assert_match(/transactionid=2762787830/, data) + end.respond_with(successful_capture_response) + assert_success capture + end + + def test_failed_authorize_using_security_key + response = stub_comms do + @gateway_secure.authorize(@amount, @credit_card) + end.respond_with(failed_authorization_response) + + assert_failure response + assert_equal 'DECLINE', response.message + assert response.test? + end + + def test_successful_purchase_using_security_key + @credit_card.number = '4111111111111111' + response = stub_comms do + @gateway_secure.purchase(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + assert_match(/security_key=#{@gateway_secure.options[:security_key]}/, data) + assert_match(/type=sale/, data) + assert_match(/amount=1.00/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + end.respond_with(successful_purchase_response) + assert_success response + assert response.test? + end + + def test_failed_purchase_using_security_key + response = stub_comms do + @gateway_secure.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + assert_failure response + assert response.test? + assert_equal 'DECLINE', response.message end def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=sale/, data) @@ -33,24 +112,83 @@ def test_successful_purchase end def test_purchase_with_options + options = @transaction_options.merge(@merchant_defined_fields) + response = stub_comms do - @gateway.purchase(@amount, @credit_card, - recurring: true, order_id: '#1001', description: 'AM test', - currency: 'GBP', dup_seconds: 15, customer: '123', - merchant_defined_field_8: 'value8') - end.check_request do |endpoint, data, headers| - assert_match(/billing_method=recurring/, data) - assert_match(/orderid=#{CGI.escape("#1001")}/, data) - assert_match(/orderdescription=AM\+test/, data) - assert_match(/currency=GBP/, data) - assert_match(/dup_seconds=15/, data) - assert_match(/customer_id=123/, data) + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + test_transaction_options(data) + assert_match(/merchant_defined_field_8=value8/, data) end.respond_with(successful_purchase_response) assert_success response end + def test_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) @@ -64,7 +202,7 @@ def test_failed_purchase def test_successful_purchase_with_echeck response = stub_comms do @gateway.purchase(@amount, @check) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=sale/, data) @@ -95,10 +233,107 @@ def test_failed_purchase_with_echeck assert_equal 'FAILED', response.message end + def test_successful_purchase_with_3ds_verified + version = '2.1.0' + authentication_response_status = 'Y' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + xid = '00000000000000000501' + options_with_3ds = @transaction_options.merge( + three_d_secure: { + version: version, + authentication_response_status: authentication_response_status, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + xid: xid + } + ) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/three_ds_version=2.1.0/, data) + assert_match(/cardholder_auth=verified/, data) + assert_match(/cavv=jJ81HADVRtXfCBATEp01CJUAAAA/, data) + assert_match(/directory_server_id=97267598-FAE6-48F2-8083-C23433990FBC/, data) + assert_match(/xid=00000000000000000501/, data) + end.respond_with(successful_3ds_purchase_response) + + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_3ds_attempted + version = '2.1.0' + authentication_response_status = 'A' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + xid = '00000000000000000501' + options_with_3ds = @transaction_options.merge( + three_d_secure: { + version: version, + authentication_response_status: authentication_response_status, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + xid: xid + } + ) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |_endpoint, data, _headers| + assert_match(/three_ds_version=2.1.0/, data) + assert_match(/cardholder_auth=attempted/, data) + assert_match(/cavv=jJ81HADVRtXfCBATEp01CJUAAAA/, data) + assert_match(/directory_server_id=97267598-FAE6-48F2-8083-C23433990FBC/, data) + assert_match(/xid=00000000000000000501/, data) + end.respond_with(successful_3ds_purchase_response) + + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + end + + def test_purchase_with_descriptor_options + options = @transaction_options.merge({ descriptors: @descriptor_options }) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/descriptor=test/, data) + assert_match(/descriptor_phone=123/, data) + assert_match(/descriptor_address=address/, data) + assert_match(/descriptor_city=city/, data) + assert_match(/descriptor_state=state/, data) + assert_match(/descriptor_postal=postal/, data) + assert_match(/descriptor_country=country/, data) + assert_match(/descriptor_mcc=mcc/, data) + assert_match(/descriptor_merchant_id=120/, data) + assert_match(/descriptor_url=url/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_authorize_with_options + options = @transaction_options.merge(@merchant_defined_fields) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + test_transaction_options(data) + + assert_match(/merchant_defined_field_8=value8/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_successful_authorize_and_capture response = stub_comms do @gateway.authorize(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=auth/, data) @@ -113,7 +348,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=capture/, data) @@ -152,7 +387,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=void/, data) @@ -180,7 +415,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=refund/, data) @@ -202,7 +437,7 @@ def test_failed_refund def test_successful_credit response = stub_comms do @gateway.credit(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/type=credit/, data) @@ -219,6 +454,16 @@ def test_successful_credit assert response.test? end + def test_credit_with_options + response = stub_comms do + @gateway.credit(@amount, @credit_card, @transaction_options) + end.check_request do |_endpoint, data, _headers| + test_transaction_options(data) + end.respond_with(successful_credit_response) + + assert_success response + end + def test_failed_credit response = stub_comms do @gateway.credit(@amount, @credit_card) @@ -230,20 +475,11 @@ def test_failed_credit end def test_successful_verify - response = stub_comms do - @gateway.verify(@credit_card) - end.check_request do |endpoint, data, headers| - assert_match(/username=#{@gateway.options[:login]}/, data) - assert_match(/password=#{@gateway.options[:password]}/, data) - assert_match(/type=validate/, data) - assert_match(/payment=creditcard/, data) - assert_match(/ccnumber=#{@credit_card.number}/, data) - assert_match(/cvv=#{@credit_card.verification_value}/, data) - assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) - end.respond_with(successful_validate_response) + test_verify + end - assert_success response - assert_equal 'Succeeded', response.message + def test_verify_with_options + test_verify(@transaction_options) end def test_failed_verify @@ -258,7 +494,7 @@ def test_failed_verify def test_successful_store response = stub_comms do @gateway.store(@credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/customer_vault=add_customer/, data) @@ -288,7 +524,7 @@ def test_failed_store def test_successful_store_with_echeck response = stub_comms do @gateway.store(@check) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/username=#{@gateway.options[:login]}/, data) assert_match(/password=#{@gateway.options[:password]}/, data) assert_match(/customer_vault=add_customer/, data) @@ -330,7 +566,7 @@ def test_transcript_scrubbing def test_includes_cvv_tag stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{cvv}, data) end.respond_with(successful_purchase_response) end @@ -339,25 +575,24 @@ def test_blank_cvv_not_sent @credit_card.verification_value = nil stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{cvv}, data) end.respond_with(successful_purchase_response) @credit_card.verification_value = ' ' stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{cvv}, data) end.respond_with(successful_purchase_response) end def test_supported_countries - assert_equal 1, - (['US'] | NmiGateway.supported_countries).size + assert_equal 2, (%w[US CA] | NmiGateway.supported_countries).size end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover], NmiGateway.supported_cardtypes + assert_equal %i[visa master american_express discover], NmiGateway.supported_cardtypes end def test_duplicate_window_deprecation @@ -366,8 +601,268 @@ def test_duplicate_window_deprecation end end + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=recurring/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_cit_used + options = stored_credential_options(:cardholder, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=recurring/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_initial + options = stored_credential_options(:merchant, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=recurring/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_used + options = stored_credential_options(:merchant, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=recurring/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_cit_initial + options = stored_credential_options(:cardholder, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=installment/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_cit_used + options = stored_credential_options(:cardholder, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_mit_initial + options = stored_credential_options(:merchant, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=installment/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_mit_used + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_initial + options = stored_credential_options(:cardholder, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + refute_match(/billing_method/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_used + options = stored_credential_options(:cardholder, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_initial + options = stored_credential_options(:merchant, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + refute_match(/billing_method/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_used + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_purchase_with_stored_credential + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_takes_precedence_over_recurring_option + options = stored_credential_options(:merchant, :installment, id: 'abc123').merge(recurring: true) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_takes_precedence_over_recurring_option + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123').merge(recurring: true) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + private + def test_verify(options = {}) + response = stub_comms do + @gateway.verify(@credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=validate/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + + test_level3_options(data) if options.any? + end.respond_with(successful_validate_response) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_level3_options(data) + assert_match(/tax=5.25/, data) + assert_match(/shipping=10.51/, data) + assert_match(/ponumber=1002/, data) + end + + def test_transaction_options(data) + assert_match(/billing_method=recurring/, data) + assert_match(/orderid=#{CGI.escape("#1001")}/, data) + assert_match(/orderdescription=AM\+test/, data) + assert_match(/currency=GBP/, data) + assert_match(/dup_seconds=15/, data) + assert_match(/customer_id=123/, data) + + test_level3_options(data) + end + + def stored_credential_options(*args, id: nil) + { + order_id: '#1001', + description: 'AM test', + currency: 'GBP', + dup_seconds: 15, + customer: '123', + tax: 5.25, + shipping: 10.51, + ponumber: 1002, + stored_credential: stored_credential(*args, id: id) + } + end + def successful_purchase_response 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762757839&avsresponse=N&cvvresponse=N&orderid=b6c1c57f709cfaa65a5cf5b8532ad181&type=&response_code=100' end @@ -384,6 +879,10 @@ def failed_echeck_purchase_response 'response=2&responsetext=FAILED&authcode=123456&transactionid=2762783009&avsresponse=&cvvresponse=&orderid=8070b75a09d75c3e84e1c17d44bbbf34&type=&response_code=200' end + def successful_3ds_purchase_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=97267598-FAE6-48F2-8083-C23433990FBC&avsresponse=&cvvresponse=&orderid=b6c1c57f709cfaa65a5cf5b8532ad181&type=&response_code=100' + end + def successful_authorization_response 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762787830&avsresponse=N&cvvresponse=N&orderid=7655856b032e28d2106d724fc26cd04d&type=&response_code=100' end @@ -446,9 +945,9 @@ def successful_echeck_store_response def transcript ' - amount=1.00&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&orderdescription=Store+purchase¤cy=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=4111111111111111&cvv=917&ccexp=0916&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=password + amount=1.00&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&orderdescription=Store+purchase¤cy=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=4111111111111111&cvv=917&ccexp=0916&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=passwordw$thsym%ols response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767466670&avsresponse=N&cvvresponse=N&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&type=sale&response_code=100 - amount=1.00&orderid=e88df316d8ba3c8c6b98aa93b78facc0&orderdescription=Store+purchase¤cy=USD&payment=check&checkname=Jim+Smith&checkaba=123123123&checkaccount=123123123&account_holder_type=personal&account_type=checking&sec_code=WEB&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=password + amount=1.00&orderid=e88df316d8ba3c8c6b98aa93b78facc0&orderdescription=Store+purchase¤cy=USD&payment=check&checkname=Jim+Smith&checkaba=123123123&checkaccount=123123123&account_holder_type=personal&account_type=checking&sec_code=WEB&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=passwordw$thsym%ols response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767467157&avsresponse=&cvvresponse=&orderid=e88df316d8ba3c8c6b98aa93b78facc0&type=sale&response_code=100 ' end diff --git a/test/unit/gateways/ogone_test.rb b/test/unit/gateways/ogone_test.rb index 8454a030cfd..906ea36c184 100644 --- a/test/unit/gateways/ogone_test.rb +++ b/test/unit/gateways/ogone_test.rb @@ -1,25 +1,24 @@ require 'test_helper' class OgoneTest < Test::Unit::TestCase - def setup - @credentials = { :login => 'pspid', - :user => 'username', - :password => 'password', - :signature => 'mynicesig', - :signature_encryptor => 'sha512', - :timeout => '30' } + @credentials = { login: 'pspid', + user: 'username', + password: 'password', + signature: 'mynicesig', + signature_encryptor: 'sha512', + timeout: '30' } @gateway = OgoneGateway.new(@credentials) @credit_card = credit_card - @mastercard = credit_card('5399999999999999', :brand => 'mastercard') + @mastercard = credit_card('5399999999999999', brand: 'mastercard') @amount = 100 @identification = '3014726' @billing_id = 'myalias' @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @parameters = { 'orderID' => '1', @@ -42,6 +41,10 @@ def teardown Base.mode = :test end + def test_should_have_homepage_url + assert_equal 'https://www.ingenico.com/login/ogone/', OgoneGateway.homepage_url + end + def test_successful_purchase @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @@ -57,7 +60,7 @@ def test_successful_purchase_with_action_param @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:action => 'SAS')) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(action: 'SAS')) assert_success response assert_equal '3014726;SAS', response.authorization assert response.params['HTML_ANSWER'].nil? @@ -77,7 +80,7 @@ def test_successful_purchase_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:eci => 4)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(eci: 4)) assert_success response assert_equal '3014726;SAL', response.authorization assert response.test? @@ -85,7 +88,7 @@ def test_successful_purchase_with_custom_eci def test_successful_purchase_with_3dsecure @gateway.expects(:ssl_post).returns(successful_3dsecure_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:d3d => true)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(d3d: true)) assert_success response assert_equal '3014726;SAL', response.authorization assert response.params['HTML_ANSWER'] @@ -127,7 +130,7 @@ def test_successful_authorize_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:eci => 4)) + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(eci: 4)) assert_success response assert_equal '3014726;RES', response.authorization assert response.test? @@ -135,7 +138,7 @@ def test_successful_authorize_with_custom_eci def test_successful_authorize_with_3dsecure @gateway.expects(:ssl_post).returns(successful_3dsecure_purchase_response) - assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:d3d => true)) + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(d3d: true)) assert_success response assert_equal '3014726;RES', response.authorization assert response.params['HTML_ANSWER'] @@ -153,7 +156,7 @@ def test_successful_capture def test_successful_capture_with_action_option @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, '3048326', :action => 'SAS') + assert response = @gateway.capture(@amount, '3048326', action: 'SAS') assert_success response assert_equal '3048326;SAS', response.authorization assert response.test? @@ -208,19 +211,19 @@ def test_failed_verify end def test_successful_store - @gateway.expects(:authorize).with(1, @credit_card, :billing_id => @billing_id).returns(OgoneResponse.new(true, '', @gateway.send(:parse, successful_purchase_response), :authorization => '3014726;RES')) + @gateway.expects(:authorize).with(1, @credit_card, billing_id: @billing_id).returns(OgoneResponse.new(true, '', @gateway.send(:parse, successful_purchase_response), authorization: '3014726;RES')) @gateway.expects(:void).with('3014726;RES') - assert response = @gateway.store(@credit_card, :billing_id => @billing_id) + assert response = @gateway.store(@credit_card, billing_id: @billing_id) assert_success response assert_equal '3014726;RES', response.authorization assert_equal @billing_id, response.billing_id end def test_store_amount_at_gateway_level - gateway = OgoneGateway.new(@credentials.merge(:store_amount => 100)) - gateway.expects(:authorize).with(100, @credit_card, :billing_id => @billing_id).returns(OgoneResponse.new(true, '', gateway.send(:parse, successful_purchase_response_100), :authorization => '3014726;RES')) + gateway = OgoneGateway.new(@credentials.merge(store_amount: 100)) + gateway.expects(:authorize).with(100, @credit_card, billing_id: @billing_id).returns(OgoneResponse.new(true, '', gateway.send(:parse, successful_purchase_response_100), authorization: '3014726;RES')) gateway.expects(:void).with('3014726;RES') - assert response = gateway.store(@credit_card, :billing_id => @billing_id) + assert response = gateway.store(@credit_card, billing_id: @billing_id) assert_success response assert_equal '3014726;RES', response.authorization assert_equal @billing_id, response.billing_id @@ -231,7 +234,7 @@ def test_deprecated_store_option @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) assert_deprecation_warning(OgoneGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE) do - assert response = @gateway.store(@credit_card, :store => @billing_id) + assert response = @gateway.store(@credit_card, store: @billing_id) assert_success response assert_equal '3014726;RES', response.authorization assert response.test? @@ -255,11 +258,11 @@ def test_create_readable_error_message_upon_failure end def test_supported_countries - assert_equal ['BE', 'DE', 'FR', 'NL', 'AT', 'CH'], OgoneGateway.supported_countries + assert_equal %w[BE DE FR NL AT CH], OgoneGateway.supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro], OgoneGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club discover jcb maestro], OgoneGateway.supported_cardtypes end def test_default_currency @@ -273,7 +276,7 @@ def test_default_currency end def test_custom_currency_at_gateway_level - gateway = OgoneGateway.new(@credentials.merge(:currency => 'USD')) + gateway = OgoneGateway.new(@credentials.merge(currency: 'USD')) gateway.expects(:add_pair).at_least(1) gateway.expects(:add_pair).with(anything, 'currency', 'USD') gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -281,11 +284,11 @@ def test_custom_currency_at_gateway_level end def test_local_custom_currency_overwrite_gateway_level - gateway = OgoneGateway.new(@credentials.merge(:currency => 'USD')) + gateway = OgoneGateway.new(@credentials.merge(currency: 'USD')) gateway.expects(:add_pair).at_least(1) gateway.expects(:add_pair).with(anything, 'currency', 'EUR') gateway.expects(:ssl_post).returns(successful_purchase_response) - gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'EUR')) + gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR')) end def test_avs_result @@ -344,13 +347,13 @@ def test_format_error_message_with_no_separator end def test_without_signature - gateway = OgoneGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => nil)) + gateway = OgoneGateway.new(@credentials.merge(signature: nil, signature_encryptor: nil)) gateway.expects(:ssl_post).returns(successful_purchase_response) assert_deprecation_warning(OgoneGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) do gateway.purchase(@amount, @credit_card, @options) end - gateway = OgoneGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => 'none')) + gateway = OgoneGateway.new(@credentials.merge(signature: nil, signature_encryptor: 'none')) gateway.expects(:ssl_post).returns(successful_purchase_response) assert_no_deprecation_warning do gateway.purchase(@amount, @credit_card, @options) @@ -358,27 +361,27 @@ def test_without_signature end def test_signature_for_accounts_created_before_10_may_20101 - gateway = OgoneGateway.new(@credentials.merge(:signature_encryptor => nil)) + gateway = OgoneGateway.new(@credentials.merge(signature_encryptor: nil)) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA1.hexdigest('1100EUR4111111111111111MrPSPIDRES2mynicesig').upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha1 - gateway = OgoneGateway.new(@credentials.merge(:signature_encryptor => 'sha1')) + gateway = OgoneGateway.new(@credentials.merge(signature_encryptor: 'sha1')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA1.hexdigest(string_to_digest).upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha256 - gateway = OgoneGateway.new(@credentials.merge(:signature_encryptor => 'sha256')) + gateway = OgoneGateway.new(@credentials.merge(signature_encryptor: 'sha256')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA256.hexdigest(string_to_digest).upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha512 - gateway = OgoneGateway.new(@credentials.merge(:signature_encryptor => 'sha512')) + gateway = OgoneGateway.new(@credentials.merge(signature_encryptor: 'sha512')) assert signature = gateway.send(:add_signature, @parameters) assert_equal Digest::SHA512.hexdigest(string_to_digest).upcase, signature end @@ -393,13 +396,13 @@ def test_3dsecure_win_3ds_option post = {} gateway = OgoneGateway.new(@credentials) - gateway.send(:add_d3d, post, { :win_3ds => :pop_up }) + gateway.send(:add_d3d, post, { win_3ds: :pop_up }) assert 'POPUP', post['WIN3DS'] - gateway.send(:add_d3d, post, { :win_3ds => :pop_ix }) + gateway.send(:add_d3d, post, { win_3ds: :pop_ix }) assert 'POPIX', post['WIN3DS'] - gateway.send(:add_d3d, post, { :win_3ds => :invalid }) + gateway.send(:add_d3d, post, { win_3ds: :invalid }) assert 'MAINW', post['WIN3DS'] end @@ -408,16 +411,16 @@ def test_3dsecure_additional_options gateway = OgoneGateway.new(@credentials) gateway.send(:add_d3d, post, { - :http_accept => 'text/html', - :http_user_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', - :accept_url => 'https://accept_url', - :decline_url => 'https://decline_url', - :exception_url => 'https://exception_url', - :cancel_url => 'https://cancel_url', - :paramvar => 'param_var', - :paramplus => 'param_plus', - :complus => 'com_plus', - :language => 'fr_FR' + http_accept: 'text/html', + http_user_agent: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', + accept_url: 'https://accept_url', + decline_url: 'https://decline_url', + exception_url: 'https://exception_url', + cancel_url: 'https://cancel_url', + paramvar: 'param_var', + paramplus: 'param_plus', + complus: 'com_plus', + language: 'fr_FR' }) assert_equal post['HTTP_ACCEPT'], 'text/html' assert_equal post['HTTP_USER_AGENT'], 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)' @@ -466,7 +469,7 @@ def d3d_string_to_digest end def successful_authorize_response - <<-END + <<-XML - END + XML end def successful_purchase_response - <<-END + <<-XML - END + XML end def successful_purchase_response_100 - <<-END + <<-XML - END + XML end def successful_3dsecure_purchase_response - <<-END + <<-XML - END + XML end def failed_purchase_response - <<-END + <<-XML - END + XML end def successful_capture_response - <<-END + <<-XML - END + XML end def successful_void_response - <<-END + <<-XML - END + XML end def successful_referenced_credit_response - <<-END + <<-XML - END + XML end def successful_unreferenced_credit_response - <<-END + <<-XML - END + XML end def failed_authorization_response - <<-END + <<-XML - END + XML end def pre_scrub @@ -806,5 +809,4 @@ def post_scrub Conn close } end - end diff --git a/test/unit/gateways/omise_test.rb b/test/unit/gateways/omise_test.rb index 5c31855fd64..2e0baf68e30 100644 --- a/test/unit/gateways/omise_test.rb +++ b/test/unit/gateways/omise_test.rb @@ -23,11 +23,11 @@ def setup end def test_supported_countries - assert_equal @gateway.supported_countries, %w( TH JP ) + assert_equal @gateway.supported_countries, %w(TH JP) end def test_supported_cardtypes - assert_equal @gateway.supported_cardtypes, [:visa, :master, :jcb] + assert_equal @gateway.supported_cardtypes, %i[visa master jcb] end def test_supports_scrubbing @@ -50,7 +50,7 @@ def test_request_headers end def test_post_data - post_data = @gateway.send(:post_data, { card: {number: '4242424242424242'} }) + post_data = @gateway.send(:post_data, { card: { number: '4242424242424242' } }) assert_equal '{"card":{"number":"4242424242424242"}}', post_data end @@ -73,7 +73,7 @@ def test_error_response def test_error_code_from response = @gateway.send(:parse, invalid_security_code_response) - error_code = @gateway.send(:error_code_from, response) + error_code = @gateway.send(:error_code_from, response) assert_equal 'invalid_security_code', error_code end @@ -90,7 +90,7 @@ def test_invalid_cvc end def test_card_declined - card_declined = @gateway.send(:parse, failed_capture_response) + card_declined = @gateway.send(:parse, failed_capture_response) card_declined_code = @gateway.send(:standard_error_code_mapping, card_declined) assert_equal 'card_declined', card_declined_code end @@ -138,7 +138,7 @@ def test_add_creditcard def test_add_customer_without_card result = {} customer_id = 'cust_test_4zjzcgm8kpdt4xdhdw2' - @gateway.send(:add_customer, result, {customer_id: customer_id}) + @gateway.send(:add_customer, result, { customer_id: customer_id }) assert_equal 'cust_test_4zjzcgm8kpdt4xdhdw2', result[:customer] end @@ -146,21 +146,21 @@ def test_add_customer_with_card_id result = {} customer_id = 'cust_test_4zjzcgm8kpdt4xdhdw2' result[:card] = 'card_test_4zguktjcxanu3dw171a' - @gateway.send(:add_customer, result, {customer_id: customer_id}) + @gateway.send(:add_customer, result, { customer_id: customer_id }) assert_equal customer_id, result[:customer] end def test_add_amount result = {} desc = 'Charge for order 3947' - @gateway.send(:add_amount, result, @amount, {description: desc}) + @gateway.send(:add_amount, result, @amount, { description: desc }) assert_equal desc, result[:description] end def test_add_amount_with_correct_currency result = {} jpy_currency = 'JPY' - @gateway.send(:add_amount, result, @amount, {currency: jpy_currency}) + @gateway.send(:add_amount, result, @amount, { currency: jpy_currency }) assert_equal jpy_currency, result[:currency] end @@ -812,5 +812,4 @@ def failed_capture_response } RESPONSE end - end diff --git a/test/unit/gateways/openpay_test.rb b/test/unit/gateways/openpay_test.rb index 5c15f77e489..1cc39b539ec 100644 --- a/test/unit/gateways/openpay_test.rb +++ b/test/unit/gateways/openpay_test.rb @@ -31,6 +31,40 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_mexico_url + gateway = OpenpayGateway.new( + key: 'key', + merchant_id: 'merchant_id', + merchant_country: 'MX' + ) + + gateway.expects(:ssl_request).returns(successful_purchase_response) + assert_equal gateway.gateway_url, OpenpayGateway.mx_test_url + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'tay1mauq3re4iuuk8bm4', response.authorization + assert response.test? + end + + def test_default_url_when_merchant_country_is_not_present + gateway = OpenpayGateway.new( + key: 'key', + merchant_id: 'merchant_id' + ) + assert_equal 'https://sandbox-api.openpay.co/v1/', gateway.gateway_url + end + + def test_set_mexico_url_using_merchant_country_flag + gateway = OpenpayGateway.new( + key: 'key', + merchant_id: 'merchant_id', + merchant_country: 'MX' + ) + assert_equal 'https://sandbox-api.openpay.mx/v1/', gateway.gateway_url + end + def test_unsuccessful_request @gateway.expects(:ssl_request).returns(failed_purchase_response) @@ -113,7 +147,7 @@ def test_unsuccessful_verify def test_successful_purchase_with_card_id @gateway.expects(:ssl_request).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, {credit_card: 'a2b79p8xmzeyvmolqfja'}, @options) + assert response = @gateway.purchase(@amount, { credit_card: 'a2b79p8xmzeyvmolqfja' }, @options) assert_instance_of Response, response assert_success response @@ -171,7 +205,7 @@ def test_successful_unstore def test_passing_device_session_id response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, device_session_id: 'TheDeviceSessionID') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(%r{"device_session_id":"TheDeviceSessionID"}, data) end.respond_with(successful_purchase_response) @@ -181,7 +215,7 @@ def test_passing_device_session_id def test_passing_payment_installments response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, payments: '6') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(%r{"payments":"6"}, data) assert_match(%r{"payment_plan":}, data) end.respond_with(successful_purchase_response) @@ -208,225 +242,225 @@ def test_whitespace_string_cvv_transcript_scrubbing private def successful_new_card - <<-RESPONSE -{ - "type":"debit", - "brand":"mastercard", - "address":{ - "line1":"Av 5 de Febrero", - "line2":"Roble 207", - "line3":"col carrillo", - "state":"Queretaro", - "city":"Queretaro", - "postal_code":"76900", - "country_code":"MX" - }, - "id":"kgipbqixvjg3gbzowl7l", - "card_number":"1111", - "holder_name":"Juan Perez Ramirez", - "expiration_year":"20", - "expiration_month":"12", - "allows_charges":true, - "allows_payouts":false, - "creation_date":"2013-12-12T17:50:00-06:00", - "bank_name":"DESCONOCIDO", - "bank_code":"000", - "customer_id":"a2b79p8xmzeyvmolqfja" -} + <<~RESPONSE + { + "type":"debit", + "brand":"mastercard", + "address":{ + "line1":"Av 5 de Febrero", + "line2":"Roble 207", + "line3":"col carrillo", + "state":"Queretaro", + "city":"Queretaro", + "postal_code":"76900", + "country_code":"MX" + }, + "id":"kgipbqixvjg3gbzowl7l", + "card_number":"1111", + "holder_name":"Juan Perez Ramirez", + "expiration_year":"20", + "expiration_month":"12", + "allows_charges":true, + "allows_payouts":false, + "creation_date":"2013-12-12T17:50:00-06:00", + "bank_name":"DESCONOCIDO", + "bank_code":"000", + "customer_id":"a2b79p8xmzeyvmolqfja" + } RESPONSE end def successful_new_customer - <<-RESPONSE -{ - "id":"a2b79p8xmzeyvmolqfja", - "name":"Anacleto", - "last_name":"Morones", - "email":"morones.an@elllano.com", - "phone_number":"44209087654", - "status":"active", - "balance":0, - "clabe":"646180109400003235", - "address":{ - "line1":"Camino Real", - "line2":"Col. San Pablo", - "state":"Queretaro", - "city":"Queretaro", - "postal_code":"76000", - "country_code":"MX" - }, - "creation_date":"2013-12-12T16:29:11-06:00" -} + <<~RESPONSE + { + "id":"a2b79p8xmzeyvmolqfja", + "name":"Anacleto", + "last_name":"Morones", + "email":"morones.an@elllano.com", + "phone_number":"44209087654", + "status":"active", + "balance":0, + "clabe":"646180109400003235", + "address":{ + "line1":"Camino Real", + "line2":"Col. San Pablo", + "state":"Queretaro", + "city":"Queretaro", + "postal_code":"76000", + "country_code":"MX" + }, + "creation_date":"2013-12-12T16:29:11-06:00" + } RESPONSE end def successful_refunded_response - <<-RESPONSE -{ - "amount": 1.00, - "authorization": "801585", - "method": "card", - "operation_type": "in", - "transaction_type": "charge", - "card": { - "type": "debit", - "brand": "mastercard", - "address": { - "line1": "1234 My Street", - "line2": "Apt 1", - "line3": null, - "state": "ON", - "city": "Ottawa", - "postal_code": "K1C2N6", - "country_code": "CA" - }, - "card_number": "1111", - "holder_name": "Longbob Longsen", - "expiration_year": "15", - "expiration_month": "09", - "allows_charges": true, - "allows_payouts": false, - "creation_date": "2014-01-20T17:08:43-06:00", - "bank_name": "DESCONOCIDO", - "bank_code": "000", - "customer_id": null - }, - "status": "completed", - "refund": { - "amount": 1.00, - "authorization": "030706", - "method": "card", - "operation_type": "out", - "transaction_type": "refund", - "status": "completed", - "currency": "MXN", - "id": "tspoc4u9msdbnkkhpcmi", - "creation_date": "2014-01-20T17:08:44-06:00", - "description": "Store Purchase", - "error_message": null, - "order_id": null - }, - "currency": "MXN", - "id": "tei4hnvyp4agt5ecnbow", - "creation_date": "2014-01-20T17:08:43-06:00", - "description": "Store Purchase", - "error_message": null, - "order_id": null, - "error_code": null -} + <<~RESPONSE + { + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": { + "line1": "1234 My Street", + "line2": "Apt 1", + "line3": null, + "state": "ON", + "city": "Ottawa", + "postal_code": "K1C2N6", + "country_code": "CA" + }, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-20T17:08:43-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "completed", + "refund": { + "amount": 1.00, + "authorization": "030706", + "method": "card", + "operation_type": "out", + "transaction_type": "refund", + "status": "completed", + "currency": "MXN", + "id": "tspoc4u9msdbnkkhpcmi", + "creation_date": "2014-01-20T17:08:44-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null + }, + "currency": "MXN", + "id": "tei4hnvyp4agt5ecnbow", + "creation_date": "2014-01-20T17:08:43-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null + } RESPONSE end def successful_capture_response - <<-RESPONSE -{ - "amount": 1.00, - "authorization": "801585", - "method": "card", - "operation_type": "in", - "transaction_type": "charge", - "card": { - "type": "debit", - "brand": "mastercard", - "address": null, - "card_number": "1111", - "holder_name": "Longbob Longsen", - "expiration_year": "15", - "expiration_month": "09", - "allows_charges": true, - "allows_payouts": false, - "creation_date": "2014-01-18T21:01:10-06:00", - "bank_name": "DESCONOCIDO", - "bank_code": "000", - "customer_id": null - }, - "status": "completed", - "currency": "MXN", - "id": "tubpycc6gtsk71fu3tsd", - "creation_date": "2014-01-18T21:01:10-06:00", - "description": "Store Purchase", - "error_message": null, - "order_id": null, - "error_code": null -} + <<~RESPONSE + { + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": null, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:01:10-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "completed", + "currency": "MXN", + "id": "tubpycc6gtsk71fu3tsd", + "creation_date": "2014-01-18T21:01:10-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null + } RESPONSE end def successful_authorization_response - <<-RESPONSE -{ - "amount": 1.00, - "authorization": "801585", - "method": "card", - "operation_type": "in", - "transaction_type": "charge", - "card": { - "type": "debit", - "brand": "mastercard", - "address": null, - "card_number": "1111", - "holder_name": "Longbob Longsen", - "expiration_year": "15", - "expiration_month": "09", - "allows_charges": true, - "allows_payouts": false, - "creation_date": "2014-01-18T21:01:10-06:00", - "bank_name": "DESCONOCIDO", - "bank_code": "000", - "customer_id": null - }, - "status": "in_progress", - "currency": "MXN", - "id": "tubpycc6gtsk71fu3tsd", - "creation_date": "2014-01-18T21:01:10-06:00", - "description": "Store Purchase", - "error_message": null, - "order_id": null, - "error_code": null -} - RESPONSE + <<~RESPONSE + { + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": null, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:01:10-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "in_progress", + "currency": "MXN", + "id": "tubpycc6gtsk71fu3tsd", + "creation_date": "2014-01-18T21:01:10-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null + } + RESPONSE end def successful_purchase_response(status = 'completed') - <<-RESPONSE -{ - "amount": 1.00, - "authorization": "801585", - "method": "card", - "operation_type": "in", - "transaction_type": "charge", - "card": { - "type": "debit", - "brand": "mastercard", - "address": { - "line1": "1234 My Street", - "line2": "Apt 1", - "line3": null, - "state": "ON", - "city": "Ottawa", - "postal_code": "K1C2N6", - "country_code": "CA" - }, - "card_number": "1111", - "holder_name": "Longbob Longsen", - "expiration_year": "15", - "expiration_month": "09", - "allows_charges": true, - "allows_payouts": false, - "creation_date": "2014-01-18T21:49:38-06:00", - "bank_name": "BANCOMER", - "bank_code": "012", - "customer_id": null - }, - "status": "#{status}", - "currency": "MXN", - "id": "tay1mauq3re4iuuk8bm4", - "creation_date": "2014-01-18T21:49:38-06:00", - "description": "Store Purchase", - "error_message": null, - "order_id": null, - "error_code": null -} + <<~RESPONSE + { + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": { + "line1": "1234 My Street", + "line2": "Apt 1", + "line3": null, + "state": "ON", + "city": "Ottawa", + "postal_code": "K1C2N6", + "country_code": "CA" + }, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:49:38-06:00", + "bank_name": "BANCOMER", + "bank_code": "012", + "customer_id": null + }, + "status": "#{status}", + "currency": "MXN", + "id": "tay1mauq3re4iuuk8bm4", + "creation_date": "2014-01-18T21:49:38-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null + } RESPONSE end @@ -435,26 +469,26 @@ def successful_void_response end def failed_purchase_response - <<-RESPONSE -{ - "category": "gateway", - "description": "The card was declined", - "http_code": 402, - "error_code": 3001, - "request_id": "337cf033-9cd6-4314-a880-c71700e1625f" -} + <<~RESPONSE + { + "category": "gateway", + "description": "The card was declined", + "http_code": 402, + "error_code": 3001, + "request_id": "337cf033-9cd6-4314-a880-c71700e1625f" + } RESPONSE end def failed_authorize_response - <<-RESPONSE -{ - "category":"gateway", - "description":"The card is not supported on online transactions", - "http_code":412, - "error_code":3008, - "request_id":"a4001ef2-7613-4ec8-a23b-4de45154dbe4" -} + <<~RESPONSE + { + "category":"gateway", + "description":"The card is not supported on online transactions", + "http_code":412, + "error_code":3008, + "request_id":"a4001ef2-7613-4ec8-a23b-4de45154dbe4" + } RESPONSE end diff --git a/test/unit/gateways/opp_test.rb b/test/unit/gateways/opp_test.rb index a59885917d5..ec7af16f8bf 100644 --- a/test/unit/gateways/opp_test.rb +++ b/test/unit/gateways/opp_test.rb @@ -7,8 +7,8 @@ def setup @gateway = OppGateway.new(fixtures(:opp)) @amount = 100 - @valid_card = credit_card('4200000000000000', month: 05, year: 2018, verification_value: '123') - @invalid_card = credit_card('4444444444444444', month: 05, year: 2018, verification_value: '123') + @valid_card = credit_card('4200000000000000', month: 05, year: Date.today.year + 2, verification_value: '123') + @invalid_card = credit_card('4444444444444444', month: 05, year: Date.today.year + 2, verification_value: '123') request_type = 'complete' # 'minimal' || 'complete' time = Time.now.to_i @@ -27,7 +27,7 @@ def setup city: 'Istambul', state: 'IS', zip: 'H12JK2354', - country: 'TR', + country: 'TR' }, shipping_address: { name: '', @@ -35,7 +35,7 @@ def setup city: 'Moskau', state: 'MO', zip: 'MO2342432', - country: 'RU', + country: 'RU' }, customer: { merchant_customer_id: "merchantCustomerId #{ip}", @@ -48,13 +48,13 @@ def setup company_name: 'No such deal Ltd.', identification_doctype: 'PASSPORT', identification_docid: 'FakeID2342431234123', - ip: ip, - }, + ip: ip + } } @minimal_request_options = { order_id: "Order #{time}", - description: 'Store Purchase - Books', + description: 'Store Purchase - Books' } @complete_request_options['customParameters[SHOPPER_test124TestName009]'] = 'customParameters_test' @@ -121,6 +121,14 @@ def test_successful_void assert void.test? end + def test_successful_store + @gateway.expects(:raw_ssl_request).returns(successful_store_response(@test_success_id)) + store = @gateway.store(@valid_card) + assert_success store + assert_equal "Request successfully processed in 'Merchant in Integrator Test Mode'", store.message + assert_equal @test_success_id, store.authorization + end + # ****************************************** FAILURE TESTS ****************************************** def test_failed_purchase @gateway.expects(:raw_ssl_request).returns(failed_response('DB', @test_failure_id)) @@ -157,12 +165,19 @@ def test_failed_void assert_equal '100.100.101', response.error_code end + def test_failed_store + @gateway.expects(:raw_ssl_request).returns(failed_store_response(@test_failure_id)) + store = @gateway.store(@invalid_card) + assert_failure store + assert_equal '100.100.101', store.error_code + end + def test_passes_3d_secure_fields - options = @complete_request_options.merge({eci: 'eci', cavv: 'cavv', xid: 'xid'}) + options = @complete_request_options.merge({ eci: 'eci', cavv: 'cavv', xid: 'xid' }) response = stub_comms(@gateway, :raw_ssl_request) do @gateway.purchase(@amount, @valid_card, options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/threeDSecure.eci=eci/, data) assert_match(/threeDSecure.verificationId=cavv/, data) assert_match(/threeDSecure.xid=xid/, data) @@ -179,27 +194,109 @@ def test_scrub private def pre_scrubbed - 'paymentType=DB&amount=1.00¤cy=EUR&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=4200000000000000&card.expiryMonth=05&card.expiryYear=2018& card.cvv=123&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.entityId=8a8294174b7ecb28014b9699220015ca&authentication.password=sy6KJsT8&authentication.userId=8a8294174b7ecb28014b9699220015cc' + 'paymentType=DB&amount=1.00¤cy=EUR&descriptor=&merchantInvoiceId=&merchantTransactionId=50b5c1763c20c456a6208f7831dd0a04&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=4200000000000000&card.expiryMonth=05&card.expiryYear=2022&card.cvv=123&customParameters[SHOPPER_pluginId]=activemerchant&authentication.entityId=5c6602174b7ecb28014b96992' end def post_scrubbed - 'paymentType=DB&amount=1.00¤cy=EUR&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=[FILTERED]&card.expiryMonth=05&card.expiryYear=2018& card.cvv=[FILTERED]&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.entityId=8a8294174b7ecb28014b9699220015ca&authentication.password=[FILTERED]&authentication.userId=8a8294174b7ecb28014b9699220015cc' + 'paymentType=DB&amount=1.00¤cy=EUR&descriptor=&merchantInvoiceId=&merchantTransactionId=50b5c1763c20c456a6208f7831dd0a04&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=[FILTERED]&card.expiryMonth=05&card.expiryYear=2022&card.cvv=[FILTERED]&customParameters[SHOPPER_pluginId]=activemerchant&authentication.entityId=5c6602174b7ecb28014b96992' end def successful_response(type, id) - OppMockResponse.new(200, - JSON.generate({'id' => id, 'paymentType' => type, 'paymentBrand' => 'VISA', 'amount' => '1.00', 'currency' => 'EUR', "des - criptor" => '5410.9959.0306 OPP_Channel ', 'result' => {'code' => '000.100.110', 'description' => "Request successfully processed in 'Merchant in Integrator Test Mode'"}, 'card' => {"bin - " => '420000', 'last4Digits' => '0000', 'holder' => 'Longbob Longsen', 'expiryMonth' => '05', 'expiryYear' => '2018'}, 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', "time - stamp" => '2015-06-20 19:31:01+0000', 'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9'}) + OppMockResponse.new( + 200, + JSON.generate({ + 'id' => id, + 'paymentType' => type, + 'paymentBrand' => 'VISA', + 'amount' => '1.00', + 'currency' => 'EUR', + 'descriptor' => '5410.9959.0306 OPP_Channel', + 'result' => { + 'code' => '000.100.110', + 'description' => "Request successfully processed in 'Merchant in Integrator Test Mode'" + }, + 'card' => { + 'bin' => '420000', + 'last4Digits' => '0000', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 19:31:01+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9' + }) ) end - def failed_response(type, id, code='100.100.101') - OppMockResponse.new(400, - JSON.generate({'id' => id, 'paymentType' => type, 'paymentBrand' => 'VISA', 'result' => {'code' => code, "des - cription" => 'invalid creditcard, bank account number or bank name'}, 'card' => {'bin' => '444444', 'last4Digits' => '4444', 'holder' => 'Longbob Longsen', 'expiryMonth' => '05', 'expiryYear' => '2018'}, - 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', 'timestamp' => '2015-06-20 20:40:26+0000', 'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d'}) + def successful_store_response(id) + OppMockResponse.new( + 200, + JSON.generate({ + 'id' => id, + 'result' => { + 'code' => '000.100.110', + 'description' => "Request successfully processed in 'Merchant in Integrator Test Mode'" + }, + 'card' => { + 'bin' => '420000', + 'last4Digits' => '0000', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 19:31:01+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9' + }) + ) + end + + def failed_response(type, id, code = '100.100.101') + OppMockResponse.new( + 400, + JSON.generate({ + 'id' => id, + 'paymentType' => type, + 'paymentBrand' => 'VISA', + 'result' => { + 'code' => code, + 'description' => 'invalid creditcard, bank account number or bank name' + }, + 'card' => { + 'bin' => '444444', + 'last4Digits' => '4444', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 20:40:26+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d' + }) + ) + end + + def failed_store_response(id, code = '100.100.101') + OppMockResponse.new( + 400, + JSON.generate({ + 'id' => id, + 'result' => { + 'code' => code, + 'description' => 'invalid creditcard, bank account number or bank name' + }, + 'card' => { + 'bin' => '444444', + 'last4Digits' => '4444', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 20:40:26+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d' + }) ) end @@ -211,5 +308,4 @@ def initialize(code, body) @body = body end end - end diff --git a/test/unit/gateways/optimal_payment_test.rb b/test/unit/gateways/optimal_payment_test.rb index f8d194e94ad..f5622504397 100644 --- a/test/unit/gateways/optimal_payment_test.rb +++ b/test/unit/gateways/optimal_payment_test.rb @@ -10,19 +10,19 @@ class OptimalPaymentTest < Test::Unit::TestCase def setup @gateway = OptimalPaymentGateway.new( - :account_number => '12345678', - :store_id => 'login', - :password => 'password' + account_number: '12345678', + store_id: 'login', + password: 'password' ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :email => 'email@example.com' + order_id: '1', + billing_address: address, + description: 'Store Purchase', + email: 'email@example.com' } end @@ -34,26 +34,26 @@ def test_full_request def test_ip_address_is_passed stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(ip: '1.2.3.4')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{customerIP%3E1.2.3.4%3C}, data end.respond_with(successful_purchase_response) end def test_minimal_request options = { - :order_id => '1', - :description => 'Store Purchase', - :billing_address => { - :zip => 'K1C2N6', + order_id: '1', + description: 'Store Purchase', + billing_address: { + zip: 'K1C2N6' } } credit_card = CreditCard.new( - :number => '4242424242424242', - :month => 9, - :year => Time.now.year + 1, - :first_name => 'Longbob', - :last_name => 'Longsen', - :brand => 'visa' + number: '4242424242424242', + month: 9, + year: Time.now.year + 1, + first_name: 'Longbob', + last_name: 'Longsen', + brand: 'visa' ) @gateway.instance_variable_set('@credit_card', credit_card) assert_match minimal_request, @gateway.cc_auth_request(@amount, options) @@ -73,7 +73,7 @@ def test_successful_purchase def test_purchase_from_canada_includes_state_field @options[:billing_address][:country] = 'CA' - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data =~ /state/ && data !~ /region/ end.returns(successful_purchase_response) @@ -82,7 +82,7 @@ def test_purchase_from_canada_includes_state_field def test_purchase_from_us_includes_state_field @options[:billing_address][:country] = 'US' - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data =~ /state/ && data !~ /region/ end.returns(successful_purchase_response) @@ -91,7 +91,7 @@ def test_purchase_from_us_includes_state_field def test_purchase_from_any_other_country_includes_region_field @options[:billing_address][:country] = 'GB' - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| data =~ /region/ && data !~ /state/ end.returns(successful_purchase_response) @@ -99,8 +99,8 @@ def test_purchase_from_any_other_country_includes_region_field end def test_purchase_with_shipping_address - @options[:shipping_address] = {:country => 'CA'} - @gateway.expects(:ssl_post).with do |url, data| + @options[:shipping_address] = { country: 'CA' } + @gateway.expects(:ssl_post).with do |_url, data| xml = data.split('&').detect { |string| string =~ /txnRequest=/ }.gsub('txnRequest=', '') doc = Nokogiri::XML.parse(CGI.unescape(xml)) doc.xpath('//xmlns:shippingDetails/xmlns:country').first.text == 'CA' && doc.to_s.include?('') @@ -111,7 +111,7 @@ def test_purchase_with_shipping_address def test_purchase_without_shipping_address @options[:shipping_address] = nil - @gateway.expects(:ssl_post).with do |url, data| + @gateway.expects(:ssl_post).with do |_url, data| xml = data.split('&').detect { |string| string =~ /txnRequest=/ }.gsub('txnRequest=', '') doc = Nokogiri::XML.parse(CGI.unescape(xml)) doc.to_s.include?('') == false @@ -131,22 +131,22 @@ def test_purchase_without_billing_address def test_cvd_fields_pass_correctly stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/cvdIndicator%3E1%3C\/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E123%3C\/cvd/, data) end.respond_with(successful_purchase_response) credit_card = CreditCard.new( - :number => '4242424242424242', - :month => 9, - :year => Time.now.year + 1, - :first_name => 'Longbob', - :last_name => 'Longsen', - :brand => 'visa' + number: '4242424242424242', + month: 9, + year: Time.now.year + 1, + first_name: 'Longbob', + last_name: 'Longsen', + brand: 'visa' ) stub_comms do @gateway.purchase(@amount, credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/cvdIndicator%3E0%3C\/cvdIndicator%3E%0A%20%20%3C\/card/, data) end.respond_with(failed_purchase_response) end @@ -174,10 +174,10 @@ def test_unsuccessful_request def test_in_production_with_test_param_sends_request_to_test_server ActiveMerchant::Billing::Base.mode = :production @gateway = OptimalPaymentGateway.new( - :account_number => '12345678', - :store_id => 'login', - :password => 'password', - :test => true + account_number: '12345678', + store_id: 'login', + password: 'password', + test: true ) @gateway.expects(:ssl_post).with('https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1', anything).returns(successful_purchase_response) @@ -214,17 +214,17 @@ def test_avs_results_not_in_response def test_deprecated_options assert_deprecation_warning("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.") do @gateway = OptimalPaymentGateway.new( - :account => '12345678', - :store_id => 'login', - :password => 'password' + account: '12345678', + store_id: 'login', + password: 'password' ) end assert_deprecation_warning("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.") do @gateway = OptimalPaymentGateway.new( - :account_number => '12345678', - :login => 'login', - :password => 'password' + account_number: '12345678', + login: 'login', + password: 'password' ) end end @@ -237,202 +237,202 @@ def test_scrub private def full_request - str = <<-XML - - - 12345678 - login - password - - 1 - 1.0 - - 4242424242424242 - - 9 - #{Time.now.year + 1} - - VI - 1 - 123 - - - WEB - Jim - Smith - 456 My Street - Apt 1 - Ottawa - ON - CA - K1C2N6 - (555)555-5555 - email@example.com - - + str = <<~XML + + + 12345678 + login + password + + 1 + 1.0 + + 4242424242424242 + + 9 + #{Time.now.year + 1} + + VI + 1 + 123 + + + WEB + Jim + Smith + 456 My Street + Apt 1 + Ottawa + ON + CA + K1C2N6 + (555)555-5555 + email@example.com + + XML Regexp.new(Regexp.escape(str).sub('xmlns', '[^>]+').sub('/>', '(/>|>]+>)')) end def minimal_request - str = <<-XML - - - 12345678 - login - password - - 1 - 1.0 - - 4242424242424242 - - 9 - #{Time.now.year + 1} - - VI - 0 - - - WEB - K1C2N6 - - + str = <<~XML + + + 12345678 + login + password + + 1 + 1.0 + + 4242424242424242 + + 9 + #{Time.now.year + 1} + + VI + 0 + + + WEB + K1C2N6 + + XML Regexp.new(Regexp.escape(str).sub('xmlns', '[^>]+').sub('/>', '(/>|>]+>)')) end # Place raw successful response from gateway here def successful_purchase_response - <<-XML - - 126740505 - ACCEPTED - 0 - No Error - 112232 - B - M - - InternalResponseCode - 0 - - - SubErrorCode - 0 - - - InternalResponseDescription - no_error - - 2009-01-08T17:00:45.210-05:00 - false - + <<~XML + + 126740505 + ACCEPTED + 0 + No Error + 112232 + B + M + + InternalResponseCode + 0 + + + SubErrorCode + 0 + + + InternalResponseDescription + no_error + + 2009-01-08T17:00:45.210-05:00 + false + XML end # Place raw successful response from gateway here def successful_purchase_response_without_avs_results - <<-XML - - 126740505 - ACCEPTED - 0 - No Error - 112232 - - InternalResponseCode - 0 - - - SubErrorCode - 0 - - - InternalResponseDescription - no_error - - 2009-01-08T17:00:45.210-05:00 - false - + <<~XML + + 126740505 + ACCEPTED + 0 + No Error + 112232 + + InternalResponseCode + 0 + + + SubErrorCode + 0 + + + InternalResponseDescription + no_error + + 2009-01-08T17:00:45.210-05:00 + false + XML end # Place raw failed response from gateway here def failed_purchase_response - <<-XML - - 126740506 - DECLINED - 3009 - D - Your request has been declined by the issuing bank. - B - M - - InternalResponseCode - 160 - - - SubErrorCode - 1005 - - - InternalResponseDescription - auth declined - - 2009-01-08T17:00:46.529-05:00 - false - + <<~XML + + 126740506 + DECLINED + 3009 + D + Your request has been declined by the issuing bank. + B + M + + InternalResponseCode + 160 + + + SubErrorCode + 1005 + + + InternalResponseDescription + auth declined + + 2009-01-08T17:00:46.529-05:00 + false + XML end def pre_scrubbed - <<-EOS -opening connection to webservices.test.optimalpayments.com:443... -opened -starting SSL for webservices.test.optimalpayments.com:443... -SSL established -<- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" -<- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3Etest%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E4387751111011%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E123%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" --> "HTTP/1.1 200 OK\r\n" --> "Server: WebServer32xS10i3\r\n" --> "Content-Length: 632\r\n" --> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" --> "Content-Type: application/xml\r\n" --> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 632 bytes... --> "<" --> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n498871860ACCEPTED0No Error369231XMInternalResponseCode0SubErrorCode0InternalResponseDescriptionno_error2018-02-12T16:57:42.289-05:00false" -read 632 bytes -Conn close - EOS + <<~REQUEST + opening connection to webservices.test.optimalpayments.com:443... + opened + starting SSL for webservices.test.optimalpayments.com:443... + SSL established + <- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" + <- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3Etest%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E4387751111011%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E123%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: WebServer32xS10i3\r\n" + -> "Content-Length: 632\r\n" + -> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" + -> "Content-Type: application/xml\r\n" + -> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 632 bytes... + -> "<" + -> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n498871860ACCEPTED0No Error369231XMInternalResponseCode0SubErrorCode0InternalResponseDescriptionno_error2018-02-12T16:57:42.289-05:00false" + read 632 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to webservices.test.optimalpayments.com:443... -opened -starting SSL for webservices.test.optimalpayments.com:443... -SSL established -<- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" -<- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3E[FILTERED]%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E[FILTERED]%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E[FILTERED]%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" --> "HTTP/1.1 200 OK\r\n" --> "Server: WebServer32xS10i3\r\n" --> "Content-Length: 632\r\n" --> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" --> "Content-Type: application/xml\r\n" --> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 632 bytes... --> "<" --> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n498871860ACCEPTED0No Error369231XMInternalResponseCode0SubErrorCode0InternalResponseDescriptionno_error2018-02-12T16:57:42.289-05:00false" -read 632 bytes -Conn close - EOS + <<~REQUEST + opening connection to webservices.test.optimalpayments.com:443... + opened + starting SSL for webservices.test.optimalpayments.com:443... + SSL established + <- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" + <- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3E[FILTERED]%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E[FILTERED]%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E[FILTERED]%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: WebServer32xS10i3\r\n" + -> "Content-Length: 632\r\n" + -> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" + -> "Content-Type: application/xml\r\n" + -> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 632 bytes... + -> "<" + -> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n498871860ACCEPTED0No Error369231XMInternalResponseCode0SubErrorCode0InternalResponseDescriptionno_error2018-02-12T16:57:42.289-05:00false" + read 632 bytes + Conn close + REQUEST end def pre_scrubbed_double_escaped diff --git a/test/unit/gateways/orbital_avs_result_test.rb b/test/unit/gateways/orbital_avs_result_test.rb index 9596e3c7e4c..32abbbded5f 100644 --- a/test/unit/gateways/orbital_avs_result_test.rb +++ b/test/unit/gateways/orbital_avs_result_test.rb @@ -26,13 +26,13 @@ def test_empty_data end def test_response_with_orbital_avs - response = Response.new(true, 'message', {}, :avs_result => OrbitalGateway::AVSResult.new('A')) + response = Response.new(true, 'message', {}, avs_result: OrbitalGateway::AVSResult.new('A')) assert_equal 'A', response.avs_result['code'] end def test_response_with_orbital_avs_nil - response = Response.new(true, 'message', {}, :avs_result => OrbitalGateway::AVSResult.new(nil)) + response = Response.new(true, 'message', {}, avs_result: OrbitalGateway::AVSResult.new(nil)) assert response.avs_result.has_key?('code') end diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb index 1ef99beb2db..16487eb36aa 100644 --- a/test/unit/gateways/orbital_test.rb +++ b/test/unit/gateways/orbital_test.rb @@ -8,13 +8,16 @@ class OrbitalGatewayTest < Test::Unit::TestCase def setup @gateway = ActiveMerchant::Billing::OrbitalGateway.new( - :login => 'login', - :password => 'password', - :merchant_id => 'merchant_id' + login: 'login', + password: 'password', + merchant_id: 'test12' ) @customer_ref_num = 'ABC' + @credit_card = credit_card('4556761029983886') + # Electronic Check object with test credentials of saving account + @echeck = check(account_number: '072403004', account_type: 'savings', routing_number: '072403004') - @level_2 = { + @level2 = { tax_indicator: '1', tax: '10', advice_addendum_1: 'taa1 - test', @@ -27,10 +30,56 @@ def setup address2: address[:address2], city: address[:city], state: address[:state], - zip: address[:zip], + zip: address[:zip] + } + + @level3 = { + freight_amount: '15', + duty_amount: '10', + dest_country: 'US', + ship_from_zip: '12345', + discount_amount: '20', + vat_tax: '25', + alt_tax: '30', + vat_rate: '7', + alt_ind: 'Y' + } + + @line_items = + [ + { + desc: 'credit card payment', + prod_cd: 'service', + qty: '30', + u_o_m: 'EAC', + tax_amt: '10', + tax_rate: '8.25', + line_tot: '20', + disc: '6', + unit_cost: '5', + gross_net: 'Y', + disc_ind: 'Y' + }, + { + desc: 'credit card payment', + prod_cd: 'service', + qty: '30', + u_o_m: 'EAC', + tax_amt: '10', + tax_rate: '8.25', + line_tot: '20', + disc: '6', + unit_cost: '5', + gross_net: 'Y', + disc_ind: 'Y' + } + ] + + @options = { + order_id: '1', + card_indicators: 'y' } - @options = { :order_id => '1'} @options_stored_credentials = { mit_msg_type: 'MRSB', mit_stored_credential_ind: 'Y', @@ -44,70 +93,365 @@ def setup network_transaction_id: 'abcdefg12345678' } } - @normalized_initial_stored_credential = { - stored_credential: { - initial_transaction: true, - initiator: 'customer' + @three_d_secure_options = { + three_d_secure: { + eci: '5', + xid: 'TESTXID', + cavv: 'TESTCAVV', + version: '2.2.0', + ds_transaction_id: '97267598FAE648F28083C23433990FBC' } } + + @google_pay_card = network_tokenization_credit_card( + '4777777777777778', + payment_cryptogram: 'BwAQCFVQdwEAABNZI1B3EGLyGC8=', + verification_value: '987', + source: :google_pay, + brand: 'visa', + eci: '5' + ) + end + + def test_supports_network_tokenization + assert_true @gateway.supports_network_tokenization? end def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(50, credit_card, :order_id => '1') + assert response = @gateway.purchase(50, credit_card, order_id: '1') assert_instance_of Response, response assert_success response assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization end - def test_level_2_data + def test_successful_purchase_with_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_with_echeck_response) + + assert response = @gateway.purchase(50, @echeck, order_id: '9baedc697f2cf06457de78') + assert_instance_of Response, response + assert_equal 'Approved', response.message + assert_success response + assert_equal '5F8E8BEE7299FD339A38F70CFF6E5D010EF55498;9baedc697f2cf06457de78', response.authorization + end + + def test_successful_purchase_with_commercial_echeck + commercial_echeck = check(account_number: '072403004', account_type: 'checking', account_holder_type: 'business', routing_number: '072403004') + stub_comms do - @gateway.purchase(50, credit_card, @options.merge(level_2_data: @level_2)) - end.check_request do |endpoint, data, headers| - assert_match %{#{@level_2[:tax_indicator].to_i}}, data - assert_match %{#{@level_2[:tax].to_i}}, data - assert_match %{#{@level_2[:advice_addendum_1]}}, data - assert_match %{#{@level_2[:advice_addendum_2]}}, data - assert_match %{#{@level_2[:advice_addendum_3]}}, data - assert_match %{#{@level_2[:advice_addendum_4]}}, data - assert_match %{#{@level_2[:purchase_order]}}, data - assert_match %{#{@level_2[:zip]}}, data - assert_match %{#{@level_2[:name]}}, data - assert_match %{#{@level_2[:address1]}}, data - assert_match %{#{@level_2[:address2]}}, data - assert_match %{#{@level_2[:city]}}, data - assert_match %{#{@level_2[:state]}}, data + @gateway.purchase(50, commercial_echeck, order_id: '9baedc697f2cf06457de78') + end.check_request do |_endpoint, data, _headers| + assert_match %{X}, data + end.respond_with(successful_purchase_with_echeck_response) + end + + def test_failed_purchase_with_echeck + @gateway.expects(:ssl_post).returns(failed_echeck_for_invalid_routing_response) + + assert response = @gateway.purchase(50, @echeck, order_id: '9baedc697f2cf06457de78') + assert_instance_of Response, response + assert_failure response + assert_equal 'Invalid ECP Account Route: []. The field is missing, invalid, or it has exceeded the max length of: [9].', response.message + assert_equal '888', response.params['proc_status'] + end + + def test_successful_force_capture_with_echeck + @gateway.expects(:ssl_post).returns(successful_force_capture_with_echeck_response) + + assert response = @gateway.purchase(31, @echeck, order_id: '2', force_capture: true) + 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 + end + + def test_successful_force_capture_with_echeck_prenote + @gateway.expects(:ssl_post).returns(successful_force_capture_with_echeck_prenote_response) + + assert response = @gateway.authorize(0, @echeck, order_id: '2', force_capture: true, action_code: 'W9') + 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 + end + + def test_failed_force_capture_with_echeck_prenote + @gateway.expects(:ssl_post).returns(failed_force_capture_with_echeck_prenote_response) + + assert response = @gateway.authorize(0, @echeck, order_id: '2', force_capture: true, action_code: 'W7') + assert_instance_of Response, response + assert_failure response + assert_equal ' EWS: Invalid Action Code [W7], For Transaction Type [A].', response.message + end + + def test_level2_data + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(level_2_data: @level2)) + end.check_request do |_endpoint, data, _headers| + assert_match %{#{@level2[:tax_indicator].to_i}}, data + assert_match %{#{@level2[:tax].to_i}}, data + assert_match %{#{@level2[:advice_addendum_1]}}, data + assert_match %{#{@level2[:advice_addendum_2]}}, data + assert_match %{#{@level2[:advice_addendum_3]}}, data + assert_match %{#{@level2[:advice_addendum_4]}}, data + assert_match %{#{@level2[:purchase_order]}}, data + assert_match %{#{@level2[:zip]}}, data + assert_match %{#{@level2[:name]}}, data + assert_match %{#{@level2[:address1]}}, data + assert_match %{#{@level2[:address2]}}, data + assert_match %{#{@level2[:city]}}, data + assert_match %{#{@level2[:state]}}, data + end.respond_with(successful_purchase_response) + end + + def test_level3_data + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(level_3_data: @level3)) + end.check_request do |_endpoint, data, _headers| + assert_match %{#{@level3[:freight_amount].to_i}}, data + assert_match %{#{@level3[:duty_amount].to_i}}, data + assert_match %{#{@level3[:dest_country]}}, data + assert_match %{#{@level3[:ship_from_zip].to_i}}, data + assert_match %{#{@level3[:discount_amount].to_i}}, data + assert_match %{#{@level3[:vat_tax].to_i}}, data + assert_match %{#{@level3[:vat_rate].to_i}}, data + assert_match %{#{@level3[:alt_tax].to_i}}, data + assert_match %{#{@level3[:alt_ind]}}, data + end.respond_with(successful_purchase_response) + end + + def test_line_items_data + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(line_items: @line_items)) + end.check_request do |_endpoint, data, _headers| + assert_match %{1}, data + assert_match %{#{@line_items[1][:desc]}}, data + assert_match %{#{@line_items[1][:prod_cd]}}, data + assert_match %{#{@line_items[1][:qty].to_i}}, data + assert_match %{#{@line_items[1][:u_o_m]}}, data + assert_match %{#{@line_items[1][:tax_amt].to_i}}, data + assert_match %{#{@line_items[1][:tax_rate]}}, data + assert_match %{#{@line_items[1][:line_tot].to_i}}, data + assert_match %{#{@line_items[1][:disc].to_i}}, data + assert_match %{#{@line_items[1][:unit_cost].to_i}}, data + assert_match %{#{@line_items[1][:gross_net]}}, data + assert_match %{#{@line_items[1][:disc_ind]}}, data + assert_match %{2}, data + end.respond_with(successful_purchase_response) + end + + def test_payment_action_ind_field + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(payment_action_ind: 'P')) + end.check_request do |_endpoint, data, _headers| + assert_match %{P}, data + end.respond_with(successful_purchase_response) + end + + def test_purchase_with_secondary_url + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(use_secondary_url: 'true')) + end.check_request do |endpoint, _data, _headers| + assert endpoint.include? 'orbitalvar2' end.respond_with(successful_purchase_response) end def test_network_tokenization_credit_card_data stub_comms do @gateway.purchase(50, network_tokenization_credit_card(nil, eci: '5', transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA='), @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %{5}, data assert_match %{Y}, data assert_match %{DigitalTokenCryptogram}, data - assert_match %{XID}, data + end.respond_with(successful_purchase_response) + end + + def test_schema_for_soft_descriptors_with_network_tokenization_credit_card_data + options = @options.merge( + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example' + } + ) + 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 + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_visa_authorization + stub_comms do + @gateway.authorize(50, credit_card, @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + assert_match %{TESTXID}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_purchase + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'master'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + assert_match %{2}, data + assert_match %{97267598FAE648F28083C23433990FBC}, data + assert_match %{4}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_authorization + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'master'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + assert_match %{2}, data + assert_match %{97267598FAE648F28083C23433990FBC}, data + assert_match %{4}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_sca_recurring + options_local = { + three_d_secure: { + eci: '7', + xid: 'TESTXID', + cavv: 'AAAEEEDDDSSSAAA2243234', + ds_transaction_id: '97267598FAE648F28083C23433990FBC', + version: '2.2.0' + }, + sca_recurring: 'Y' + } + + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'master'), @options.merge(options_local)) + end.check_request do |_endpoint, data, _headers| + assert_match %{7}, data + assert_match %{AAAEEEDDDSSSAAA2243234}, data + assert_match %{2}, data + assert_match %{97267598FAE648F28083C23433990FBC}, data + assert_match %{Y}, data + assert_match %{4}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_sca_merchant_initiated_master_card + options_local = { + three_d_secure: { + eci: '7', + xid: 'TESTXID', + cavv: 'AAAEEEDDDSSSAAA2243234', + ds_transaction_id: '97267598FAE648F28083C23433990FBC', + version: '2.2.0' + }, + sca_merchant_initiated: 'Y' + } + + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'master'), @options.merge(options_local)) + end.check_request do |_endpoint, data, _headers| + assert_match %{7}, data + assert_match %{AAAEEEDDDSSSAAA2243234}, data + assert_match %{2}, data + assert_match %{97267598FAE648F28083C23433990FBC}, data + assert_match %{Y}, data + assert_match %{4}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_sca_recurring_with_invalid_eci + options_local = { + three_d_secure: { + eci: '5', + xid: 'TESTXID', + cavv: 'AAAEEEDDDSSSAAA2243234', + ds_transaction_id: '97267598FAE648F28083C23433990FBC', + version: '2.2.0' + }, + sca_recurring: 'Y' + } + + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'master'), @options.merge(options_local)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{AAAEEEDDDSSSAAA2243234}, data + assert_match %{2}, data + assert_match %{97267598FAE648F28083C23433990FBC}, data + assert_match %{4}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_american_express_purchase + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'american_express'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + assert_match %{ASK}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_american_express_authorization + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'american_express'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + assert_match %{ASK}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_discover_purchase + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'discover'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_discover_authorization + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'discover'), @options.merge(@three_d_secure_options)) + end.check_request do |_endpoint, data, _headers| + assert_match %{5}, data + assert_match %{TESTCAVV}, data end.respond_with(successful_purchase_response) end def test_currency_exponents stub_comms do - @gateway.purchase(50, credit_card, :order_id => '1') - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: '1') + end.check_request do |_endpoint, data, _headers| assert_match %r{2<\/CurrencyExponent>}, data end.respond_with(successful_purchase_response) stub_comms do - @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'CAD') - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: '1', currency: 'CAD') + end.check_request do |_endpoint, data, _headers| assert_match %r{2<\/CurrencyExponent>}, data end.respond_with(successful_purchase_response) stub_comms do - @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'JPY') - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: '1', currency: 'JPY') + end.check_request do |_endpoint, data, _headers| assert_match %r{0<\/CurrencyExponent>}, data end.respond_with(successful_purchase_response) end @@ -115,7 +459,7 @@ def test_currency_exponents def test_unauthenticated_response @gateway.expects(:ssl_post).returns(failed_purchase_response) - assert response = @gateway.purchase(101, credit_card, :order_id => '1') + assert response = @gateway.purchase(101, credit_card, order_id: '1') assert_instance_of Response, response assert_failure response assert_equal 'AUTH DECLINED 12001', response.message @@ -150,14 +494,14 @@ def test_order_id_required def test_order_id_as_number @gateway.expects(:ssl_post).returns(successful_purchase_response) assert_nothing_raised do - @gateway.purchase(101, credit_card, :order_id => 1) + @gateway.purchase(101, credit_card, order_id: 1) end end def test_order_id_format response = stub_comms do - @gateway.purchase(101, credit_card, :order_id => ' #101.23,56 $Hi &thére@Friends') - end.check_request do |endpoint, data, headers| + @gateway.purchase(101, credit_card, order_id: ' #101.23,56 $Hi &thére@Friends') + end.check_request do |_endpoint, data, _headers| assert_match(/101-23,56 \$Hi &thre@Fr<\/OrderID>/, data) end.respond_with(successful_purchase_response) assert_success response @@ -165,8 +509,8 @@ 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') - end.check_request do |endpoint, data, headers| + @gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1001.1', order_id: '#1001.1') + end.check_request do |_endpoint, data, _headers| assert_match(/1001-1<\/OrderID>/, data) end.respond_with(successful_purchase_response) assert_success response @@ -174,14 +518,14 @@ def test_order_id_format_for_capture def test_numeric_merchant_id_for_caputre gateway = ActiveMerchant::Billing::OrbitalGateway.new( - :login => 'login', - :password => 'password', - :merchant_id => 700000123456 + login: 'login', + password: 'password', + merchant_id: 700000123456 ) response = stub_comms(gateway) do gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/700000123456<\/MerchantID>/, data) end.respond_with(successful_purchase_response) assert_success response @@ -194,8 +538,8 @@ def test_expiry_date def test_phone_number response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:phone => '123-456-7890')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(phone: '123-456-7890')) + end.check_request do |_endpoint, data, _headers| assert_match(/1234567890/, data) end.respond_with(successful_purchase_response) assert_success response @@ -205,8 +549,8 @@ def test_truncates_address long_address = '1850 Treebeard Drive in Fangorn Forest by the Fields of Rohan' response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:address1 => long_address)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(address1: long_address)) + end.check_request do |_endpoint, data, _headers| assert_match(/1850 Treebeard Drive/, data) assert_no_match(/Fields of Rohan/, data) end.respond_with(successful_purchase_response) @@ -214,25 +558,37 @@ 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) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, card, order_id: 1, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/John Jacob/, data) assert_no_match(/Jones/, data) end.respond_with(successful_purchase_response) assert_success response end + def test_splits_first_middle_name + name_test_check = check(name: 'Jane P Doe', + account_number: '072403004', account_type: 'checking', routing_number: '072403004') + + response = stub_comms do + @gateway.purchase(50, name_test_check, order_id: 1, action_code: 'W3') + end.check_request do |_endpoint, data, _headers| + assert_match(/JanePDoe 1, :billing_address => address(:city => long_city)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(city: long_city)) + end.check_request do |_endpoint, data, _headers| assert_match(/Friendly Village/, data) assert_no_match(/Creek/, data) end.respond_with(successful_purchase_response) @@ -243,8 +599,8 @@ def test_truncates_phone long_phone = '123456789012345' response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:phone => long_phone)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(phone: long_phone)) + end.check_request do |_endpoint, data, _headers| assert_match(/12345678901234 1, :billing_address => address(:zip => long_zip)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(zip: long_zip)) + end.check_request do |_endpoint, data, _headers| assert_match(/1234567890 '456% M|a^in \\S/treet', - :address2 => '|Apt. ^Num\\ber /One%', - :city => 'R^ise o\\f /th%e P|hoenix', - :state => '%O|H\\I/O', - :dest_address1 => '2/21%B |B^aker\\ St.', - :dest_address2 => 'L%u%xury S|u^i\\t/e', - :dest_city => '/Winn/i%p|e^g\\', - :dest_zip => 'A1A 2B2', - :dest_state => '^MB' + address1: '456% M|a^in \\S/treet', + address2: '|Apt. ^Num\\ber /One%', + city: 'R^ise o\\f /th%e P|hoenix', + state: '%O|H\\I/O', + dest_address1: '2/21%B |B^aker\\ St.', + dest_address2: 'L%u%xury S|u^i\\t/e', + dest_city: '/Winn/i%p|e^g\\', + dest_zip: 'A1A 2B2', + dest_state: '^MB' ) response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, - :billing_address => address_with_invalid_chars) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, + billing_address: address_with_invalid_chars) + end.check_request do |_endpoint, data, _headers| assert_match(/456 Main Street address_with_invalid_chars) + @gateway.add_customer_profile(credit_card, billing_address: address_with_invalid_chars) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/456 Main Street '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', - :address2 => 'Apårtmeñt 123456789012345678901', - :city => '¡Vancouver-by-the-sea!', - :state => 'ßC', - :zip => 'Postäl Cøde', - :dest_name => 'Pierré von Bürgermeister de Queso', - :dest_address1 => '9876 Stréêt Name is Really Long', - :dest_address2 => 'Apårtmeñt 987654321098765432109', - :dest_city => 'Montréal-of-the-south!', - :dest_state => 'Oñtario', - :dest_zip => 'Postäl Zïps' + address1: '456 Stréêt Name is Really Long', + address2: 'Apårtmeñt 123456789012345678901', + city: '¡Vancouver-by-the-sea!', + state: 'ßC', + zip: 'Postäl Cøde', + dest_name: 'Pierré von Bürgermeister de Queso', + dest_address1: '9876 Stréêt Name is Really Long', + dest_address2: 'Apårtmeñt 987654321098765432109', + dest_city: 'Montréal-of-the-south!', + dest_state: 'Oñtario', + dest_zip: 'Postäl Zïps' ) response = stub_comms do - @gateway.purchase(50, card, :order_id => 1, - :billing_address => long_address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, card, order_id: 1, + billing_address: long_address) + end.check_request do |_endpoint, data, _headers| assert_match(/456 Stréêt Name is Really Lo long_address) + @gateway.add_customer_profile(credit_card, billing_address: long_address) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/456 Stréêt Name is Really Lo nil, - :address2 => nil, - :city => nil, - :state => nil, - :zip => nil, - :email => nil, - :phone => nil, - :fax => nil + address1: nil, + address2: nil, + city: nil, + state: nil, + zip: nil, + email: nil, + phone: nil, + fax: nil } - response = @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(address_options)) + response = @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(address_options)) assert_success response end def test_dest_address billing_address = address( - :dest_zip => '90001', - :dest_address1 => '456 Main St.', - :dest_city => 'Somewhere', - :dest_state => 'CA', - :dest_name => 'Joan Smith', - :dest_phone => '(123) 456-7890', - :dest_country => 'US') - - response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, - :billing_address => billing_address) - end.check_request do |endpoint, data, headers| + dest_zip: '90001', + dest_address1: '456 Main St.', + dest_city: 'Somewhere', + dest_state: 'CA', + dest_name: 'Joan Smith', + dest_phone: '(123) 456-7890', + dest_country: 'US' + ) + + response = stub_comms do + @gateway.purchase(50, credit_card, order_id: 1, + billing_address: billing_address) + end.check_request do |_endpoint, data, _headers| assert_match(/90001/, data) assert_match(/456 Main St./, data) assert_match(/\n/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_with_negative_stored_credentials_indicator + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(mit_stored_credential_ind: 'N')) + end.check_request do |_endpoint, data, _headers| + assert_no_match(//, data) + assert_no_match(//, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_stored_credentials + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@options_stored_credentials)) + end.check_request do |_endpoint, data, _headers| + assert_match %{#{@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 + end.respond_with(successful_purchase_response) + end + + def test_dpanind_for_rc_and_ec_transactions + stub_comms do + @gateway.purchase(50, @google_pay_card, @options.merge(industry_type: 'RC')) + end.check_request do |_endpoint, data, _headers| + assert_false data.include?('DPANInd') + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, @google_pay_card, @options.merge(industry_type: 'EC')) + end.check_request do |_endpoint, data, _headers| + assert data.include?('DPANInd') + end.respond_with(successful_purchase_response) + end + + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.purchase(50, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/CSTOYCRECYCSTOYMRECYabc123CSTOYCUSEYCSTOYMUSEYabc123CSTOYCINSY 1, - :billing_address => billing_address.merge(:dest_country => 'BR')) - end.check_request do |endpoint, data, headers| - assert_match(/CSTOY/, data) - assert_no_match(//, data) + def test_stored_credential_installment_mit_used + credit_card.verification_value = nil + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.purchase(50, credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/MINSYabc123#{@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 - end.respond_with(successful_purchase_response) + @gateway.store(credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %{GT}, data + end end - def test_successful_purchase_with_normalized_stored_credentials + def test_successful_payment_request_with_token_stored + options = @options.merge(card_brand: 'MC', token_txn_type: 'UT') stub_comms do - @gateway.purchase(50, credit_card, @options.merge(@normalized_mit_stored_credential)) - end.check_request do |endpoint, data, headers| - assert_match %{MUSE}, data - assert_match %{Y}, data - assert_match %{abcdefg12345678}, data - end.respond_with(successful_purchase_response) + @gateway.purchase(50, '2521002395820006', options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %{MC}, data + assert_match %{2521002395820006}, data + end end - def test_successful_initial_purchase_with_normalized_stored_credentials - stub_comms do - @gateway.purchase(50, credit_card, @options.merge(@normalized_initial_stored_credential)) - end.check_request do |endpoint, data, headers| - assert_match %{CSTO}, data - assert_match %{Y}, data - end.respond_with(successful_purchase_response) + 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)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %{MRSB}, data assert_match %{Y}, data assert_match %{123456abcdef}, data @@ -459,10 +1101,10 @@ def test_default_managed_billing response = stub_comms do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do assert_deprecation_warning do - @gateway.add_customer_profile(credit_card, :managed_billing => {:start_date => '10-10-2014' }) + @gateway.add_customer_profile(credit_card, managed_billing: { start_date: '10-10-2014' }) end end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/R/, data) assert_match(/IO/, data) assert_match(/10102014/, data) @@ -475,15 +1117,18 @@ 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, - :managed_billing => { - :start_date => '10-10-2014', - :end_date => '10-10-2015', - :max_dollar_value => 1500, - :max_transactions => 12}) + @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| + end.check_request do |_endpoint, data, _headers| assert_match(/R/, data) assert_match(/IO/, data) assert_match(/10102014/, data) @@ -496,8 +1141,8 @@ def test_managed_billing def test_dont_send_customer_data_by_default response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1) + end.check_request do |_endpoint, data, _headers| assert_no_match(/K1C2N6/, data) assert_no_match(/456 My Street/, data) assert_no_match(/Apt 1/, data) @@ -508,8 +1153,8 @@ def test_dont_send_customer_data_by_default def test_send_customer_data_when_customer_profiles_is_enabled @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1) + end.check_request do |_endpoint, data, _headers| assert_match(/A/, data) assert_match(/NO/, data) end.respond_with(successful_purchase_response) @@ -519,8 +1164,8 @@ def test_send_customer_data_when_customer_profiles_is_enabled def test_send_customer_data_when_customer_ref_is_provided @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :customer_ref_num => @customer_ref_num) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, customer_ref_num: @customer_ref_num) + end.check_request do |_endpoint, data, _headers| assert_match(/ABC/, data) assert_match(/S/, data) assert_match(/NO/, data) @@ -528,11 +1173,29 @@ def test_send_customer_data_when_customer_ref_is_provided assert_success response end + def test_send_card_indicators_when_provided_purchase + response = stub_comms do + @gateway.purchase(50, credit_card, order_id: 1, card_indicators: @options[:card_indicators]) + end.check_request do |_endpoint, data, _headers| + assert_match(/y/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_send_card_indicators_when_provided_authorize + response = stub_comms do + @gateway.authorize(50, credit_card, order_id: 1, card_indicators: @options[:card_indicators]) + end.check_request do |_endpoint, data, _headers| + assert_match(/y/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + def test_dont_send_customer_profile_from_order_ind_for_profile_purchase @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, nil, order_id: 1, customer_ref_num: @customer_ref_num) + end.check_request do |_endpoint, data, _headers| assert_no_match(//, data) end.respond_with(successful_purchase_response) assert_success response @@ -541,8 +1204,8 @@ def test_dont_send_customer_profile_from_order_ind_for_profile_purchase def test_dont_send_customer_profile_from_order_ind_for_profile_authorize @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) - end.check_request do |endpoint, data, headers| + @gateway.authorize(50, nil, order_id: 1, customer_ref_num: @customer_ref_num) + end.check_request do |_endpoint, data, _headers| assert_no_match(//, data) end.respond_with(successful_purchase_response) assert_success response @@ -551,8 +1214,8 @@ def test_dont_send_customer_profile_from_order_ind_for_profile_authorize def test_currency_code_and_exponent_are_set_for_profile_purchase @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, nil, order_id: 1, customer_ref_num: @customer_ref_num) + end.check_request do |_endpoint, data, _headers| assert_match(/ABC/, data) assert_match(/124/, data) assert_match(/2/, data) @@ -563,8 +1226,8 @@ def test_currency_code_and_exponent_are_set_for_profile_purchase def test_currency_code_and_exponent_are_set_for_profile_authorizations @gateway.options[:customer_profiles] = true response = stub_comms do - @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) - end.check_request do |endpoint, data, headers| + @gateway.authorize(50, nil, order_id: 1, customer_ref_num: @customer_ref_num) + end.check_request do |_endpoint, data, _headers| assert_match(/ABC/, data) assert_match(/124/, data) assert_match(/2/, data) @@ -572,18 +1235,108 @@ def test_currency_code_and_exponent_are_set_for_profile_authorizations assert_success response end - # K1C2N6 - # 456 My Street - # Apt 1 - # Ottawa - # ON - # 5555555555 - # Longbob Longsen - # CA + def test_successful_authorize_with_echeck + @gateway.expects(:ssl_post).returns(successful_authorize_with_echeck_response) + + assert response = @gateway.authorize(50, @echeck, order_id: '2') + assert_instance_of Response, response + assert_equal 'Approved', response.message + assert_success response + assert_equal '5F8E8D2B077217F3EF1ACD3B61610E4CD12954A3;2', response.authorization + end + + def test_failed_authorize_with_echeck + @gateway.expects(:ssl_post).returns(failed_echeck_for_invalid_amount_response) + + assert response = @gateway.authorize(-1, @echeck, order_id: '9baedc697f2cf06457de78') + assert_instance_of Response, response + assert_failure response + assert_equal 'Error validating amount. Must be numeric, equal to zero or greater [-1]', response.message + assert_equal '885', response.params['proc_status'] + end + + def test_successful_refund_with_echeck + @gateway.expects(:ssl_post).returns(successful_refund_with_echeck_response) + + assert response = @gateway.refund(50, '1;2', @options) + assert_instance_of Response, response + assert_success response + assert_equal '1', response.params['approval_status'] + end + + def test_failed_refund_with_echeck + @gateway.expects(:ssl_post).returns(failed_refund_with_echeck_response) + + assert response = @gateway.refund(50, '1;2', @options) + assert_instance_of Response, response + assert_failure response + assert_equal 'Refund Transactions By TxRefNum Are Only Valid When The Original Transaction Was An AUTH Or AUTH CAPTURE.', response.message + assert_equal '9806', response.params['proc_status'] + end + + def test_successful_refund + authorization = '123456789;abc987654321' + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert response = @gateway.refund(100, authorization, @options) + assert_instance_of Response, response + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert response = @gateway.refund(100, '', @options) + assert_instance_of Response, response + assert_failure response + end + + def test_not_approved_refund + @gateway.expects(:ssl_post).returns(not_approved_refund_response) + + assert response = @gateway.refund(100, '123;123', @options) + assert_instance_of Response, response + assert_failure response + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + assert response = @gateway.credit(100, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal '1', response.params['approval_status'] + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert response = @gateway.credit(100, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_not_approved_credit + @gateway.expects(:ssl_post).returns(not_approved_credit_response) + + assert response = @gateway.credit(100, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_always_send_avs_for_echeck + response = stub_comms do + @gateway.purchase(50, @echeck, order_id: 1, address: nil, billing_address: address(country: nil)) + end.check_request do |_endpoint, data, _headers| + assert_match(/Jim Smith 1, :billing_address => address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/K1C2N6/, data) assert_match(/456 My Street/, data) assert_match(/Apt 1/, data) @@ -600,8 +1353,8 @@ def test_send_address_details_for_united_states def test_dont_send_address_details_for_germany response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:country => 'DE')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(country: 'DE')) + end.check_request do |_endpoint, data, _headers| assert_no_match(/K1C2N6/, data) assert_no_match(/456 My Street/, data) assert_no_match(/Apt 1/, data) @@ -616,8 +1369,8 @@ def test_dont_send_address_details_for_germany def test_allow_sending_avs_parts_when_no_country_specified response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:country => nil)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, billing_address: address(country: nil)) + end.check_request do |_endpoint, data, _headers| assert_match(/K1C2N6/, data) assert_match(/456 My Street/, data) assert_match(/Apt 1/, data) @@ -632,9 +1385,9 @@ def test_allow_sending_avs_parts_when_no_country_specified 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_PTI77.xsd") + @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' @@ -644,9 +1397,9 @@ def test_american_requests_adhere_to_xml_schema 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_PTI77.xsd") + @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' @@ -659,7 +1412,7 @@ def test_add_customer_profile assert_deprecation_warning do @gateway.add_customer_profile(credit_card) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/C/, data) assert_match(/Longbob Longsen/, data) end.respond_with(successful_profile_response) @@ -669,9 +1422,9 @@ def test_add_customer_profile def test_add_customer_profile_with_email response = stub_comms do assert_deprecation_warning do - @gateway.add_customer_profile(credit_card, { :billing_address => { :email => 'xiaobozzz@example.com' } }) + @gateway.add_customer_profile(credit_card, { billing_address: { email: 'xiaobozzz@example.com' } }) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/C/, data) assert_match(/xiaobozzz@example.com/, data) end.respond_with(successful_profile_response) @@ -683,7 +1436,7 @@ def test_update_customer_profile assert_deprecation_warning do @gateway.update_customer_profile(credit_card) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/U/, data) assert_match(/Longbob Longsen/, data) end.respond_with(successful_profile_response) @@ -695,7 +1448,7 @@ def test_retrieve_customer_profile assert_deprecation_warning do @gateway.retrieve_customer_profile(@customer_ref_num) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/Longbob Longsen/, data) assert_match(/R/, data) assert_match(/ABC/, data) @@ -708,7 +1461,7 @@ def test_delete_customer_profile assert_deprecation_warning do @gateway.delete_customer_profile(@customer_ref_num) end - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(/Longbob Longsen/, data) assert_match(/D/, data) assert_match(/ABC/, data) @@ -720,7 +1473,7 @@ def test_attempts_seconday_url @gateway.expects(:ssl_post).with(OrbitalGateway.test_url, anything, anything).raises(ActiveMerchant::ConnectionError.new('message', nil)) @gateway.expects(:ssl_post).with(OrbitalGateway.secondary_test_url, anything, anything).returns(successful_purchase_response) - response = @gateway.purchase(50, credit_card, :order_id => '1') + response = @gateway.purchase(50, credit_card, order_id: '1') assert_success response end @@ -728,10 +1481,10 @@ def test_attempts_seconday_url def test_headers_when_retry_logic_is_enabled @gateway.options[:retry_logic] = true response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :trace_number => 1) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, trace_number: 1) + end.check_request do |_endpoint, _data, headers| assert_equal('1', headers['Trace-number']) - assert_equal('merchant_id', headers['Merchant-Id']) + assert_equal('test12', headers['Merchant-Id']) end.respond_with(successful_purchase_response) assert_success response end @@ -739,19 +1492,67 @@ def test_headers_when_retry_logic_is_enabled def test_retry_logic_not_enabled @gateway.options[:retry_logic] = false response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :trace_number => 1) - end.check_request do |endpoint, data, headers| + @gateway.purchase(50, credit_card, order_id: 1, trace_number: 1) + end.check_request do |_endpoint, _data, headers| + assert_equal(false, headers.has_key?('Trace-number')) + assert_equal(false, headers.has_key?('Merchant-Id')) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_headers_when_retry_logic_param_exists + response = stub_comms do + @gateway.purchase(50, credit_card, order_id: 1, retry_logic: 'true', trace_number: 1) + end.check_request do |_endpoint, _data, headers| + assert_equal('1', headers['Trace-number']) + assert_equal('test12', headers['Merchant-Id']) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_retry_logic_when_param_nonexistant + response = stub_comms do + @gateway.purchase(50, credit_card, order_id: 1, trace_number: 1) + end.check_request do |_endpoint, _data, headers| assert_equal(false, headers.has_key?('Trace-number')) assert_equal(false, headers.has_key?('Merchant-Id')) end.respond_with(successful_purchase_response) assert_success response end + def test_headers_when_trace_number_nonexistant + response = stub_comms do + @gateway.purchase(50, credit_card, order_id: 1, retry_logic: 'true') + end.check_request do |_endpoint, _data, headers| + assert_equal(nil, headers['Trace-number']) + assert_equal(nil, headers['Merchant-Id']) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_payment_delivery_when_param_correct + response = stub_comms do + @gateway.purchase(50, @echeck, order_id: 1, payment_delivery: 'A') + end.check_request do |_endpoint, data, _headers| + assert_match(/A/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_payment_delivery_when_no_payment_delivery_param + response = stub_comms do + @gateway.purchase(50, @echeck, order_id: 1) + end.check_request do |_endpoint, data, _headers| + assert_match(/B/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + ActiveMerchant::Billing::OrbitalGateway::APPROVED.each do |resp_code| define_method "test_approval_response_code_#{resp_code}" do @gateway.expects(:ssl_post).returns(successful_purchase_response(resp_code)) - assert response = @gateway.purchase(50, credit_card, :order_id => '1') + assert response = @gateway.purchase(50, credit_card, order_id: '1') assert_instance_of Response, response assert_success response end @@ -760,7 +1561,7 @@ def test_retry_logic_not_enabled def test_account_num_is_removed_from_response @gateway.expects(:ssl_post).returns(successful_purchase_response) - response = @gateway.purchase(50, credit_card, :order_id => '1') + response = @gateway.purchase(50, credit_card, order_id: '1') assert_instance_of Response, response assert_success response assert_nil response.params['account_num'] @@ -772,8 +1573,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 @@ -790,6 +1590,63 @@ def test_successful_verify assert_equal 'Approved', response.message end + def test_custom_amount_on_verify + response = stub_comms do + @gateway.verify(credit_card, @options.merge({ verify_amount: '101' })) + end.check_request do |_endpoint, data, _headers| + assert_match %r{101<\/Amount>}, data if data.include?('MessageType') + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_valid_amount_with_jcb_card + @credit_card.brand = 'jcb' + stub_comms do + @gateway.verify(@credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %r{0<\/Amount>}, data + end + end + + def test_successful_verify_zero_auth_different_cards + @credit_card.brand = 'master' + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_purchase_response) + assert_success response + assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization + assert_equal 'Approved', response.message + end + + def test_valid_amount_with_discover_brand + @credit_card.brand = 'discover' + stub_comms do + @gateway.verify(@credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %r{100<\/Amount>}, data + end + end + + def test_successful_verify_with_discover_brand + @credit_card.brand = 'discover' + response = stub_comms do + @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 'Approved', response.message + end + + def test_successful_verify_and_failed_void_discover_brand + @credit_card.brand = 'discover' + response = stub_comms do + @gateway.verify(credit_card, @options) + end.respond_with(successful_purchase_response, failed_purchase_response) + assert_success response + assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization + assert_equal 'Approved', response.message + end + def test_successful_verify_and_failed_void response = stub_comms do @gateway.verify(credit_card, @options) @@ -807,31 +1664,83 @@ def test_failed_verify assert_equal 'AUTH DECLINED 12001', response.message end - def test_cvv_indicator_present_for_visas_with_cvvs + def test_cvv_indicator_present_for_visa_and_discovers_with_cvvs + discover = credit_card('4556761029983886', brand: 'discover') + diners_club = credit_card('4556761029983886', brand: 'diners_club') + stub_comms do @gateway.purchase(50, credit_card, @options) end.check_request do |_endpoint, data, _headers| assert_match %r{1<\/CardSecValInd>}, data assert_match %r{123<\/CardSecVal>}, data end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, discover, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{1<\/CardSecValInd>}, data + assert_match %r{123<\/CardSecVal>}, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, diners_club, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{1<\/CardSecValInd>}, data + assert_match %r{123<\/CardSecVal>}, data + end.respond_with(successful_purchase_response) + end + + def test_cvv_indicator_absent_for_mastercard + mastercard = credit_card('4556761029983886', brand: 'master') + + stub_comms do + @gateway.purchase(50, mastercard, @options) + end.check_request do |_endpoint, data, _headers| + assert_no_match %r{}, data + assert_match %r{123<\/CardSecVal>}, data + end.respond_with(successful_purchase_response) end def test_cvv_indicator_absent_for_recurring stub_comms do - @gateway.purchase(50, credit_card(nil, {verification_value: nil}), @options) + @gateway.purchase(50, credit_card(nil, { verification_value: nil }), @options) end.check_request do |_endpoint, data, _headers| assert_no_match %r{}, data assert_no_match %r{}, data end.respond_with(successful_purchase_response) end + def test_invalid_characters_in_response + @gateway.expects(:ssl_post).returns(invalid_characters_response) + + assert response = @gateway.purchase(50, credit_card, order_id: '1') + assert_instance_of Response, response + assert_failure response + assert_equal response.message, 'INV FIELD IN MSG AAAAAAAAA1[null][null][null][null][null]' + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end + def test_scrub_echeck + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + private + def stored_credential_options(*args, id: nil) + { + order_id: '#1001', + description: 'AM test', + currency: 'GBP', + customer: '123', + stored_credential: stored_credential(*args, id: id) + } + end + def successful_purchase_response(resp_code = '00') %Q{AC700000000000001VI411111111111111114A5398CF9B87744GG84A1D30F2F2321C66249416101#{resp_code}H N091922Approved00YN144951} end @@ -848,63 +1757,201 @@ def successful_void_response '700000208761001250FB1C41FEC9D016FF0BEBAD0884B174AD0853B010001192013172049' end + def successful_purchase_with_echeck_response + 'AC041756001EC9baedc697f2cf06457de785F8E8BEE7299FD339A38F70CFF6E5D010EF55498201003 123456Approved102 030414' + end + + def successful_force_capture_with_echeck_response + 'FC041756001EC2930847bc732eb4e8102cf5F8ED3D950A43BD63369845D5385B6354C3654B420100Approved and CompletedAPPROVAL 081105' + end + + def successful_force_capture_with_echeck_prenote_response + 'FC041756001EC2930847bc732eb4e8102cf5F8ED3D950A43BD63369845D5385B6354C3654B420100Approved and CompletedAPPROVAL 081105' + end + + def failed_force_capture_with_echeck_prenote_response + '19784 EWS: Invalid Action Code [W7], For Transaction Type [A].' + end + + def failed_echeck_for_invalid_routing_response + '888Invalid ECP Account Route: []. The field is missing, invalid, or it has exceeded the max length of: [9].' + end + + def failed_echeck_for_invalid_amount_response + '885Error validating amount. Must be numeric, equal to zero or greater [-1]' + end + + def successful_authorize_with_echeck_response + 'A041756001EC25F8E8D2B077217F3EF1ACD3B61610E4CD12954A3001003 123456Approved102 030931' + end + + def successful_refund_with_echeck_response + 'R041756001ECXXXXX3004b67774a1bbfe1387f5e1855F8E8D8A542ED5CC24449BC4CECD337BE05754C2201031106' + end + + def failed_refund_with_echeck_response + '9806Refund Transactions By TxRefNum Are Only Valid When The Original Transaction Was An AUTH Or AUTH CAPTURE.' + end + + 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 + + def not_approved_refund_response + 'R253997001VI45567610299838860c1792db5d167e0b96dd9f60D1E12322FD50E1517A2598593A48EEA9965445200123 Invalid Transaction Type606 110503' + end + + def successful_credit_response + 'R253997001MCXXXXX54546102f8d4ca9d5c08d6ea02605266890AF5BA833E6190D89256B892981C531D101003Mtst627Approved100162857' + end + + def failed_credit_response + '881The LIDM you supplied (3F3F3F) does not match with any existing transaction' + end + + def not_approved_credit_response + 'R253997001VI45567610299838860c1792db5d167e0b96dd9f60D1E12322FD50E1517A2598593A48EEA9965445200123 Invalid Transaction Type606 110503' + end + + def invalid_characters_response + "A[FILTERED]001VI[FILTERED]0c1792db5d167e0b96dd9f60D1E12322FD50E1517A2598593A48EEA996544520230 Invalid value in messageINV FIELD IN MSG AAAAAAAAA1\x00\x00\x00\x00\x0030105700" + end + def pre_scrubbed - <<-EOS -opening connection to orbitalvar1.paymentech.net:443... -opened -starting SSL for orbitalvar1.paymentech.net:443... -SSL established -<- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" -<- "\n\n \n T16WAYSACT\n zbp8X1ykGZ\n EC\n AC\n 000001\n 041756\n 001\n 4112344112344113\n 0917\n 840\n 2\n 1\n 123\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Longbob Longsen\n CA\n b141cf3ce2a442732e1906\n 100\n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" --> "content-type: text/plain; charset=ISO-8859-1\r\n" --> "content-length: 1200\r\n" --> "content-transfer-encoding: text/xml\r\n" --> "document-type: Response\r\n" --> "mime-version: 1.0\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 1200 bytes... --> "AC041756001VI4112344112344113b141cf3ce2a442732e1906574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B201007 Mtst595Approved100IUM030444" -read 1200 bytes -Conn close - EOS + <<~REQUEST + opening connection to orbitalvar1.paymentech.net:443... + opened + starting SSL for orbitalvar1.paymentech.net:443... + SSL established + <- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" + <- "\n\n \n T16WAYSACT\n zbp8X1ykGZ\n EC\n AC\n 000001\n 041756\n 001\n 4112344112344113\n 0917\n 840\n 2\n 1\n 123\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Longbob Longsen\n CA\n b141cf3ce2a442732e1906\n 100\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" + -> "content-type: text/plain; charset=ISO-8859-1\r\n" + -> "content-length: 1200\r\n" + -> "content-transfer-encoding: text/xml\r\n" + -> "document-type: Response\r\n" + -> "mime-version: 1.0\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1200 bytes... + -> "AC041756001VI4112344112344113b141cf3ce2a442732e1906574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B201007 Mtst595Approved100IUM030444" + read 1200 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to orbitalvar1.paymentech.net:443... -opened -starting SSL for orbitalvar1.paymentech.net:443... -SSL established -<- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" -<- "\n\n \n [FILTERED]\n [FILTERED]\n EC\n AC\n 000001\n [FILTERED]\n 001\n [FILTERED]\n 0917\n 840\n 2\n 1\n [FILTERED]\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Longbob Longsen\n CA\n b141cf3ce2a442732e1906\n 100\n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" --> "content-type: text/plain; charset=ISO-8859-1\r\n" --> "content-length: 1200\r\n" --> "content-transfer-encoding: text/xml\r\n" --> "document-type: Response\r\n" --> "mime-version: 1.0\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 1200 bytes... --> "AC[FILTERED]001VI[FILTERED]b141cf3ce2a442732e1906574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B201007 Mtst595Approved100IUM030444" -read 1200 bytes -Conn close - EOS + <<~REQUEST + opening connection to orbitalvar1.paymentech.net:443... + opened + starting SSL for orbitalvar1.paymentech.net:443... + SSL established + <- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" + <- "\n\n \n [FILTERED]\n [FILTERED]\n EC\n AC\n 000001\n [FILTERED]\n 001\n [FILTERED]\n 0917\n 840\n 2\n 1\n [FILTERED]\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Longbob Longsen\n CA\n b141cf3ce2a442732e1906\n 100\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" + -> "content-type: text/plain; charset=ISO-8859-1\r\n" + -> "content-length: 1200\r\n" + -> "content-transfer-encoding: text/xml\r\n" + -> "document-type: Response\r\n" + -> "mime-version: 1.0\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1200 bytes... + -> "AC[FILTERED]001VI[FILTERED]b141cf3ce2a442732e1906574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B201007 Mtst595Approved100IUM030444" + read 1200 bytes + Conn close + REQUEST end def pre_scrubbed_profile - <<-EOS -000001253997LONGBOB LONGSEN109273631CREATE0Profile Request Processed456 MY STREETAPT 1OTTAWAONK1C2N65555555555CANOCCA41123441123441130919 - EOS + <<~REQUEST + 000001253997LONGBOB LONGSEN109273631CREATE0Profile Request Processed456 MY STREETAPT 1OTTAWAONK1C2N65555555555CANOCCA41123441123441130919 + REQUEST end def post_scrubbed_profile - <<-EOS -000001[FILTERED]LONGBOB LONGSEN109273631CREATE0Profile Request Processed456 MY STREETAPT 1OTTAWAONK1C2N65555555555CANOCCA[FILTERED]0919 - EOS + <<~REQUEST + 000001[FILTERED]LONGBOB LONGSEN109273631CREATE0Profile Request Processed456 MY STREETAPT 1OTTAWAONK1C2N65555555555CANOCCA[FILTERED]0919 + REQUEST + end + + def pre_scrubbed_echeck + <<~REQUEST + opening connection to orbitalvar1.chasepaymentech.com:443... + opened + starting SSL for orbitalvar1.chasepaymentech.com:443... + SSL established, protocol: TLSv1.2, cipher: AES128-GCM-SHA256 + <- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI81\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 999\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: orbitalvar1.chasepaymentech.com\r\n\r\n" + <- "\n\n \n SPREEDLYTEST1\n 2NnPnYZylV8ft\n EC\n AC\n 000001\n 408449\n 001\n EC\n 124\n 2\n 072403004\n 072403004\n S\n B\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Jim Smith\n CA\n 5fe1bb6dcd2cd401f2a277\n 20\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 09 Aug 2021 21:00:41 GMT\r\n" + -> "Strict-Transport-Security: max-age=63072000; includeSubdomains; preload\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token, MerchantID, OrbitalConnectionUsername, OrbitalConnectionPassword\r\n" + -> "Access-Control-Allow-credentials: true\r\n" + -> "content-type: text/plain; charset=ISO-8859-1\r\n" + -> "content-length: 1185\r\n" + -> "content-transfer-encoding: text/xml\r\n" + -> "document-type: Response\r\n" + -> "mime-version: 1.0\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1185 bytes... + -> "AC408449001EC5fe1bb6dcd2cd401f2a277611197795A217041FDC714407285C2FC74F9533B101003 123456 "ode>Approved102 170041" + read 1185 bytes + Conn close + REQUEST + end + + def post_scrubbed_echeck + <<~REQUEST + opening connection to orbitalvar1.chasepaymentech.com:443... + opened + starting SSL for orbitalvar1.chasepaymentech.com:443... + SSL established, protocol: TLSv1.2, cipher: AES128-GCM-SHA256 + <- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI81\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 999\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: orbitalvar1.chasepaymentech.com\r\n\r\n" + <- "\n\n \n [FILTERED]\n [FILTERED]\n EC\n AC\n 000001\n [FILTERED]\n 001\n EC\n 124\n 2\n [FILTERED]\n [FILTERED]\n S\n B\n K1C2N6\n 456 My Street\n Apt 1\n Ottawa\n ON\n 5555555555\n Jim Smith\n CA\n 5fe1bb6dcd2cd401f2a277\n 20\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 09 Aug 2021 21:00:41 GMT\r\n" + -> "Strict-Transport-Security: max-age=63072000; includeSubdomains; preload\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token, MerchantID, OrbitalConnectionUsername, OrbitalConnectionPassword\r\n" + -> "Access-Control-Allow-credentials: true\r\n" + -> "content-type: text/plain; charset=ISO-8859-1\r\n" + -> "content-length: 1185\r\n" + -> "content-transfer-encoding: text/xml\r\n" + -> "document-type: Response\r\n" + -> "mime-version: 1.0\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1185 bytes... + -> "AC[FILTERED]001EC5fe1bb6dcd2cd401f2a277611197795A217041FDC714407285C2FC74F9533B101003 123456 "ode>Approved102 170041" + read 1185 bytes + Conn close + REQUEST end end diff --git a/test/unit/gateways/pac_net_raven_test.rb b/test/unit/gateways/pac_net_raven_test.rb index 79530834e28..d206b211448 100644 --- a/test/unit/gateways/pac_net_raven_test.rb +++ b/test/unit/gateways/pac_net_raven_test.rb @@ -158,29 +158,29 @@ def test_failed_void def test_argument_error_prn exception = assert_raises(ArgumentError) { - PacNetRavenGateway.new(:user => 'user', :secret => 'secret') + PacNetRavenGateway.new(user: 'user', secret: 'secret') } assert_equal 'Missing required parameter: prn', exception.message end def test_argument_error_user exception = assert_raises(ArgumentError) { - PacNetRavenGateway.new(:secret => 'secret', :prn => 123456) + PacNetRavenGateway.new(secret: 'secret', prn: 123456) } assert_equal 'Missing required parameter: user', exception.message end def test_argument_error_secret exception = assert_raises(ArgumentError) { - PacNetRavenGateway.new(:user => 'user', :prn => 123456) + PacNetRavenGateway.new(user: 'user', prn: 123456) } assert_equal 'Missing required parameter: secret', exception.message end def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => 'Address 1', :address2 => 'Address 2', :zip => 'ZIP'}) - assert_equal ['BillingPostalCode', 'BillingStreetAddressLineFour', 'BillingStreetAddressLineOne'], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, billing_address: { address1: 'Address 1', address2: 'Address 2', zip: 'ZIP' }) + assert_equal %w[BillingPostalCode BillingStreetAddressLineFour BillingStreetAddressLineOne], result.stringify_keys.keys.sort assert_equal 'ZIP', result['BillingPostalCode'] assert_equal 'Address 2', result['BillingStreetAddressLineFour'] assert_equal 'Address 1', result['BillingStreetAddressLineOne'] @@ -189,7 +189,7 @@ def test_add_address def test_add_creditcard result = {} @gateway.send(:add_creditcard, result, @credit_card) - assert_equal ['CVV2', 'CardNumber', 'Expiry'], result.stringify_keys.keys.sort + assert_equal %w[CVV2 CardNumber Expiry], result.stringify_keys.keys.sort assert_equal @credit_card.number, result['CardNumber'] assert_equal @gateway.send(:expdate, @credit_card), result['Expiry'] assert_equal @credit_card.verification_value, result['CVV2'] @@ -203,13 +203,13 @@ def test_add_currency_code_default def test_add_currency_code_from_options result = {} - @gateway.send(:add_currency_code, result, 100, {currency: 'CAN'}) + @gateway.send(:add_currency_code, result, 100, { currency: 'CAN' }) assert_equal 'CAN', result['Currency'] end def test_parse result = @gateway.send(:parse, 'key1=value1&key2=value2') - h = {'key1' => 'value1', 'key2' => 'value2'} + h = { 'key1' => 'value1', 'key2' => 'value2' } assert_equal h, result end @@ -308,45 +308,47 @@ def test_success def test_message_from_approved assert_equal 'This transaction has been approved', @gateway.send(:message_from, { 'Status' => 'Approved', - 'Message'=> nil + 'Message' => nil }) end def test_message_from_declined assert_equal 'This transaction has been declined', @gateway.send(:message_from, { 'Status' => 'Declined', - 'Message'=> nil + 'Message' => nil }) end def test_message_from_voided assert_equal 'This transaction has been voided', @gateway.send(:message_from, { 'Status' => 'Voided', - 'Message'=> nil + 'Message' => nil }) end def test_message_from_status assert_equal 'This is the message', @gateway.send(:message_from, { 'Status' => 'SomeStatus', - 'Message'=> 'This is the message' + 'Message' => 'This is the message' }) end def test_post_data - @gateway.stubs(:request_id => 'wouykiikdvqbwwxueppby') - @gateway.stubs(:timestamp => '2013-10-08T14:31:54.Z') + @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), - 'CVV2' => @credit_card.verification_value, - 'Currency' => 'USD', - 'BillingStreetAddressLineOne' => 'Address 1', - 'BillingStreetAddressLineFour' => 'Address 2', - 'BillingPostalCode' => 'ZIP123' - }) + 'CardNumber' => @credit_card.number, + 'Expiry' => @gateway.send(:expdate, @credit_card), + 'CVV2' => @credit_card.verification_value, + 'Currency' => 'USD', + 'BillingStreetAddressLineOne' => 'Address 1', + 'BillingStreetAddressLineFour' => 'Address 2', + 'BillingPostalCode' => 'ZIP123' + }) + ) end def test_signature_for_cc_preauth_action @@ -356,9 +358,9 @@ def test_signature_for_cc_preauth_action 'RequestID' => 'wouykiikdvqbwwxueppby', 'PymtType' => 'cc_preauth' }, { - 'Amount' => 100, - 'Currency' => 'USD', - 'TrackingNumber' => '123456789' + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' }) end @@ -369,9 +371,9 @@ def test_signature_for_cc_settle_action 'RequestID' => 'wouykiikdvqbwwxueppby', 'PymtType' => 'cc_settle' }, { - 'Amount' => 100, - 'Currency' => 'USD', - 'TrackingNumber' => '123456789' + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' }) end @@ -382,9 +384,9 @@ def test_signature_for_cc_debit_action 'RequestID' => 'wouykiikdvqbwwxueppby', 'PymtType' => 'cc_debit' }, { - 'Amount' => 100, - 'Currency' => 'USD', - 'TrackingNumber' => '123456789' + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' }) end @@ -395,9 +397,9 @@ def test_signature_for_cc_refund_action 'RequestID' => 'wouykiikdvqbwwxueppby', 'PymtType' => 'cc_refund' }, { - 'Amount' => 100, - 'Currency' => 'USD', - 'TrackingNumber' => '123456789' + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' }) end @@ -407,9 +409,9 @@ def test_signature_for_void_action 'Timestamp' => '2013-10-08T14:31:54.Z', 'RequestID' => 'wouykiikdvqbwwxueppby' }, { - 'Amount' => 100, - 'Currency' => 'USD', - 'TrackingNumber' => '123456789' + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' }) end diff --git a/test/unit/gateways/pagarme_test.rb b/test/unit/gateways/pagarme_test.rb index 3944a780cd2..b8a0a644db0 100644 --- a/test/unit/gateways/pagarme_test.rb +++ b/test/unit/gateways/pagarme_test.rb @@ -626,7 +626,7 @@ def failed_capture_response "method": "post", "url": "/transactions/429356/capture" } - FAILED_RESPONSE + FAILED_RESPONSE end def successful_refund_response @@ -684,7 +684,7 @@ def successful_refund_response "subscription_id": null, "tid": "1458844196661" } - SUCCESS_RESPONSE + SUCCESS_RESPONSE end def failed_refund_response @@ -700,7 +700,7 @@ def failed_refund_response "method": "post", "url": "/transactions/429356/refund" } - FAILED_RESPONSE + FAILED_RESPONSE end def successful_void_response @@ -758,7 +758,7 @@ def successful_void_response "subscription_id": null, "tid": 472218 } - SUCCESS_RESPONSE + SUCCESS_RESPONSE end def failed_void_response @@ -774,7 +774,7 @@ def failed_void_response "method": "post", "url": "/transactions/472218/refund" } - FAILED_RESPONSE + FAILED_RESPONSE end def successful_verify_response @@ -832,7 +832,7 @@ def successful_verify_response "subscription_id": null, "tid": 476135 } - SUCCESS_RESPONSE + SUCCESS_RESPONSE end def successful_verify_void_response @@ -890,7 +890,7 @@ def successful_verify_void_response "subscription_id": null, "tid": 476135 } - SUCCESS_RESPONSE + SUCCESS_RESPONSE end def failed_verify_response @@ -948,7 +948,7 @@ def failed_verify_response "subscription_id": null, "tid": 476143 } - FAILED_RESPONSE + FAILED_RESPONSE end def failed_error_response @@ -964,7 +964,7 @@ def failed_error_response "method": "post", "url": "/transactions" } - FAILED_RESPONSE + FAILED_RESPONSE end def failed_json_response @@ -972,7 +972,6 @@ def failed_json_response { foo: bar } - SUCCESS_RESPONSE + SUCCESS_RESPONSE end - end diff --git a/test/unit/gateways/pago_facil_test.rb b/test/unit/gateways/pago_facil_test.rb index cb75a847402..6d6e119997e 100644 --- a/test/unit/gateways/pago_facil_test.rb +++ b/test/unit/gateways/pago_facil_test.rb @@ -70,159 +70,153 @@ def test_invalid_json private def successful_purchase_response - {'WebServices_Transacciones'=> - {'transaccion'=> - {'autorizado'=>'1', - 'autorizacion'=>'305638', - 'transaccion'=>'S-PFE12S12I12568', - 'texto'=>'Transaction has been successful!-Approved', - 'mode'=>'R', - 'empresa'=>'Usuario Invitado', - 'TransIni'=>'15:33:18 pm 25/02/2014', - 'TransFin'=>'15:33:27 pm 25/02/2014', - 'param1'=>'', - 'param2'=>'', - 'param3'=>'', - 'param4'=>'', - 'param5'=>'', - 'TipoTC'=>'Visa', - 'data'=> - {'anyoExpiracion'=>'(2) **', - 'apellidos'=>'Reyes Garza', - 'calleyNumero'=>'Anatole France 311', - 'celular'=>'5550123456', - 'colonia'=>'Polanco', - 'cp'=>'11560', - 'cvt'=>'(3) ***', - 'email'=>'comprador@correo.com', - 'estado'=>'Distrito Federal', - 'idPedido'=>'1', - 'idServicio'=>'3', - 'idSucursal'=>'60f961360ca187d533d5adba7d969d6334771370', - 'idUsuario'=>'62ad6f592ecf2faa87ef2437ed85a4d175e73c58', - 'mesExpiracion'=>'(2) **', - 'monto'=>'1.00', - 'municipio'=>'Miguel Hidalgo', - 'nombre'=>'Juan', - 'numeroTarjeta'=>'(16) **** **** ****1111', - 'pais'=>'Mexico', - 'telefono'=>'5550220910', - 'transFechaHora'=>'1393363998', - 'bin'=>'(6) ***1'}, - 'dataVal'=> - {'idSucursal'=>'12', - 'cp'=>'11560', - 'nombre'=>'Juan', - 'apellidos'=>'Reyes Garza', - 'numeroTarjeta'=>'(16) **** **** ****1111', - 'cvt'=>'(3) ***', - 'monto'=>'1.00', - 'mesExpiracion'=>'(2) **', - 'anyoExpiracion'=>'(2) **', - 'idUsuario'=>'14', - 'source'=>'1', - 'idServicio'=>'3', - 'recurrente'=>'0', - 'plan'=>'NOR', - 'diferenciado'=>'00', - 'mensualidades'=>'00', - 'ip'=>'187.162.238.170', - 'httpUserAgent'=>'Ruby', - 'idPedido'=>'1', - 'tipoTarjeta'=>'Visa', - 'hashKeyCC'=>'e5be0afe08f125ec4f6f1251141c60df88d65eae', - 'idEmpresa'=>'12', - 'nombre_comercial'=>'Usuario Invitado', - 'transFechaHora'=>'1393363998', - 'noProcess'=>'', - 'noMail'=>'', - 'notaMail'=>'', - 'settingsTransaction'=> - {'noMontoMes'=>'0.00', - 'noTransaccionesDia'=>'0', - 'minTransaccionTc'=>'5', - 'tiempoDevolucion'=>'30', - 'sendPdfTransCliente'=>'1', - 'noMontoDia'=>'0.00', - 'noTransaccionesMes'=>'0'}, - 'email'=>'comprador@correo.com', - 'telefono'=>'5550220910', - 'celular'=>'5550123456', - 'calleyNumero'=>'Anatole France 311', - 'colonia'=>'Polanco', - 'municipio'=>'Miguel Hidalgo', - 'estado'=>'Distrito Federal', - 'pais'=>'Mexico', - 'idCaja'=>'', - 'paisDetectedIP'=>'MX', - 'qa'=>'1', - 'https'=>'on'}, - 'status'=>'success' - } - } - }.to_json + { 'WebServices_Transacciones' => + { 'transaccion' => + { 'autorizado' => '1', + 'autorizacion' => '305638', + 'transaccion' => 'S-PFE12S12I12568', + 'texto' => 'Transaction has been successful!-Approved', + 'mode' => 'R', + 'empresa' => 'Usuario Invitado', + 'TransIni' => '15:33:18 pm 25/02/2014', + 'TransFin' => '15:33:27 pm 25/02/2014', + 'param1' => '', + 'param2' => '', + 'param3' => '', + 'param4' => '', + 'param5' => '', + 'TipoTC' => 'Visa', + 'data' => + { 'anyoExpiracion' => '(2) **', + 'apellidos' => 'Reyes Garza', + 'calleyNumero' => 'Anatole France 311', + 'celular' => '5550123456', + 'colonia' => 'Polanco', + 'cp' => '11560', + 'cvt' => '(3) ***', + 'email' => 'comprador@correo.com', + 'estado' => 'Distrito Federal', + 'idPedido' => '1', + 'idServicio' => '3', + 'idSucursal' => '60f961360ca187d533d5adba7d969d6334771370', + 'idUsuario' => '62ad6f592ecf2faa87ef2437ed85a4d175e73c58', + 'mesExpiracion' => '(2) **', + 'monto' => '1.00', + 'municipio' => 'Miguel Hidalgo', + 'nombre' => 'Juan', + 'numeroTarjeta' => '(16) **** **** ****1111', + 'pais' => 'Mexico', + 'telefono' => '5550220910', + 'transFechaHora' => '1393363998', + 'bin' => '(6) ***1' }, + 'dataVal' => + { 'idSucursal' => '12', + 'cp' => '11560', + 'nombre' => 'Juan', + 'apellidos' => 'Reyes Garza', + 'numeroTarjeta' => '(16) **** **** ****1111', + 'cvt' => '(3) ***', + 'monto' => '1.00', + 'mesExpiracion' => '(2) **', + 'anyoExpiracion' => '(2) **', + 'idUsuario' => '14', + 'source' => '1', + 'idServicio' => '3', + 'recurrente' => '0', + 'plan' => 'NOR', + 'diferenciado' => '00', + 'mensualidades' => '00', + 'ip' => '187.162.238.170', + 'httpUserAgent' => 'Ruby', + 'idPedido' => '1', + 'tipoTarjeta' => 'Visa', + 'hashKeyCC' => 'e5be0afe08f125ec4f6f1251141c60df88d65eae', + 'idEmpresa' => '12', + 'nombre_comercial' => 'Usuario Invitado', + 'transFechaHora' => '1393363998', + 'noProcess' => '', + 'noMail' => '', + 'notaMail' => '', + 'settingsTransaction' => + { 'noMontoMes' => '0.00', + 'noTransaccionesDia' => '0', + 'minTransaccionTc' => '5', + 'tiempoDevolucion' => '30', + 'sendPdfTransCliente' => '1', + 'noMontoDia' => '0.00', + 'noTransaccionesMes' => '0' }, + 'email' => 'comprador@correo.com', + 'telefono' => '5550220910', + 'celular' => '5550123456', + 'calleyNumero' => 'Anatole France 311', + 'colonia' => 'Polanco', + 'municipio' => 'Miguel Hidalgo', + 'estado' => 'Distrito Federal', + 'pais' => 'Mexico', + 'idCaja' => '', + 'paisDetectedIP' => 'MX', + 'qa' => '1', + 'https' => 'on' }, + 'status' => 'success' } } }.to_json end def failed_purchase_response - {'WebServices_Transacciones'=> - {'transaccion'=> - {'autorizado'=>'0', - 'transaccion'=>'n/a', - 'autorizacion'=>'n/a', - 'texto'=>'Errores en los datos de entrada Validaciones', - 'error'=> - {'numeroTarjeta'=>"'1111111111111111' no es de una institucion permitida"}, - 'empresa'=>'Sin determinar', - 'TransIni'=>'16:10:20 pm 25/02/2014', - 'TransFin'=>'16:10:20 pm 25/02/2014', - 'param1'=>'', - 'param2'=>'', - 'param3'=>'', - 'param4'=>'', - 'param5'=>'', - 'TipoTC'=>'', - 'data'=> - {'anyoExpiracion'=>'(2) **', - 'apellidos'=>'Reyes Garza', - 'calleyNumero'=>'Anatole France 311', - 'celular'=>'5550123456', - 'colonia'=>'Polanco', - 'cp'=>'11560', - 'cvt'=>'(3) ***', - 'email'=>'comprador@correo.com', - 'estado'=>'Distrito Federal', - 'idPedido'=>'1', - 'idServicio'=>'3', - 'idSucursal'=>'60f961360ca187d533d5adba7d969d6334771370', - 'idUsuario'=>'62ad6f592ecf2faa87ef2437ed85a4d175e73c58', - 'mesExpiracion'=>'(2) **', - 'monto'=>'1.00', - 'municipio'=>'Miguel Hidalgo', - 'nombre'=>'Juan', - 'numeroTarjeta'=>'(16) **** **** ****1111', - 'pais'=>'Mexico', - 'telefono'=>'5550220910', - 'transFechaHora'=>'1393366220', - 'bin'=>'(6) ***1'}, - 'dataVal'=> - {'email'=>'comprador@correo.com', - 'telefono'=>'5550220910', - 'celular'=>'5550123456', - 'calleyNumero'=>'Anatole France 311', - 'colonia'=>'Polanco', - 'municipio'=>'Miguel Hidalgo', - 'estado'=>'Distrito Federal', - 'pais'=>'Mexico', - 'idCaja'=>'', - 'numeroTarjeta'=>'', - 'cvt'=>'', - 'anyoExpiracion'=>'', - 'mesExpiracion'=>'', - 'https'=>'on'}, - 'status'=>'success' - } - } - }.to_json + { 'WebServices_Transacciones' => + { 'transaccion' => + { 'autorizado' => '0', + 'transaccion' => 'n/a', + 'autorizacion' => 'n/a', + 'texto' => 'Errores en los datos de entrada Validaciones', + 'error' => + { 'numeroTarjeta' => "'1111111111111111' no es de una institucion permitida" }, + 'empresa' => 'Sin determinar', + 'TransIni' => '16:10:20 pm 25/02/2014', + 'TransFin' => '16:10:20 pm 25/02/2014', + 'param1' => '', + 'param2' => '', + 'param3' => '', + 'param4' => '', + 'param5' => '', + 'TipoTC' => '', + 'data' => + { 'anyoExpiracion' => '(2) **', + 'apellidos' => 'Reyes Garza', + 'calleyNumero' => 'Anatole France 311', + 'celular' => '5550123456', + 'colonia' => 'Polanco', + 'cp' => '11560', + 'cvt' => '(3) ***', + 'email' => 'comprador@correo.com', + 'estado' => 'Distrito Federal', + 'idPedido' => '1', + 'idServicio' => '3', + 'idSucursal' => '60f961360ca187d533d5adba7d969d6334771370', + 'idUsuario' => '62ad6f592ecf2faa87ef2437ed85a4d175e73c58', + 'mesExpiracion' => '(2) **', + 'monto' => '1.00', + 'municipio' => 'Miguel Hidalgo', + 'nombre' => 'Juan', + 'numeroTarjeta' => '(16) **** **** ****1111', + 'pais' => 'Mexico', + 'telefono' => '5550220910', + 'transFechaHora' => '1393366220', + 'bin' => '(6) ***1' }, + 'dataVal' => + { 'email' => 'comprador@correo.com', + 'telefono' => '5550220910', + 'celular' => '5550123456', + 'calleyNumero' => 'Anatole France 311', + 'colonia' => 'Polanco', + 'municipio' => 'Miguel Hidalgo', + 'estado' => 'Distrito Federal', + 'pais' => 'Mexico', + 'idCaja' => '', + 'numeroTarjeta' => '', + 'cvt' => '', + 'anyoExpiracion' => '', + 'mesExpiracion' => '', + 'https' => 'on' }, + 'status' => 'success' } } }.to_json end def invalid_json_response diff --git a/test/unit/gateways/pay_arc_test.rb b/test/unit/gateways/pay_arc_test.rb new file mode 100644 index 00000000000..4e883160672 --- /dev/null +++ b/test/unit/gateways/pay_arc_test.rb @@ -0,0 +1,792 @@ +require 'test_helper' + +class PayArcTest < Test::Unit::TestCase + def setup + @gateway = PayArcGateway.new(fixtures(:pay_arc)) + credit_card_options = { + month: '12', + year: '2022', + first_name: 'Rex Joseph', + last_name: '', + verification_value: '999' + } + @credit_card = credit_card('4111111111111111', credit_card_options) + @invalid_credit_card = credit_card('3111111111111111', credit_card_options) + @invalid_cvv_card = credit_card('3111111111111111', credit_card_options.update(verification_value: '123')) + @amount = 100 + + @options = { + billing_address: address, + description: 'Store Purchase', + card_source: 'INTERNET', + address_line1: '920 Sunnyslope Ave', + address_line2: 'Bronx', + city: 'New York', + state: 'New York', + zip: '10469', + country: 'USA' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns( + successful_token_response + ).then.returns( + successful_charge_response + ) + response = @gateway.purchase(1022, @credit_card, @options) + assert_success response + + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + assert response.test? + end + + # Failed due to already used / invalid token + def test_failure_purchase + @gateway.expects(:ssl_post).times(2).returns( + successful_token_response + ).then.returns( + failed_charge_response + ) + response = @gateway.purchase(1022, @credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + # Failed due to invalid credit card + def test_failed_token + @gateway.expects(:ssl_post).returns(failed_token_response) + response = @gateway.token(@invalid_credit_card, @options) + assert_failure response + end + + # Failed due to invalid cvv + def test_invalid_cvv + @gateway.expects(:ssl_post).returns(failed_token_response) + response = @gateway.token(@invalid_cvv_card, @options) + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_token_response) + response = @gateway.verify(@invalid_cvv_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_token_response) + response = @gateway.verify(@invalid_credit_card, @options) + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void('FHBDKH123DFKG', @options) + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void('12345', @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).times(2).returns( + successful_token_response + ).then.returns( + successful_authorize_response + ) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'authorized', response.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).times(2).returns( + successful_token_response + ).then.returns( + failed_authorize_response + ) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, 'WSHDHEHKDH') + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + assert_equal 'refunded', response.message + end + + def test_successful_partial_refund + @gateway.expects(:ssl_post).returns(successful_partial_refund_response) + response = @gateway.refund(@amount - 1, 'WSHDHEHKDH') + assert_success response + assert_block do + PayArcGateway::SUCCESS_STATUS.include? response.message + end + assert_equal 'partial_refund', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + response = @gateway.refund(@amount, 'WSHDHEHKDH') + assert_failure response + assert_equal 'error', response.params['status'] + end + + def test_scrub + assert @gateway.supports_scrubbing? + transcript = @gateway.scrub(pre_scrubbed) + assert_scrubbed('quaslad-test.123.token-for-scrub', transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_equal transcript, post_scrubbed + end + + private + + def pre_scrubbed + %{ + <- "POST /v1/tokens HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Bearer token-fortesting\nAccept: application/json\r\nUser-Agent: PayArc ActiveMerchantBindings/1.119.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nHost: testapi.payarc.net\r\nContent-Length: 253\r\n\r\n" + <- "card_source=INTERNET&amount=100¤cy=usd&statement_description=&card_number=23445123456&exp_month=12&exp_year=2022&cvv=983&address_line1=920+Sunnyslope+Ave&address_line2=Bronx&city=New+York&state=New+York&zip=10469&country=USA&card_holder_name=" + + -> "{\"data\":{\"object\":\"Token\",\"id\":\"0q8lLw88mlqEwYNE\",\"used\":false,\"ip\":null,\"tokenization_method\":null,\"created_at\":1620645488,\"updated_at\":1620645488,\"card\":{\"data\":{\"object\":\"Card\",\"id\":\"PMyLv0m5v151095m\",\"address1\":\"920 Sunnyslope Ave\",\"address2\":\"Bronx\",\"card_source\":\"INTERNET\",\"card_holder_name\":\"\",\"is_default\":0,\"exp_month\":\"12\",\"exp_year\":\"2022\",\"is_verified\":0,\"fingerprint\":\"1Lv0NL11yvy5yL05\",\"city\":\"New York\",\"state\":\"New York\",\"zip\":\"10469\",\"brand\":\"V\",\"last4digit\":\"1111\",\"first6digi" + -> "t\":411111,\"country\":\"USA\",\"avs_status\":null,\"cvc_status\":null,\"address_check_passed\":0,\"zip_check_passed\":0,\"customer_id\":null,\"created_at\":1620645488,\"updated_at\":1620645488}}},\"meta\":{\"include\":[],\"custom\":[]}}" + } + end + + def post_scrubbed + %{ + <- "POST /v1/tokens HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Bearer [FILTERED]Accept: application/json\r\nUser-Agent: PayArc ActiveMerchantBindings/1.119.0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nHost: testapi.payarc.net\r\nContent-Length: 253\r\n\r\n" + <- "card_source=INTERNET&amount=100¤cy=usd&statement_description=&card_number=[FILTERED]&exp_month=12&exp_year=2022&cvv=[BLANK]&address_line1=920+Sunnyslope+Ave&address_line2=Bronx&city=New+York&state=New+York&zip=10469&country=USA&card_holder_name=" + + -> "{\"data\":{\"object\":\"Token\",\"id\":\"0q8lLw88mlqEwYNE\",\"used\":false,\"ip\":null,\"tokenization_method\":null,\"created_at\":1620645488,\"updated_at\":1620645488,\"card\":{\"data\":{\"object\":\"Card\",\"id\":\"PMyLv0m5v151095m\",\"address1\":\"920 Sunnyslope Ave\",\"address2\":\"Bronx\",\"card_source\":\"INTERNET\",\"card_holder_name\":\"\",\"is_default\":0,\"exp_month\":\"12\",\"exp_year\":\"2022\",\"is_verified\":0,\"fingerprint\":\"1Lv0NL11yvy5yL05\",\"city\":\"New York\",\"state\":\"New York\",\"zip\":\"10469\",\"brand\":\"V\",\"last4digit\":\"1111\",\"first6digi" + -> "t\":411111,\"country\":\"USA\",\"avs_status\":null,\"cvc_status\":null,\"address_check_passed\":0,\"zip_check_passed\":0,\"customer_id\":null,\"created_at\":1620645488,\"updated_at\":1620645488}}},\"meta\":{\"include\":[],\"custom\":[]}}" + } + end + + def successful_purchase_response + %( + { + "data": { + "object": "Charge", + "id": "LDoBnOnRnRWLOyWX", + "amount": 1010, + "amount_approved": 0, + "amount_refunded": 0, + "amount_captured": 1010, + "amount_voided": 0, + "application_fee_amount": 0, + "tip_amount": 0, + "payarc_fees": 0, + "type": "Sale", + "net_amount": 0, + "captured": 1, + "is_refunded": 0, + "status": "Bad Request - Try Again", + "auth_code": null, + "failure_code": "E0911", + "failure_message": "SystemError", + "charge_description": null, + "statement_description": "Bubbles Shop", + "invoice": null, + "under_review": 0, + "created_at": 1622000885, + "updated_at": 1622000896, + "email": null, + "phone_number": null, + "card_level": "LEVEL2", + "sales_tax": 10, + "purchase_order": "ABCD", + "supplier_reference_number": null, + "customer_ref_id": null, + "ship_to_zip": null, + "amex_descriptor": null, + "customer_vat_number": null, + "summary_commodity_code": null, + "shipping_charges": null, + "duty_charges": null, + "ship_from_zip": null, + "destination_country_code": null, + "vat_invoice": null, + "order_date": null, + "tax_category": null, + "tax_type": null, + "tax_rate": null, + "tax_amount": null, + "created_by": "bubbles@eyepaste.com", + "terminal_register": null, + "tip_amount_refunded": null, + "sales_tax_refunded": null, + "shipping_charges_refunded": null, + "duty_charges_refunded": null, + "pax_reference_number": null, + "refund_reason": null, + "refund_description": null, + "surcharge": 0, + "toll_amount": null, + "refund": { + "data": [] + }, + "card": { + "data": { + "object": "Card", + "id": "15y2901NPMP90MLv", + "address1": "920 Sunnyslope Ave", + "address2": "Bronx", + "card_source": "INTERNET", + "card_holder_name": "Rex Joseph", + "is_default": 0, + "exp_month": "12", + "exp_year": "2022", + "is_verified": 0, + "fingerprint": "1Lv0NN9LyN5Pm105", + "city": "New York", + "state": "New York", + "zip": "10469", + "brand": "V", + "last4digit": "1111", + "first6digit": 411111, + "country": "USA", + "avs_status": null, + "cvc_status": null, + "address_check_passed": 0, + "zip_check_passed": 0, + "customer_id": null, + "created_at": 1622000879, + "updated_at": 1622000896 + } + } + }, + "meta": { + "include": [ + "review" + ], + "custom": [] + } + } + ) + end + + def successful_token_response + %{ + { + "data": { + "object": "Token", + "id": "0mYL8wllq08YwlNE", + "used": false, + "ip": null, + "tokenization_method": null, + "created_at": 1620412546, + "updated_at": 1620412546, + "card": { + "data": { + "object": "Card", + "id": "59P1y0PL1M9L0vML", + "address1": "920 Sunnyslope Ave", + "address2": "Bronx", + "card_source": "INTERNET", + "card_holder_name": "Rex Joseph", + "is_default": 0, + "exp_month": "12", + "exp_year": "2022", + "is_verified": 0, + "fingerprint": "1Lv0NN9LyN5Pm105", + "city": "New York", + "state": "New York", + "zip": "10469", + "brand": "V", + "last4digit": "1111", + "first6digit": 411111, + "country": "USA", + "avs_status": null, + "cvc_status": null, + "address_check_passed": 0, + "zip_check_passed": 0, + "customer_id": null, + "created_at": 1620412546, + "updated_at": 1620412546 + } + } + }, + "meta": { + "include": [], + "custom": [] + } + } + } + end + + def failed_token_response + %{ + { + "status": "error", + "code": 0, + "message": "Invalid Card", + "status_code": 409, + "exception": "App\\Containers\\Card\\Exceptions\\InvalidCardDetailsException", + "file": "/home/deploy/payarc.com/app/Containers/Token/Actions/CreateTokenAction.php", + "line": 45 + } + } + end + + def successful_charge_response + %{ + { + "data": { + "object": "Charge", + "id": "LDoBnOnRnyLyOyWX", + "amount": 1010, + "amount_approved": "1010", + "amount_refunded": 0, + "amount_captured": "1010", + "amount_voided": 0, + "application_fee_amount": 0, + "tip_amount": 0, + "payarc_fees": 29, + "type": "Sale", + "net_amount": 981, + "captured": "1", + "is_refunded": 0, + "status": "submitted_for_settlement", + "auth_code": "TAS353", + "failure_code": null, + "failure_message": null, + "charge_description": null, + "statement_description": "Testing", + "invoice": null, + "under_review": false, + "created_at": 1620473990, + "updated_at": 1620473992, + "email": null, + "phone_number": null, + "card_level": "LEVEL1", + "sales_tax": null, + "purchase_order": null, + "supplier_reference_number": null, + "customer_ref_id": null, + "ship_to_zip": null, + "amex_descriptor": null, + "customer_vat_number": null, + "summary_commodity_code": null, + "shipping_charges": null, + "duty_charges": null, + "ship_from_zip": null, + "destination_country_code": null, + "vat_invoice": null, + "order_date": null, + "tax_category": null, + "tax_type": null, + "tax_rate": null, + "tax_amount": null, + "created_by": "bubbles@eyepaste.com", + "terminal_register": null, + "tip_amount_refunded": null, + "sales_tax_refunded": null, + "shipping_charges_refunded": null, + "duty_charges_refunded": null, + "pax_reference_number": null, + "refund_reason": null, + "refund_description": null, + "surcharge": 0, + "toll_amount": null, + "refund": { + "data": [] + }, + "card": { + "data": { + "object": "Card", + "id": "mP1Lv0NP19mN05MN", + "address1": "920 Sunnyslope Ave", + "address2": "Bronx", + "card_source": "INTERNET", + "card_holder_name": "Rex Joseph", + "is_default": 0, + "exp_month": "12", + "exp_year": "2022", + "is_verified": 0, + "fingerprint": "1Lv0NN9LyN5Pm105", + "city": "New York", + "state": "New York", + "zip": "10469", + "brand": "V", + "last4digit": "1111", + "first6digit": 411111, + "country": "USA", + "avs_status": null, + "cvc_status": null, + "address_check_passed": 0, + "zip_check_passed": 0, + "customer_id": null, + "created_at": 1620473969, + "updated_at": 1620473992 + } + } + }, + "meta": { + "include": [ + "review" + ], + "custom": [] + } + } + } + end + + def failed_capture_response + %{ + { + "status": "error", + "code": 0, + "message": "The given data was invalid.", + "errors": { + "currency": [ + "The selected currency is invalid." + ], + "customer_id": [ + "The customer id field is required when none of token id / cvv / exp year / exp month / card number are present." + ], + "token_id": [ + "The token id field is required when none of customer id / cvv / exp year / exp month / card number are present." + ], + "card_number": [ + "The card number field is required when none of token id / customer id are present." + ], + "exp_month": [ + "The exp month field is required when none of token id / customer id are present." + ], + "exp_year": [ + "The exp year field is required when none of token id / customer id are present." + ] + }, + "status_code": 422, + "exception": "Illuminate\\Validation\\ValidationException", + "file": "/home/deploy/payarc.com/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php", + "line": 130 + } + } + end + + def successful_void_response + %{ + { + "data": { + "object": "Charge", + "id": "LDoBnOnRnyLyOyWX", + "amount": 1010, + "amount_approved": 1010, + "amount_refunded": 0, + "amount_captured": 1010, + "amount_voided": 1010, + "application_fee_amount": 0, + "tip_amount": 0, + "payarc_fees": 29, + "type": "Sale", + "net_amount": 0, + "captured": 1, + "is_refunded": 0, + "status": "void", + "auth_code": "TAS353", + "failure_code": null, + "failure_message": null, + "charge_description": null, + "statement_description": "Testing", + "invoice": null, + "under_review": 0, + "created_at": 1620473990, + "updated_at": 1620495791, + "email": null, + "phone_number": null, + "card_level": "LEVEL1", + "sales_tax": null, + "purchase_order": null, + "supplier_reference_number": null, + "customer_ref_id": null, + "ship_to_zip": null, + "amex_descriptor": null, + "customer_vat_number": null, + "summary_commodity_code": null, + "shipping_charges": null, + "duty_charges": null, + "ship_from_zip": null, + "destination_country_code": null, + "vat_invoice": null, + "order_date": null, + "tax_category": null, + "tax_type": null, + "tax_rate": null, + "tax_amount": null, + "created_by": "bubbles@eyepaste.com", + "terminal_register": null, + "tip_amount_refunded": null, + "sales_tax_refunded": null, + "shipping_charges_refunded": null, + "duty_charges_refunded": null, + "pax_reference_number": null, + "refund_reason": null, + "refund_description": null, + "surcharge": 0, + "toll_amount": null, + "refund": { + "data": [] + }, + "card": { + "data": { + "object": "Card", + "id": "mP1Lv0NP19mN05MN", + "address1": "920 Sunnyslope Ave", + "address2": "Bronx", + "card_source": "INTERNET", + "card_holder_name": "Rex Joseph", + "is_default": 0, + "exp_month": "12", + "exp_year": "2022", + "is_verified": 0, + "fingerprint": "1Lv0NN9LyN5Pm105", + "city": "New York", + "state": "New York", + "zip": "10469", + "brand": "V", + "last4digit": "1111", + "first6digit": 411111, + "country": "USA", + "avs_status": null, + "cvc_status": null, + "address_check_passed": 0, + "zip_check_passed": 0, + "customer_id": null, + "created_at": 1620473969, + "updated_at": 1620473992 + } + } + }, + "meta": { + "include": [ + "review" + ], + "custom": [] + } + } + } + end + + def failed_void_response + %{ + { + "status": "error", + "code": 0, + "message": "Property [is_under_review] does not exist on this collection instance.", + "status_code": 500, + "exception": "Exception", + "file": "/home/deploy/payarc.com/vendor/laravel/framework/src/Illuminate/Support/Collection.php", + "line": 2160 + } + } + end + + def successful_authorize_response + %{ + { + "data": { + "object": "Charge", + "id": "BXMbnObLnoDMORoD", + "amount": 1010, + "amount_approved": "1010", + "amount_refunded": 0, + "amount_captured": 0, + "amount_voided": 0, + "application_fee_amount": 0, + "tip_amount": 0, + "payarc_fees": 0, + "type": "Sale", + "net_amount": 0, + "captured": "0", + "is_refunded": 0, + "status": "authorized", + "auth_code": "TAS363", + "failure_code": null, + "failure_message": null, + "charge_description": null, + "statement_description": "Testing", + "invoice": null, + "under_review": false, + "created_at": 1620651112, + "updated_at": 1620651115, + "email": null, + "phone_number": null, + "card_level": "LEVEL1", + "sales_tax": null, + "purchase_order": null, + "supplier_reference_number": null, + "customer_ref_id": null, + "ship_to_zip": null, + "amex_descriptor": null, + "customer_vat_number": null, + "summary_commodity_code": null, + "shipping_charges": null, + "duty_charges": null, + "ship_from_zip": null, + "destination_country_code": null, + "vat_invoice": null, + "order_date": null, + "tax_category": null, + "tax_type": null, + "tax_rate": null, + "tax_amount": null, + "created_by": "bubbles@eyepaste.com", + "terminal_register": null, + "tip_amount_refunded": null, + "sales_tax_refunded": null, + "shipping_charges_refunded": null, + "duty_charges_refunded": null, + "pax_reference_number": null, + "refund_reason": null, + "refund_description": null, + "surcharge": 0, + "toll_amount": null, + "refund": { + "data": [] + }, + "card": { + "data": { + "object": "Card", + "id": "mP1Lv0NP19y105MN", + "address1": "920 Sunnyslope Ave", + "address2": "Bronx", + "card_source": "INTERNET", + "card_holder_name": "Rex Joseph", + "is_default": 0, + "exp_month": "12", + "exp_year": "2022", + "is_verified": 0, + "fingerprint": "1Lv0NN9LyN5Pm105", + "city": "New York", + "state": "New York", + "zip": "10469", + "brand": "V", + "last4digit": "1111", + "first6digit": 411111, + "country": "USA", + "avs_status": null, + "cvc_status": null, + "address_check_passed": 0, + "zip_check_passed": 0, + "customer_id": null, + "created_at": 1620651066, + "updated_at": 1620651115 + } + } + }, + "meta": { + "include": [ + "review" + ], + "custom": [] + } + } + } + end + + def failed_authorize_response + %{ + { + "status": "error", + "code": 0, + "message": "The requested token is not valid or already used", + "status_code": 400, + "exception": "App\\Containers\\Customer\\Exceptions\\InvalidTokenException", + "file": "/home/deploy/payarc.com/app/Containers/Charge/Actions/CreateSaleAction.php", + "line": 260 + } + } + end + + def failed_charge_response + %{ + { + "status": "error", + "code": 0, + "message": "The requested token is not valid or already used", + "status_code": 400, + "exception": "App\\Containers\\Customer\\Exceptions\\InvalidTokenException", + "file": "/home/deploy/payarc.com/app/Containers/Charge/Actions/CreateSaleAction.php", + "line": 260 + } + } + end + + def successful_refund_response + %{ + { + "data": { + "object": "Refund", + "id": "x9bQvpYvxBOYOqyB", + "refund_amount": "1010", + "currency": "usd", + "status": "refunded", + "reason": "requested_by_customer", + "description": "", + "email": null, + "receipt_number": null, + "charge_id": "LnbDBOMMbWXyORXM", + "created_at": 1620734715, + "updated_at": 1620734715 + }, + "meta": { + "include": [], + "custom": [] + } + } + } + end + + def successful_partial_refund_response + %{ + { + "data": { + "object": "Refund", + "id": "Pqy8QxY8vb9YvB1O", + "refund_amount": "500", + "currency": "usd", + "status": "partial_refund", + "reason": "requested_by_customer", + "description": "", + "email": null, + "receipt_number": null, + "charge_id": "RbWLnOyBbyWBODBX", + "created_at": 1620734893, + "updated_at": 1620734893 + }, + "meta": { + "include": [], + "custom": [] + } + } + } + end + + def failed_refund_response + %{ + { + "status": "error", + "code": 0, + "message": "Amount requested is not available for Refund ", + "status_code": 409, + "exception": "Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException", + "file": "/home/deploy/payarc.com/app/Containers/Refund/Tasks/CheckAmountTask.php", + "line": 39 } + } + end +end diff --git a/test/unit/gateways/pay_conex_test.rb b/test/unit/gateways/pay_conex_test.rb index 0269f29396d..78b62196eb5 100644 --- a/test/unit/gateways/pay_conex_test.rb +++ b/test/unit/gateways/pay_conex_test.rb @@ -140,7 +140,7 @@ def test_failed_store def test_card_present_purchase_passes_track_data stub_comms do @gateway.purchase(@amount, credit_card_with_track_data('4000100011112224')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/card_tracks/, data) end.respond_with(successful_card_present_purchase_response) end @@ -171,6 +171,11 @@ def test_scrub assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) end + def test_scrub_check + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_check), post_scrubbed_check + end + private def pre_scrubbed @@ -197,6 +202,18 @@ def post_scrubbed POST_SCRUBBED end + def pre_scrubbed_check + <<-PRE_SCRUBBED + <- "account_id=220614968961&api_accesskey=69e9c4dd6b8ab9ab47da4e288df78315&tender_type=ACH&first_name=Jim&last_name=Smith&bank_account_number=15378535&bank_routing_number=244183602&check_number=1&ach_account_type=checking&street_address1=456+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00¤cy=USD&email=joe%40example.com&transaction_type=SALE" + PRE_SCRUBBED + end + + def post_scrubbed_check + <<-POST_SCRUBBED + <- "account_id=220614968961&api_accesskey=[FILTERED]&tender_type=ACH&first_name=Jim&last_name=Smith&bank_account_number=[FILTERED]&bank_routing_number=[FILTERED]&check_number=1&ach_account_type=checking&street_address1=456+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00¤cy=USD&email=joe%40example.com&transaction_type=SALE" + POST_SCRUBBED + end + def successful_purchase_response %({"transaction_id":"000000001681","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:35:52","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"0916","authorization_code":"CVI877","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) end diff --git a/test/unit/gateways/pay_gate_xml_test.rb b/test/unit/gateways/pay_gate_xml_test.rb index 157cd2bd89b..39ef9455139 100644 --- a/test/unit/gateways/pay_gate_xml_test.rb +++ b/test/unit/gateways/pay_gate_xml_test.rb @@ -10,11 +10,11 @@ def setup # May need to generate a unique order id as server responds with duplicate order detected @options = { - :order_id => Time.now.getutc, - :billing_address => address, - :email => 'john.doe@example.com', - :ip => '127.0.0.1', - :description => 'Store Purchase', + order_id: Time.now.getutc, + billing_address: address, + email: 'john.doe@example.com', + ip: '127.0.0.1', + description: 'Store Purchase' } end @@ -101,5 +101,4 @@ def successful_refund_response ENDOFXML end - end diff --git a/test/unit/gateways/pay_hub_test.rb b/test/unit/gateways/pay_hub_test.rb index 0226cea3ec0..3f8317e5d75 100644 --- a/test/unit/gateways/pay_hub_test.rb +++ b/test/unit/gateways/pay_hub_test.rb @@ -137,7 +137,7 @@ def test_expired_card end def test_card_declined - ['05', '61', '62', '65', '93'].each do |error_code| + %w[05 61 62 65 93].each do |error_code| @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) response = @gateway.purchase(@amount, @credit_card, @options) @@ -147,7 +147,7 @@ def test_card_declined end def test_call_issuer - ['01', '02'].each do |error_code| + %w[01 02].each do |error_code| @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) response = @gateway.purchase(@amount, @credit_card, @options) @@ -157,7 +157,7 @@ def test_call_issuer end def test_pickup_card - ['04', '07', '41', '43'].each do |error_code| + %w[04 07 41 43].each do |error_code| @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) response = @gateway.purchase(@amount, @credit_card, @options) @@ -190,7 +190,7 @@ def test_cvv_codes def test_unsuccessful_request @gateway.expects(:ssl_request).returns(failed_purchase_or_authorize_response) - @gateway.options.merge!({:mode => 'live', :test => false}) + @gateway.options.merge!({ mode: 'live', test: false }) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -200,7 +200,7 @@ def test_unsuccessful_request def test_unsuccessful_authorize @gateway.expects(:ssl_request).returns(failed_purchase_or_authorize_response) - @gateway.options.merge!({:mode => 'live', :test => false}) + @gateway.options.merge!({ mode: 'live', test: false }) response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response diff --git a/test/unit/gateways/pay_junction_test.rb b/test/unit/gateways/pay_junction_test.rb index 16a3139be79..045f2221ab2 100644 --- a/test/unit/gateways/pay_junction_test.rb +++ b/test/unit/gateways/pay_junction_test.rb @@ -8,14 +8,14 @@ def setup Base.mode = :test @gateway = PayJunctionGateway.new( - :login => 'pj-ql-01', - :password => 'pj-ql-01p' - ) + login: 'pj-ql-01', + password: 'pj-ql-01p' + ) @credit_card = credit_card @options = { - :billing_address => address, - :description => 'Test purchase' + billing_address: address, + description: 'Test purchase' } @amount = 100 end @@ -23,16 +23,16 @@ def setup def test_detect_test_credentials_when_in_production Base.mode = :production - live_gw = PayJunctionGateway.new( - :login => 'l', - :password => 'p' - ) + live_gw = PayJunctionGateway.new( + login: 'l', + password: 'p' + ) assert_false live_gw.test? test_gw = PayJunctionGateway.new( - :login => 'pj-ql-01', - :password => 'pj-ql-01p' - ) + login: 'pj-ql-01', + password: 'pj-ql-01p' + ) assert test_gw.test? end @@ -84,7 +84,7 @@ def test_add_creditcard_with_track_data @credit_card.track_data = 'Tracking data' stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match 'dc_track=Tracking+data', data assert_no_match(/dc_name=/, data) assert_no_match(/dc_number=/, data) @@ -97,108 +97,108 @@ def test_add_creditcard_with_track_data private def successful_authorization_response - <<-RESPONSE -dc_merchant_name=PayJunction - (demo)dc_merchant_address=3 W. Carrillodc_merchant_city=Santa Barbaradc_merchant_state=CAdc_merchant_zip=93101dc_merchant_phone=800-601-0230dc_device_id=1174dc_transaction_date=2007-11-28 19:22:33.791634dc_transaction_action=chargedc_approval_code=TAS193dc_response_code=00dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302dc_posture=holddc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fadc_notes=--START QUICK-LINK DEBUG-- -----Vars Received---- -dc_expiration_month => * -dc_expiration_year => * -dc_invoice => 9f76c4e4bd66a36dc5aeb4bd7b3a02fa -dc_logon => pj-ql-01 -dc_name => Cody Fauser -dc_number => * -dc_password => * -dc_transaction_amount => 4.00 -dc_transaction_type => AUTHORIZATION -dc_verification_number => * -dc_version => 1.2 -----End Vars---- - -----Start Response Sent---- -dc_merchant_name=PayJunction - (demo) -dc_merchant_address=3 W. Carrillo -dc_merchant_city=Santa Barbara -dc_merchant_state=CA -dc_merchant_zip=93101 -dc_merchant_phone=800-601-0230 -dc_device_id=1174 -dc_transaction_date=2007-11-28 19:22:33.791634 -dc_transaction_action=charge -dc_approval_code=TAS193 -dc_response_code=00 -dc_response_message=APPROVAL TAS193 -dc_transaction_id=3144302 -dc_posture=hold -dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa -dc_notes=null -dc_card_name=cody fauser -dc_card_brand=VSA -dc_card_exp=XX/XX -dc_card_number=XXXX-XXXX-XXXX-3344 -dc_card_address= -dc_card_city= -dc_card_zipcode= -dc_card_state= -dc_card_country= -dc_base_amount=4.00 -dc_tax_amount=0.00 -dc_capture_amount=4.00 -dc_cashback_amount=0.00 -dc_shipping_amount=0.00 -----End Response Sent---- -dc_card_name=cody fauserdc_card_brand=VSAdc_card_exp=XX/XXdc_card_number=XXXX-XXXX-XXXX-3344dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=4.00dc_tax_amount=0.00dc_capture_amount=4.00dc_cashback_amount=0.00dc_shipping_amount=0.00 + <<~RESPONSE + dc_merchant_name=PayJunction - (demo)dc_merchant_address=3 W. Carrillodc_merchant_city=Santa Barbaradc_merchant_state=CAdc_merchant_zip=93101dc_merchant_phone=800-601-0230dc_device_id=1174dc_transaction_date=2007-11-28 19:22:33.791634dc_transaction_action=chargedc_approval_code=TAS193dc_response_code=00dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302dc_posture=holddc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fadc_notes=--START QUICK-LINK DEBUG-- + ----Vars Received---- + dc_expiration_month => * + dc_expiration_year => * + dc_invoice => 9f76c4e4bd66a36dc5aeb4bd7b3a02fa + dc_logon => pj-ql-01 + dc_name => Cody Fauser + dc_number => * + dc_password => * + dc_transaction_amount => 4.00 + dc_transaction_type => AUTHORIZATION + dc_verification_number => * + dc_version => 1.2 + ----End Vars---- + + ----Start Response Sent---- + dc_merchant_name=PayJunction - (demo) + dc_merchant_address=3 W. Carrillo + dc_merchant_city=Santa Barbara + dc_merchant_state=CA + dc_merchant_zip=93101 + dc_merchant_phone=800-601-0230 + dc_device_id=1174 + dc_transaction_date=2007-11-28 19:22:33.791634 + dc_transaction_action=charge + dc_approval_code=TAS193 + dc_response_code=00 + dc_response_message=APPROVAL TAS193 + dc_transaction_id=3144302 + dc_posture=hold + dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa + dc_notes=null + dc_card_name=cody fauser + dc_card_brand=VSA + dc_card_exp=XX/XX + dc_card_number=XXXX-XXXX-XXXX-3344 + dc_card_address= + dc_card_city= + dc_card_zipcode= + dc_card_state= + dc_card_country= + dc_base_amount=4.00 + dc_tax_amount=0.00 + dc_capture_amount=4.00 + dc_cashback_amount=0.00 + dc_shipping_amount=0.00 + ----End Response Sent---- + dc_card_name=cody fauserdc_card_brand=VSAdc_card_exp=XX/XXdc_card_number=XXXX-XXXX-XXXX-3344dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=4.00dc_tax_amount=0.00dc_capture_amount=4.00dc_cashback_amount=0.00dc_shipping_amount=0.00 RESPONSE end def successful_refund_response - <<-RESPONSE -dc_merchant_name=PayJunction - (demo)dc_merchant_address=3 W. Carrillodc_merchant_city=Santa Barbaradc_merchant_state=CAdc_merchant_zip=93101dc_merchant_phone=800-601-0230dc_device_id=1174dc_transaction_date=2007-11-28 19:22:33.791634dc_transaction_action=creditdc_approval_code=TAS193dc_response_code=00dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302dc_posture=holddc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fadc_notes=--START QUICK-LINK DEBUG-- -----Vars Received---- -dc_expiration_month => * -dc_expiration_year => * -dc_invoice => 9f76c4e4bd66a36dc5aeb4bd7b3a02fa -dc_logon => pj-ql-01 -dc_name => Cody Fauser -dc_number => * -dc_password => * -dc_transaction_amount => 4.00 -dc_transaction_type => CREDIT -dc_verification_number => * -dc_version => 1.2 -----End Vars---- - -----Start Response Sent---- -dc_merchant_name=PayJunction - (demo) -dc_merchant_address=3 W. Carrillo -dc_merchant_city=Santa Barbara -dc_merchant_state=CA -dc_merchant_zip=93101 -dc_merchant_phone=800-601-0230 -dc_device_id=1174 -dc_transaction_date=2007-11-28 19:22:33.791634 -dc_transaction_action=charge -dc_approval_code=TAS193 -dc_response_code=00 -dc_response_message=APPROVAL TAS193 -dc_transaction_id=3144302 -dc_posture=hold -dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa -dc_notes=null -dc_card_name=cody fauser -dc_card_brand=VSA -dc_card_exp=XX/XX -dc_card_number=XXXX-XXXX-XXXX-3344 -dc_card_address= -dc_card_city= -dc_card_zipcode= -dc_card_state= -dc_card_country= -dc_base_amount=4.00 -dc_tax_amount=0.00 -dc_capture_amount=4.00 -dc_cashback_amount=0.00 -dc_shipping_amount=0.00 -----End Response Sent---- -dc_card_name=cody fauserdc_card_brand=VSAdc_card_exp=XX/XXdc_card_number=XXXX-XXXX-XXXX-3344dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=4.00dc_tax_amount=0.00dc_capture_amount=4.00dc_cashback_amount=0.00dc_shipping_amount=0.00 + <<~RESPONSE + dc_merchant_name=PayJunction - (demo)dc_merchant_address=3 W. Carrillodc_merchant_city=Santa Barbaradc_merchant_state=CAdc_merchant_zip=93101dc_merchant_phone=800-601-0230dc_device_id=1174dc_transaction_date=2007-11-28 19:22:33.791634dc_transaction_action=creditdc_approval_code=TAS193dc_response_code=00dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302dc_posture=holddc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fadc_notes=--START QUICK-LINK DEBUG-- + ----Vars Received---- + dc_expiration_month => * + dc_expiration_year => * + dc_invoice => 9f76c4e4bd66a36dc5aeb4bd7b3a02fa + dc_logon => pj-ql-01 + dc_name => Cody Fauser + dc_number => * + dc_password => * + dc_transaction_amount => 4.00 + dc_transaction_type => CREDIT + dc_verification_number => * + dc_version => 1.2 + ----End Vars---- + + ----Start Response Sent---- + dc_merchant_name=PayJunction - (demo) + dc_merchant_address=3 W. Carrillo + dc_merchant_city=Santa Barbara + dc_merchant_state=CA + dc_merchant_zip=93101 + dc_merchant_phone=800-601-0230 + dc_device_id=1174 + dc_transaction_date=2007-11-28 19:22:33.791634 + dc_transaction_action=charge + dc_approval_code=TAS193 + dc_response_code=00 + dc_response_message=APPROVAL TAS193 + dc_transaction_id=3144302 + dc_posture=hold + dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa + dc_notes=null + dc_card_name=cody fauser + dc_card_brand=VSA + dc_card_exp=XX/XX + dc_card_number=XXXX-XXXX-XXXX-3344 + dc_card_address= + dc_card_city= + dc_card_zipcode= + dc_card_state= + dc_card_country= + dc_base_amount=4.00 + dc_tax_amount=0.00 + dc_capture_amount=4.00 + dc_cashback_amount=0.00 + dc_shipping_amount=0.00 + ----End Response Sent---- + dc_card_name=cody fauserdc_card_brand=VSAdc_card_exp=XX/XXdc_card_number=XXXX-XXXX-XXXX-3344dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=4.00dc_tax_amount=0.00dc_capture_amount=4.00dc_cashback_amount=0.00dc_shipping_amount=0.00 RESPONSE end diff --git a/test/unit/gateways/pay_junction_v2_test.rb b/test/unit/gateways/pay_junction_v2_test.rb index 99a40a808ec..7e27979f33b 100644 --- a/test/unit/gateways/pay_junction_v2_test.rb +++ b/test/unit/gateways/pay_junction_v2_test.rb @@ -5,9 +5,10 @@ def setup @gateway = PayJunctionV2Gateway.new(api_login: 'api_login', api_password: 'api_password', api_key: 'api_key') @amount = 99 - @credit_card = credit_card('4444333322221111', month: 01, year: 2020, verification_value: 999) + @credit_card = credit_card('4444333322221111', month: 01, year: 2022, verification_value: 999) @options = { - order_id: generate_unique_id + order_id: generate_unique_id, + billing_address: address } end @@ -205,6 +206,20 @@ def test_failed_store assert_match %r{Card Number is not a valid card number}, response.message end + def test_add_address + post = { card: { billingAddress: {} } } + @gateway.send(:add_address, post, @options) + assert_equal @options[:billing_address][:first_name], post[:billingFirstName] + assert_equal @options[:billing_address][:last_name], post[:billingLastName] + assert_equal @options[:billing_address][:company], post[:billingCompanyName] + assert_equal @options[:billing_address][:phone_number], post[:billingPhone] + assert_equal @options[:billing_address][:address1], post[:billingAddress] + assert_equal @options[:billing_address][:city], post[:billingCity] + assert_equal @options[:billing_address][:state], post[:billingState] + assert_equal @options[:billing_address][:country], post[:billingCountry] + assert_equal @options[:billing_address][:zip], post[:billingZip] + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed diff --git a/test/unit/gateways/pay_secure_test.rb b/test/unit/gateways/pay_secure_test.rb index a49bf1a60f0..37e3db47250 100644 --- a/test/unit/gateways/pay_secure_test.rb +++ b/test/unit/gateways/pay_secure_test.rb @@ -1,18 +1,17 @@ require 'test_helper' class PaySecureTest < Test::Unit::TestCase - def setup @gateway = PaySecureGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @options = { - :order_id => '1000', - :billing_address => address, - :description => 'Test purchase' + order_id: '1000', + billing_address: address, + description: 'Test purchase' } @amount = 100 end @@ -51,22 +50,22 @@ def test_cvv_result_not_supported private def successful_purchase_response - <<-RESPONSE -Status: Accepted -SettlementDate: 2007-10-09 -AUTHNUM: 2778 -ErrorString: No Error -CardBin: 1 -ERROR: 0 -TransID: SimProxy 54041670 + <<~RESPONSE + Status: Accepted + SettlementDate: 2007-10-09 + AUTHNUM: 2778 + ErrorString: No Error + CardBin: 1 + ERROR: 0 + TransID: SimProxy 54041670 RESPONSE end def failure_response - <<-RESPONSE -Status: Declined -ErrorString: Field value '8f796cb29a1be32af5ce12d4ca7425c2' does not match required format. -ERROR: 1 + <<~RESPONSE + Status: Declined + ErrorString: Field value '8f796cb29a1be32af5ce12d4ca7425c2' does not match required format. + ERROR: 1 RESPONSE end end diff --git a/test/unit/gateways/pay_trace_test.rb b/test/unit/gateways/pay_trace_test.rb new file mode 100644 index 00000000000..42be462bbf0 --- /dev/null +++ b/test/unit/gateways/pay_trace_test.rb @@ -0,0 +1,565 @@ +require 'test_helper' + +class PayTraceTest < Test::Unit::TestCase + include CommStub + + def setup + @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 + + @options = { + billing_address: address + } + 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 = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 392483066, response.authorization + end + + def test_successful_purchase_with_ach + response = stub_comms(@gateway) do + @gateway.purchase(@amount, @echeck, @options) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/sale/by_account' + assert_equal request['amount'], '1.00' + 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] + end.respond_with(successful_ach_processing_response) + + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_successful_purchase_by_customer_with_ach + customer_id = 'customerId121' + response = stub_comms(@gateway) do + @gateway.purchase(@amount, customer_id, { check_transaction: 'true' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/sale/by_customer' + assert_equal request['amount'], '1.00' + assert_equal request['customer_id'], customer_id + assert_equal request['integrator_id'], @gateway.options[:integrator_id] + end.respond_with(successful_ach_processing_response) + + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_successful_purchase_with_level_3_data + @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response).then.returns(successful_level_3_response) + + options = { + visa_or_mastercard: 'visa', + invoice_id: 'inv12345', + customer_reference_id: '123abcd', + tax_amount: 499, + national_tax_amount: 172, + merchant_tax_id: '3456defg', + customer_tax_id: '3456test', + commodity_code: '4321', + discount_amount: 99, + freight_amount: 75, + duty_amount: 32, + source_address: { + zip: '94947' + }, + shipping_address: { + zip: '94948', + country: 'US' + }, + additional_tax_amount: 4, + additional_tax_rate: 1, + line_items: [ + { + additional_tax_amount: 0, + additional_tax_rate: 8, + amount: 1999, + commodity_code: '123commodity', + description: 'plumbing', + discount_amount: 327, + product_id: 'skucode123', + quantity: 4, + unit_of_measure: 'EACH', + unit_cost: 424 + } + ] + } + + response = @gateway.purchase(100, @credit_card, options) + assert_success response + assert_equal 101, response.params['response_code'] + end + + def test_omitting_level_3_fields_with_nil_values + options = { + visa_or_mastercard: 'mastercard', + additional_tax_included: nil, + line_items: [ + { + description: 'business services', + discount_included: nil + } + ] + } + stub_comms(@gateway) do + @gateway.purchase(100, @credit_card, options) + end.check_request do |endpoint, data, _headers| + next unless endpoint == 'https://api.paytrace.com/v1/level_three/mastercard' + + refute_includes data, 'discount_included' + refute_includes data, 'additional_tax_included' + end.respond_with(successful_level_3_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal PayTraceGateway::STANDARD_ERROR_CODE[:declined], response.error_code + end + + def test_failed_ach_processing + @gateway.expects(:ssl_post).returns(failed_ach_processing_response) + + response = @gateway.purchase(@amount, @echeck, @options) + assert_failure response + assert_equal response.message, 'Your check was NOT successfully processed. ' + end + + def test_failed_bad_request_ach_processing + @gateway.expects(:ssl_post).returns(failed_bad_request_ach_processing_response) + + response = @gateway.authorize(@amount, @echeck, @options) + assert_failure response + assert_include response.message, 'Please provide a valid Checking Account Number.' + 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 true, response.success? + end + + def test_successful_authorize_with_ach + response = stub_comms(@gateway) do + @gateway.authorize(@amount, @echeck, @options) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/hold/by_account' + assert_equal request['amount'], '1.00' + 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] + end.respond_with(successful_ach_processing_response) + + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_successful_authorize_by_customer_with_ach + customer_id = 'customerId121' + response = stub_comms(@gateway) do + @gateway.authorize(@amount, customer_id, { check_transaction: 'true' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/hold/by_customer' + assert_equal request['amount'], '1.00' + assert_equal request['customer_id'], customer_id + assert_equal request['integrator_id'], @gateway.options[:integrator_id] + end.respond_with(successful_ach_processing_response) + + assert_success response + assert_equal response.message, 'Your check was successfully processed.' + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Your transaction was not approved. EXPIRED CARD - Expired card', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + transaction_id = 10598543 + + response = @gateway.capture(@amount, transaction_id, @options) + assert_success response + assert_equal 'Your transaction was successfully captured.', response.message + end + + def test_successful_capture_with_ach + check_transaction_id = 9981615 + response = stub_comms(@gateway) do + @gateway.capture(@amount, check_transaction_id, { check_transaction: 'true' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/manage/fund' + assert_equal request['amount'], '1.00' + assert_equal request['check_transaction_id'], check_transaction_id + assert_equal request['integrator_id'], @gateway.options[:integrator_id] + end.respond_with(successful_ach_capture_void_response) + + assert_success response + assert_equal response.message, 'Your check was successfully managed.' + end + + def test_successful_level_3_data_field_mapping + authorization = 123456789 + options = { + visa_or_mastercard: 'visa', + address: { + zip: '99201' + } + } + stub_comms(@gateway) do + @gateway.capture(@amount, authorization, options) + end.check_request do |endpoint, data, _headers| + next unless endpoint == 'https://api.paytrace.com/v1/level_three/visa' + + assert_match(/"source_address":{"zip":"99201"}/, data) + end.respond_with(successful_level_3_visa) + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_equal 'Errors- code:58, message:["Please provide a valid Transaction ID."]', response.message + end + + def test_failed_capture_void_response + @gateway.expects(:ssl_post).returns(failed_ach_capture_void_response) + + response = @gateway.capture(@amount, @echeck, @options) + assert_failure response + assert_include response.message, 'The Check ID that you have provided was not found in the PayTrace records. It may already be voided or settled.' + end + + def test_successful_refund + transaction_id = 105968532 + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(100, transaction_id) + assert_success response + assert_equal 'Your transaction successfully refunded.', response.message + end + + def test_successful_refund_with_ach + check_transaction_id = 9981615 + response = stub_comms(@gateway) do + @gateway.refund(@amount, check_transaction_id, { check_transaction: 'true' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/refund/by_transaction' + assert_equal request['amount'], '1.00' + assert_equal request['check_transaction_id'], check_transaction_id + assert_equal request['integrator_id'], @gateway.options[:integrator_id] + end.respond_with(successful_ach_refund_response) + + assert_success response + assert_equal response.message, 'Your check was successfully refunded.' + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(200, '', @options) + assert_failure response + assert_equal 'Errors- code:981, message:["Log in failed for insufficient permissions."]', response.message + end + + def test_failed_refund_response + @gateway.expects(:ssl_post).returns(failed_ach_refund_response) + + response = @gateway.refund(@amount, @echeck, @options) + assert_failure response + assert_include response.message, 'The Check ID that you provided was not found in the PayTrace record.' + end + + def test_successful_void + transaction_id = 105968551 + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void(transaction_id, @options) + assert_success void + assert_equal 'Your transaction was successfully voided.', void.message + end + + def test_successful_void_with_ach + check_transaction_id = 9981615 + response = stub_comms(@gateway) do + @gateway.void(check_transaction_id, { check_transaction: 'true' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_include endpoint, 'checks/manage/void' + assert_equal request['check_transaction_id'], check_transaction_id + assert_equal request['integrator_id'], @gateway.options[:integrator_id] + end.respond_with(successful_ach_capture_void_response) + + assert_success response + assert_equal response.message, 'Your check was successfully managed.' + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'Errors- code:58, message:["Please provide a valid Transaction ID."]', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'Your transaction was not approved. EXPIRED CARD - Expired card', response.message + end + + def test_successful_customer_creation + @gateway.expects(:ssl_post).returns(successful_create_customer_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal true, response.success? + end + + def test_duplicate_customer_creation + options = { + customer_id: '7cad678781bf0456d50e1478', + billing_address: { + address1: '8320 This Way Lane', + city: 'Placeville', + state: 'CA', + zip: '85284' + } + } + @gateway.expects(:ssl_post).returns(failed_customer_creation_response) + response = @gateway.store(@credit_card, options) + assert_failure response + assert_equal false, response.success? + assert_match 'Please provide a unique customer ID.', response.params['errors'].to_s + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.paytrace.com:443... + opened + 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\"}" + -> "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" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Status: 200 OK\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "Referrer-Policy: strict-origin-when-cross-origin\r\n" + -> "X-Permitted-Cross-Domain-Policies: none\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Request-Id: f008583e-3755-4eca-b8a0-83d8d82cefca\r\n" + -> "X-Download-Options: noopen\r\n" + -> "ETag: W/\"4edcbabd892d2f033a4cbc7932f26fae\"\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Runtime: 1.984489\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Security-Policy: frame-ancestors 'self';\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "\r\n" + -> "142\r\n" + reading 322 bytes... + -> "{\"success\":true,\"response_code\":101,\"status_message\":\"Your transaction was successfully approved.\",\"transaction_id\":395970044,\"approval_code\":\"TAS679\",\"approval_message\":\" NO MATCH - Approved and completed\",\"avs_response\":\"No Match\",\"csc_response\":\"\",\"external_transaction_id\":\"\",\"masked_card_number\":\"xxxxxxxxxxxx5439\"}" + read 322 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.paytrace.com:443... + opened + 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]\"}" + -> "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" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Status: 200 OK\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "Referrer-Policy: strict-origin-when-cross-origin\r\n" + -> "X-Permitted-Cross-Domain-Policies: none\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Request-Id: f008583e-3755-4eca-b8a0-83d8d82cefca\r\n" + -> "X-Download-Options: noopen\r\n" + -> "ETag: W/\"4edcbabd892d2f033a4cbc7932f26fae\"\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Runtime: 1.984489\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Security-Policy: frame-ancestors 'self';\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "\r\n" + -> "142\r\n" + reading 322 bytes... + -> "{\"success\":true,\"response_code\":101,\"status_message\":\"Your transaction was successfully approved.\",\"transaction_id\":395970044,\"approval_code\":\"TAS679\",\"approval_message\":\" NO MATCH - Approved and completed\",\"avs_response\":\"No Match\",\"csc_response\":\"\",\"external_transaction_id\":\"\",\"masked_card_number\":\"xxxxxxxxxxxx5439\"}" + read 322 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + '{"success":true,"response_code":101,"status_message":"Your transaction was successfully approved.","transaction_id":392483066,"approval_code":"TAS610","approval_message":" NO MATCH - Approved and completed","avs_response":"No Match","csc_response":"","external_transaction_id":"","masked_card_number":"xxxxxxxxxxxx5439"}' + end + + def successful_level_3_response + '{"success":true,"response_code":170,"status_message":"Visa/MasterCard enhanced data was successfully added to Transaction ID 392483066. 1 line item records were created."}' + end + + def successful_level_3_visa + '{"success":true,"response_code":170,"status_message":"Visa/MasterCard enhanced data was successfully added to Transaction ID 123456789. 2 line item records were created."}' + end + + def failed_purchase_response + '{"success":false,"response_code":102,"status_message":"Your transaction was not approved.","transaction_id":392501201,"approval_code":"","approval_message":" DECLINE - Do not honor","avs_response":"No Match","csc_response":"","external_transaction_id":"","masked_card_number":"xxxxxxxxxxxx5439"}' + end + + def successful_authorize_response + '{"success":true,"response_code":101,"status_message":"Your transaction was successfully approved.","transaction_id":392224547,"approval_code":"TAS161","approval_message":" NO MATCH - Approved and completed","avs_response":"No Match","csc_response":"","external_transaction_id":"","masked_card_number":"xxxxxxxxxxxx2224"}' + end + + def failed_authorize_response + '{"success":false,"response_code":102,"status_message":"Your transaction was not approved.","transaction_id":395971008,"approval_code":"","approval_message":" EXPIRED CARD - Expired card","avs_response":"No Match","csc_response":"","external_transaction_id":"","masked_card_number":"xxxxxxxxxxxx5439"}' + end + + def successful_capture_response + '{"success":true,"response_code":112,"status_message":"Your transaction was successfully captured.","transaction_id":392442990,"external_transaction_id":""}' + end + + def failed_capture_response + '{"success":false,"response_code":1,"status_message":"One or more errors has occurred.","errors":{"58":["Please provide a valid Transaction ID."]},"external_transaction_id":""}' + end + + def successful_refund_response + '{"success":true,"response_code":106,"status_message":"Your transaction successfully refunded.","transaction_id":105968559,"external_transaction_id":""}' + end + + def failed_refund_response + '{"success":false,"response_code":1,"status_message":"One or more errors has occurred.","errors":{"981":["Log in failed for insufficient permissions."]},"external_transaction_id":""}' + end + + def successful_void_response + '{"success":true,"response_code":109,"status_message":"Your transaction was successfully voided.","transaction_id":395971574}' + end + + def failed_void_response + '{"success":false,"response_code":1,"status_message":"One or more errors has occurred.","errors":{"58":["Please provide a valid Transaction ID."]}}' + end + + def successful_create_customer_response + '{"success":true,"response_code":160,"status_message":"The customer profile for customerTest150/Steve Smith was successfully created","customer_id":"customerTest150","masked_card_number":"xxxxxxxxxxxx1111"}' + end + + def failed_customer_creation_response + '{"success":false,"response_code":1,"status_message":"One or more errors has occurred.","errors":{"171":["Please provide a unique customer ID."]},"masked_card_number":"xxxxxxxxxxxx5439"}' + end + + def successful_ach_processing_response + '{ "success":true, "response_code":120, "status_message":"Your check was successfully processed.", "check_transaction_id":981619 }' + end + + def successful_ach_capture_void_response + '{ "success":true, "response_code":124, "status_message":"Your check was successfully managed.", "check_transaction_id":9981614 }' + end + + def successful_ach_refund_response + '{ "success":true, "response_code":122, "status_message":"Your check was successfully refunded.", "check_transaction_id":9981632 }' + end + + def failed_ach_processing_response + '{ "success":false, "response_code":125, "status_message":"Your check was NOT successfully processed.", "check_transaction_id":981610, "ach_code":0, "ach_message":"" }' + end + + def failed_bad_request_ach_processing_response + '{ "success":false, "response_code":1, "status_message":"One or more errors has occurred.", "errors":{ "30":[ "Customer ID, customerid123, was not found or is incomplete." ], "45":[ "Please provide a valid Checking Account Number." ], "46":[ "Please provide a valid Transit Routing Number." ] } }' + end + + def failed_ach_refund_response + '{ "success":false, "response_code":1, "status_message":"One or more errors has occurred.", "errors":{ "80":[ "The Check ID that you provided was not found in the PayTrace record." ] } }' + end + + def failed_ach_capture_void_response + '{ "success":false, "response_code":1, "status_message":"One or more errors has occurred.", "errors":{ "80":[ "The Check ID that you have provided was not found in the PayTrace records. It may already be voided or settled." ] } }' + end +end diff --git a/test/unit/gateways/paybox_direct_test.rb b/test/unit/gateways/paybox_direct_test.rb index e6e29d1ad93..0676dc82ef9 100644 --- a/test/unit/gateways/paybox_direct_test.rb +++ b/test/unit/gateways/paybox_direct_test.rb @@ -5,19 +5,17 @@ class PayboxDirectTest < Test::Unit::TestCase def setup @gateway = PayboxDirectGateway.new( - :login => 'l', - :password => 'p' - ) - - @credit_card = credit_card('1111222233334444', - :brand => 'visa' + login: 'l', + password: 'p' ) + + @credit_card = credit_card('1111222233334444', brand: 'visa') @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -82,7 +80,7 @@ def test_unsuccessful_request end def test_keep_the_card_code_not_considered_fraudulent - @gateway.expects(:ssl_post).returns(purchase_response('00104')) + @gateway.expects(:ssl_post).returns(purchase_response('00103')) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -113,7 +111,7 @@ def test_version private # Place raw successful response from gateway here - def purchase_response(code='00000') + def purchase_response(code = '00000') "NUMTRANS=0720248861&NUMAPPEL=0713790302&NUMQUESTION=0000790217&SITE=1999888&RANG=99&AUTORISATION=XXXXXX&CODEREPONSE=#{code}&COMMENTAIRE=Demande trait?e avec succ?s ✔漢" end diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb index abc9e1e0131..f95a219a8a4 100644 --- a/test/unit/gateways/payeezy_test.rb +++ b/test/unit/gateways/payeezy_test.rb @@ -12,18 +12,59 @@ def setup @check = check @amount = 100 @options = { - :billing_address => address, - :ta_token => '123' + billing_address: address, + 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', auth_type_override: 'A' } + @options_standardized_stored_credentials = { + stored_credential: { + network_transaction_id: 'stored_credential_abc123', + initial_transaction: false, + reason_type: 'recurring', + initiator: 'cardholder' + } + } @authorization = 'ET1700|106625152|credit_card|4738' @reversal_id = SecureRandom.random_number(1000000).to_s + + @options_mdd = { + soft_descriptors: { + dba_name: 'Caddyshack', + street: '1234 Any Street', + city: 'Durham', + region: 'North Carolina', + mid: 'mid_1234', + mcc: 'mcc_5678', + postal_code: '27701', + country_code: 'US', + merchant_contact_info: '8885551212' + } + } + @apple_pay_card = network_tokenization_credit_card( + '4761209980011439', + payment_cryptogram: 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + month: '11', + year: '2022', + eci: 5, + 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 @@ -65,6 +106,52 @@ def test_successful_purchase assert_equal 'Transaction Normal - Approved', response.message end + def test_successful_purchase_with_apple_pay + stub_comms do + @gateway.purchase(@amount, @apple_pay_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['method'], '3DS' + assert_equal request['3DS']['type'], 'D' + assert_equal request['3DS']['wallet_provider_id'], '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 + @options[:billing_address] = nil + stub_comms do + @gateway.purchase(@amount, @apple_pay_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal nil, request['cardholder_name'] + end.respond_with(failed_purchase_no_name_response) + end + def test_successful_store response = stub_comms(@gateway, :ssl_request) do @gateway.store(@credit_card, @options.merge(js_security_key: 'js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c')) @@ -113,7 +200,7 @@ def test_successful_purchase_defaulting_check_number response = stub_comms do @gateway.purchase(@amount, check_without_number, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/001/, data) end.respond_with(successful_purchase_echeck_response) @@ -123,11 +210,88 @@ def test_successful_purchase_defaulting_check_number assert_equal 'Transaction Normal - Approved', response.message end + def test_successful_purchase_with_customer_ref + options = @options.merge(level2: { customer_ref: 'An important customer' }) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"level2":{"customer_ref":"An important customer"}/, data) + end.respond_with(successful_purchase_response) + + 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) + 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_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| + 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 @@ -143,6 +307,15 @@ def test_failed_purchase assert_equal response.error_code, 'card_expired' end + def test_failed_purchase_with_insufficient_funds + response = stub_comms do + @gateway.purchase(530200, @credit_card, @options) + end.respond_with(failed_purchase_response_for_insufficient_funds) + + assert_failure response + assert_equal '302', response.error_code + end + def test_successful_authorize @gateway.expects(:ssl_post).returns(successful_authorize_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -187,16 +360,65 @@ def test_successful_refund_with_echeck assert_success response end + def test_successful_refund_with_soft_descriptors + response = stub_comms do + @gateway.refund(@amount, @authorization, @options.merge(@options_mdd)) + end.check_request do |_endpoint, data, _headers| + json = '{"transaction_type":"refund","method":"credit_card","transaction_tag":"106625152","currency_code":"USD","amount":"100","soft_descriptors":{"dba_name":"Caddyshack","street":"1234 Any Street","city":"Durham","region":"North Carolina","mid":"mid_1234","mcc":"mcc_5678","postal_code":"27701","country_code":"US","merchant_contact_info":"8885551212"},"merchant_ref":null}' + assert_match json, data + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_successful_refund_with_order_id + response = stub_comms do + @gateway.refund(@amount, @authorization, @options.merge(order_id: 1234)) + end.check_request do |_endpoint, data, _headers| + json = '{"transaction_type":"refund","method":"credit_card","transaction_tag":"106625152","currency_code":"USD","amount":"100","merchant_ref":1234}' + assert_match json, data + end.respond_with(successful_refund_response) + + assert_success response + end + def test_failed_refund @gateway.expects(:ssl_post).raises(failed_refund_response) assert response = @gateway.refund(@amount, @authorization) assert_failure response end + def test_successful_general_credit + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.credit(@amount, @credit_card) + assert_success response + end + + def test_successful_general_credit_with_soft_descriptors + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(@options_mdd)) + end.check_request do |_endpoint, data, _headers| + soft_descriptors_regex = %r("soft_descriptors":{"dba_name":"Caddyshack","street":"1234 Any Street","city":"Durham","region":"North Carolina","mid":"mid_1234","mcc":"mcc_5678","postal_code":"27701","country_code":"US","merchant_contact_info":"8885551212"}) + assert_match soft_descriptors_regex, data + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_successful_general_credit_with_order_id + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(order_id: 1234)) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"merchant_ref\":1234/, data) + end.respond_with(successful_refund_response) + + assert_success response + end + def test_successful_void response = stub_comms do @gateway.void(@authorization, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| json = '{"transaction_type":"void","method":"credit_card","transaction_tag":"106625152","currency_code":"USD","amount":"4738"}' assert_match json, data end.respond_with(successful_void_response) @@ -207,7 +429,7 @@ def test_successful_void def test_successful_void_with_reversal_id stub_comms do @gateway.void(@authorization, @options.merge(reversal_id: @reversal_id)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| json = "{\"transaction_type\":\"void\",\"method\":\"credit_card\",\"reversal_id\":\"#{@reversal_id}\",\"currency_code\":\"USD\",\"amount\":\"4738\"}" assert_match json, data end.respond_with(successful_void_response) @@ -239,16 +461,18 @@ def test_invalid_transaction_tag assert response = @gateway.capture(@amount, @authorization) assert_instance_of Response, response assert_failure response - assert_equal response.error_code, 'server_error' + error_msg = response.params['Error']['messages'] + error_code = error_msg.map { |x| x.values[0] } + assert_equal error_code[0], 'server_error' assert_equal response.message, 'ProcessedBad Request (69) - Invalid Transaction Tag' end def test_supported_countries - assert_equal ['CA', 'US'].sort, PayeezyGateway.supported_countries.sort + assert_equal %w[CA US].sort, PayeezyGateway.supported_countries.sort end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club], PayeezyGateway.supported_cardtypes + assert_equal %i[visa master american_express discover jcb diners_club], PayeezyGateway.supported_cardtypes end def test_avs_result @@ -268,7 +492,7 @@ def test_cvv_result def test_requests_include_verification_string stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| json_address = '{"street":"456 My Street","city":"Ottawa","state_province":"ON","zip_postal_code":"K1C2N6","country":"CA"}' assert_match json_address, data end.respond_with(successful_purchase_response) @@ -303,189 +527,281 @@ def test_scrub_echeck assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck end + def test_scrub_network_token + assert_equal @gateway.scrub(pre_scrubbed_network_token), post_scrubbed_network_token + end + private def pre_scrubbed - <<-TRANSCRIPT - opening connection to api-cert.payeezy.com:443... - opened - starting SSL for api-cert.payeezy.com:443... - SSL established - <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: oKB61AAxbN3xwC6gVAH3dp58FmioHSAT\r\nToken: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" - <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242424242424242\",\"exp_date\":\"0916\",\"cvv\":\"123\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" - -> "HTTP/1.1 201 Created\r\n" - -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" - -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" - -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" - -> "Access-Control-Max-Age: 3628800\r\n" - -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json;charset=UTF-8\r\n" - -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" - -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" - -> "Server: Apigee Router\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" - -> "X-Global-Transaction-ID: 74768541\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 549\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 549 bytes... - -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" - read 549 bytes - Conn close + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: oKB61AAxbN3xwC6gVAH3dp58FmioHSAT\r\nToken: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242424242424242\",\"exp_date\":\"0916\",\"cvv\":\"123\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" + -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" + -> "X-Global-Transaction-ID: 74768541\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 549\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 549 bytes... + -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" + read 549 bytes + Conn close TRANSCRIPT end def post_scrubbed - <<-TRANSCRIPT - opening connection to api-cert.payeezy.com:443... - opened - starting SSL for api-cert.payeezy.com:443... - SSL established - <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nToken: [FILTERED]\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" - <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\",\"cvv\":\"[FILTERED]\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" - -> "HTTP/1.1 201 Created\r\n" - -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" - -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" - -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" - -> "Access-Control-Max-Age: 3628800\r\n" - -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json;charset=UTF-8\r\n" - -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" - -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" - -> "Server: Apigee Router\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" - -> "X-Global-Transaction-ID: 74768541\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 549\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 549 bytes... - -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" - read 549 bytes - Conn close + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nToken: [FILTERED]\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\",\"cvv\":\"[FILTERED]\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" + -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" + -> "X-Global-Transaction-ID: 74768541\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 549\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 549 bytes... + -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" + read 549 bytes + Conn close TRANSCRIPT end def pre_scrubbed_echeck - <<-TRANSCRIPT - {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"244183602\",\"account_number\":\"15378535\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" - -> "HTTP/1.1 201 Created\r\n" - -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" - -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" - -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" - -> "Access-Control-Max-Age: 3628800\r\n" - -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json;charset=UTF-8\r\n" - -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" - -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" - -> "Server: Apigee Router\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" - -> "X-Global-Transaction-ID: 97138449\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 491\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 491 bytes... - -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} + <<~TRANSCRIPT + {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"244183602\",\"account_number\":\"15378535\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" + -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" + -> "X-Global-Transaction-ID: 97138449\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 491\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 491 bytes... + -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} TRANSCRIPT end def post_scrubbed_echeck - <<-TRANSCRIPT - {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"[FILTERED]\",\"account_number\":\"[FILTERED]\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" - -> "HTTP/1.1 201 Created\r\n" - -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" - -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" - -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" - -> "Access-Control-Max-Age: 3628800\r\n" - -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json;charset=UTF-8\r\n" - -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" - -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" - -> "Server: Apigee Router\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" - -> "X-Global-Transaction-ID: 97138449\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 491\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 491 bytes... - -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"[FILTERED]\",\"routing_number\":\"[FILTERED]\"}} + <<~TRANSCRIPT + {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"[FILTERED]\",\"account_number\":\"[FILTERED]\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" + -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" + -> "X-Global-Transaction-ID: 97138449\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 491\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 491 bytes... + -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"[FILTERED]\",\"routing_number\":\"[FILTERED]\"}} TRANSCRIPT end def pre_scrubbed_store - <<-TRANSCRIPT - opening connection to api-cert.payeezy.com:443... - opened - starting SSL for api-cert.payeezy.com:443... - SSL established - <- "GET /v1/securitytokens?apikey=UyDMTXx6TD9WErF6ynw7xeEfCAn8fcGs&js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=4242424242424242&credit_card.exp_date=0919&credit_card.cvv=123 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" - -> "HTTP/1.1 200 Success\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json\r\n" - -> "correlation_id: 228.1574930196886\r\n" - -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" - -> "statuscode: 201\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" - -> "X-Global-Transaction-ID: 463881989\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 266\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 266 bytes... - -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " - read 266 bytes - Conn close + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "GET /v1/securitytokens?apikey=UyDMTXx6TD9WErF6ynw7xeEfCAn8fcGs&js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=4242424242424242&credit_card.exp_date=0919&credit_card.cvv=123 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" + -> "HTTP/1.1 200 Success\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json\r\n" + -> "correlation_id: 228.1574930196886\r\n" + -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" + -> "statuscode: 201\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" + -> "X-Global-Transaction-ID: 463881989\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 266\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 266 bytes... + -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " + read 266 bytes + Conn close TRANSCRIPT end def post_scrubbed_store - <<-TRANSCRIPT - opening connection to api-cert.payeezy.com:443... - opened - starting SSL for api-cert.payeezy.com:443... - SSL established - <- "GET /v1/securitytokens?apikey=[FILTERED]js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=[FILTERED]credit_card.exp_date=0919&credit_card.cvv=[FILTERED] HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" - -> "HTTP/1.1 200 Success\r\n" - -> "Content-Language: en-US\r\n" - -> "Content-Type: application/json\r\n" - -> "correlation_id: 228.1574930196886\r\n" - -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" - -> "statuscode: 201\r\n" - -> "X-Archived-Client-IP: 10.180.205.250\r\n" - -> "X-Backside-Transport: OK OK,OK OK\r\n" - -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" - -> "X-Global-Transaction-ID: 463881989\r\n" - -> "X-Powered-By: Servlet/3.0\r\n" - -> "Content-Length: 266\r\n" - -> "Connection: Close\r\n" - -> "\r\n" - reading 266 bytes... - -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " - read 266 bytes - Conn close + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "GET /v1/securitytokens?apikey=[FILTERED]js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=[FILTERED]credit_card.exp_date=0919&credit_card.cvv=[FILTERED] HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" + -> "HTTP/1.1 200 Success\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json\r\n" + -> "correlation_id: 228.1574930196886\r\n" + -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" + -> "statuscode: 201\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" + -> "X-Global-Transaction-ID: 463881989\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 266\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 266 bytes... + -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " + read 266 bytes + Conn close + TRANSCRIPT + end + + def pre_scrubbed_network_token + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: oKB61AAxbN3xwC6gVAH3dp58FmioHSAT\r\nToken: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6\r\nNonce: 2713241561.4909368\r\nTimestamp: 1668784714406\r\nAuthorization: NDU2ZWRiNmUwMmUxNGMwOGIwYjMxYTAxMDkzZDcwNWNhM2Y0ODExNmRmMTNjNDVjMTFhODMyNTg4NDdiNzZiNw==\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-cert.payeezy.com\r\nContent-Length: 462\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"3DS\":{\"type\":\"D\",\"cardholder_name\":\"Longbob\",\"card_number\":\"4761209980011439\",\"exp_date\":\"1122\",\"cvv\":569,\"xid\":\"YwAAAAAABaYcCMX/OhNRQAAAAAA=\",\"cavv\":\"YwAAAAAABaYcCMX/OhNRQAAAAAA=\",\"wallet_provider_id\":\"APPLE_PAY\"},\"method\":\"3DS\",\"eci_indicator\":5,\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Fri, 18 Nov 2022 15:18:35 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "Content-Language: en-US\r\n" + -> "X-Global-Transaction-ID: 7f41427d6377a24aa50b34df\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "X-Xss-Protection: 1; mode=block\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Referrer-Policy: strict-origin\r\n" + -> "Feature-Policy: vibrate 'self'\r\n" + -> "Content-Security-Policy: default-src 'none'; frame-ancestors 'self'; script-src 'unsafe-inline' 'self' *.googleapis.com *.klarna.com *.masterpass.com *.mastercard.com *.newrelic.com *.npci.org.in *.nr-data.net *.google-analytics.com *.google.com *.getsitecontrol.com *.gstatic.com *.kxcdn.com 'strict-dynamic' 'nonce-6f62fa22a79de4c553d2bbde' 'unsafe-eval' 'unsafe-inline'; connect-src 'self'; img-src 'self'; style-src 'self'; base-uri 'self';\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Via: 1.1 dca1-bit16021\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "249\r\n" + reading 585 bytes... + -> "{\"correlation_id\":\"134.6878471461658\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET188163\",\"transaction_tag\":\"10032826722\",\"method\":\"3ds\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"U\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"9324008290401439\"}},\"card\":{\"type\":\"VISA\",\"cardholder_name\":\"Longbob\",\"card_number\":\"1439\",\"exp_date\":\"1122\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"eCommerce_flag\":\"5\",\"retrieval_ref_no\":\"221118\"}" + read 585 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT + end + + def post_scrubbed_network_token + <<~TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nToken: [FILTERED]\r\nNonce: 2713241561.4909368\r\nTimestamp: 1668784714406\r\nAuthorization: NDU2ZWRiNmUwMmUxNGMwOGIwYjMxYTAxMDkzZDcwNWNhM2Y0ODExNmRmMTNjNDVjMTFhODMyNTg4NDdiNzZiNw==\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-cert.payeezy.com\r\nContent-Length: 462\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"3DS\":{\"type\":\"D\",\"cardholder_name\":\"Longbob\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"1122\",\"cvv\":[FILTERED],\"xid\":[FILTERED],\"cavv\":[FILTERED],\"wallet_provider_id\":\"APPLE_PAY\"},\"method\":\"3DS\",\"eci_indicator\":5,\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Fri, 18 Nov 2022 15:18:35 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "Content-Language: en-US\r\n" + -> "X-Global-Transaction-ID: 7f41427d6377a24aa50b34df\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "X-Xss-Protection: 1; mode=block\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Referrer-Policy: strict-origin\r\n" + -> "Feature-Policy: vibrate 'self'\r\n" + -> "Content-Security-Policy: default-src 'none'; frame-ancestors 'self'; script-src 'unsafe-inline' 'self' *.googleapis.com *.klarna.com *.masterpass.com *.mastercard.com *.newrelic.com *.npci.org.in *.nr-data.net *.google-analytics.com *.google.com *.getsitecontrol.com *.gstatic.com *.kxcdn.com 'strict-dynamic' 'nonce-6f62fa22a79de4c553d2bbde' 'unsafe-eval' 'unsafe-inline'; connect-src 'self'; img-src 'self'; style-src 'self'; base-uri 'self';\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Via: 1.1 dca1-bit16021\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "249\r\n" + reading 585 bytes... + -> "{\"correlation_id\":\"134.6878471461658\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET188163\",\"transaction_tag\":\"10032826722\",\"method\":\"3ds\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"U\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"9324008290401439\"}},\"card\":{\"type\":\"VISA\",\"cardholder_name\":\"Longbob\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"1122\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"eCommerce_flag\":\"5\",\"retrieval_ref_no\":\"221118\"}" + read 585 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close TRANSCRIPT end def successful_purchase_response - <<-RESPONSE - {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"avs\":\"4\",\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Bobsen 995\",\"card_number\":\"4242\",\"exp_date\":\"0816\"},\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"0152552999534242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET114541\",\"transaction_tag\":\"55083431\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433862672836\"} + <<~RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"avs\":\"4\",\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Bobsen 995\",\"card_number\":\"4242\",\"exp_date\":\"0816\"},\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"0152552999534242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET114541\",\"transaction_tag\":\"55083431\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433862672836\"} RESPONSE end @@ -494,310 +810,320 @@ def successful_purchase_stored_credentials_response end def successful_purchase_echeck_response - <<-RESPONSE - {\"correlation_id\":\"228.1449688619062\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET133078\",\"transaction_tag\":\"69864362\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} + <<~RESPONSE + {\"correlation_id\":\"228.1449688619062\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET133078\",\"transaction_tag\":\"69864362\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} RESPONSE end def successful_store_response - <<-RESPONSE - {\"correlation_id\":\"124.1792879391754\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"9045348309244242\"}} - RESPONSE + <<~RESPONSE + {\"correlation_id\":\"124.1792879391754\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"9045348309244242\"}} + RESPONSE end def failed_store_response - <<-RESPONSE - {\"correlation_id\":\"124.1792940806770\",\"status\":\"failed\",\"Error\":{\"messages\":[{\"code\":\"invalid_card_number\",\"description\":\"The credit card number check failed\"}]},\"type\":\"FDToken\"} + <<~RESPONSE + {\"correlation_id\":\"124.1792940806770\",\"status\":\"failed\",\"Error\":{\"messages\":[{\"code\":\"invalid_card_number\",\"description\":\"The credit card number check failed\"}]},\"type\":\"FDToken\"} RESPONSE end def failed_purchase_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPBadRequest - http_version: '1.1' - code: '400' - message: Bad Request - header: - content-language: - - en-US - content-type: - - application/json - date: - - Tue, 09 Jun 2015 15:46:44 GMT - optr_cxt: - - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; - x-archived-client-ip: - - 10.174.197.250 - x-backside-transport: - - FAIL FAIL,FAIL FAIL - x-client-ip: - - 10.174.197.250,54.236.202.5 - x-powered-by: - - Servlet/3.0 - content-length: - - '384' - connection: - - Close - body: '{"method":"credit_card","amount":"10000000","currency":"USD","card":{"type":"Visa","cvv":"000","cardholder_name":"Bobsen - 5675","card_number":"4242","exp_date":"0810"},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"purchase","Error":{"messages":[{"code":"card_expired","description":"The - card has expired"}]},"correlation_id":"124.1433864804381"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"method":"credit_card","amount":"10000000","currency":"USD","card":{"type":"Visa","cvv":"000","cardholder_name":"Bobsen + 5675","card_number":"4242","exp_date":"0810"},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"purchase","Error":{"messages":[{"code":"card_expired","description":"The + card has expired"}]},"correlation_id":"124.1433864804381"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def failed_purchase_response_for_insufficient_funds + '{"correlation_id":"124.1342365067332","transaction_status":"declined","validation_status":"success","transaction_type":"purchase","transaction_tag":"4611610442","method":"credit_card","amount":"530200","currency":"USD","avs":"4","cvv2":"M","token":{"token_type":"FDToken", "token_data":{"value":"0788934280684242"}},"card":{"type":"Visa","cardholder_name":"Longbob Longsen","card_number":"4242","exp_date":"0922"},"bank_resp_code":"302","bank_message":"Insufficient Funds","gateway_resp_code":"00","gateway_message":"Transaction Normal"}' end def successful_authorize_response - <<-RESPONSE + <<~RESPONSE {\"correlation_id\":\"228.1449517682800\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_id\":\"ET156862\",\"transaction_tag\":\"69601979\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1446473518714242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} RESPONSE end def failed_authorize_response - <<-RESPONSE + <<~RESPONSE {\"correlation_id\":\"228.1449522605561\",\"transaction_status\":\"declined\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_tag\":\"69607256\",\"method\":\"credit_card\",\"amount\":\"501300\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"0843687226934242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"013\",\"bank_message\":\"Transaction not approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} RESPONSE end def successful_capture_response - <<-RESPONSE + <<~RESPONSE {\"correlation_id\":\"228.1449517473876\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"capture\",\"transaction_id\":\"ET176427\",\"transaction_tag\":\"69601874\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"8129044621504242\"}},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} RESPONSE end def successful_refund_response - <<-RESPONSE - {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9968749582724242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"55084328\",\"transaction_tag\":\"55084328\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433864648126\"} + <<~RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9968749582724242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"55084328\",\"transaction_tag\":\"55084328\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433864648126\"} RESPONSE end def successful_refund_echeck_response - <<-RESPONSE - {\"correlation_id\":\"228.1449688783287\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"69864710\",\"transaction_tag\":\"69864710\",\"method\":\"tele_check\",\"amount\":\"50\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} + <<~RESPONSE + {\"correlation_id\":\"228.1449688783287\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"69864710\",\"transaction_tag\":\"69864710\",\"method\":\"tele_check\",\"amount\":\"50\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} RESPONSE end def below_minimum_response - <<-RESPONSE - {\"correlation_id\":\"123.1234678982\",\"transaction_status\":\"declined\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_tag\":\"92384753\",\"method\":\"credit_card\",\"amount\":\"250\",\"currency\":\"USD\",\"card\":{\"type\":\"Mastercard\",\"cardholder_name\":\"Omri Test\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0123\"},\"gateway_resp_code\":\"36\",\"gateway_message\":\"Below Minimum Sale\"} + <<~RESPONSE + {\"correlation_id\":\"123.1234678982\",\"transaction_status\":\"declined\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_tag\":\"92384753\",\"method\":\"credit_card\",\"amount\":\"250\",\"currency\":\"USD\",\"card\":{\"type\":\"Mastercard\",\"cardholder_name\":\"Omri Test\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0123\"},\"gateway_resp_code\":\"36\",\"gateway_message\":\"Below Minimum Sale\"} + RESPONSE + end + + def failed_purchase_no_name_response + <<~RESPONSE + {\"correlation_id\":\"29.7337367613551\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET106024\",\"transaction_tag\":\"10049930801\",\"method\":\"3ds\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"U\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1141044316391439\"}},\"card\":{\"type\":\"VISA\",\"cardholder_name\":\"Jim Smith\",\"card_number\":\"1439\",\"exp_date\":\"1124\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"eCommerce_flag\":\"5\",\"retrieval_ref_no\":\"230110\"} RESPONSE end def failed_refund_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPBadRequest - http_version: '1.1' - code: '400' - message: Bad Request - header: - content-language: - - en-US - content-type: - - application/json - date: - - Tue, 09 Jun 2015 15:46:44 GMT - optr_cxt: - - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; - x-archived-client-ip: - - 10.174.197.250 - x-backside-transport: - - FAIL FAIL,FAIL FAIL - x-client-ip: - - 10.174.197.250,54.236.202.5 - x-powered-by: - - Servlet/3.0 - content-length: - - '384' - connection: - - Close - body: '{"correlation_id":"228.1449520714925","Error":{"messages":[{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"refund","amount":"50","currency":"USD"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"correlation_id":"228.1449520714925","Error":{"messages":[{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"refund","amount":"50","currency":"USD"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) end def successful_void_response - <<-RESPONSE - {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9594258319174242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"void\",\"transaction_id\":\"ET196233\",\"transaction_tag\":\"55083674\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433863576596\"} -RESPONSE + <<~RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9594258319174242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"void\",\"transaction_id\":\"ET196233\",\"transaction_tag\":\"55083674\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433863576596\"} + RESPONSE end def failed_void_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPBadRequest - http_version: '1.1' - code: '400' - message: Bad Request - header: - content-language: - - en-US - content-type: - - application/json - date: - - Tue, 09 Jun 2015 15:46:44 GMT - optr_cxt: - - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; - x-archived-client-ip: - - 10.174.197.250 - x-backside-transport: - - FAIL FAIL,FAIL FAIL - x-client-ip: - - 10.174.197.250,54.236.202.5 - x-powered-by: - - Servlet/3.0 - content-length: - - '384' - connection: - - Close - body: '{"correlation_id":"228.1449520846984","Error":{"messages":[{"code":"missing_transaction_id","description":"The transaction id is not provided"},{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"void","amount":"0","currency":"USD"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"correlation_id":"228.1449520846984","Error":{"messages":[{"code":"missing_transaction_id","description":"The transaction id is not provided"},{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"void","amount":"0","currency":"USD"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) end def failed_capture_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPBadRequest - http_version: '1.1' - code: '400' - message: Bad Request - header: - content-language: - - en-US - content-type: - - application/json - date: - - Tue, 09 Jun 2015 17:33:50 GMT - optr_cxt: - - 0100010000d084138f-24f3-4686-8a51-3c17406a572500000000-0000-0000-0000-000000000000-1 HTTP ; - x-archived-client-ip: - - 10.174.197.250 - x-backside-transport: - - FAIL FAIL,FAIL FAIL - x-client-ip: - - 10.174.197.250,107.23.55.229 - x-powered-by: - - Servlet/3.0 - content-length: - - '190' - connection: - - Close - body: '{"transaction_status":"Not Processed","Error":{"messages":[{"code":"server_error","description":"ProcessedBad - Request (69) - Invalid Transaction Tag"}]},"correlation_id":"124.1433871231542"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: - RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 17:33:50 GMT + optr_cxt: + - 0100010000d084138f-24f3-4686-8a51-3c17406a572500000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,107.23.55.229 + x-powered-by: + - Servlet/3.0 + content-length: + - '190' + connection: + - Close + body: '{"transaction_status":"Not Processed","Error":{"messages":[{"code":"server_error","description":"ProcessedBad + Request (69) - Invalid Transaction Tag"}]},"correlation_id":"124.1433871231542"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: + RESPONSE + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) end def invalid_token_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPUnauthorized - http_version: '1.1' - code: '401' - message: Unauthorized - header: - content-language: - - en-US - content-type: - - application/json;charset=utf-8 - date: - - Tue, 23 Jun 2015 15:13:02 GMT - optr_cxt: - - 435543224354-37b2-4369-9cfe-26543635465346346-0000-0000-0000-000000000000-1 HTTP ; - x-archived-client-ip: - - 10.180.205.250 - x-backside-transport: - - FAIL FAIL,FAIL FAIL - x-client-ip: - - 10.180.205.250,107.23.55.229 - x-powered-by: - - Servlet/3.0 - content-length: - - '25' - connection: - - Close - body: '{"error":"Access denied"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPUnauthorized + http_version: '1.1' + code: '401' + message: Unauthorized + header: + content-language: + - en-US + content-type: + - application/json;charset=utf-8 + date: + - Tue, 23 Jun 2015 15:13:02 GMT + optr_cxt: + - 435543224354-37b2-4369-9cfe-26543635465346346-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.180.205.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.180.205.250,107.23.55.229 + x-powered-by: + - Servlet/3.0 + content-length: + - '25' + connection: + - Close + body: '{"error":"Access denied"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) end def invalid_token_response_integration - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPUnauthorized - http_version: '1.1' - code: '401' - message: Unauthorized - header: - content-type: - - application/json - content-length: - - '125' - connection: - - Close - body: '{\"fault\":{\"faultstring\":\"Invalid ApiKey for given resource\",\"detail\":{\"errorcode\":\"oauth.v2.InvalidApiKeyForGivenResource\"}}}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPUnauthorized + http_version: '1.1' + code: '401' + message: Unauthorized + header: + content-type: + - application/json + content-length: + - '125' + connection: + - Close + body: '{\"fault\":{\"faultstring\":\"Invalid ApiKey for given resource\",\"detail\":{\"errorcode\":\"oauth.v2.InvalidApiKeyForGivenResource\"}}}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) end def bad_credentials_response - yamlexcep = <<-RESPONSE ---- !ruby/exception:ActiveMerchant::ResponseError -response: !ruby/object:Net::HTTPForbidden - http_version: '1.1' - code: '403' - message: Forbidden - header: - content-type: - - application/json - content-length: - - '51' - connection: - - Close - body: '{"code":"403", "message":"HMAC validation Failure"}' - read: true - uri: - decode_content: true - socket: - body_exist: true -message: + yamlexcep = <<~RESPONSE + --- !ruby/exception:ActiveMerchant::ResponseError + response: !ruby/object:Net::HTTPForbidden + http_version: '1.1' + code: '403' + message: Forbidden + header: + content-type: + - application/json + content-length: + - '51' + connection: + - Close + body: '{"code":"403", "message":"HMAC validation Failure"}' + read: true + uri: + decode_content: true + socket: + body_exist: true + message: RESPONSE - YAML.safe_load(yamlexcep, ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError']) + YAML.safe_load(yamlexcep, permitted_classes: ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError']) end end diff --git a/test/unit/gateways/payex_test.rb b/test/unit/gateways/payex_test.rb index a567c1a08c1..d3b5e071ed4 100644 --- a/test/unit/gateways/payex_test.rb +++ b/test/unit/gateways/payex_test.rb @@ -3,15 +3,15 @@ class PayexTest < Test::Unit::TestCase def setup @gateway = PayexGateway.new( - :account => 'account', - :encryption_key => 'encryption_key' - ) + account: 'account', + encryption_key: 'encryption_key' + ) @credit_card = credit_card @amount = 1000 @options = { - :order_id => '1234', + order_id: '1234' } end @@ -98,7 +98,7 @@ def test_unsuccessful_refund def test_successful_store @gateway.expects(:ssl_post).times(3).returns(successful_store_response, successful_initialize_response, successful_purchase_response) - assert response = @gateway.store(@credit_card, @options.merge({merchant_ref: '9876'})) + assert response = @gateway.store(@credit_card, @options.merge({ merchant_ref: '9876' })) assert_success response assert_equal 'OK', response.message assert_equal 'bcea4ac8d1f44640bff7a8c93caa249c', response.authorization @@ -115,7 +115,7 @@ def test_successful_unstore def test_successful_purchase_with_stored_card @gateway.expects(:ssl_post).returns(successful_autopay_response) - assert response = @gateway.purchase(@amount, 'fakeauth', @options.merge({order_id: '5678'})) + assert response = @gateway.purchase(@amount, 'fakeauth', @options.merge({ order_id: '5678' })) assert_success response assert_equal 'OK', response.message assert_equal '2624657', response.authorization diff --git a/test/unit/gateways/payflow_express_test.rb b/test/unit/gateways/payflow_express_test.rb index ef8fab1fcb4..0e74a0aaa87 100644 --- a/test/unit/gateways/payflow_express_test.rb +++ b/test/unit/gateways/payflow_express_test.rb @@ -15,19 +15,18 @@ def setup Base.mode = :test @gateway = PayflowExpressGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) - @address = { :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' - } + @address = { address1: '1234 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'Canada', + phone: '(555)555-5555' } end def teardown @@ -42,9 +41,9 @@ def test_overriding_test_mode Base.mode = :production gateway = PayflowExpressGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD', - :test => true + login: 'LOGIN', + password: 'PASSWORD', + test: true ) assert gateway.test? @@ -54,8 +53,8 @@ def test_using_production_mode Base.mode = :production gateway = PayflowExpressGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) assert !gateway.test? @@ -64,24 +63,24 @@ def test_using_production_mode def test_live_redirect_url Base.mode = :production assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') - assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) + assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', mobile: true) end def test_test_redirect_url assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') - assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) + assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', mobile: true) end def test_live_redirect_url_without_review Base.mode = :production - assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) - assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) + assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false, mobile: true) end def test_test_redirect_url_without_review assert_equal :test, Base.mode - assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) - assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) + assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false, mobile: true) end def test_invalid_get_express_details_request @@ -143,7 +142,7 @@ def test_get_express_details_with_ship_to_name end def test_get_express_details_with_invalid_xml - @gateway.expects(:ssl_post).returns(successful_get_express_details_response(:street => 'Main & Magic')) + @gateway.expects(:ssl_post).returns(successful_get_express_details_response(street: 'Main & Magic')) response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') assert_instance_of PayflowExpressResponse, response assert_success response @@ -162,95 +161,95 @@ def test_button_source private - def successful_get_express_details_response(options={:street => '111 Main St.'}) - <<-RESPONSE - - - TEST - verisign - - - 0 - Approved - - Buyer1@paypal.com - 12345678901234567 - EC-2OPN7UJGFWK9OYFV - 0 - verified - Joe - 555-555-5555 - -
- #{options[:street]} - San Jose - CA - 95100 - US -
-
- 9c3706997455e -
- -
-
-
-
+ def successful_get_express_details_response(options = { street: '111 Main St.' }) + <<~RESPONSE + + + TEST + verisign + + + 0 + Approved + + Buyer1@paypal.com + 12345678901234567 + EC-2OPN7UJGFWK9OYFV + 0 + verified + Joe + 555-555-5555 + +
+ #{options[:street]} + San Jose + CA + 95100 + US +
+
+ 9c3706997455e +
+ +
+
+
+
RESPONSE end def successful_get_express_details_response_with_ship_to_name - <<-RESPONSE - - - TEST - verisign - - - 0 - Approved - - Buyer1@paypal.com - 12345678901234567 - EC-2OPN7UJGFWK9OYFV - 0 - verified - Joe - 555-555-5555 - -
- 111 Main St. - San Jose - CA - 95100 - US -
-
- 9c3706997455e -
- - -
-
-
-
+ <<~RESPONSE + + + TEST + verisign + + + 0 + Approved + + Buyer1@paypal.com + 12345678901234567 + EC-2OPN7UJGFWK9OYFV + 0 + verified + Joe + 555-555-5555 + +
+ 111 Main St. + San Jose + CA + 95100 + US +
+
+ 9c3706997455e +
+ + +
+
+
+
RESPONSE end def invalid_get_express_details_response - <<-RESPONSE - - - TEST - verisign - - - 7 - Field format error: Invalid Token - - - - + <<~RESPONSE + + + TEST + verisign + + + 7 + Field format error: Invalid Token + + + + RESPONSE end end diff --git a/test/unit/gateways/payflow_express_uk_test.rb b/test/unit/gateways/payflow_express_uk_test.rb index 60a18a88be8..ef28d6b4d53 100644 --- a/test/unit/gateways/payflow_express_uk_test.rb +++ b/test/unit/gateways/payflow_express_uk_test.rb @@ -3,8 +3,8 @@ class PayflowExpressUkTest < Test::Unit::TestCase def setup @gateway = PayflowExpressUkGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) end @@ -65,93 +65,93 @@ def test_get_express_details_with_ship_to_name private def successful_get_express_details_response - <<-RESPONSE - - - - markcoop - paypaluk - - - 0 - - Match - Match - - Approved - - paul@test.com - LYWCMEN4FA7ZQ - EC-2OPN7UJGFWK9OYFV - 0 - unverified - paul - -
- 10 keyworth avenue - hinterland - Tyne and Wear - sr5 2uh - GB -
-
- 1ea22ef3873ba -
- - - - - -
-
-
-
+ <<~RESPONSE + + + + markcoop + paypaluk + + + 0 + + Match + Match + + Approved + + paul@test.com + LYWCMEN4FA7ZQ + EC-2OPN7UJGFWK9OYFV + 0 + unverified + paul + +
+ 10 keyworth avenue + hinterland + Tyne and Wear + sr5 2uh + GB +
+
+ 1ea22ef3873ba +
+ + + + + +
+
+
+
RESPONSE end def successful_get_express_details_response_with_ship_to_name - <<-RESPONSE - - - - markcoop - paypaluk - - - 0 - - Match - Match - - Approved - - paul@test.com - LYWCMEN4FA7ZQ - EC-2OPN7UJGFWK9OYFV - 0 - unverified - paul - -
- 10 keyworth avenue - hinterland - Tyne and Wear - sr5 2uh - GB -
-
- 1ea22ef3873ba -
- - - - - - -
-
-
-
+ <<~RESPONSE + + + + markcoop + paypaluk + + + 0 + + Match + Match + + Approved + + paul@test.com + LYWCMEN4FA7ZQ + EC-2OPN7UJGFWK9OYFV + 0 + unverified + paul + +
+ 10 keyworth avenue + hinterland + Tyne and Wear + sr5 2uh + GB +
+
+ 1ea22ef3873ba +
+ + + + + + +
+
+
+
RESPONSE end end diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb index 01a4ed9cb0b..69fc901992f 100644 --- a/test/unit/gateways/payflow_test.rb +++ b/test/unit/gateways/payflow_test.rb @@ -3,18 +3,40 @@ class PayflowTest < Test::Unit::TestCase include CommStub + # From `BuyerAuthStatusEnum` in https://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/pp_payflowpro_xmlpay_guide.pdf, page 109 + SUCCESSFUL_AUTHENTICATION_STATUS = 'Y' + CHALLENGE_REQUIRED_AUTHENTICATION_STATUS = 'C' + def setup Base.mode = :test @gateway = PayflowGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @amount = 100 @credit_card = credit_card('4242424242424242') - @options = { :billing_address => address.merge(:first_name => 'Longbob', :last_name => 'Longsen') } - @check = check(:name => 'Jim Smith') + @options = { billing_address: address.merge(first_name: 'Longbob', last_name: 'Longsen') } + @check = check(name: 'Jim Smith') + @l2_json = '{ + "Tender": { + "ACH": { + "AcctType": "C", + "AcctNum": "6355059797", + "ABA": "021000021" + } + } + }' + + @l3_json = '{ + "Invoice": { + "Date": "20190104", + "Level3Invoice": { + "CountyTax": {"Amount": "3.23"} + } + } + }' end def test_successful_authorization @@ -28,6 +50,70 @@ def test_successful_authorization refute response.fraud_review? end + def test_successful_purchase_with_stored_credential + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'cardholder', + network_transaction_id: nil + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(CITR), data + end.respond_with(successful_purchase_with_fraud_review_response) + + @options[:stored_credential] = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(CITI), data + end.respond_with(successful_purchase_with_fraud_review_response) + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(CITU), data + end.respond_with(successful_purchase_with_fraud_review_response) + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: '1234' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(MITR), data + assert_match %r(1234), data + end.respond_with(successful_purchase_with_fraud_review_response) + + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: '123' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(MITU), data + assert_match %r(123), data + end.respond_with(successful_purchase_with_fraud_review_response) + end + def test_failed_authorization @gateway.stubs(:ssl_post).returns(failed_authorization_response) @@ -40,7 +126,7 @@ def test_failed_authorization def test_authorization_with_three_d_secure_option response = stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path end.respond_with(successful_authorization_response) assert_equal 'Approved', response.message @@ -50,25 +136,88 @@ def test_authorization_with_three_d_secure_option refute response.fraud_review? end + def test_authorization_with_three_d_secure_option_with_version_includes_three_ds_version + expected_version = '1.0.2' + three_d_secure_option = three_d_secure_option(options: { version: expected_version }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path, expected_version: expected_version + end.respond_with(successful_authorization_response) + end + + def test_authorization_with_three_d_secure_option_with_ds_transaction_id_includes_ds_transaction_id + expected_ds_transaction_id = 'any ds_transaction id' + three_d_secure_option = three_d_secure_option(options: { ds_transaction_id: expected_ds_transaction_id }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path, expected_ds_transaction_id: expected_ds_transaction_id + end.respond_with(successful_authorization_response) + end + + def test_authorization_with_three_d_secure_option_with_version_2_x_via_mpi + expected_version = '2.2.0' + expected_authentication_status = SUCCESSFUL_AUTHENTICATION_STATUS + expected_ds_transaction_id = 'f38e6948-5388-41a6-bca4-b49723c19437' + + three_d_secure_option = three_d_secure_option(options: { version: expected_version, ds_transaction_id: expected_ds_transaction_id }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + xml = REXML::Document.new(data) + assert_three_d_secure_via_mpi(xml, tx_type: 'Authorization', expected_version: expected_version, expected_ds_transaction_id: expected_ds_transaction_id) + assert_three_d_secure xml, authorize_buyer_auth_result_path, expected_version: expected_version, expected_authentication_status: expected_authentication_status, expected_ds_transaction_id: expected_ds_transaction_id + end.respond_with(successful_authorization_response) + end + + def test_authorization_with_three_d_secure_option_with_version_2_x_and_authentication_response_status_include_authentication_status + expected_version = '2.2.0' + expected_authentication_status = SUCCESSFUL_AUTHENTICATION_STATUS + three_d_secure_option = three_d_secure_option(options: { version: expected_version }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path, expected_version: expected_version, expected_authentication_status: expected_authentication_status + end.respond_with(successful_authorization_response) + end + + def test_authorization_with_three_d_secure_option_with_version_1_x_and_authentication_response_status_does_not_include_authentication_status + expected_version = '1.0.2' + expected_authentication_status = nil + three_d_secure_option = three_d_secure_option(options: { version: expected_version }) + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path, expected_version: expected_version, expected_authentication_status: expected_authentication_status + end.respond_with(successful_authorization_response) + end + def test_successful_authorization_with_more_options + partner_id = 'partner_id' + PayflowGateway.application_id = partner_id + options = @options.merge( { order_id: '123', description: 'Description string', order_desc: 'OrderDesc string', comment: 'Comment string', - comment2: 'Comment2 string' + comment2: 'Comment2 string', + merch_descr: 'MerchDescr string' } ) response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(123), data assert_match %r(Description string), data assert_match %r(OrderDesc string), data assert_match %r(Comment string), data assert_match %r(), data + assert_match %r(), data + assert_match %r(MerchDescr string), data end.respond_with(successful_authorization_response) assert_equal 'Approved', response.message assert_success response @@ -89,7 +238,7 @@ def test_successful_purchase_with_fraud_review def test_successful_purchase_with_three_d_secure_option response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_three_d_secure REXML::Document.new(data), purchase_buyer_auth_result_path end.respond_with(successful_purchase_with_fraud_review_response) assert_success response @@ -97,6 +246,76 @@ def test_successful_purchase_with_three_d_secure_option assert response.fraud_review? end + def test_successful_purchase_with_three_d_secure_option_with_version_2_x_via_mpi + expected_version = '2.2.0' + expected_authentication_status = SUCCESSFUL_AUTHENTICATION_STATUS + expected_ds_transaction_id = 'f38e6948-5388-41a6-bca4-b49723c19437' + + three_d_secure_option = three_d_secure_option(options: { version: expected_version, ds_transaction_id: expected_ds_transaction_id }) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |_endpoint, data, _headers| + xml = REXML::Document.new(data) + assert_three_d_secure_via_mpi xml, tx_type: 'Sale', expected_version: expected_version, expected_ds_transaction_id: expected_ds_transaction_id + + assert_three_d_secure xml, purchase_buyer_auth_result_path, expected_version: expected_version, expected_authentication_status: expected_authentication_status, expected_ds_transaction_id: expected_ds_transaction_id + end.respond_with(successful_purchase_with_3ds_mpi) + assert_success response + + # see https://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/pp_payflowpro_xmlpay_guide.pdf, page 145, Table C.1 + assert_equal '0', response.params['result'] + refute response.fraud_review? + end + + def test_successful_purchase_with_level_2_fields + options = @options.merge(level_two_fields: @l2_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(6355059797), data + assert_match %r(), data.tr("\n ", '') + end.respond_with(successful_l2_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A1ADADCE9B12', response.authorization + refute response.fraud_review? + end + + def test_successful_purchase_with_level_3_fields + options = @options.merge(level_three_fields: @l3_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(20190104), data + assert_match %r(3.23), data + assert_match %r(), data.tr("\n ", '') + end.respond_with(successful_l3_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A71AAC3B60A1', response.authorization + refute response.fraud_review? + end + + def test_successful_purchase_with_level_2_3_fields + options = @options.merge(level_two_fields: @l2_json).merge(level_three_fields: @l3_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(20190104), data + assert_match %r(3.23), data + assert_match %r(6355059797), data + assert_match %r(), data.tr("\n ", '') + assert_match %r(), data.tr("\n ", '') + end.respond_with(successful_l2_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A1ADADCE9B12', response.authorization + refute response.fraud_review? + end + def test_credit @gateway.expects(:ssl_post).with(anything, regexp_matches(/#{@credit_card.number}<\//), anything).returns('') @gateway.expects(:parse).returns({}) @@ -162,9 +381,9 @@ def test_overriding_test_mode Base.mode = :production gateway = PayflowGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD', - :test => true + login: 'LOGIN', + password: 'PASSWORD', + test: true ) assert gateway.test? @@ -174,8 +393,8 @@ def test_using_production_mode Base.mode = :production gateway = PayflowGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) refute gateway.test? @@ -183,26 +402,26 @@ def test_using_production_mode def test_partner_class_accessor assert_equal 'PayPal', PayflowGateway.partner - gateway = PayflowGateway.new(:login => 'test', :password => 'test') + gateway = PayflowGateway.new(login: 'test', password: 'test') assert_equal 'PayPal', gateway.options[:partner] end def test_partner_class_accessor_used_when_passed_in_partner_is_blank assert_equal 'PayPal', PayflowGateway.partner - gateway = PayflowGateway.new(:login => 'test', :password => 'test', :partner => '') + gateway = PayflowGateway.new(login: 'test', password: 'test', partner: '') assert_equal 'PayPal', gateway.options[:partner] end def test_passed_in_partner_overrides_class_accessor assert_equal 'PayPal', PayflowGateway.partner - gateway = PayflowGateway.new(:login => 'test', :password => 'test', :partner => 'PayPalUk') + gateway = PayflowGateway.new(login: 'test', password: 'test', partner: 'PayPalUk') assert_equal 'PayPalUk', gateway.options[:partner] end def test_express_instance gateway = PayflowGateway.new( - :login => 'test', - :password => 'password' + login: 'test', + password: 'password' ) express = gateway.express assert_instance_of PayflowExpressGateway, express @@ -216,11 +435,11 @@ def test_default_currency end def test_supported_countries - assert_equal ['US', 'CA', 'NZ', 'AU'], PayflowGateway.supported_countries + assert_equal %w[US CA NZ AU], PayflowGateway.supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :jcb, :discover, :diners_club], PayflowGateway.supported_cardtypes + assert_equal %i[visa master american_express jcb discover diners_club], PayflowGateway.supported_cardtypes end def test_successful_verify @@ -238,12 +457,19 @@ def test_unsuccessful_verify assert_equal 'Declined', response.message end + def test_store_returns_error + error = assert_raises(ArgumentError) { @gateway.store(@credit_card, @options) } + assert_equal 'Store is not supported on Payflow gateways', error.message + end + def test_initial_recurring_transaction_missing_parameters assert_raises ArgumentError do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, @credit_card, - :periodicity => :monthly, - :initial_transaction => { } + @gateway.recurring( + @amount, + @credit_card, + periodicity: :monthly, + initial_transaction: {} ) end end @@ -252,9 +478,11 @@ 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, - :periodicity => :monthly, - :initial_transaction => { :amount => :purchase } + @gateway.recurring( + @amount, + @credit_card, + periodicity: :monthly, + initial_transaction: { amount: :purchase } ) end end @@ -280,7 +508,7 @@ def test_successful_recurring_action @gateway.stubs(:ssl_post).returns(successful_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, @credit_card, :periodicity => :monthly) + @gateway.recurring(@amount, @credit_card, periodicity: :monthly) end assert_instance_of PayflowResponse, response @@ -294,7 +522,7 @@ def test_successful_recurring_modify_action @gateway.stubs(:ssl_post).returns(successful_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :periodicity => :monthly) + @gateway.recurring(@amount, nil, profile_id: 'RT0000000009', periodicity: :monthly) end assert_instance_of PayflowResponse, response @@ -308,7 +536,7 @@ def test_successful_recurring_modify_action_with_retry_num_days @gateway.stubs(:ssl_post).returns(successful_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :retry_num_days => 3, :periodicity => :monthly) + @gateway.recurring(@amount, nil, profile_id: 'RT0000000009', retry_num_days: 3, periodicity: :monthly) end assert_instance_of PayflowResponse, response @@ -322,7 +550,7 @@ def test_falied_recurring_modify_action_with_starting_at_in_the_past @gateway.stubs(:ssl_post).returns(start_date_error_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :starting_at => Date.yesterday, :periodicity => :monthly) + @gateway.recurring(@amount, nil, profile_id: 'RT0000000009', starting_at: Date.yesterday, periodicity: :monthly) end assert_instance_of PayflowResponse, response @@ -337,7 +565,7 @@ def test_falied_recurring_modify_action_with_starting_at_missing_and_changed_per @gateway.stubs(:ssl_post).returns(start_date_missing_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :periodicity => :yearly) + @gateway.recurring(@amount, nil, profile_id: 'RT0000000009', periodicity: :yearly) end assert_instance_of PayflowResponse, response @@ -352,7 +580,7 @@ def test_recurring_profile_payment_history_inquiry @gateway.stubs(:ssl_post).returns(successful_payment_history_recurring_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring_inquiry('RT0000000009', :history => true) + @gateway.recurring_inquiry('RT0000000009', history: true) end assert_equal 1, response.payment_history.size assert_equal '1', response.payment_history.first['payment_num'] @@ -360,7 +588,7 @@ def test_recurring_profile_payment_history_inquiry end def test_recurring_profile_payment_history_inquiry_contains_the_proper_xml - request = @gateway.send(:build_recurring_request, :inquiry, nil, :profile_id => 'RT0000000009', :history => true) + request = @gateway.send(:build_recurring_request, :inquiry, nil, profile_id: 'RT0000000009', history: true) assert_match %r(Y 'maestro' + brand: 'maestro' ) @gateway.send(:add_credit_card, xml, credit_card, @options.merge(three_d_secure_option)) assert_three_d_secure REXML::Document.new(xml.target!), '/Card/BuyerAuthResult' end + def test_add_credit_card_with_three_d_secure_challenge_required + xml = Builder::XmlMarkup.new + credit_card = credit_card( + '5641820000000005', + brand: 'maestro' + ) + + three_d_secure_option = three_d_secure_option( + options: { + authentication_response_status: nil, + directory_response_status: CHALLENGE_REQUIRED_AUTHENTICATION_STATUS + } + ) + @gateway.send(:add_credit_card, xml, credit_card, @options.merge(three_d_secure_option)) + assert_three_d_secure( + REXML::Document.new(xml.target!), + '/Card/BuyerAuthResult', + expected_status: CHALLENGE_REQUIRED_AUTHENTICATION_STATUS + ) + end + def test_duplicate_response_flag @gateway.expects(:ssl_post).returns(successful_duplicate_response) @@ -398,7 +647,7 @@ def test_timeout_is_same_in_header_and_xml end def test_name_field_are_included_instead_of_first_and_last - @gateway.expects(:ssl_post).returns(successful_authorization_response).with do |url, data| + @gateway.expects(:ssl_post).returns(successful_authorization_response).with do |_url, data| data !~ /FirstName/ && data !~ /LastName/ && data =~ // end response = @gateway.authorize(@amount, @credit_card, @options) @@ -406,8 +655,8 @@ def test_name_field_are_included_instead_of_first_and_last end def test_passed_in_verbosity - assert_nil PayflowGateway.new(:login => 'test', :password => 'test').options[:verbosity] - gateway = PayflowGateway.new(:login => 'test', :password => 'test', :verbosity => 'HIGH') + assert_nil PayflowGateway.new(login: 'test', password: 'test').options[:verbosity] + gateway = PayflowGateway.new(login: 'test', password: 'test', verbosity: 'HIGH') assert_equal 'HIGH', gateway.options[:verbosity] @gateway.expects(:ssl_post).returns(verbose_transaction_response) response = @gateway.purchase(100, @credit_card, @options) @@ -435,199 +684,288 @@ def test_scrub private def pre_scrubbed - <<-EOS -opening connection to pilot-payflowpro.paypal.com:443... -opened -starting SSL for pilot-payflowpro.paypal.com:443... -SSL established -<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" -<- "spreedlyIntegrationsPayPalMEDIUMcody@example.comJim Smithcody@example.com(555)555-5555codyexample
456 My StreetOttawaONCAK1C2N6
MasterCard5105105105105100201909Longbob123
spreedlyIntegrationsL9DjqEKjXCkU
" --> "HTTP/1.1 200 OK\r\n" --> "Connection: close\r\n" --> "Server: VPS-3.033.00\r\n" --> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" --> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" --> "Content-type: text/xml\r\n" --> "Content-length: 267\r\n" --> "\r\n" -reading 267 bytes... --> "4Invalid amount" -read 267 bytes -Conn close - EOS + <<~REQUEST + opening connection to pilot-payflowpro.paypal.com:443... + opened + starting SSL for pilot-payflowpro.paypal.com:443... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" + <- "spreedlyIntegrationsPayPalMEDIUMcody@example.comJim Smithcody@example.com(555)555-5555codyexample
456 My StreetOttawaONCAK1C2N6
MasterCard5105105105105100201909Longbob123
spreedlyIntegrationsL9DjqEKjXCkU
" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Server: VPS-3.033.00\r\n" + -> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" + -> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" + -> "Content-type: text/xml\r\n" + -> "Content-length: 267\r\n" + -> "\r\n" + reading 267 bytes... + -> "4Invalid amount" + read 267 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to pilot-payflowpro.paypal.com:443... -opened -starting SSL for pilot-payflowpro.paypal.com:443... -SSL established -<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" -<- "spreedlyIntegrationsPayPalMEDIUMcody@example.comJim Smithcody@example.com(555)555-5555codyexample
456 My StreetOttawaONCAK1C2N6
MasterCard[FILTERED]201909Longbob[FILTERED]
spreedlyIntegrations[FILTERED]
" --> "HTTP/1.1 200 OK\r\n" --> "Connection: close\r\n" --> "Server: VPS-3.033.00\r\n" --> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" --> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" --> "Content-type: text/xml\r\n" --> "Content-length: 267\r\n" --> "\r\n" -reading 267 bytes... --> "4Invalid amount" -read 267 bytes -Conn close - EOS + <<~REQUEST + opening connection to pilot-payflowpro.paypal.com:443... + opened + starting SSL for pilot-payflowpro.paypal.com:443... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" + <- "spreedlyIntegrationsPayPalMEDIUMcody@example.comJim Smithcody@example.com(555)555-5555codyexample
456 My StreetOttawaONCAK1C2N6
MasterCard[FILTERED]201909Longbob[FILTERED]
spreedlyIntegrations[FILTERED]
" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Server: VPS-3.033.00\r\n" + -> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" + -> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" + -> "Content-type: text/xml\r\n" + -> "Content-length: 267\r\n" + -> "\r\n" + reading 267 bytes... + -> "4Invalid amount" + read 267 bytes + Conn close + REQUEST end def pre_scrubbed_check - <<-EOS -opening connection to pilot-payflowpro.paypal.com:443... -opened -starting SSL for pilot-payflowpro.paypal.com:443... -SSL established -<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" -<- "spreedlyIntegrationsPayPalMEDIUMJim SmithC1234567801111111118spreedlyIntegrationsL9DjqEKjXCkU" --> "HTTP/1.1 200 OK\r\n" --> "Connection: close\r\n" --> "Server: VPS-3.033.00\r\n" --> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" --> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" --> "Content-type: text/xml\r\n" --> "Content-length: 267\r\n" --> "\r\n" -reading 267 bytes... --> "4Invalid amount" -read 267 bytes -Conn close - EOS + <<~REQUEST + opening connection to pilot-payflowpro.paypal.com:443... + opened + starting SSL for pilot-payflowpro.paypal.com:443... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" + <- "spreedlyIntegrationsPayPalMEDIUMJim SmithC1234567801111111118spreedlyIntegrationsL9DjqEKjXCkU" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Server: VPS-3.033.00\r\n" + -> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" + -> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" + -> "Content-type: text/xml\r\n" + -> "Content-length: 267\r\n" + -> "\r\n" + reading 267 bytes... + -> "4Invalid amount" + read 267 bytes + Conn close + REQUEST end def post_scrubbed_check - <<-EOS -opening connection to pilot-payflowpro.paypal.com:443... -opened -starting SSL for pilot-payflowpro.paypal.com:443... -SSL established -<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" -<- "spreedlyIntegrationsPayPalMEDIUMJim SmithC[FILTERED]111111118spreedlyIntegrations[FILTERED]" --> "HTTP/1.1 200 OK\r\n" --> "Connection: close\r\n" --> "Server: VPS-3.033.00\r\n" --> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" --> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" --> "Content-type: text/xml\r\n" --> "Content-length: 267\r\n" --> "\r\n" -reading 267 bytes... --> "4Invalid amount" -read 267 bytes -Conn close - EOS + <<~REQUEST + opening connection to pilot-payflowpro.paypal.com:443... + opened + starting SSL for pilot-payflowpro.paypal.com:443... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" + <- "spreedlyIntegrationsPayPalMEDIUMJim SmithC[FILTERED]111111118spreedlyIntegrations[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Server: VPS-3.033.00\r\n" + -> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" + -> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" + -> "Content-type: text/xml\r\n" + -> "Content-length: 267\r\n" + -> "\r\n" + reading 267 bytes... + -> "4Invalid amount" + read 267 bytes + Conn close + REQUEST end def successful_recurring_response - <<-XML - - 0 - Approved - paypal - R7960E739F80 - ActiveMerchant - RT0000000009 - - XML + <<~XML + + 0 + Approved + paypal + R7960E739F80 + ActiveMerchant + RT0000000009 + + XML end def start_date_error_recurring_response - <<-XML - - 0 - Field format error: START or NEXTPAYMENTDATE older than last payment date - paypal - R7960E739F80 - ActiveMerchant - RT0000000009 - + <<~XML + + 0 + Field format error: START or NEXTPAYMENTDATE older than last payment date + paypal + R7960E739F80 + ActiveMerchant + RT0000000009 + XML end def start_date_missing_recurring_response - <<-XML - - 0 - Field format error: START field missing - paypal - R7960E739F80 - ActiveMerchant - RT0000000009 - + <<~XML + + 0 + Field format error: START field missing + paypal + R7960E739F80 + ActiveMerchant + RT0000000009 + XML end def successful_payment_history_recurring_response - <<-XML - - 0 - paypal - R7960E739F80 - ActiveMerchant - RT0000000009 - - 1 - V18A0D3048AF - 12-Jan-08 04:30 AM - 0 - C - - 6 - - - XML + <<~XML + + 0 + paypal + R7960E739F80 + ActiveMerchant + RT0000000009 + + 1 + V18A0D3048AF + 12-Jan-08 04:30 AM + 0 + C + + 6 + + + XML end def successful_authorization_response - <<-XML - - 0 - Approved - verisign - 000 - AP - VUJN1A6E11D9 - N - Match - 094016 - ActiveMerchant - Y - Match - Match - + <<~XML + + 0 + Approved + verisign + 000 + AP + VUJN1A6E11D9 + N + Match + 094016 + ActiveMerchant + Y + Match + Match + + XML + end + + def successful_purchase_with_3ds_mpi + <<~XML + + + spreedlyIntegrations + paypal + + + 0 + + Z + M + A + + + No Rules Triggered + + + No Rules Triggered + + N + + No Match + Match + + Match + Approved + A11AB1C8156A + 980PNI + + + + + XML + end + + def successful_l3_response + <<~XML + + spreedlyIntegrations + paypal + + + 0 + + Z + M + A + + + No Rules Triggered + + + No Rules Triggered + + N + + No Match + Match + + Match + Approved + A71AAC3B60A1 + 240PNI + + + + XML + end + + def successful_l2_response + <<~XML + + spreedlyIntegrations + paypal + + + 0 + + A + + Approved + A1ADADCE9B12 + + + XML end def failed_authorization_response - <<-XML - - 12 - Declined - verisign - 000 - AP - VUJN1A6E11D9 - N - Match - 094016 - ActiveMerchant - Y - Match - Match - + <<~XML + + 12 + Declined + verisign + 000 + AP + VUJN1A6E11D9 + N + Match + 094016 + ActiveMerchant + Y + Match + Match + XML end def successful_purchase_with_fraud_review_response - <<-XML + <<~XML spreedly @@ -667,114 +1005,161 @@ def successful_purchase_with_fraud_review_response end def successful_duplicate_response - <<-XML - - - - ActiveMerchant - paypal - - - 0 - - A - M - A - - N - - Match - No Match - - Match - Approved - V18A0CBB04CF - 692PNI - - - - - + <<~XML + + + + ActiveMerchant + paypal + + + 0 + + A + M + A + + N + + Match + No Match + + Match + Approved + V18A0CBB04CF + 692PNI + + + + + XML end def verbose_transaction_response - <<-XML - - - - ActiveMerchant - paypal - - - 0 - - U - M - A - - - No Rules Triggered - - - No Rules Triggered - - X - - Service Not Available - Service Not Available - - Match - Approved - A70A6C93C4C8 - 242PNI - 1.00 - 12 - 2014-06-25 09:33:41 - 4242 - 0714 - 0 - - 0 - Longbob - Longsen - - - - - + <<~XML + + + + ActiveMerchant + paypal + + + 0 + + U + M + A + + + No Rules Triggered + + + No Rules Triggered + + X + + Service Not Available + Service Not Available + + Match + Approved + A70A6C93C4C8 + 242PNI + 1.00 + 12 + 2014-06-25 09:33:41 + 4242 + 0714 + 0 + + 0 + Longbob + Longsen + + + + + XML end - def assert_three_d_secure(xml_doc, buyer_auth_result_path) - assert_equal 'Y', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/Status").text - assert_equal 'QvDbSAxSiaQs241899E0', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/AuthenticationId").text - assert_equal 'pareq block', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/PAReq").text - assert_equal 'https://bankacs.bank.com/ascurl', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ACSUrl").text - assert_equal '02', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ECI").text - assert_equal 'jGvQIvG/5UhjAREALGYa6Vu/hto=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/CAVV").text - assert_equal 'UXZEYlNBeFNpYVFzMjQxODk5RTA=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/XID").text + def three_d_secure_option(options: {}) + { + three_d_secure: { + authentication_id: 'QvDbSAxSiaQs241899E0', + authentication_response_status: SUCCESSFUL_AUTHENTICATION_STATUS, + pareq: 'pareq block', + acs_url: 'https://bankacs.bank.com/ascurl', + eci: '02', + cavv: 'jGvQIvG/5UhjAREALGYa6Vu/hto=', + xid: 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' + }. + merge(options). + compact + } end - def authorize_buyer_auth_result_path - '/XMLPayRequest/RequestData/Transactions/Transaction/Authorization/PayData/Tender/Card/BuyerAuthResult' + def assert_three_d_secure_via_mpi(xml_doc, tx_type: 'Authorization', expected_version: nil, expected_ds_transaction_id: nil) + [ + { name: 'AUTHENTICATION_STATUS', expected: SUCCESSFUL_AUTHENTICATION_STATUS }, + { name: 'AUTHENTICATION_ID', expected: 'QvDbSAxSiaQs241899E0' }, + { name: 'ECI', expected: '02' }, + { name: 'CAVV', expected: 'jGvQIvG/5UhjAREALGYa6Vu/hto=' }, + { name: 'XID', expected: 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' }, + { name: 'THREEDSVERSION', expected: expected_version }, + { name: 'DSTRANSACTIONID', expected: expected_ds_transaction_id } + ].each do |item| + assert_equal item[:expected], REXML::XPath.first(xml_doc, threeds_xpath_for_extdata(item[:name], tx_type: tx_type)) + end end - def purchase_buyer_auth_result_path - '/XMLPayRequest/RequestData/Transactions/Transaction/Sale/PayData/Tender/Card/BuyerAuthResult' + def assert_three_d_secure( + xml_doc, + buyer_auth_result_path, + expected_status: SUCCESSFUL_AUTHENTICATION_STATUS, + expected_authentication_status: nil, + expected_version: nil, + expected_ds_transaction_id: nil + ) + assert_text_value_or_nil expected_status, REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/Status") + assert_text_value_or_nil(expected_authentication_status, REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/AuthenticationStatus")) + assert_text_value_or_nil 'QvDbSAxSiaQs241899E0', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/AuthenticationId") + assert_text_value_or_nil 'pareq block', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/PAReq") + assert_text_value_or_nil 'https://bankacs.bank.com/ascurl', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ACSUrl") + assert_text_value_or_nil '02', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ECI") + assert_text_value_or_nil 'jGvQIvG/5UhjAREALGYa6Vu/hto=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/CAVV") + assert_text_value_or_nil 'UXZEYlNBeFNpYVFzMjQxODk5RTA=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/XID") + assert_text_value_or_nil(expected_version, REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ThreeDSVersion")) + assert_text_value_or_nil(expected_ds_transaction_id, REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/DSTransactionID")) end - def three_d_secure_option - { - :three_d_secure => { - :status => 'Y', - :authentication_id => 'QvDbSAxSiaQs241899E0', - :pareq => 'pareq block', - :acs_url => 'https://bankacs.bank.com/ascurl', - :eci => '02', - :cavv => 'jGvQIvG/5UhjAREALGYa6Vu/hto=', - :xid => 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' - } - } + def assert_text_value_or_nil(expected_text_value, xml_element) + if expected_text_value + assert_equal expected_text_value, xml_element.text + else + assert_nil xml_element + end + end + + def xpath_prefix_for_transaction_type(tx_type) + return '/XMLPayRequest/RequestData/Transactions/Transaction/Authorization/' unless tx_type == 'Sale' + + '/XMLPayRequest/RequestData/Transactions/Transaction/Sale/' + end + + 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)) + end + + def authorize_buyer_auth_result_path + xpath_prefix = xpath_prefix_for_transaction_type('Authorization') + "#{xpath_prefix}/PayData/Tender/Card/BuyerAuthResult" + end + + def purchase_buyer_auth_result_path + xpath_prefix = xpath_prefix_for_transaction_type('Sale') + "#{xpath_prefix}/PayData/Tender/Card/BuyerAuthResult" end end diff --git a/test/unit/gateways/payflow_uk_test.rb b/test/unit/gateways/payflow_uk_test.rb index 9d50c599edd..0b6f812d752 100644 --- a/test/unit/gateways/payflow_uk_test.rb +++ b/test/unit/gateways/payflow_uk_test.rb @@ -3,8 +3,8 @@ class PayflowUkTest < Test::Unit::TestCase def setup @gateway = PayflowUkGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) end @@ -25,6 +25,6 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover], PayflowUkGateway.supported_cardtypes + assert_equal %i[visa master american_express discover], PayflowUkGateway.supported_cardtypes end end diff --git a/test/unit/gateways/payment_express_test.rb b/test/unit/gateways/payment_express_test.rb index cf15aa1ad92..6b6b8e6af74 100644 --- a/test/unit/gateways/payment_express_test.rb +++ b/test/unit/gateways/payment_express_test.rb @@ -5,19 +5,19 @@ class PaymentExpressTest < Test::Unit::TestCase def setup @gateway = PaymentExpressGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @visa = credit_card - @solo = credit_card('6334900000000005', :brand => 'maestro') + @solo = credit_card('6334900000000005', brand: 'maestro') @options = { - :order_id => generate_unique_id, - :billing_address => address, - :email => 'cody@example.com', - :description => 'Store purchase' + order_id: generate_unique_id, + billing_address: address, + email: 'cody@example.com', + description: 'Store purchase' } @amount = 100 @@ -45,6 +45,24 @@ def test_successful_authorization assert_equal '00000004011a2478', response.authorization end + def test_pass_currency_code_on_validation + stub_comms do + @gateway.verify(@visa, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/NZD<\/InputCurrency>/, data) + end.respond_with(successful_validation_response) + end + + def test_successful_validation + @gateway.expects(:ssl_post).returns(successful_validation_response) + + assert response = @gateway.verify(@visa, @options) + assert_success response + assert response.test? + assert_equal 'The Transaction was approved', response.message + assert_equal '0000000c025d2744', response.authorization + end + def test_purchase_request_should_include_cvc2_presence @gateway.expects(:commit).with do |type, request| type == :purchase && request.to_s =~ %r{1<\/Cvc2Presence>} @@ -74,9 +92,9 @@ def test_successful_card_store end def test_successful_card_store_with_custom_billing_id - @gateway.expects(:ssl_post).returns(successful_store_response(:billing_id => 'my-custom-id')) + @gateway.expects(:ssl_post).returns(successful_store_response(billing_id: 'my-custom-id')) - assert response = @gateway.store(@visa, :billing_id => 'my-custom-id') + assert response = @gateway.store(@visa, billing_id: 'my-custom-id') assert_success response assert response.test? assert_equal 'my-custom-id', response.token @@ -107,14 +125,14 @@ def test_purchase_using_dps_billing_id_token def test_purchase_using_merchant_specified_billing_id_token @gateway = PaymentExpressGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD', - :use_custom_payment_token => true + login: 'LOGIN', + password: 'PASSWORD', + use_custom_payment_token: true ) - @gateway.expects(:ssl_post).returns(successful_store_response({:billing_id => 'TEST1234'})) + @gateway.expects(:ssl_post).returns(successful_store_response({ billing_id: 'TEST1234' })) - assert response = @gateway.store(@visa, {:billing_id => 'TEST1234'}) + assert response = @gateway.store(@visa, { billing_id: 'TEST1234' }) assert_equal 'TEST1234', response.token @gateway.expects(:ssl_post).returns(successful_billing_id_token_purchase_response) @@ -130,7 +148,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :diners_club, :jcb ], PaymentExpressGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club jcb], PaymentExpressGateway.supported_cardtypes end def test_avs_result_not_supported @@ -158,9 +176,9 @@ def test_expect_no_optional_fields_by_default def test_pass_optional_txn_data options = { - :txn_data1 => 'Transaction Data 1', - :txn_data2 => 'Transaction Data 2', - :txn_data3 => 'Transaction Data 3' + txn_data1: 'Transaction Data 1', + txn_data2: 'Transaction Data 2', + txn_data3: 'Transaction Data 3' } perform_each_transaction_type_with_request_body_assertions(options) do |body| @@ -172,9 +190,9 @@ def test_pass_optional_txn_data def test_pass_optional_txn_data_truncated_to_255_chars options = { - :txn_data1 => 'Transaction Data 1-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', - :txn_data2 => 'Transaction Data 2-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', - :txn_data3 => 'Transaction Data 3-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA' + txn_data1: 'Transaction Data 1-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', + txn_data2: 'Transaction Data 2-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', + txn_data3: 'Transaction Data 3-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA' } truncated_addendum = '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345' @@ -186,8 +204,26 @@ def test_pass_optional_txn_data_truncated_to_255_chars end end + def test_pass_enable_avs_data_and_avs_action + options = { + address: { + address1: '123 Pine Street', + zip: '12345' + }, + enable_avs_data: 0, + avs_action: 3 + } + + stub_comms do + @gateway.purchase(@amount, @visa, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/0<\/EnableAvsData>/, data) + assert_match(/3<\/AvsAction>/, data) + end.respond_with(successful_authorization_response) + end + def test_pass_client_type_as_symbol_for_web - options = {:client_type => :web} + options = { client_type: :web } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/Web<\/ClientType>/, body) @@ -195,7 +231,7 @@ def test_pass_client_type_as_symbol_for_web end def test_pass_client_type_as_symbol_for_ivr - options = {:client_type => :ivr} + options = { client_type: :ivr } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/IVR<\/ClientType>/, body) @@ -203,7 +239,7 @@ def test_pass_client_type_as_symbol_for_ivr end def test_pass_client_type_as_symbol_for_moto - options = {:client_type => :moto} + options = { client_type: :moto } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/MOTO<\/ClientType>/, body) @@ -211,7 +247,7 @@ def test_pass_client_type_as_symbol_for_moto end def test_pass_client_type_as_symbol_for_unattended - options = {:client_type => :unattended} + options = { client_type: :unattended } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/Unattended<\/ClientType>/, body) @@ -219,7 +255,7 @@ def test_pass_client_type_as_symbol_for_unattended end def test_pass_client_type_as_symbol_for_internet - options = {:client_type => :internet} + options = { client_type: :internet } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/Internet<\/ClientType>/, body) @@ -227,7 +263,7 @@ def test_pass_client_type_as_symbol_for_internet end def test_pass_client_type_as_symbol_for_recurring - options = {:client_type => :recurring} + options = { client_type: :recurring } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/Recurring<\/ClientType>/, body) @@ -235,7 +271,7 @@ def test_pass_client_type_as_symbol_for_recurring end def test_pass_client_type_as_symbol_for_unknown_type_omits_element - options = {:client_type => :unknown} + options = { client_type: :unknown } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_no_match(//, body) @@ -243,7 +279,7 @@ def test_pass_client_type_as_symbol_for_unknown_type_omits_element end def test_pass_ip_as_client_info - options = {:ip => '192.168.0.1'} + options = { ip: '192.168.0.1' } perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/192.168.0.1<\/ClientInfo>/, body) @@ -252,64 +288,64 @@ def test_pass_ip_as_client_info def test_purchase_truncates_order_id_to_16_chars stub_comms do - @gateway.purchase(@amount, @visa, {:order_id => '16chars---------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @visa, { order_id: '16chars---------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) end def test_authorize_truncates_order_id_to_16_chars stub_comms do - @gateway.authorize(@amount, @visa, {:order_id => '16chars---------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @visa, { order_id: '16chars---------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) end def test_capture_truncates_order_id_to_16_chars stub_comms do - @gateway.capture(@amount, 'identification', {:order_id => '16chars---------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.capture(@amount, 'identification', { order_id: '16chars---------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) end def test_refund_truncates_order_id_to_16_chars stub_comms do - @gateway.refund(@amount, 'identification', {:description => 'refund', :order_id => '16chars---------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, 'identification', { description: 'refund', order_id: '16chars---------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) end def test_purchase_truncates_description_to_50_chars stub_comms do - @gateway.purchase(@amount, @visa, {:description => '50chars-------------------------------------------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @visa, { description: '50chars-------------------------------------------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) end def test_authorize_truncates_description_to_50_chars stub_comms do - @gateway.authorize(@amount, @visa, {:description => '50chars-------------------------------------------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @visa, { description: '50chars-------------------------------------------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) end def test_capture_truncates_description_to_50_chars stub_comms do - @gateway.capture(@amount, 'identification', {:description => '50chars-------------------------------------------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.capture(@amount, 'identification', { description: '50chars-------------------------------------------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) end def test_refund_truncates_description_to_50_chars stub_comms do - @gateway.capture(@amount, 'identification', {:description => '50chars-------------------------------------------EXTRA'}) - end.check_request do |endpoint, data, headers| + @gateway.capture(@amount, 'identification', { description: '50chars-------------------------------------------EXTRA' }) + end.check_request do |_endpoint, data, _headers| assert_match(/50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) end @@ -324,35 +360,35 @@ def perform_each_transaction_type_with_request_body_assertions(options = {}) # purchase stub_comms do @gateway.purchase(@amount, @visa, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| yield data end.respond_with(successful_authorization_response) # authorize stub_comms do @gateway.authorize(@amount, @visa, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| yield data end.respond_with(successful_authorization_response) # capture stub_comms do @gateway.capture(@amount, 'identification', options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| yield data end.respond_with(successful_authorization_response) # refund stub_comms do - @gateway.refund(@amount, 'identification', {:description => 'description'}.merge(options)) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, 'identification', { description: 'description' }.merge(options)) + end.check_request do |_endpoint, data, _headers| yield data end.respond_with(successful_authorization_response) # store stub_comms do @gateway.store(@visa, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| yield data end.respond_with(successful_store_response) end @@ -366,74 +402,398 @@ def invalid_credentials_response end def successful_authorization_response - <<-RESPONSE - - - 1 - Test Transaction - M - Visa - 0 - 0 - 015921 - 1.23 - 1 - NZD - WestpacTrust - 1 - NZD - 1.00 - WestpacTrust - 30102000 - 1 - DPS - 20050811 - Purchase - 411111 - 0807 - - 20050811 - 060039 - 9000 - Test - 1 - 2 - APPROVED - The Transaction was approved - The Transaction was approved - APPROVED - The Transaction was approved - The Transaction was approved - 9997 - 00000004011a2478 - 0 - - - 011a2478 - - 00 - APPROVED - The Transaction was approved - 1 - 00000004011a2478 - + <<~RESPONSE + + + 1 + Test Transaction + M + Visa + 0 + 0 + 015921 + 1.23 + 1 + NZD + WestpacTrust + 1 + NZD + 1.00 + WestpacTrust + 30102000 + 1 + DPS + 20050811 + Purchase + 411111 + 0807 + + 20050811 + 060039 + 9000 + Test + 1 + 2 + APPROVED + The Transaction was approved + The Transaction was approved + APPROVED + The Transaction was approved + The Transaction was approved + 9997 + 00000004011a2478 + 0 + + + 011a2478 + + 00 + APPROVED + The Transaction was approved + 1 + 00000004011a2478 + + RESPONSE + end + + def successful_validation_response + <<~RESPONSE + + + 1 + 00 + 20210126161020 + 20210127051020 + NZT + Store purchase + Visa + 0 + 0 + 051020XXX + 0.00 + 1.00 + 554 + NZD + 554 + NZD + 1.00 + LONGBOB LONGSEN + 20210127 + Auth + 411111........11 + 2BC20210 + 0922 + + 20210127 + 051020 + 9001 + Undefined + 00 + APPROVED + 1 + 2 + APPROVED + The Transaction was approved + The Transaction was approved + APPROVED + The Transaction was approved + The Transaction was approved + + + 0 + + + + + + + U + K1C2N6 + 456 My Street + E + E - AVS data is invalid, or AVS not allowed for this card type + Attempt + 10000000-10003813 + 451734 + 9997 + 0000000c025d2744 + 0 + + + + + + 9310200000000010 + 0 + 025d2744 + 0000000c + + 0000000000000000 + -1 + + + + + 0000000000000000 + -1 + + 00 + APPROVED + Transaction Approved + 1 + 0000000c025d2744 + + 5f3d2cde30deeef0 + RESPONSE end def successful_store_response(options = {}) - %(1Visa0002381203accf5c000000030.01554554NZD1.00NZDBOB BOBSEN20070323Auth424242........420809200703230238129000Test12APPROVEDThe Transaction was approvedThe Transaction was approvedAPPROVEDThe Transaction was approvedThe Transaction was approved09999999999-999999991283599970000000303accf5c00000030000141581#{options[:billing_id]}03accf5c0000000300APPROVEDThe Transaction was approved10000000303accf5c) + <<~RESPONSE + + + 1 + + Visa + 0 + 0 + 02381203accf5c00000003 + 0.01 + 554 + 554 + NZD + 1.00 + NZD + BOB BOBSEN + 20070323 + Auth + 424242........42 + 0809 + + 20070323 + 023812 + 9000 + Test + 1 + 2 + APPROVED + The Transaction was approved + The Transaction was approved + APPROVED + The Transaction was approved + The Transaction was approved + + + 0 + + + + + + 9999999999-99999999 + 12835 + 9997 + 0000000303accf5c + 0 + 0000030000141581 + #{options[:billing_id]} + 03accf5c + 00000003 + + 00 + APPROVED + The Transaction was approved + 1 + 0000000303accf5c + + + RESPONSE end def unsuccessful_store_response(options = {}) - %(0000.01554554NZD1.00NZDLONGBOB LONGSEN19800101Validate000000........000808900000INVALID CARD NUMBERAn Invalid Card Number was entered. Check the card numberAn Invalid Card Number was entered. Check the card numberINVALID CARD NUMBERAn Invalid Card Number was entered. Check the card numberAn Invalid Card Number was entered. Check the card number09999999999-999999990999700000000000000003QKINVALID CARD NUMBERAn Invalid Card Number was entered. Check the card number0) + <<~RESPONSE + + + 0 + + + 0 + 0 + + 0.01 + 554 + 554 + NZD + 1.00 + NZD + LONGBOB LONGSEN + 19800101 + Validate + 000000........00 + 0808 + + + + 9000 + + 0 + 0 + INVALID CARD NUMBER + An Invalid Card Number was entered. Check the card number + An Invalid Card Number was entered. Check the card number + INVALID CARD NUMBER + An Invalid Card Number was entered. Check the card number + An Invalid Card Number was entered. Check the card number + + + 0 + + + + + + 9999999999-99999999 + 0 + 9997 + + 0 + + + 00000000 + 00000003 + + QK + INVALID CARD NUMBER + An Invalid Card Number was entered. Check the card number + 0 + + + + RESPONSE end def successful_dps_billing_id_token_purchase_response - %(1Visa0003081710.00554554NZD1.00NZDLONGBOB LONGSEN20070323Purchase424242........420808200703230308179000Test12APPROVEDThe Transaction was approvedThe Transaction was approvedAPPROVEDThe Transaction was approvedThe Transaction was approved09999999999-999999991285999970000000303ace8db0000003000014158103ace8db0000000300APPROVEDThe Transaction was approved10000000303ace8db) + <<~RESPONSE + + + 1 + + Visa + 0 + 0 + 030817 + 10.00 + 554 + 554 + NZD + 1.00 + NZD + LONGBOB LONGSEN + 20070323 + Purchase + 424242........42 + 0808 + + 20070323 + 030817 + 9000 + Test + 1 + 2 + APPROVED + The Transaction was approved + The Transaction was approved + APPROVED + The Transaction was approved + The Transaction was approved + + + 0 + + + + + + 9999999999-99999999 + 12859 + 9997 + 0000000303ace8db + 0 + 0000030000141581 + + 03ace8db + 00000003 + + 00 + APPROVED + The Transaction was approved + 1 + 0000000303ace8db + + + RESPONSE end def successful_billing_id_token_purchase_response - %(1Visa0003081710.00554554NZD1.00NZDLONGBOB LONGSEN20070323Purchase424242........420808200703230308179000Test12APPROVEDThe Transaction was approvedThe Transaction was approvedAPPROVEDThe Transaction was approvedThe Transaction was approved09999999999-999999991285999970000000303ace8db0TEST123403ace8db0000000300APPROVEDThe Transaction was approved10000000303ace8db) + <<~RESPONSE + + + 1 + + Visa + 0 + 0 + 030817 + 10.00 + 554 + 554 + NZD + 1.00 + NZD + LONGBOB LONGSEN + 20070323 + Purchase + 424242........42 + 0808 + + 20070323 + 030817 + 9000 + Test + 1 + 2 + APPROVED + The Transaction was approved + The Transaction was approved + APPROVED + The Transaction was approved + The Transaction was approved + + + 0 + + + + + + 9999999999-99999999 + 12859 + 9997 + 0000000303ace8db + 0 + + TEST1234 + 03ace8db + 00000003 + + 00 + APPROVED + The Transaction was approved + 1 + 0000000303ace8db + + + RESPONSE end def transcript diff --git a/test/unit/gateways/paymentez_test.rb b/test/unit/gateways/paymentez_test.rb index 3e6df5428e6..07223ab61d9 100644 --- a/test/unit/gateways/paymentez_test.rb +++ b/test/unit/gateways/paymentez_test.rb @@ -6,13 +6,14 @@ 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', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @elo_credit_card = credit_card( + '6362970000457013', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'elo' ) @amount = 100 @@ -23,6 +24,29 @@ def setup description: 'Store Purchase', email: 'a@b.com' } + + @cavv = 'example-cavv-value' + @xid = 'three-ds-v1-trans-id' + @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' + + @three_ds_v1_mpi = { + cavv: @cavv, + eci: @eci, + version: @three_ds_v1_version, + xid: @xid + } + + @three_ds_v2_mpi = { + 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 + } end def test_successful_purchase @@ -45,6 +69,21 @@ def test_successful_purchase_with_elo assert response.test? end + def test_successful_capture_with_otp + authorization = 'CI-14952' + options = @options.merge({ type: 'BY_OTP', value: '012345' }) + response = stub_comms do + @gateway.capture(nil, authorization, options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal 'BY_OTP', request['type'] + assert_equal '012345', request['value'] + assert_equal authorization, request['transaction']['id'] + assert_equal '123', request['user']['id'] + end.respond_with(successful_otp_capture_response) + assert_success response + end + def test_successful_purchase_with_token @gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -55,6 +94,41 @@ def test_successful_purchase_with_token assert response.test? end + def test_purchase_3ds1_mpi_fields + @options[:three_d_secure] = @three_ds_v1_mpi + + expected_auth_data = { + cavv: @cavv, + xid: @xid, + eci: @eci, + version: @three_ds_v1_version + } + + @gateway.expects(:commit_transaction).with do |_, post_data| + post_data['extra_params'][:auth_data] == expected_auth_data + end + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_purchase_3ds2_mpi_fields + @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, + status: @authentication_response_status + } + + @gateway.expects(:commit_transaction).with() do |_, post_data| + post_data['extra_params'][:auth_data] == expected_auth_data + end + + @gateway.purchase(@amount, @credit_card, @options) + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -99,6 +173,41 @@ def test_successful_authorize_with_token assert response.test? end + def test_authorize_3ds1_mpi_fields + @options[:three_d_secure] = @three_ds_v1_mpi + + expected_auth_data = { + cavv: @cavv, + xid: @xid, + eci: @eci, + version: @three_ds_v1_version + } + + @gateway.expects(:commit_transaction).with() do |_, post_data| + post_data['extra_params'][:auth_data] == expected_auth_data + end + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_authorize_3ds2_mpi_fields + @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, + status: @authentication_response_status + } + + @gateway.expects(:commit_transaction).with() do |_, post_data| + post_data['extra_params'][:auth_data] == expected_auth_data + end + + @gateway.authorize(@amount, @credit_card, @options) + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(failed_authorize_response) @@ -157,6 +266,7 @@ def test_partial_refund assert_match(/"amount":1.0/, data) end.respond_with(successful_refund_response) assert_success response + assert_equal 'Completed', response.message assert response.test? end @@ -165,6 +275,7 @@ def test_failed_refund response = @gateway.refund(@amount, '1234', @options) assert_failure response + assert_equal 'Invalid Status', response.message assert response.test? end @@ -173,6 +284,7 @@ def test_successful_void response = @gateway.void('1234', @options) assert_success response + assert_equal 'Completed', response.message assert response.test? end @@ -180,9 +292,21 @@ 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 end + def test_successful_void_with_more_info + @gateway.expects(:ssl_post).returns(successful_void_response_with_more_info) + + response = @gateway.void('1234', @options.merge(more_info: true)) + assert_success response + assert_equal 'Completed', response.message + assert_equal '00', response.params['transaction']['carrier_code'] + assert_equal 'Reverse by mock', response.params['transaction']['message'] + assert response.test? + end + def test_simple_store @gateway.expects(:ssl_post).returns(successful_store_response) @@ -219,6 +343,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 @@ -499,6 +635,17 @@ def successful_capture_with_elo_response ' end + def successful_otp_capture_response + '{ + "status": 1, + "payment_date": "2017-09-26T21:16:00", + "amount": 99.0, + "transaction_id": "CI-14952", + "status_detail": 3, + "message": "" + }' + end + def failed_capture_response '{"error": {"type": "Carrier not supported", "help": "", "description": "{}"}}' end @@ -508,11 +655,16 @@ def successful_void_response end def failed_void_response - '{"error": {"type": "Carrier not supported", "help": "", "description": "{}"}}' + '{"status": "failure", "detail": "Invalid Status"}' + end + + def successful_void_response_with_more_info + '{"status": "success", "detail": "Completed", "transaction": {"carrier_code": "00", "message": "Reverse by mock"}}' end - alias_method :successful_refund_response, :successful_void_response - alias_method :failed_refund_response, :failed_void_response + alias successful_refund_response successful_void_response + alias failed_refund_response failed_void_response + alias successful_refund_response_with_more_info successful_void_response_with_more_info def already_stored_response '{"error": {"type": "Card already added: 14436664108567261211", "help": "If you want to update the card, first delete it", "description": "{}"}}' diff --git a/test/unit/gateways/paymill_test.rb b/test/unit/gateways/paymill_test.rb index 546a98b9125..40fd6191ee2 100644 --- a/test/unit/gateways/paymill_test.rb +++ b/test/unit/gateways/paymill_test.rb @@ -2,7 +2,7 @@ class PaymillTest < Test::Unit::TestCase def setup - @gateway = PaymillGateway.new(:public_key => 'PUBLIC', :private_key => 'PRIVATE') + @gateway = PaymillGateway.new(public_key: 'PUBLIC', private_key: 'PRIVATE') @credit_card = credit_card @amount = 100 @@ -181,6 +181,20 @@ def test_successful_store assert response.test? end + def test_store_includes_currency_and_amount + expected_currency = 'USD' + expected_amount = 100 + + @gateway.expects(:raw_ssl_request).with( + :get, + store_endpoint_url(@credit_card, expected_currency, expected_amount), + nil, + {} + ).returns(successful_store_response, successful_purchase_response) + + @gateway.store(@credit_card) + end + def test_failed_store_with_invalid_credit_card @gateway.expects(:raw_ssl_request).returns(failed_store_response) response = @gateway.store(@credit_card) @@ -225,6 +239,10 @@ def test_transcript_scrubbing private + def store_endpoint_url(credit_card, currency, amount) + "https://test-token.paymill.com?account.holder=#{credit_card.first_name}+#{credit_card.last_name}&account.number=#{credit_card.number}&account.expiry.month=#{'%02d' % credit_card.month}&account.expiry.year=#{credit_card.year}&account.verification=#{credit_card.verification_value}&presentation.amount3D=#{amount}&presentation.currency3D=#{currency}&channel.id=PUBLIC&jsonPFunction=jsonPFunction&transaction.mode=CONNECTOR_TEST" + end + def successful_store_response MockResponse.new 200, %[jsonPFunction({"transaction":{"mode":"CONNECTOR_TEST","channel":"57313835619696ac361dc591bc973626","response":"SYNC","payment":{"code":"CC.DB"},"processing":{"code":"CC.DB.90.00","reason":{"code":"00","message":"Successful Processing"},"result":"ACK","return":{"code":"000.100.112","message":"Request successfully processed in 'Merchant in Connector Test Mode'"},"timestamp":"2013-02-12 21:33:43"},"identification":{"shortId":"1998.1832.1612","uniqueId":"tok_4f9a571b39bd8d0b4db5"}}})] end @@ -758,5 +776,4 @@ def transcript def scrubbed_transcript 'connection_uri=https://test-token.paymill.com?account.number=[FILTERED]&account.expiry.month=09&account.expiry.year=2016&account.verification=[FILTERED]' end - end diff --git a/test/unit/gateways/paypal/paypal_common_api_test.rb b/test/unit/gateways/paypal/paypal_common_api_test.rb index 49751604ab9..ebf2a530be4 100644 --- a/test/unit/gateways/paypal/paypal_common_api_test.rb +++ b/test/unit/gateways/paypal/paypal_common_api_test.rb @@ -18,20 +18,20 @@ def setup CommonPaypalGateway.pem_file = nil @gateway = CommonPaypalGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' + login: 'cody', + password: 'test', + pem: 'PEM' ) @address = { - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' + address1: '1234 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'Canada', + phone: '(555)555-5555' } end @@ -44,34 +44,68 @@ def wrap_xml(&block) end def test_add_payment_details_adds_express_only_payment_details_when_necessary - options = {:express_request => true} + options = { express_request: true } @gateway.expects(:add_express_only_payment_details) @gateway.send(:add_payment_details, xml_builder, 100, 'USD', options) end def test_add_payment_details_adds_items_details - options = {:items => [1]} + options = { items: [1] } @gateway.expects(:add_payment_details_items_xml) @gateway.send(:add_payment_details, xml_builder, 100, 'USD', options) end def test_add_payment_details_adds_address - options = {:shipping_address => @address} + options = { shipping_address: @address } @gateway.expects(:add_address) @gateway.send(:add_payment_details, xml_builder, 100, 'USD', options) end def test_add_payment_details_adds_items_details_elements - options = {:items => [{:name => 'foo'}]} + options = { items: [{ name: 'foo' }] } request = wrap_xml do |xml| @gateway.send(:add_payment_details, xml, 100, 'USD', options) end assert_equal 'foo', REXML::XPath.first(request, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Name').text end + def test_add_payment_details_adds_order_total_elements + options = { + subtotal: 25, + shipping: 5, + handling: 2, + tax: 1 + } + request = wrap_xml do |xml| + @gateway.send(:add_payment_details, xml, 100, 'USD', options) + end + + assert_equal '25', REXML::XPath.first(request, '//n2:PaymentDetails/n2:ItemTotal').text + assert_equal '5', REXML::XPath.first(request, '//n2:PaymentDetails/n2:ShippingTotal').text + assert_equal '2', REXML::XPath.first(request, '//n2:PaymentDetails/n2:HandlingTotal').text + assert_equal '1', REXML::XPath.first(request, '//n2:PaymentDetails/n2:TaxTotal').text + end + + def test_add_payment_details_does_not_add_order_total_elements_when_any_element_is_nil + options = { + subtotal: nil, + shipping: 5, + handling: 2, + tax: 1 + } + request = wrap_xml do |xml| + @gateway.send(:add_payment_details, xml, 100, 'USD', options) + end + + assert_equal nil, REXML::XPath.first(request, '//n2:PaymentDetails/n2:ItemTotal') + assert_equal nil, REXML::XPath.first(request, '//n2:PaymentDetails/n2:ShippingTotal') + assert_equal nil, REXML::XPath.first(request, '//n2:PaymentDetails/n2:HandlingTotal') + assert_equal nil, REXML::XPath.first(request, '//n2:PaymentDetails/n2:TaxTotal') + end + def test_add_express_only_payment_details_adds_non_blank_fields request = wrap_xml do |xml| - @gateway.send(:add_express_only_payment_details, xml, {:payment_action => 'Sale', :payment_request_id => ''}) + @gateway.send(:add_express_only_payment_details, xml, { payment_action: 'Sale', payment_request_id: '' }) end assert_equal 'Sale', REXML::XPath.first(request, '//n2:PaymentAction').text assert_nil REXML::XPath.first(request, '//n2:PaymentRequestID') @@ -85,7 +119,7 @@ def test_build_request_wrapper_plain end def test_build_request_wrapper_with_request_details - result = @gateway.send(:build_request_wrapper, 'Action', :request_details => true) do |xml| + result = @gateway.send(:build_request_wrapper, 'Action', request_details: true) do |xml| xml.tag! 'n2:TransactionID', 'baz' end assert_equal 'baz', REXML::XPath.first(REXML::Document.new(result), '//ActionReq/ActionRequest/n2:ActionRequestDetails/n2:TransactionID').text @@ -101,24 +135,24 @@ def test_build_get_balance assert_equal '1', REXML::XPath.first(request, '//GetBalanceReq/GetBalanceRequest/ReturnAllCurrencies').text end - def test_balance_cleans_up_currencies_values_like_1 + def test_balance_cleans_up_currencies_values_like_one @gateway.stubs(:commit) - [1, '1', true].each do |values_like_1| + [1, '1', true].each do |values_like_one| @gateway.expects(:build_get_balance).with('1') - @gateway.balance(values_like_1) + @gateway.balance(values_like_one) end end - def test_balance_cleans_up_currencies_values_like_0 + def test_balance_cleans_up_currencies_values_like_zero @gateway.stubs(:commit) - [0, '0', false, nil, :foo].each do |values_like_0| + [0, '0', false, nil, :foo].each do |values_like_zero| @gateway.expects(:build_get_balance).with('0') - @gateway.balance(values_like_0) + @gateway.balance(values_like_zero) end end def test_build_do_authorize_request - request = REXML::Document.new(@gateway.send(:build_do_authorize, 123, 100, :currency => 'USD')) + request = REXML::Document.new(@gateway.send(:build_do_authorize, 123, 100, currency: 'USD')) assert_equal '123', REXML::XPath.first(request, '//DoAuthorizationReq/DoAuthorizationRequest/TransactionID').text assert_equal '1.00', REXML::XPath.first(request, '//DoAuthorizationReq/DoAuthorizationRequest/Amount').text end @@ -137,10 +171,10 @@ def test_transaction_search_requires def test_build_transaction_search_request options = { - :start_date => DateTime.new(2012, 2, 21, 0), - :end_date => DateTime.new(2012, 3, 21, 0), - :receiver => 'foo@example.com', - :first_name => 'Robert' + start_date: DateTime.new(2012, 2, 21, 0), + end_date: DateTime.new(2012, 3, 21, 0), + receiver: 'foo@example.com', + first_name: 'Robert' } request = REXML::Document.new(@gateway.send(:build_transaction_search, options)) assert_match %r{^2012-02-21T\d{2}:00:00Z$}, REXML::XPath.first(request, '//TransactionSearchReq/TransactionSearchRequest/StartDate').text @@ -152,17 +186,20 @@ def test_build_reference_transaction_request assert_raise ArgumentError do @gateway.reference_transaction(100) end - @gateway.reference_transaction(100, :reference_id => 'id') + @gateway.reference_transaction(100, reference_id: 'id') end def test_build_reference_transaction_gets_ip - request = REXML::Document.new(@gateway.send(:build_reference_transaction_request, - 100, - :reference_id => 'id', - :ip => '127.0.0.1')) + request = REXML::Document.new( + @gateway.send( + :build_reference_transaction_request, + 100, + reference_id: 'id', + ip: '127.0.0.1' + ) + ) assert_equal '100', REXML::XPath.first(request, '//n2:PaymentDetails/n2:OrderTotal').text assert_equal 'id', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text assert_equal '127.0.0.1', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text end - end diff --git a/test/unit/gateways/paypal_digital_goods_test.rb b/test/unit/gateways/paypal_digital_goods_test.rb index 4b433f38843..886253a6673 100644 --- a/test/unit/gateways/paypal_digital_goods_test.rb +++ b/test/unit/gateways/paypal_digital_goods_test.rb @@ -8,9 +8,9 @@ class PaypalDigitalGoodsTest < Test::Unit::TestCase def setup @gateway = PaypalDigitalGoodsGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' + login: 'cody', + password: 'test', + pem: 'PEM' ) Base.mode = :test @@ -34,60 +34,78 @@ def test_test_redirect_url def test_setup_request_invalid_requests assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => '127.0.0.1', - :description => 'Test Title', - :return_url => 'http://return.url', - :cancel_return_url => 'http://cancel.url') + @gateway.setup_purchase( + 100, + ip: '127.0.0.1', + description: 'Test Title', + return_url: 'http://return.url', + cancel_return_url: 'http://cancel.url' + ) end assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => '127.0.0.1', - :description => 'Test Title', - :return_url => 'http://return.url', - :cancel_return_url => 'http://cancel.url', - :items => [ ]) + @gateway.setup_purchase( + 100, + ip: '127.0.0.1', + description: 'Test Title', + return_url: 'http://return.url', + cancel_return_url: 'http://cancel.url', + items: [] + ) end assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => '127.0.0.1', - :description => 'Test Title', - :return_url => 'http://return.url', - :cancel_return_url => 'http://cancel.url', - :items => [ Hash.new ]) + @gateway.setup_purchase( + 100, + ip: '127.0.0.1', + description: 'Test Title', + return_url: 'http://return.url', + cancel_return_url: 'http://cancel.url', + items: [Hash.new] + ) end assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => '127.0.0.1', - :description => 'Test Title', - :return_url => 'http://return.url', - :cancel_return_url => 'http://cancel.url', - :items => [ { :name => 'Charge', - :number => '1', - :quantity => '1', - :amount => 100, - :description => 'Description', - :category => 'Physical' } ]) + @gateway.setup_purchase( + 100, + ip: '127.0.0.1', + description: 'Test Title', + return_url: 'http://return.url', + cancel_return_url: 'http://cancel.url', + items: [ + { + name: 'Charge', + number: '1', + quantity: '1', + amount: 100, + description: 'Description', + category: 'Physical' + } + ] + ) end end def test_build_setup_request_valid @gateway.expects(:ssl_post).returns(successful_setup_response) - @gateway.setup_purchase(100, - :ip => '127.0.0.1', - :description => 'Test Title', - :return_url => 'http://return.url', - :cancel_return_url => 'http://cancel.url', - :items => [ { :name => 'Charge', - :number => '1', - :quantity => '1', - :amount => 100, - :description => 'Description', - :category => 'Digital' } ]) + @gateway.setup_purchase( + 100, + ip: '127.0.0.1', + description: 'Test Title', + return_url: 'http://return.url', + cancel_return_url: 'http://cancel.url', + items: [ + { + name: 'Charge', + number: '1', + quantity: '1', + amount: 100, + description: 'Description', + category: 'Digital' + } + ] + ) end private @@ -118,5 +136,4 @@ def successful_setup_response " end - end diff --git a/test/unit/gateways/paypal_express_test.rb b/test/unit/gateways/paypal_express_test.rb index be0274025f8..5be4717bfed 100644 --- a/test/unit/gateways/paypal_express_test.rb +++ b/test/unit/gateways/paypal_express_test.rb @@ -14,20 +14,20 @@ class PaypalExpressTest < Test::Unit::TestCase def setup @gateway = PaypalExpressGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' + login: 'cody', + password: 'test', + pem: 'PEM' ) @address = { - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' + address1: '1234 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'Canada', + phone: '(555)555-5555' } Base.mode = :test @@ -40,40 +40,40 @@ def teardown def test_live_redirect_url Base.mode = :production assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') - assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) + assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', mobile: true) end def test_live_redirect_url_without_review Base.mode = :production - assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) - assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) + assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false, mobile: true) end def test_force_sandbox_redirect_url Base.mode = :production gateway = PaypalExpressGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM', - :test => true + login: 'cody', + password: 'test', + pem: 'PEM', + test: true ) assert gateway.test? assert_equal TEST_REDIRECT_URL, gateway.redirect_url_for('1234567890') - assert_equal TEST_REDIRECT_URL_MOBILE, gateway.redirect_url_for('1234567890', :mobile => true) + assert_equal TEST_REDIRECT_URL_MOBILE, gateway.redirect_url_for('1234567890', mobile: true) end def test_test_redirect_url assert_equal :test, Base.mode assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') - assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) + assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', mobile: true) end def test_test_redirect_url_without_review assert_equal :test, Base.mode - assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) - assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) + assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false, mobile: true) end def test_get_express_details @@ -88,6 +88,7 @@ def test_get_express_details assert_equal 'FWRVKNRRZ3WUC', response.payer_id assert_equal 'buyer@jadedpallet.com', response.email assert_equal 'This is a test note', response.note + assert_equal 'PaymentActionNotInitiated', response.checkout_status assert address = response.address assert_equal 'Fred Brooks', address['name'] @@ -111,7 +112,7 @@ def test_express_response_missing_address def test_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - response = @gateway.authorize(300, :token => 'EC-6WS104951Y388951L', :payer_id => 'FWRVKNRRZ3WUC') + response = @gateway.authorize(300, token: 'EC-6WS104951Y388951L', payer_id: 'FWRVKNRRZ3WUC') assert response.success? assert_not_nil response.authorization assert response.test? @@ -130,25 +131,25 @@ def test_uk_partner end def test_includes_description - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :description => 'a description' })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { description: 'a description' })) assert_equal 'a description', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:OrderDescription').text end def test_includes_order_id - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :order_id => '12345' })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { order_id: '12345' })) assert_equal '12345', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:InvoiceID').text end def test_includes_correct_payment_action - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {})) assert_equal 'SetExpressCheckout', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentAction').text end def test_includes_custom_tag_if_specified - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:custom => 'Foo'})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { custom: 'Foo' })) assert_equal 'Foo', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:Custom').text end @@ -167,21 +168,20 @@ def test_does_not_include_items_if_not_specified def test_items_are_included_if_specified_in_build_setup_request xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { - :currency => 'GBP', - :items => [ + currency: 'GBP', + items: [ { - :name => 'item one', - :description => 'item one description', - :amount => 10000, - :number => 1, - :quantity => 3 + name: 'item one', + description: 'item one description', + amount: 10000, + number: 1, + quantity: 3 }, - { :name => 'item two', - :description => 'item two description', - :amount => 20000, - :number => 2, - :quantity => 4 - } + { name: 'item two', + description: 'item two description', + amount: 20000, + number: 2, + quantity: 4 } ] })) @@ -207,7 +207,7 @@ def test_does_not_include_callback_url_if_not_specified end def test_callback_url_is_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:callback_url => 'http://example.com/update_callback'})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { callback_url: 'http://example.com/update_callback' })) assert_equal 'http://example.com/update_callback', REXML::XPath.first(xml, '//n2:CallbackURL').text end @@ -219,7 +219,7 @@ def test_does_not_include_callback_timeout_if_not_specified end def test_callback_timeout_is_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:callback_timeout => 2})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { callback_timeout: 2 })) assert_equal '2', REXML::XPath.first(xml, '//n2:CallbackTimeout').text end @@ -231,7 +231,7 @@ def test_does_not_include_callback_version_if_not_specified end def test_callback_version_is_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:callback_version => '53.0'})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { callback_version: '53.0' })) assert_equal '53.0', REXML::XPath.first(xml, '//n2:CallbackVersion').text end @@ -243,22 +243,28 @@ def test_does_not_include_flatrate_shipping_options_if_not_specified end def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, - { - :currency => 'AUD', - :shipping_options => [ - { - :default => true, - :name => 'first one', - :amount => 1000 - }, - { - :default => false, - :name => 'second one', - :amount => 2000 - } - ] - })) + xml = REXML::Document.new( + @gateway.send( + :build_setup_request, + 'SetExpressCheckout', + 0, + { + currency: 'AUD', + shipping_options: [ + { + default: true, + name: 'first one', + amount: 1000 + }, + { + default: false, + name: 'second one', + amount: 2000 + } + ] + } + ) + ) assert_equal 'true', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionIsDefault').text assert_equal 'first one', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionName').text @@ -272,18 +278,24 @@ def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_requ end def test_address_is_included_if_specified - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'Sale', 0, - { - :currency => 'GBP', - :address => { - :name => 'John Doe', - :address1 => '123 somewhere', - :city => 'Townville', - :country => 'Canada', - :zip => 'k1l4p2', - :phone => '1231231231' + xml = REXML::Document.new( + @gateway.send( + :build_setup_request, + 'Sale', + 0, + { + currency: 'GBP', + address: { + name: 'John Doe', + address1: '123 somewhere', + city: 'Townville', + country: 'Canada', + zip: 'k1l4p2', + phone: '1231231231' } - })) + } + ) + ) assert_equal 'John Doe', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Name').text assert_equal '123 somewhere', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Street1').text @@ -301,41 +313,47 @@ def test_handle_non_zero_amount def test_amount_format_for_jpy_currency @gateway.expects(:ssl_post).with(anything, regexp_matches(/n2:OrderTotal currencyID=.JPY.>1<\/n2:OrderTotal>/), {}).returns(successful_authorization_response) - response = @gateway.authorize(100, :token => 'EC-6WS104951Y388951L', :payer_id => 'FWRVKNRRZ3WUC', :currency => 'JPY') + response = @gateway.authorize(100, token: 'EC-6WS104951Y388951L', payer_id: 'FWRVKNRRZ3WUC', currency: 'JPY') assert response.success? end def test_removes_fractional_amounts_with_twd_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 150, {:currency => 'TWD'})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 150, { currency: 'TWD' })) assert_equal '1', REXML::XPath.first(xml, '//n2:OrderTotal').text end def test_fractional_discounts_are_correctly_calculated_with_jpy_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, - { - :items => [ - { - :name => 'item one', - :description => 'description', - :amount => 15000, - :number => 1, - :quantity => 1 - }, - { - :name => 'Discount', - :description => 'Discount', - :amount => -750, - :number => 2, - :quantity => 1 - } - ], - :subtotal => 14250, - :currency => 'JPY', - :shipping => 0, - :handling => 0, - :tax => 0 - })) + xml = REXML::Document.new( + @gateway.send( + :build_setup_request, + 'SetExpressCheckout', + 14250, + { + items: [ + { + name: 'item one', + description: 'description', + amount: 15000, + number: 1, + quantity: 1 + }, + { + name: 'Discount', + description: 'Discount', + amount: -750, + number: 2, + quantity: 1 + } + ], + subtotal: 14250, + currency: 'JPY', + shipping: 0, + handling: 0, + tax: 0 + } + ) + ) assert_equal '142', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '142', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -345,30 +363,36 @@ def test_fractional_discounts_are_correctly_calculated_with_jpy_currency end def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14300, - { - :items => [ - { - :name => 'item one', - :description => 'description', - :amount => 15000, - :number => 1, - :quantity => 1 - }, - { - :name => 'Discount', - :description => 'Discount', - :amount => -700, - :number => 2, - :quantity => 1 - } - ], - :subtotal => 14300, - :currency => 'JPY', - :shipping => 0, - :handling => 0, - :tax => 0 - })) + xml = REXML::Document.new( + @gateway.send( + :build_setup_request, + 'SetExpressCheckout', + 14300, + { + items: [ + { + name: 'item one', + description: 'description', + amount: 15000, + number: 1, + quantity: 1 + }, + { + name: 'Discount', + description: 'Discount', + amount: -700, + number: 2, + quantity: 1 + } + ], + subtotal: 14300, + currency: 'JPY', + shipping: 0, + handling: 0, + tax: 0 + } + ) + ) assert_equal '143', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '143', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -378,30 +402,36 @@ def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency end def test_fractional_discounts_are_correctly_calculated_with_usd_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, - { - :items => [ + xml = REXML::Document.new( + @gateway.send( + :build_setup_request, + 'SetExpressCheckout', + 14250, { - :name => 'item one', - :description => 'description', - :amount => 15000, - :number => 1, - :quantity => 1 - }, - { - :name => 'Discount', - :description => 'Discount', - :amount => -750, - :number => 2, - :quantity => 1 + items: [ + { + name: 'item one', + description: 'description', + amount: 15000, + number: 1, + quantity: 1 + }, + { + name: 'Discount', + description: 'Discount', + amount: -750, + number: 2, + quantity: 1 + } + ], + subtotal: 14250, + currency: 'USD', + shipping: 0, + handling: 0, + tax: 0 } - ], - :subtotal => 14250, - :currency => 'USD', - :shipping => 0, - :handling => 0, - :tax => 0 - })) + ) + ) assert_equal '142.50', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '142.50', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -411,33 +441,33 @@ def test_fractional_discounts_are_correctly_calculated_with_usd_currency end def test_does_not_add_allow_note_if_not_specified - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {})) assert_nil REXML::XPath.first(xml, '//n2:AllowNote') end def test_adds_allow_note_if_specified - allow_notes_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :allow_note => true })) - do_not_allow_notes_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :allow_note => false })) + allow_notes_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { allow_note: true })) + do_not_allow_notes_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { allow_note: false })) assert_equal '1', REXML::XPath.first(allow_notes_xml, '//n2:AllowNote').text assert_equal '0', REXML::XPath.first(do_not_allow_notes_xml, '//n2:AllowNote').text end def test_handle_locale_code - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :locale => 'GB' })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { locale: 'GB' })) assert_equal 'GB', REXML::XPath.first(xml, '//n2:LocaleCode').text end def test_handle_non_standard_locale_code - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :locale => 'IL' })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { locale: 'IL' })) assert_equal 'he_IL', REXML::XPath.first(xml, '//n2:LocaleCode').text end def test_does_not_include_locale_in_request_unless_provided_in_options - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { :locale => nil })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { locale: nil })) assert_nil REXML::XPath.first(xml, '//n2:LocaleCode') end @@ -454,25 +484,31 @@ def test_button_source end def test_items_are_included_if_specified_in_build_sale_or_authorization_request - xml = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Sale', 100, - { - :items => [ - { - :name => 'item one', - :description => 'item one description', - :amount => 10000, - :number => 1, - :quantity => 3 - }, - { - :name => 'item two', - :description => 'item two description', - :amount => 20000, - :number => 2, - :quantity => 4 - } - ] - })) + xml = REXML::Document.new( + @gateway.send( + :build_sale_or_authorization_request, + 'Sale', + 100, + { + items: [ + { + name: 'item one', + description: 'item one description', + amount: 10000, + number: 1, + quantity: 3 + }, + { + name: 'item two', + description: 'item two description', + amount: 20000, + number: 2, + quantity: 4 + } + ] + } + ) + ) assert_equal 'item one', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Name').text assert_equal 'item one description', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Description').text @@ -548,14 +584,21 @@ def test_agreement_details_failure def test_build_reference_transaction_test PaypalExpressGateway.application_id = 'ActiveMerchant_FOO' - xml = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Sale', 2000, - { - :reference_id => 'ref_id', - :payment_type => 'Any', - :invoice_id => 'invoice_id', - :description => 'Description', - :ip => '127.0.0.1' - })) + xml = REXML::Document.new( + @gateway.send( + :build_reference_transaction_request, + 'Sale', + 2000, + { + reference_id: 'ref_id', + payment_type: 'Any', + invoice_id: 'invoice_id', + description: 'Description', + ip: '127.0.0.1', + merchant_session_id: 'example_merchant_session_id' + } + ) + ) assert_equal '124', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text assert_equal 'ref_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text @@ -566,6 +609,36 @@ def test_build_reference_transaction_test assert_equal 'invoice_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:InvoiceID').text assert_equal 'ActiveMerchant_FOO', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:ButtonSource').text assert_equal '127.0.0.1', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text + assert_equal 'example_merchant_session_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:MerchantSessionId').text + end + + def test_build_reference_transaction_without_merchant_session_test + PaypalExpressGateway.application_id = 'ActiveMerchant_FOO' + xml = REXML::Document.new( + @gateway.send( + :build_reference_transaction_request, + 'Sale', + 2000, + { + reference_id: 'ref_id', + payment_type: 'Any', + invoice_id: 'invoice_id', + description: 'Description', + ip: '127.0.0.1' + } + ) + ) + + assert_equal '124', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text + assert_equal 'ref_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text + assert_equal 'Sale', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentAction').text + assert_equal 'Any', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentType').text + assert_equal '20.00', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:OrderTotal').text + assert_equal 'Description', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:OrderDescription').text + assert_equal 'invoice_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:InvoiceID').text + assert_equal 'ActiveMerchant_FOO', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentDetails/n2:ButtonSource').text + assert_equal '127.0.0.1', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text + assert_nil REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:MerchantSessionId') end def test_build_details_billing_agreement_request_test @@ -577,23 +650,23 @@ def test_build_details_billing_agreement_request_test def test_authorize_reference_transaction @gateway.expects(:ssl_post).returns(successful_authorize_reference_transaction_response) - response = @gateway.authorize_reference_transaction(2000, - { - :reference_id => 'ref_id', - :payment_type => 'Any', - :invoice_id => 'invoice_id', - :description => 'Description', - :ip => '127.0.0.1' }) + response = @gateway.authorize_reference_transaction(2000, reference_id: 'ref_id') assert_equal 'Success', response.params['ack'] assert_equal 'Success', response.message assert_equal '9R43552341412482K', response.authorization end + def test_authorize_reference_transaction_requires_fields + assert_raise ArgumentError do + @gateway.authorize_reference_transaction(2000, {}) + end + end + def test_reference_transaction @gateway.expects(:ssl_post).returns(successful_reference_transaction_response) - response = @gateway.reference_transaction(2000, { :reference_id => 'ref_id' }) + response = @gateway.reference_transaction(2000, { reference_id: 'ref_id' }) assert_equal 'Success', response.params['ack'] assert_equal 'Success', response.message @@ -608,42 +681,33 @@ def test_reference_transaction_requires_fields def test_error_code_for_single_error @gateway.expects(:ssl_post).returns(response_with_error) - response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) + response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com') assert_equal '10736', response.params['error_codes'] end def test_ensure_only_unique_error_codes @gateway.expects(:ssl_post).returns(response_with_duplicate_errors) - response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) + response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com') assert_equal '10736', response.params['error_codes'] end def test_error_codes_for_multiple_errors @gateway.expects(:ssl_post).returns(response_with_errors) - response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) + response = @gateway.setup_authorization(100, return_url: 'http://example.com', cancel_return_url: 'http://example.com') - assert_equal ['10736', '10002'], response.params['error_codes'].split(',') + assert_equal %w[10736 10002], response.params['error_codes'].split(',') end def test_allow_guest_checkout - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:allow_guest_checkout => true})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { allow_guest_checkout: true })) assert_equal 'Sole', REXML::XPath.first(xml, '//n2:SolutionType').text assert_equal 'Billing', REXML::XPath.first(xml, '//n2:LandingPage').text end def test_paypal_chooses_landing_page - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:allow_guest_checkout => true, :paypal_chooses_landing_page=> true})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { allow_guest_checkout: true, paypal_chooses_landing_page: true })) assert_equal 'Sole', REXML::XPath.first(xml, '//n2:SolutionType').text assert_nil REXML::XPath.first(xml, '//n2:LandingPage') @@ -656,7 +720,7 @@ def test_not_adds_brand_name_if_not_specified end def test_adds_brand_name_if_specified - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:brand_name => 'Acme'})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { brand_name: 'Acme' })) assert_equal 'Acme', REXML::XPath.first(xml, '//n2:BrandName').text end @@ -674,15 +738,15 @@ def test_not_adds_buyer_optin_if_not_specified end def test_adds_buyer_optin_if_specified - allow_optin_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:allow_buyer_optin => true})) - do_not_allow_optin_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:allow_buyer_optin => false})) + allow_optin_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { allow_buyer_optin: true })) + do_not_allow_optin_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { allow_buyer_optin: false })) assert_equal '1', REXML::XPath.first(allow_optin_xml, '//n2:BuyerEmailOptInEnable').text assert_equal '0', REXML::XPath.first(do_not_allow_optin_xml, '//n2:BuyerEmailOptInEnable').text end def test_add_total_type_if_specified - total_type_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:total_type => 'EstimatedTotal'})) + total_type_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, { total_type: 'EstimatedTotal' })) no_total_type_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {})) assert_equal 'EstimatedTotal', REXML::XPath.first(total_type_xml, '//n2:TotalType').text @@ -691,55 +755,54 @@ def test_add_total_type_if_specified def test_structure_correct all_options_enabled = { - :allow_guest_checkout => true, - :max_amount => 50, - :locale => 'AU', - :page_style => 'test-gray', - :header_image => 'https://example.com/my_business', - :header_background_color => 'CAFE00', - :header_border_color => 'CAFE00', - :background_color => 'CAFE00', - :email => 'joe@example.com', - :billing_agreement => {:type => 'MerchantInitiatedBilling', :description => '9.99 per month for a year'}, - :allow_note => true, - :allow_buyer_optin => true, - :subtotal => 35, - :shipping => 10, - :handling => 0, - :tax => 5, - :total_type => 'EstimatedTotal', - :items => [ - { - :name => 'item one', - :number => 'number 1', - :quantity => 3, - :amount => 35, - :description => 'one description', - :url => 'http://example.com/number_1' - } - ], - :address => - { - :name => 'John Doe', - :address1 => 'Apartment 1', - :address2 => '1 Road St', - :city => 'First City', - :state => 'NSW', - :country => 'AU', - :zip => '2000', - :phone => '555 5555' - }, - :callback_url => 'http://example.com/update_callback', - :callback_timeout => 2, - :callback_version => '53.0', - :funding_sources => {:source => 'BML'}, - :shipping_options => [ - { - :default => true, - :name => 'first one', - :amount => 10 - } - ] + allow_guest_checkout: true, + max_amount: 50, + locale: 'AU', + page_style: 'test-gray', + header_image: 'https://example.com/my_business', + header_background_color: 'CAFE00', + header_border_color: 'CAFE00', + background_color: 'CAFE00', + email: 'joe@example.com', + billing_agreement: { type: 'MerchantInitiatedBilling', description: '9.99 per month for a year' }, + allow_note: true, + allow_buyer_optin: true, + subtotal: 35, + shipping: 10, + handling: 0, + tax: 5, + total_type: 'EstimatedTotal', + items: [ + { + name: 'item one', + number: 'number 1', + quantity: 3, + amount: 35, + description: 'one description', + url: 'http://example.com/number_1' + } + ], + address: { + name: 'John Doe', + address1: 'Apartment 1', + address2: '1 Road St', + city: 'First City', + state: 'NSW', + country: 'AU', + zip: '2000', + phone: '555 5555' + }, + callback_url: 'http://example.com/update_callback', + callback_timeout: 2, + callback_version: '53.0', + funding_sources: { source: 'BML' }, + shipping_options: [ + { + default: true, + name: 'first one', + amount: 10 + } + ] } doc = Nokogiri::XML(@gateway.send(:build_setup_request, 'Sale', 10, all_options_enabled)) @@ -752,483 +815,493 @@ def test_structure_correct assert_equal [], schema.validate(sub_doc) end + def test_build_reference_transaction_sets_idempotency_key + request = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Authorization', 100, idempotency_key: 'idempotency_key')) + assert_equal 'idempotency_key', REXML::XPath.first(request, '//n2:DoReferenceTransactionRequestDetails/n2:MsgSubID').text + end + + def test_build_sale_or_authorization_request_sets_idempotency_key + request = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Authorization', 100, idempotency_key: 'idempotency_key')) + assert_equal 'idempotency_key', REXML::XPath.first(request, '//n2:DoExpressCheckoutPaymentRequestDetails/n2:MsgSubID').text + end + private def successful_create_billing_agreement_response - <<-RESPONSE - - - - - - - - - - OMGOMGOMG - - - - - - - 2013-02-28T16:34:47Z - Success - 8007ac99c51af - 72 - 5331358 - B-3R788221G4476823M - - - + <<~RESPONSE + + + + + + + + + + OMGOMGOMG + + + + + + + 2013-02-28T16:34:47Z + Success + 8007ac99c51af + 72 + 5331358 + B-3R788221G4476823M + + + RESPONSE end def successful_authorize_reference_transaction_response - <<-RESPONSE - - - - - - - - - OMGOMGOMG - - - - - - - 2011-05-23T21:36:32Z - Success - 4d6d3af55369b - 72 - 1863577 - - B-3R788221G4476823M - - 9R43552341412482K - - - mercht-pmt - instant - 2011-05-23T21:36:32Z - 190.00 - 5.81 - 0.00 - - Completed - none - none - Ineligible - None - - - - - + <<~RESPONSE + + + + + + + + + OMGOMGOMG + + + + + + + 2011-05-23T21:36:32Z + Success + 4d6d3af55369b + 72 + 1863577 + + B-3R788221G4476823M + + 9R43552341412482K + + + mercht-pmt + instant + 2011-05-23T21:36:32Z + 190.00 + 5.81 + 0.00 + + Completed + none + none + Ineligible + None + + + + + RESPONSE end def successful_reference_transaction_response - <<-RESPONSE - - - - - - - - - OMGOMGOMG - - - - - - - 2011-05-23T21:36:32Z - Success - 4d6d3af55369b - 72 - 1863577 - - B-3R788221G4476823M - - 9R43552341412482K - - - mercht-pmt - instant - 2011-05-23T21:36:32Z - 190.00 - 5.81 - 0.00 - - Completed - none - none - Ineligible - None - - - - - + <<~RESPONSE + + + + + + + + + OMGOMGOMG + + + + + + + 2011-05-23T21:36:32Z + Success + 4d6d3af55369b + 72 + 1863577 + + B-3R788221G4476823M + + 9R43552341412482K + + + mercht-pmt + instant + 2011-05-23T21:36:32Z + 190.00 + 5.81 + 0.00 + + Completed + none + none + Ineligible + None + + + + + RESPONSE end def successful_details_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2011-03-01T20:19:35Z - Success - 84aff0e17b6f - 62.0 - 1741654 - - EC-2XE90996XX9870316 - - buyer@jadedpallet.com - FWRVKNRRZ3WUC - verified - - - Fred - - Brooks - - - US - -
- Fred Brooks - 1 Infinite Loop - - Cupertino - CA - US - United States - 95014 - PayPal - Confirmed -
-
- 1230123 - 416-618-9984 - - 19.00 - 19.00 - 0.00 - 0.00 - 0.00 - - Fred Brooks - 1234 Penny Lane - - Jonsetown - NC - US - United States - 123-456-7890 - 23456 - - PayPal - - Confirmed - - - Shopify T-Shirt - 1 - 0.00 - 19.00 - - - 0.00 - 0.00 - false - This is a test note - - - - - - - Callback - false - true - 2.95 - default - - PaymentActionNotInitiated - -
-
-
-
+ <<~RESPONSE + + + + + + + + + + + + + + + + 2011-03-01T20:19:35Z + Success + 84aff0e17b6f + 62.0 + 1741654 + + EC-2XE90996XX9870316 + + buyer@jadedpallet.com + FWRVKNRRZ3WUC + verified + + + Fred + + Brooks + + + US + +
+ Fred Brooks + 1 Infinite Loop + + Cupertino + CA + US + United States + 95014 + PayPal + Confirmed +
+
+ 1230123 + 416-618-9984 + + 19.00 + 19.00 + 0.00 + 0.00 + 0.00 + + Fred Brooks + 1234 Penny Lane + + Jonsetown + NC + US + United States + 123-456-7890 + 23456 + + PayPal + + Confirmed + + + Shopify T-Shirt + 1 + 0.00 + 19.00 + + + 0.00 + 0.00 + false + This is a test note + + + + + + + Callback + false + true + 2.95 + default + + PaymentActionNotInitiated + +
+
+
+
RESPONSE end def successful_authorization_response - <<-RESPONSE - - - - - - - - - - - - - - - 2007-02-13T00:18:50Z - Success - 62450a4266d04 - 2.000000 - 1.0006 - - EC-6WS104951Y388951L - - 8B266858CH956410C - - - express-checkout - instant - 2007-02-13T00:18:48Z - 3.00 - 0.00 - - Pending - authorization - none - - - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2007-02-13T00:18:50Z + Success + 62450a4266d04 + 2.000000 + 1.0006 + + EC-6WS104951Y388951L + + 8B266858CH956410C + + + express-checkout + instant + 2007-02-13T00:18:48Z + 3.00 + 0.00 + + Pending + authorization + none + + + + + RESPONSE end def response_with_error - <<-RESPONSE - - - - - - - - - - - - - - - 2008-04-02T17:38:02Z - Failure - cdb720feada30 - - Shipping Address Invalid City State Postal Code - A match of the Shipping Address City, State, and Postal Code failed. - 10736 - Error - - 2.000000 - 543066 - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-04-02T17:38:02Z + Failure + cdb720feada30 + + Shipping Address Invalid City State Postal Code + A match of the Shipping Address City, State, and Postal Code failed. + 10736 + Error + + 2.000000 + 543066 + + + RESPONSE end def response_with_errors - <<-RESPONSE - - - - - - - - - - - - - - - 2008-04-02T17:38:02Z - Failure - cdb720feada30 - - Shipping Address Invalid City State Postal Code - A match of the Shipping Address City, State, and Postal Code failed. - 10736 - Error - - - Authentication/Authorization Failed - You do not have permissions to make this API call - 10002 - Error - - 2.000000 - 543066 - - - - RESPONSE + <<~RESPONSE + + + + + + + + + + + + + + + 2008-04-02T17:38:02Z + Failure + cdb720feada30 + + Shipping Address Invalid City State Postal Code + A match of the Shipping Address City, State, and Postal Code failed. + 10736 + Error + + + Authentication/Authorization Failed + You do not have permissions to make this API call + 10002 + Error + + 2.000000 + 543066 + + + + RESPONSE end def response_with_duplicate_errors - <<-RESPONSE - - - - - - - - - - - - - - - 2008-04-02T17:38:02Z - Failure - cdb720feada30 - - Shipping Address Invalid City State Postal Code - A match of the Shipping Address City, State, and Postal Code failed. - 10736 - Error - - - Shipping Address Invalid City State Postal Code - A match of the Shipping Address City, State, and Postal Code failed. - 10736 - Error - - 2.000000 - 543066 - - - - RESPONSE + <<~RESPONSE + + + + + + + + + + + + + + + 2008-04-02T17:38:02Z + Failure + cdb720feada30 + + Shipping Address Invalid City State Postal Code + A match of the Shipping Address City, State, and Postal Code failed. + 10736 + Error + + + Shipping Address Invalid City State Postal Code + A match of the Shipping Address City, State, and Postal Code failed. + 10736 + Error + + 2.000000 + 543066 + + + + RESPONSE end def successful_cancel_billing_agreement_response - <<-RESPONSE - 2013-06-04T16:24:31ZSuccessaecbb96bd4658726118442B-3RU433629T663020SWow. Amazing.Canceledduff@example.comVZNKJ2ZWMYK2EverifiedDuffJonesUS
PayPalNone
- RESPONSE + <<~RESPONSE + 2013-06-04T16:24:31ZSuccessaecbb96bd4658726118442B-3RU433629T663020SWow. Amazing.Canceledduff@example.comVZNKJ2ZWMYK2EverifiedDuffJonesUS
PayPalNone
+ RESPONSE end def failed_cancel_billing_agreement_response - <<-RESPONSE - 2013-06-04T16:25:06ZFailure5ec2d55830b40Billing - Agreement was cancelledBilling Agreement was cancelled10201Error726118442unverified
PayPalNone
- RESPONSE + <<~RESPONSE + 2013-06-04T16:25:06ZFailure5ec2d55830b40Billing + Agreement was cancelledBilling Agreement was cancelled10201Error726118442unverified
PayPalNone
+ RESPONSE end def successful_billing_agreement_details_response - <<-RESPONSE - - 2014-05-08T09:22:03Z - Successf84ed24f5bd6d - 7210918103 - B-6VE21702A47915521My active merchant custom description - Activeivan.rostovsky.xan@gmail.comSW3AR2WYZ3NJWverifiedIvanRostovsky - US -
- PayPal - None -
- RESPONSE + <<~RESPONSE + + 2014-05-08T09:22:03Z + Successf84ed24f5bd6d + 7210918103 + B-6VE21702A47915521My active merchant custom description + Activeivan.rostovsky.xan@gmail.comSW3AR2WYZ3NJWverifiedIvanRostovsky + US +
+ PayPal + None +
+ RESPONSE end def failure_billing_agreement_details_response - <<-RESPONSE + <<~RESPONSE PayPal None
- RESPONSE + RESPONSE end def pre_scrubbed - <<-TRANSCRIPT -activemerchant-cert-test_api1.example.comERDD3JRFU5H5DQXS - - 124 - - http://example.com/return - http://example.com/cancel - 0 - 0 - 0 - buyer@jadedpallet.com - - 5.00 - Stuff that you purchased, yo! - 230000 - Authorization - - - - - -2018-05-24T20:23:54ZSuccessb6dd2a043921b12446549960EC-7KR85820NC734104L - TRANSCRIPT + <<~TRANSCRIPT + activemerchant-cert-test_api1.example.comERDD3JRFU5H5DQXS + + 124 + + http://example.com/return + http://example.com/cancel + 0 + 0 + 0 + buyer@jadedpallet.com + + 5.00 + Stuff that you purchased, yo! + 230000 + Authorization + + + + + + 2018-05-24T20:23:54ZSuccessb6dd2a043921b12446549960EC-7KR85820NC734104L + TRANSCRIPT end def post_scrubbed - <<-TRANSCRIPT -[FILTERED][FILTERED] - - 124 - - http://example.com/return - http://example.com/cancel - 0 - 0 - 0 - buyer@jadedpallet.com - - 5.00 - Stuff that you purchased, yo! - 230000 - Authorization - - - - - -2018-05-24T20:23:54ZSuccessb6dd2a043921b12446549960EC-7KR85820NC734104L - TRANSCRIPT + <<~TRANSCRIPT + [FILTERED][FILTERED] + + 124 + + http://example.com/return + http://example.com/cancel + 0 + 0 + 0 + buyer@jadedpallet.com + + 5.00 + Stuff that you purchased, yo! + 230000 + Authorization + + + + + + 2018-05-24T20:23:54ZSuccessb6dd2a043921b12446549960EC-7KR85820NC734104L + TRANSCRIPT end end diff --git a/test/unit/gateways/paypal_test.rb b/test/unit/gateways/paypal_test.rb index a9ae07cbfc4..db9f5c760a0 100644 --- a/test/unit/gateways/paypal_test.rb +++ b/test/unit/gateways/paypal_test.rb @@ -8,19 +8,19 @@ def setup @amount = 100 @gateway = PaypalGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' + login: 'cody', + password: 'test', + pem: 'PEM' ) @credit_card = credit_card('4242424242424242') - @options = { :billing_address => address, :ip => '127.0.0.1' } - @recurring_required_fields = {:start_date => Date.today, :frequency => :Month, :period => 'Month', :description => 'A description'} + @options = { billing_address: address, ip: '127.0.0.1' } + @recurring_required_fields = { start_date: Date.today, frequency: :Month, period: 'Month', description: 'A description' } end def test_no_ip_address assert_raise(ArgumentError) do - @gateway.purchase(@amount, @credit_card, :billing_address => address) + @gateway.purchase(@amount, @credit_card, billing_address: address) end end @@ -63,7 +63,7 @@ def test_recurring_requires_period def test_update_recurring_requires_profile_id assert_raise(ArgumentError) do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.update_recurring(:amount => 100) + @gateway.update_recurring(amount: 100) end end end @@ -71,7 +71,7 @@ def test_update_recurring_requires_profile_id def test_cancel_recurring_requires_profile_id assert_raise(ArgumentError) do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.cancel_recurring(nil, :note => 'Note') + @gateway.cancel_recurring(nil, note: 'Note') end end end @@ -87,7 +87,7 @@ def test_status_recurring_requires_profile_id def test_suspend_recurring_requires_profile_id assert_raise(ArgumentError) do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.suspend_recurring(nil, :note => 'Note') + @gateway.suspend_recurring(nil, note: 'Note') end end end @@ -95,14 +95,14 @@ def test_suspend_recurring_requires_profile_id def test_reactivate_recurring_requires_profile_id assert_raise(ArgumentError) do assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.reactivate_recurring(nil, :note => 'Note') + @gateway.reactivate_recurring(nil, note: 'Note') end end end def test_successful_purchase_with_auth_signature - @gateway = PaypalGateway.new(:login => 'cody', :password => 'test', :pem => 'PEM', :auth_signature => 123) - expected_header = {'X-PP-AUTHORIZATION' => 123, 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} + @gateway = PaypalGateway.new(login: 'cody', password: 'test', pem: 'PEM', auth_signature: 123) + expected_header = { 'X-PP-AUTHORIZATION' => 123, 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11' } @gateway.expects(:ssl_post).with(anything, anything, expected_header).returns(successful_purchase_response) @gateway.expects(:add_credentials).never @@ -110,7 +110,7 @@ def test_successful_purchase_with_auth_signature end def test_successful_purchase_without_auth_signature - @gateway = PaypalGateway.new(:login => 'cody', :password => 'test', :pem => 'PEM') + @gateway = PaypalGateway.new(login: 'cody', password: 'test', pem: 'PEM') @gateway.expects(:ssl_post).returns(successful_purchase_response) @gateway.expects(:add_credentials) @@ -136,7 +136,7 @@ def test_successful_reference_purchase ref_id = response.authorization - gateway2 = PaypalGateway.new(:login => 'cody', :password => 'test', :pem => 'PEM') + gateway2 = PaypalGateway.new(login: 'cody', password: 'test', pem: 'PEM') gateway2.expects(:ssl_post).returns(successful_reference_purchase_response) assert response = gateway2.purchase(@amount, ref_id, @options) assert_instance_of Response, response @@ -157,7 +157,7 @@ def test_failed_purchase def test_descriptors_passed stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(soft_descriptor: 'Eggcellent', soft_descriptor_city: 'New York')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{Eggcellent}, data) assert_match(%r{New York}, data) end.respond_with(successful_purchase_response) @@ -198,19 +198,19 @@ def test_paypal_timeout_error def test_pem_file_accessor PaypalGateway.pem_file = '123456' - gateway = PaypalGateway.new(:login => 'test', :password => 'test') + gateway = PaypalGateway.new(login: 'test', password: 'test') assert_equal '123456', gateway.options[:pem] end def test_passed_in_pem_overrides_class_accessor PaypalGateway.pem_file = '123456' - gateway = PaypalGateway.new(:login => 'test', :password => 'test', :pem => 'Clobber') + gateway = PaypalGateway.new(login: 'test', password: 'test', pem: 'Clobber') assert_equal 'Clobber', gateway.options[:pem] end def test_ensure_options_are_transferred_to_express_instance PaypalGateway.pem_file = '123456' - gateway = PaypalGateway.new(:login => 'test', :password => 'password') + gateway = PaypalGateway.new(login: 'test', password: 'password') express = gateway.express assert_instance_of PaypalExpressGateway, express assert_equal 'test', express.options[:login] @@ -219,11 +219,11 @@ def test_ensure_options_are_transferred_to_express_instance end def test_supported_countries - assert_equal ['US'], PaypalGateway.supported_countries + assert_equal %w[CA NZ GB US], PaypalGateway.supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover], PaypalGateway.supported_cardtypes + assert_equal %i[visa master american_express discover], PaypalGateway.supported_cardtypes end def test_button_source @@ -260,10 +260,12 @@ def test_button_source_via_credentials_with_no_application_id end def test_item_total_shipping_handling_and_tax_not_included_unless_all_are_present - xml = @gateway.send(:build_sale_or_authorization_request, 'Authorization', @amount, @credit_card, - :tax => @amount, - :shipping => @amount, - :handling => @amount + xml = @gateway.send( + :build_sale_or_authorization_request, + 'Authorization', @amount, @credit_card, + tax: @amount, + shipping: @amount, + handling: @amount ) doc = REXML::Document.new(xml) @@ -271,11 +273,15 @@ def test_item_total_shipping_handling_and_tax_not_included_unless_all_are_presen end def test_item_total_shipping_handling_and_tax - xml = @gateway.send(:build_sale_or_authorization_request, 'Authorization', @amount, @credit_card, - :tax => @amount, - :shipping => @amount, - :handling => @amount, - :subtotal => 200 + xml = @gateway.send( + :build_sale_or_authorization_request, + 'Authorization', + @amount, + @credit_card, + tax: @amount, + shipping: @amount, + handling: @amount, + subtotal: 200 ) doc = REXML::Document.new(xml) @@ -284,19 +290,19 @@ def test_item_total_shipping_handling_and_tax def test_should_use_test_certificate_endpoint gateway = PaypalGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' - ) + login: 'cody', + password: 'test', + pem: 'PEM' + ) assert_equal PaypalGateway::URLS[:test][:certificate], gateway.send(:endpoint_url) end def test_should_use_live_certificate_endpoint gateway = PaypalGateway.new( - :login => 'cody', - :password => 'test', - :pem => 'PEM' - ) + login: 'cody', + password: 'test', + pem: 'PEM' + ) gateway.expects(:test?).returns(false) assert_equal PaypalGateway::URLS[:live][:certificate], gateway.send(:endpoint_url) @@ -304,20 +310,20 @@ def test_should_use_live_certificate_endpoint def test_should_use_test_signature_endpoint gateway = PaypalGateway.new( - :login => 'cody', - :password => 'test', - :signature => 'SIG' - ) + login: 'cody', + password: 'test', + signature: 'SIG' + ) assert_equal PaypalGateway::URLS[:test][:signature], gateway.send(:endpoint_url) end def test_should_use_live_signature_endpoint gateway = PaypalGateway.new( - :login => 'cody', - :password => 'test', - :signature => 'SIG' - ) + login: 'cody', + password: 'test', + signature: 'SIG' + ) gateway.expects(:test?).returns(false) assert_equal PaypalGateway::URLS[:live][:signature], gateway.send(:endpoint_url) @@ -325,7 +331,7 @@ def test_should_use_live_signature_endpoint def test_should_raise_argument_when_credentials_not_present assert_raises(ArgumentError) do - PaypalGateway.new(:login => 'cody', :password => 'test') + PaypalGateway.new(login: 'cody', password: 'test') end end @@ -372,14 +378,14 @@ def test_authentication_failed_response def test_amount_format_for_jpy_currency @gateway.expects(:ssl_post).with(anything, regexp_matches(/n2:OrderTotal currencyID=.JPY.>1<\/n2:OrderTotal>/), {}).returns(successful_purchase_response) - response = @gateway.purchase(100, @credit_card, @options.merge(:currency => 'JPY')) + response = @gateway.purchase(100, @credit_card, @options.merge(currency: 'JPY')) assert response.success? end def test_successful_create_profile @gateway.expects(:ssl_post).returns(successful_create_profile_paypal_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, @credit_card, :description => 'some description', :start_date => Time.now, :frequency => 12, :period => 'Month') + @gateway.recurring(@amount, @credit_card, description: 'some description', start_date: Time.now, frequency: 12, period: 'Month') end assert_instance_of Response, response assert response.success? @@ -391,7 +397,7 @@ def test_successful_create_profile def test_failed_create_profile @gateway.expects(:ssl_post).returns(failed_create_profile_paypal_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.recurring(@amount, @credit_card, :description => 'some description', :start_date => Time.now, :frequency => 12, :period => 'Month') + @gateway.recurring(@amount, @credit_card, description: 'some description', start_date: Time.now, frequency: 12, period: 'Month') end assert_instance_of Response, response assert !response.success? @@ -401,42 +407,42 @@ def test_failed_create_profile end def test_update_recurring_delegation - @gateway.expects(:build_change_profile_request).with('I-G7A2FF8V75JY', :amount => 200) + @gateway.expects(:build_change_profile_request).with('I-G7A2FF8V75JY', amount: 200) @gateway.stubs(:commit) assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + @gateway.update_recurring(profile_id: 'I-G7A2FF8V75JY', amount: 200) end end def test_update_recurring_response @gateway.expects(:ssl_post).returns(successful_update_recurring_payment_profile_response) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + @gateway.update_recurring(profile_id: 'I-G7A2FF8V75JY', amount: 200) end assert response.success? end def test_cancel_recurring_delegation - @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Cancel', :note => 'A Note').returns(:cancel_request) + @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Cancel', note: 'A Note').returns(:cancel_request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :cancel_request) assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.cancel_recurring('I-G7A2FF8V75JY', :note => 'A Note') + @gateway.cancel_recurring('I-G7A2FF8V75JY', note: 'A Note') end end def test_suspend_recurring_delegation - @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Suspend', :note => 'A Note').returns(:request) + @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Suspend', note: 'A Note').returns(:request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :request) assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.suspend_recurring('I-G7A2FF8V75JY', :note => 'A Note') + @gateway.suspend_recurring('I-G7A2FF8V75JY', note: 'A Note') end end def test_reactivate_recurring_delegation - @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Reactivate', :note => 'A Note').returns(:request) + @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Reactivate', note: 'A Note').returns(:request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :request) assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.reactivate_recurring('I-G7A2FF8V75JY', :note => 'A Note') + @gateway.reactivate_recurring('I-G7A2FF8V75JY', note: 'A Note') end end @@ -458,17 +464,17 @@ def test_status_recurring_response end def test_bill_outstanding_amoung_delegation - @gateway.expects(:build_bill_outstanding_amount).with('I-G7A2FF8V75JY', :amount => 400).returns(:request) + @gateway.expects(:build_bill_outstanding_amount).with('I-G7A2FF8V75JY', amount: 400).returns(:request) @gateway.expects(:commit).with('BillOutstandingAmount', :request) assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', amount: 400) end end def test_bill_outstanding_amoung_response @gateway.expects(:ssl_post).returns(successful_bill_outstanding_amount) response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do - @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', amount: 400) end assert response.success? end @@ -476,20 +482,20 @@ def test_bill_outstanding_amoung_response def test_mass_pay_transfer_recipient_types stub_comms do @gateway.transfer 1000, 'fred@example.com' - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r{ReceiverType}, data end.respond_with(successful_purchase_response) stub_comms do - @gateway.transfer 1000, 'fred@example.com', :receiver_type => 'EmailAddress' - end.check_request do |endpoint, data, headers| + @gateway.transfer 1000, 'fred@example.com', receiver_type: 'EmailAddress' + end.check_request do |_endpoint, data, _headers| assert_match %r{EmailAddress}, data assert_match %r{fred@example\.com}, data end.respond_with(successful_purchase_response) stub_comms do - @gateway.transfer 1000, 'fred@example.com', :receiver_type => 'UserID' - end.check_request do |endpoint, data, headers| + @gateway.transfer 1000, 'fred@example.com', receiver_type: 'UserID' + end.check_request do |_endpoint, data, _headers| assert_match %r{UserID}, data assert_match %r{fred@example\.com}, data end.respond_with(successful_purchase_response) @@ -553,7 +559,7 @@ def test_supports_scrubbing? def test_includes_cvv_tag stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{CVV2}, data) end.respond_with(successful_purchase_response) end @@ -562,20 +568,20 @@ def test_blank_cvv_not_sent @credit_card.verification_value = nil stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{CVV2}, data) end.respond_with(successful_purchase_response) @credit_card.verification_value = ' ' stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{CVV2}, data) end.respond_with(successful_purchase_response) end def test_card_declined - ['15005', '10754', '10752', '10759', '10761', '15002', '11084'].each do |error_code| + %w[15005 10754 10752 10759 10761 15002 11084].each do |error_code| @gateway.expects(:ssl_request).returns(response_with_error_code(error_code)) response = @gateway.purchase(@amount, @credit_card, @options) @@ -611,10 +617,35 @@ def test_error_code_with_no_mapping_returns_standardized_processing_error assert_equal(:processing_error, response.error_code) end + def test_3ds_version_1_request + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option(version: '1.0.2', xid: 'xid'))) + end.check_request do |_endpoint, data, _headers| + assert_match %r{124}, data + assert_match %r{Y}, data + assert_match %r{cavv}, data + assert_match %r{eci}, data + assert_match %r{xid}, data + end.respond_with(successful_purchase_response) + end + + def test_3ds_version_2_request + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option(version: '2.1.0', ds_transaction_id: 'ds_transaction_id'))) + end.check_request do |_endpoint, data, _headers| + assert_match %r{214.0}, data + assert_match %r{Y}, data + assert_match %r{cavv}, data + assert_match %r{eci}, data + assert_match %r{2.1.0}, data + assert_match %r{ds_transaction_id}, data + end.respond_with(successful_purchase_response) + end + private def pre_scrubbed - <<-PRE_SCRUBBED + <<~PRE_SCRUBBED opening connection to api-3t.sandbox.paypal.com:443... opened starting SSL for api-3t.sandbox.paypal.com:443... @@ -636,7 +667,7 @@ def pre_scrubbed end def post_scrubbed - <<-POST_SCRUBBED + <<~POST_SCRUBBED opening connection to api-3t.sandbox.paypal.com:443... opened starting SSL for api-3t.sandbox.paypal.com:443... @@ -658,762 +689,775 @@ def post_scrubbed end def successful_purchase_response - <<-RESPONSE - - - - - - - - - - - - - - - 2008-01-06T23:41:25Z - Success - fee61882e6f47 - 2.000000 - 1.0006 - 3.00 - X - M - 62U664727W5914806 - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-01-06T23:41:25Z + Success + fee61882e6f47 + 2.000000 + 1.0006 + 3.00 + X + M + 62U664727W5914806 + + + RESPONSE end def successful_reference_purchase_response - <<-RESPONSE - - - - - - - - - - - - - - - 2008-01-06T23:41:25Z - Success - fee61882e6f47 - 2.000000 - 1.0006 - 3.00 - X - M - 62U664727W5915049 - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-01-06T23:41:25Z + Success + fee61882e6f47 + 2.000000 + 1.0006 + 3.00 + X + M + 62U664727W5915049 + + + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - - - - - - - - - - - - - 2008-01-06T23:41:25Z - Failure - fee61882e6f47 - 2.000000 - 1.0006 - 3.00 - X - M - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-01-06T23:41:25Z + Failure + fee61882e6f47 + 2.000000 + 1.0006 + 3.00 + X + M + + + RESPONSE end def successful_zero_dollar_auth_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:14:48Z - SuccessWithWarning - e33ce283dd3d3 - - Credit card verified. - This card authorization verification is not a payment transaction. - 10574 - Warning - - 72 - 11660982 - 0.00 - XM - 86D41672SH9764158 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:14:48Z + SuccessWithWarning + e33ce283dd3d3 + + Credit card verified. + This card authorization verification is not a payment transaction. + 10574 + Warning + + 72 + 11660982 + 0.00 + XM + 86D41672SH9764158 + + + RESPONSE end def failed_zero_dollar_auth_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:25:51Z - Failure - 5dda14853a55d - - Invalid Data - This transaction cannot be processed. Please enter a valid credit card number and type. - 10527 - Error - - 72 - 11660982 - 0.00 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:25:51Z + Failure + 5dda14853a55d + + Invalid Data + This transaction cannot be processed. Please enter a valid credit card number and type. + 10527 + Error + + 72 + 11660982 + 0.00 + + + RESPONSE end def successful_one_dollar_auth_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:39:40Z - Success - 814bcb1ced3d - 72 - 11660982 - 1.00 - X - M - 521683708W7313256 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:39:40Z + Success + 814bcb1ced3d + 72 + 11660982 + 1.00 + X + M + 521683708W7313256 + + + RESPONSE end def failed_one_dollar_auth_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:47:18Z - Failure - f3ab2d6fc76e4 - - Invalid Data - This transaction cannot be processed. Please enter a valid credit card number and type. - 10527 - Error - - 72 - 11660982 - 1.00 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:47:18Z + Failure + f3ab2d6fc76e4 + + Invalid Data + This transaction cannot be processed. Please enter a valid credit card number and type. + 10527 + Error + + 72 + 11660982 + 1.00 + + + RESPONSE end def response_with_error_code(error_code) - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:47:18Z - Failure - f3ab2d6fc76e4 - - Invalid Data - This transaction cannot be processed. Please enter a valid credit card number and type. - #{error_code} - Error - - 72 - 11660982 - 1.00 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:47:18Z + Failure + f3ab2d6fc76e4 + + Invalid Data + This transaction cannot be processed. Please enter a valid credit card number and type. + #{error_code} + Error + + 72 + 11660982 + 1.00 + + + RESPONSE end def successful_void_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:39:41Z - Success - 5c184c86a25bc - 72 - 11624049 - 521683708W7313256 - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:39:41Z + Success + 5c184c86a25bc + 72 + 11624049 + 521683708W7313256 + + + RESPONSE end def failed_void_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2014-06-27T18:50:11Z - Failure - e99444d222eaf - - Transaction refused because of an invalid argument. See additional error messages for details. - The transaction id is not valid - 10004 - Error - - 72 - 11624049 - - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2014-06-27T18:50:11Z + Failure + e99444d222eaf + + Transaction refused because of an invalid argument. See additional error messages for details. + The transaction id is not valid + 10004 + Error + + 72 + 11624049 + + + + RESPONSE end def paypal_timeout_error_response - <<-RESPONSE - - - - - - - - - - - - - - - SOAP-ENV:Server - Internal error - Timeout processing request - - - + <<~RESPONSE + + + + + + + + + + + + + + + SOAP-ENV:Server + Internal error + Timeout processing request + + + RESPONSE end def successful_reauthorization_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2007-03-04T23:34:42Z - Success - e444ddb7b3ed9 - 2.000000 - 1.0006 - 1TX27389GX108740X - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2007-03-04T23:34:42Z + Success + e444ddb7b3ed9 + 2.000000 + 1.0006 + 1TX27389GX108740X + + + RESPONSE end def successful_with_warning_reauthorization_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2007-03-04T23:34:42Z - SuccessWithWarning - e444ddb7b3ed9 - 2.000000 - 1.0006 - 1TX27389GX108740X - - - + <<~RESPONSE + + + + + + + + + + + + + + + + 2007-03-04T23:34:42Z + SuccessWithWarning + e444ddb7b3ed9 + 2.000000 + 1.0006 + 1TX27389GX108740X + + + RESPONSE end def fraud_review_response - <<-RESPONSE - - - - - - - - - An5ns1Kso7MWUdW4ErQKJJJ4qi4-Azffuo82oMt-Cv9I8QTOs-lG5sAv - - - - - - - 2008-07-04T19:27:39Z - SuccessWithWarning - 205d8397e7ed - - Payment Pending your review in Fraud Management Filters - Payment Pending your review in Fraud Management Filters - 11610 - Warning - - 50.0 - 623197 - 1500.00 - X - M - 5V117995ER6796022 - - - + <<~RESPONSE + + + + + + + + + An5ns1Kso7MWUdW4ErQKJJJ4qi4-Azffuo82oMt-Cv9I8QTOs-lG5sAv + + + + + + + 2008-07-04T19:27:39Z + SuccessWithWarning + 205d8397e7ed + + Payment Pending your review in Fraud Management Filters + Payment Pending your review in Fraud Management Filters + 11610 + Warning + + 50.0 + 623197 + 1500.00 + X + M + 5V117995ER6796022 + + + RESPONSE end def failed_capture_due_to_pending_fraud_review - <<-RESPONSE - - - - - - - - - - - - - - - 2008-07-04T21:45:35Z - Failure - 32a3855bd35b7 - - Transaction must be accepted in Fraud Management Filters before capture. - - 11612 - Error - - 52.000000 - 588340 - - - none - none - None - none - none - - - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-07-04T21:45:35Z + Failure + 32a3855bd35b7 + + Transaction must be accepted in Fraud Management Filters before capture. + + 11612 + Error + + 52.000000 + 588340 + + + none + none + None + none + none + + + + + RESPONSE end def authentication_failed_response - <<-RESPONSE - - - - - - - - - - - - - - - 2008-08-12T19:40:59Z - Failure - b874109bfd11 - - Authentication/Authorization Failed - You do not have permissions to make this API call - 10002 - Error - - 52.000000 - 628921 - - - + <<~RESPONSE + + + + + + + + + + + + + + + 2008-08-12T19:40:59Z + Failure + b874109bfd11 + + Authentication/Authorization Failed + You do not have permissions to make this API call + 10002 + Error + + 52.000000 + 628921 + + + RESPONSE end def successful_create_profile_paypal_response - <<-RESPONSE - - - - - - - - - - - - - 2011-08-28T18:59:40Z - Success - 4b8eaecc084b - 59.0 - 2085867 - - I-G7A2FF8V75JY - ActiveProfile - - - - + <<~RESPONSE + + + + + + + + + + + + + 2011-08-28T18:59:40Z + Success + 4b8eaecc084b + 59.0 + 2085867 + + I-G7A2FF8V75JY + ActiveProfile + + + + RESPONSE end def failed_create_profile_paypal_response - <<-RESPONSE - - - - - - - - - - - - - - - - 2011-08-28T18:59:40Z - This is a test failure - 4b8eaecc084b - 59.0 - 2085867 - - I-G7A2FF8V75JY - ActiveProfile - - - - - " - RESPONSE + <<~RESPONSE + + + + + + + + + + + + + + + + 2011-08-28T18:59:40Z + This is a test failure + 4b8eaecc084b + 59.0 + 2085867 + + I-G7A2FF8V75JY + ActiveProfile + + + + + " + RESPONSE end def successful_details_response - <<-RESPONSE - - - - - - - - - - - - - - - 2011-03-01T20:19:35Z - Success - 84aff0e17b6f - 62.0 - 1741654 - - EC-2XE90996XX9870316 - - buyer@jadedpallet.com - FWRVKNRRZ3WUC - verified - - - Fred - - Brooks - - - US - -
- Fred Brooks - 1 Infinite Loop - - Cupertino - CA - US - United States - 95014 - PayPal - Confirmed -
-
- 1230123 - 416-618-9984 - - 19.00 - 19.00 - 0.00 - 0.00 - 0.00 - - Fred Brooks - 1234 Penny Lane - - Jonsetown - NC - US - United States - 123-456-7890 - 23456 - - PayPal - - Confirmed - - - Shopify T-Shirt - 1 - 0.00 - 19.00 - - - 0.00 - 0.00 - false - - - - - - PaymentActionNotInitiated -
-
-
-
+ <<~RESPONSE + + + + + + + + + + + + + + + 2011-03-01T20:19:35Z + Success + 84aff0e17b6f + 62.0 + 1741654 + + EC-2XE90996XX9870316 + + buyer@jadedpallet.com + FWRVKNRRZ3WUC + verified + + + Fred + + Brooks + + + US + +
+ Fred Brooks + 1 Infinite Loop + + Cupertino + CA + US + United States + 95014 + PayPal + Confirmed +
+
+ 1230123 + 416-618-9984 + + 19.00 + 19.00 + 0.00 + 0.00 + 0.00 + + Fred Brooks + 1234 Penny Lane + + Jonsetown + NC + US + United States + 123-456-7890 + 23456 + + PayPal + + Confirmed + + + Shopify T-Shirt + 1 + 0.00 + 19.00 + + + 0.00 + 0.00 + false + + + + + + PaymentActionNotInitiated +
+
+
+
RESPONSE end def successful_update_recurring_payment_profile_response - <<-RESPONSE - - - 2012-03-19T20:30:02Z - Success - 9ad0f67c1127c - 72 - 2649250 - - I-M1L3RX91DPDD - - - + <<~RESPONSE + + + 2012-03-19T20:30:02Z + Success + 9ad0f67c1127c + 72 + 2649250 + + I-M1L3RX91DPDD + + + RESPONSE end def successful_manage_recurring_payment_profile_response - <<-RESPONSE - - - 2012-03-19T20:41:03ZSuccess3c02ea62138c4722649250I-M1L3RX91DPDD + <<~RESPONSE + + + 2012-03-19T20:41:03ZSuccess3c02ea62138c4722649250I-M1L3RX91DPDD RESPONSE end def successful_bill_outstanding_amount - <<-RESPONSE - 2012-03-19T20:50:49ZSuccess2c1cbe06d718e722649250I-M1L3RX91DPDD + <<~RESPONSE + 2012-03-19T20:50:49ZSuccess2c1cbe06d718e722649250I-M1L3RX91DPDD RESPONSE end def successful_get_recurring_payments_profile_response - <<-RESPONSE - 2012-03-19T21:34:40ZSuccess6f24b53c49232722649250I-M1L3RX91DPDDCancelledProfileA descriptionNoAutoBill0Ryan BatesPayPalUnconfirmed2012-03-19T11:00:00ZMonth101.230.000.001-11.231Visa357612013unverifiedRyanBates
PayPalUnconfirmed
00
Month101.230.000.000.000.000.000.001970-01-01T00:00:00Z
+ <<~RESPONSE + 2012-03-19T21:34:40ZSuccess6f24b53c49232722649250I-M1L3RX91DPDDCancelledProfileA descriptionNoAutoBill0Ryan BatesPayPalUnconfirmed2012-03-19T11:00:00ZMonth101.230.000.001-11.231Visa357612013unverifiedRyanBates
PayPalUnconfirmed
00
Month101.230.000.000.000.000.000.001970-01-01T00:00:00Z
RESPONSE end + + def three_d_secure_option(version:, xid: nil, ds_transaction_id: nil) + { + three_d_secure: { + authentication_response_status: 'Y', + eci: 'eci', + cavv: 'cavv', + xid: xid, + ds_transaction_id: ds_transaction_id, + version: version + } + } + end end diff --git a/test/unit/gateways/paysafe_test.rb b/test/unit/gateways/paysafe_test.rb new file mode 100644 index 00000000000..2d7c73d90ec --- /dev/null +++ b/test/unit/gateways/paysafe_test.rb @@ -0,0 +1,385 @@ +require 'test_helper' + +class PaysafeTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PaysafeGateway.new(username: 'username', password: 'password', account_id: 'account_id') + @credit_card = credit_card + @mastercard = credit_card('5454545454545454', brand: 'master') + @amount = 100 + + @options = { + billing_address: address, + merchant_descriptor: { + dynamic_descriptor: 'Store Purchase' + } + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'cddbd29d-4983-4719-983a-c6a862895781', response.authorization + assert response.test? + end + + def test_successful_purchase_with_mastercard_3ds2 + mc_three_d_secure_2_options = { + currency: 'EUR', + three_d_secure: { + eci: 0, + cavv: 'AAABBhkXYgAAAAACBxdiENhf7A+=', + version: '2.1.0', + ds_transaction_id: 'a3a721f3-b6fa-4cb5-84ea-c7b5c39890a2' + } + } + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @mastercard, mc_three_d_secure_2_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"threeDSecureVersion":"2.1.0"/, data) + assert_match(/"directoryServerTransactionId":"a3a721f3-b6fa-4cb5-84ea-c7b5c39890a2"/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_airline_details + airline_details = { + airline_travel_details: { + passenger_name: 'Joe Smith', + departure_date: '2021-11-30', + origin: 'SXF', + computerized_reservation_system: 'DATS', + ticket: { + ticket_number: 9876789, + is_restricted_ticket: false + }, + customer_reference_number: 107854099, + travel_agency: { + name: 'Sally Travel', + code: 'AGENTS' + }, + trip_legs: { + leg1: { + flight: { + carrier_code: 'LH', + flight_number: '344' + }, + service_class: 'F', + is_stop_over_allowed: true, + departure_date: '2021-11-30' + }, + leg2: { + flight: { + flight_number: '999' + }, + destination: 'SOF', + fare_basis: 'VMAY' + } + } + } + } + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, airline_details) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"airlineTravelDetails"/, data) + assert_match(/"computerizedReservationSystem":"DATS"/, data) + assert_match(/"tripLegs":{"leg1":{"flight":{"carrierCode":"LH"/, data) + assert_match(/"leg2":{"flight":{"flightNumber":"999"/, data) + assert_match(/"departureDate":"2021-11-30"/, data) + assert_match(/"travelAgency":{"name":"Sally Travel","code":"AGENTS"}/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_stored_credentials + stored_credential_options = { + stored_credential: { + initial_transaction: true, + reason_type: 'installment', + initiator: 'merchant' + } + } + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential_options)) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{"type":"RECURRING"}, data) + assert_match(%r{"occurrence":"INITIAL"}, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_funding_transaction + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ funding_transaction: 'SDW_WALLET_TRANSFER' })) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r("fundingTransaction":{"type":"SDW_WALLET_TRANSFER"}), data) + assert_match(%r("profile":{.+"merchantCustomerId"), data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '3022', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '3155d89c-dfff-49a2-9352-b531e69102f7', response.authorization + assert_equal 'COMPLETED', response.message + 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 '3009', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + auth = '3155d89c-dfff-49a2-9352-b531e69102f7' + + response = @gateway.capture(@amount, auth) + assert_success response + + assert_equal '6ee71dc2-00c0-4891-b226-ab741e63f43a', response.authorization + assert_equal 'PENDING', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '') + assert_failure response + + assert_equal '5023', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + auth = 'originaltransactionsauthorization' + + response = @gateway.refund(@amount, auth) + assert_success response + + assert_equal 'e86fe7c3-9d92-4149-89a9-fd2b3da95b05', response.authorization + assert_equal 'PENDING', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + auth = 'invalidauthorizationid' + + response = @gateway.refund(@amount, auth) + assert_failure response + + assert_equal '3407', response.error_code + assert_equal 'Error(s)- code:3407, message:The settlement referred to by the transaction response ID you provided cannot be found.', response.message + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + auth = '3155d89c-dfff-49a2-9352-b531e69102f7' + + response = @gateway.void(auth) + assert_success response + + assert_equal 'eb4d45ac-35ef-49e8-93d0-58b20a4c470e', response.authorization + assert_equal 'COMPLETED', response.message + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + + assert_equal '5023', response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal '493936', response.params['authCode'] + end + + def test_successful_store + profile_options = { + phone: '111-222-3456', + email: 'profile@memail.com', + date_of_birth: { + month: 1, + year: 1979, + day: 1 + } + } + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, profile_options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"holderName":"Longbob Longsen"/, data) + assert_match(/"dateOfBirth":{"year":1979,"month":1,"day":1}/, data) + end.respond_with(successful_store_response) + + assert_success response + end + + def test_merchant_ref_num_and_order_id + options = @options.merge({ order_id: '12345678' }) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"merchantRefNum":"12345678"/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + options = @options.merge({ order_id: '12345678', merchant_ref_num: '87654321' }) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"merchantRefNum":"87654321"/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_truncate_long_address_fields + 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 = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"street":"This is an extremely long address, it is unreasona"/, data) + assert_match(/"street2":"This is an extremely long address2, it is unreason"/, data) + assert_match(/"city":"Lake Chargoggagoggmanchauggagoggchaubuna"/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + ' + <- "POST /cardpayments/v1/accounts/1002158490/auths HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic cG1sZS03MTA1MjA6Qi1xYTItMC02MGY1YTg5MS0wLTMwMmMwMjE0NDkwZTdlYjliM2IxOWRlOTRlM2FkNjVhOTcxMGM4MTFmYjc4NzhiZTAyMTQxNzQwM2FiYjgyNmQ1NDg2MDdhZGQ3NTNjNmZhMjE0YjYxYmU5YTdj\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.test.paysafe.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"amount\":100,\"card\":{\"cardExpiry\":{\"month\":9,\"year\":2022},\"cardNum\":\"4107857757053670\",\"cvv\":\"123\"},\"billingDetails\":{\"street\":\"999 This Way Lane\",\"city\":\"Hereville\",\"state\":\"NC\",\"country\":\"FR\",\"zip\":\"98989\",\"phone\":\"999-9999999\"},\"profile\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Store Purchase\",\"phone\":\"999-8887777\"},\"settleWithAuth\":true,\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: envoy\r\n" + -> "Content-Length: 1324\r\n" + -> "X-Applicationuid: GUID=f26c8e32-e8a4-435d-a288-703750d8a941\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Envoy-Upstream-Service-Time: 144\r\n" + -> "Expires: Tue, 10 Aug 2021 16:56:06 GMT\r\n" + -> "Cache-Control: max-age=0, no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Date: Tue, 10 Aug 2021 16:56:06 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: WLSESSIONID=g3Ew_iMEkM_6zDo4AisqhBlyuLi5UbyaVrkLVx3hmj-gOgZeKDl9!-2065395402!6582410; path=/; secure; HttpOnly\r\n" + -> "\r\n" + reading 1324 bytes... + -> "{\"id\":\"f26c8e32-e8a4-435d-a288-703750d8a941\",\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\",\"txnTime\":\"2021-08-10T16:56:06Z\",\"status\":\"COMPLETED\",\"amount\":100,\"settleWithAuth\":true,\"preAuth\":false,\"availableToSettle\":0,\"card\":{\"type\":\"VI\",\"lastDigits\":\"3670\",\"cardExpiry\":{\"month\":9,\"year\":2022}},\"authCode\":\"976920\",\"profile\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\"},\"billingDetails\":{\"street\":\"999 This Way Lane\",\"city\":\"Hereville\",\"state\":\"NC\",\"country\":\"FR\",\"zip\":\"98989\",\"phone\":\"999-9999999\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Store Purchase\",\"phone\":\"999-8887777\"},\"visaAdditionalAuthData\":{},\"currencyCode\":\"EUR\",\"avsResponse\":\"MATCH\",\"cvvVerification\":\"MATCH\",\"settlements\":[{\"id\":\"f26c8e32-e8a4-435d-a288-703750d8a941\",\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\",\"txnTime\":\"2021-08-10T16:56:06Z\",\"status\":\"PENDING\",\"amount\":100,\"availableToRefund\":100,\"links\":[{\"rel\":\"self\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/f26c8e32-e8a4-435d-a288-703750d8a941\"}]}],\"links\":[{\"rel\":\"settlement\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/f26c8e32-e8a4-435d-a288-703750d8a941\"},{\"rel\":\"self\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/f26c8e32-e8a4-435d-a288-703750d8a941\"}]}" + ' + end + + def post_scrubbed + ' + <- "POST /cardpayments/v1/accounts/1002158490/auths HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: api.test.paysafe.com\r\nContent-Length: 443\r\n\r\n" + <- "{\"amount\":100,\"card\":{\"cardExpiry\":{\"month\":9,\"year\":2022},\"cardNum\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\"},\"billingDetails\":{\"street\":\"999 This Way Lane\",\"city\":\"Hereville\",\"state\":\"NC\",\"country\":\"FR\",\"zip\":\"98989\",\"phone\":\"999-9999999\"},\"profile\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Store Purchase\",\"phone\":\"999-8887777\"},\"settleWithAuth\":true,\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: envoy\r\n" + -> "Content-Length: 1324\r\n" + -> "X-Applicationuid: GUID=f26c8e32-e8a4-435d-a288-703750d8a941\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Envoy-Upstream-Service-Time: 144\r\n" + -> "Expires: Tue, 10 Aug 2021 16:56:06 GMT\r\n" + -> "Cache-Control: max-age=0, no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Date: Tue, 10 Aug 2021 16:56:06 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: WLSESSIONID=g3Ew_iMEkM_6zDo4AisqhBlyuLi5UbyaVrkLVx3hmj-gOgZeKDl9!-2065395402!6582410; path=/; secure; HttpOnly\r\n" + -> "\r\n" + reading 1324 bytes... + -> "{\"id\":\"f26c8e32-e8a4-435d-a288-703750d8a941\",\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\",\"txnTime\":\"2021-08-10T16:56:06Z\",\"status\":\"COMPLETED\",\"amount\":100,\"settleWithAuth\":true,\"preAuth\":false,\"availableToSettle\":0,\"card\":{\"type\":\"VI\",\"lastDigits\":\"3670\",\"cardExpiry\":{\"month\":9,\"year\":2022}},\"authCode\":\"976920\",\"profile\":{\"firstName\":\"Longbob\",\"lastName\":\"Longsen\"},\"billingDetails\":{\"street\":\"999 This Way Lane\",\"city\":\"Hereville\",\"state\":\"NC\",\"country\":\"FR\",\"zip\":\"98989\",\"phone\":\"999-9999999\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Store Purchase\",\"phone\":\"999-8887777\"},\"visaAdditionalAuthData\":{},\"currencyCode\":\"EUR\",\"avsResponse\":\"MATCH\",\"cvvVerification\":\"MATCH\",\"settlements\":[{\"id\":\"f26c8e32-e8a4-435d-a288-703750d8a941\",\"merchantRefNum\":\"08498355c7f86bf096dc5f3fe77bd1da\",\"txnTime\":\"2021-08-10T16:56:06Z\",\"status\":\"PENDING\",\"amount\":100,\"availableToRefund\":100,\"links\":[{\"rel\":\"self\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/f26c8e32-e8a4-435d-a288-703750d8a941\"}]}],\"links\":[{\"rel\":\"settlement\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/f26c8e32-e8a4-435d-a288-703750d8a941\"},{\"rel\":\"self\",\"href\":\"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/f26c8e32-e8a4-435d-a288-703750d8a941\"}]}" + ' + end + + def successful_purchase_response + '{"id":"cddbd29d-4983-4719-983a-c6a862895781","merchantRefNum":"c9b2ad852a1a37c1cc5c39b741be7484","txnTime":"2021-08-10T18:25:40Z","status":"COMPLETED","amount":100,"settleWithAuth":true,"preAuth":false,"availableToSettle":0,"card":{"type":"VI","lastDigits":"3670","cardExpiry":{"month":9,"year":2022}},"authCode":"544454","profile":{"firstName":"Longbob","lastName":"Longsen"},"billingDetails":{"street":"999 This Way Lane","city":"Hereville","state":"NC","country":"FR","zip":"98989","phone":"999-9999999"},"merchantDescriptor":{"dynamicDescriptor":"Store Purchase","phone":"999-8887777"},"visaAdditionalAuthData":{},"currencyCode":"EUR","avsResponse":"MATCH","cvvVerification":"MATCH","settlements":[{"id":"cddbd29d-4983-4719-983a-c6a862895781","merchantRefNum":"c9b2ad852a1a37c1cc5c39b741be7484","txnTime":"2021-08-10T18:25:40Z","status":"PENDING","amount":100,"availableToRefund":100,"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/cddbd29d-4983-4719-983a-c6a862895781"}]}],"links":[{"rel":"settlement","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/cddbd29d-4983-4719-983a-c6a862895781"},{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/cddbd29d-4983-4719-983a-c6a862895781"}]}' + end + + def failed_purchase_response + '{"id":"c671d488-3f27-46f1-b0a7-2123e4e68f35","merchantRefNum":"12b616e548d7b866c6a61e6d585a762b","error":{"code":"3022","message":"The card has been declined due to insufficient funds.","links":[{"rel":"errorinfo","href":"https://developer.paysafe.com/en/rest-api/cards/test-and-go-live/card-errors/#ErrorCode3022"}]},"riskReasonCode":[1059],"settleWithAuth":true,"cvvVerification":"MATCH","links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/c671d488-3f27-46f1-b0a7-2123e4e68f35"}]}' + end + + def successful_authorize_response + '{"id":"3155d89c-dfff-49a2-9352-b531e69102f7","merchantRefNum":"8b3c5142fdce91299e76a39d89e32bc1","txnTime":"2021-08-10T18:31:26Z","status":"COMPLETED","amount":100,"settleWithAuth":false,"preAuth":false,"availableToSettle":100,"card":{"type":"VI","lastDigits":"3670","cardExpiry":{"month":9,"year":2022}},"authCode":"659078","profile":{"firstName":"Longbob","lastName":"Longsen"},"billingDetails":{"street":"999 This Way Lane","city":"Hereville","state":"NC","country":"FR","zip":"98989","phone":"999-9999999"},"merchantDescriptor":{"dynamicDescriptor":"Store Purchase","phone":"999-8887777"},"visaAdditionalAuthData":{},"currencyCode":"EUR","avsResponse":"MATCH","cvvVerification":"MATCH","links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/3155d89c-dfff-49a2-9352-b531e69102f7"}]}' + end + + def failed_authorize_response + '{"id":"bde4a254-7df9-462e-8de1-bfaa205d299a","merchantRefNum":"939b24ab14825b7d365842957dbda683","error":{"code":"3009","message":"Your request has been declined by the issuing bank.","links":[{"rel":"errorinfo","href":"https://developer.paysafe.com/en/rest-api/cards/test-and-go-live/card-errors/#ErrorCode3009"}]},"riskReasonCode":[1100],"settleWithAuth":false,"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/auths/bde4a254-7df9-462e-8de1-bfaa205d299a"}]}' + end + + def successful_capture_response + '{"id":"6ee71dc2-00c0-4891-b226-ab741e63f43a","merchantRefNum":"09bf1e741aa1485ceae9b779e550f929","txnTime":"2021-08-10T18:31:26Z","status":"PENDING","amount":100,"availableToRefund":100,"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/settlements/6ee71dc2-00c0-4891-b226-ab741e63f43a"}]}' + end + + def failed_capture_response + '{"error":{"code":"5023","message":"Request method POST not supported","links":[{"rel":"errorinfo","href":"https://developer.paysafe.com/en/rest-api/cards/test-and-go-live/card-errors/#ErrorCode5023"}]}}' + end + + def successful_verify_response + '{"id":"2b48475a-e3e7-47b0-8d84-66a331db9945","merchantRefNum":"fe95dee377466d9a54550c228227c5be","txnTime":"2021-08-18T20:06:55Z","status":"COMPLETED","card":{"type":"VI","lastDigits":"3670","cardExpiry":{"month":9,"year":2022}},"authCode":"493936","profile":{"firstName":"Longbob","lastName":"Longsen"},"billingDetails":{"street":"999 This Way Lane","city":"Hereville","state":"NC","country":"FR","zip":"98989","phone":"999-9999999"},"merchantDescriptor":{"dynamicDescriptor":"Test","phone":"123-1234123"},"visaAdditionalAuthData":{},"currencyCode":"EUR","avsResponse":"NOT_PROCESSED","cvvVerification":"MATCH","links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/verifications/2b48475a-e3e7-47b0-8d84-66a331db9945"}]}' + end + + def successful_refund_response + '{"id":"e86fe7c3-9d92-4149-89a9-fd2b3da95b05","merchantRefNum":"b8e04a4ff196b20f8aea42558aec8cbd","txnTime":"2021-08-11T13:40:59Z","status":"PENDING","amount":100,"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/refunds/e86fe7c3-9d92-4149-89a9-fd2b3da95b05"}]}' + end + + def failed_refund_response + '{"id":"0c498606-3690-4b24-a083-0f8a75b8e043","merchantRefNum":"2becb71485cb38c862d2589decce99df","error":{"code":"3407","message":"The settlement referred to by the transaction response ID you provided cannot be found.","links":[{"rel":"errorinfo","href":"https://developer.paysafe.com/en/rest-api/cards/test-and-go-live/card-errors/#ErrorCode3407"}]},"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/refunds/0c498606-3690-4b24-a083-0f8a75b8e043"}]}' + end + + def successful_void_response + '{"id":"eb4d45ac-35ef-49e8-93d0-58b20a4c470e","merchantRefNum":"dbeb1095b191c16715052d4bcc98b42d","txnTime":"2021-08-10T18:35:05Z","status":"COMPLETED","amount":100,"links":[{"rel":"self","href":"https://api.test.paysafe.com/cardpayments/v1/accounts/1002158490/voidauths/eb4d45ac-35ef-49e8-93d0-58b20a4c470e"}]}' + end + + def failed_void_response + '{"error":{"code":"5023","message":"Request method POST not supported","links":[{"rel":"errorinfo","href":"https://developer.paysafe.com/en/rest-api/cards/test-and-go-live/card-errors/#ErrorCode5023"}]}}' + end + + def successful_store_response + '{"id":"bd4e8c66-b023-4b38-b499-bc6d447d1466","status":"ACTIVE","merchantCustomerId":"965d3aff71fb93343ee48513","locale":"en_US","firstName":"Longbob","lastName":"Longsen","dateOfBirth":{"year":1979,"month":1,"day":1},"paymentToken":"PnCQ1xyGCB4sOEq","phone":"111-222-3456","email":"profile@memail.com","addresses":[],"cards":[{"status":"ACTIVE","id":"77685b40-e953-4999-a161-d13b46a8232a","cardBin":"411111","lastDigits":"1111","cardExpiry":{"year":2022,"month":9},"holderName":"Longbob Longsen","cardType":"VI","cardCategory":"CREDIT","paymentToken":"Ct0RrnyIs4lizeH","defaultCardIndicator":true}]}' + end +end diff --git a/test/unit/gateways/payscout_test.rb b/test/unit/gateways/payscout_test.rb index f5cb64b3a3f..6fc666505f1 100644 --- a/test/unit/gateways/payscout_test.rb +++ b/test/unit/gateways/payscout_test.rb @@ -3,17 +3,17 @@ class PayscoutTest < Test::Unit::TestCase def setup @gateway = PayscoutGateway.new( - :username => 'xxx', - :password => 'xxx' - ) + username: 'xxx', + password: 'xxx' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -230,7 +230,7 @@ def test_add_currency_from_money def test_add_invoice post = {} - options = {description: 'Order Description', order_id: '123'} + options = { description: 'Order Description', order_id: '123' } @gateway.send(:add_invoice, post, options) assert_equal 'Order Description', post[:orderdescription] @@ -281,25 +281,25 @@ def test_parse end def test_message_from_for_approved_response - assert_equal 'The transaction has been approved', @gateway.send(:message_from, {'response' => '1'}) + assert_equal 'The transaction has been approved', @gateway.send(:message_from, { 'response' => '1' }) end def test_message_from_for_declined_response - assert_equal 'The transaction has been declined', @gateway.send(:message_from, {'response' => '2'}) + assert_equal 'The transaction has been declined', @gateway.send(:message_from, { 'response' => '2' }) end def test_message_from_for_failed_response - assert_equal 'Error message', @gateway.send(:message_from, {'response' => '3', 'responsetext' => 'Error message'}) + assert_equal 'Error message', @gateway.send(:message_from, { 'response' => '3', 'responsetext' => 'Error message' }) end def test_success - assert @gateway.send(:success?, {'response' => '1'}) - refute @gateway.send(:success?, {'response' => '2'}) - refute @gateway.send(:success?, {'response' => '3'}) + assert @gateway.send(:success?, { 'response' => '1' }) + refute @gateway.send(:success?, { 'response' => '2' }) + refute @gateway.send(:success?, { 'response' => '3' }) end def test_post_data - parameters = {param1: 'value1', param2: 'value2'} + parameters = { param1: 'value1', param2: 'value2' } result = @gateway.send(:post_data, 'auth', parameters) assert_match 'username=xxx', result diff --git a/test/unit/gateways/paystation_test.rb b/test/unit/gateways/paystation_test.rb index c32716c1383..ccb6a3525a1 100644 --- a/test/unit/gateways/paystation_test.rb +++ b/test/unit/gateways/paystation_test.rb @@ -4,17 +4,17 @@ class PaystationTest < Test::Unit::TestCase include CommStub def setup @gateway = PaystationGateway.new( - :paystation_id => 'some_id_number', - :gateway_id => 'another_id_number' - ) + paystation_id: 'some_id_number', + gateway_id: 'another_id_number' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :customer => 'Joe Bloggs, Customer ID #56', - :description => 'Store Purchase' + order_id: '1', + customer: 'Joe Bloggs, Customer ID #56', + description: 'Store Purchase' } end @@ -41,7 +41,7 @@ def test_unsuccessful_request def test_successful_store @gateway.expects(:ssl_post).returns(successful_store_response) - assert response = @gateway.store(@credit_card, @options.merge(:token => 'justatest1310263135')) + assert response = @gateway.store(@credit_card, @options.merge(token: 'justatest1310263135')) assert_success response assert response.test? @@ -74,7 +74,7 @@ def test_successful_authorization def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, '0009062250-01', @options.merge(:credit_card_verification => 123)) + assert response = @gateway.capture(@amount, '0009062250-01', @options.merge(credit_card_verification: 123)) assert_success response end @@ -89,7 +89,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/0008813023-01/, data) end.respond_with(successful_refund_response) @@ -425,5 +425,4 @@ def pre_scrubbed def post_scrubbed 'pstn_pi=609035&pstn_gi=PUSHPAY&pstn_2p=t&pstn_nr=t&pstn_df=yymm&pstn_ms=a755b9c84a530aee91dc3077f57294b0&pstn_mo=Store+Purchase&pstn_mr=&pstn_am=&pstn_cu=NZD&pstn_cn=[FILTERED]&pstn_ct=visa&pstn_ex=1305&pstn_cc=[FILTERED]&pstn_tm=T&paystation=_empty' end - end diff --git a/test/unit/gateways/payu_in_test.rb b/test/unit/gateways/payu_in_test.rb index 876bae34162..b2d3c981cc9 100644 --- a/test/unit/gateways/payu_in_test.rb +++ b/test/unit/gateways/payu_in_test.rb @@ -16,7 +16,7 @@ def setup } end - def assert_parameter(parameter, expected_value, data, options={}) + def assert_parameter(parameter, expected_value, data, options = {}) assert (data =~ %r{(?:^|&)#{parameter}=([^&]*)(?:&|$)}), "Unable to find #{parameter} in #{data}" value = CGI.unescape($1 || '') case expected_value @@ -25,9 +25,7 @@ def assert_parameter(parameter, expected_value, data, options={}) else assert_equal expected_value.to_s, value, "#{parameter} value does not match expected" end - if options[:length] - assert_equal options[:length], value.length, "#{parameter} value of #{value} is the wrong length" - end + assert_equal options[:length], value.length, "#{parameter} value of #{value} is the wrong length" if options[:length] end def test_successful_purchase @@ -205,7 +203,7 @@ def test_input_constraint_cleanup phone: ('a-' + ('1' * 51)) } ) - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| case endpoint when /_payment/ assert_parameter('txnid', /^a/, data, length: 30) @@ -293,7 +291,7 @@ def test_failed_purchase def test_successful_refund response = stub_comms do @gateway.refund(100, 'abc') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_parameter('command', 'cancel_refund_transaction', data) assert_parameter('var1', 'abc', data) assert_parameter('var2', /./, data) @@ -345,108 +343,108 @@ def test_invalid_json private def pre_scrubbed - <<-PRE_SCRUBBED -opening connection to test.payu.in:443... -opened -starting SSL for test.payu.in:443... -SSL established -<- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" -<- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=5123456789012346&ccvv=123&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" --> "HTTP/1.1 200 OK\r\n" --> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" --> "Server: Apache\r\n" --> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" --> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" --> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" --> "Pragma: no-cache\r\n" --> "Vary: Accept-Encoding\r\n" --> "Content-Length: 691\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 691 bytes... --> "" --> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" -read 691 bytes -Conn close -opening connection to test.payu.in:443... -opened -starting SSL for test.payu.in:443... -SSL established -<- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" -<- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" --> "HTTP/1.1 200 OK\r\n" --> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" --> "Server: Apache\r\n" --> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" --> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" --> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" --> "Pragma: no-cache\r\n" --> "Vary: Accept-Encoding\r\n" --> "Content-Length: 1012\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 1012 bytes... --> "" --> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=f25e4f9ea802050c23423966d35adc54046f651f0d9a2b837b49c75f964d1fa7\"}" -read 1012 bytes -Conn close + <<~PRE_SCRUBBED + opening connection to test.payu.in:443... + opened + starting SSL for test.payu.in:443... + SSL established + <- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" + <- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=5123456789012346&ccvv=123&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" + -> "Server: Apache\r\n" + -> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" + -> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" + -> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" + -> "Pragma: no-cache\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Content-Length: 691\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 691 bytes... + -> "" + -> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" + read 691 bytes + Conn close + opening connection to test.payu.in:443... + opened + starting SSL for test.payu.in:443... + SSL established + <- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" + <- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" + -> "Server: Apache\r\n" + -> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" + -> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" + -> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" + -> "Pragma: no-cache\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Content-Length: 1012\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 1012 bytes... + -> "" + -> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=f25e4f9ea802050c23423966d35adc54046f651f0d9a2b837b49c75f964d1fa7\"}" + read 1012 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-POST_SCRUBBED -opening connection to test.payu.in:443... -opened -starting SSL for test.payu.in:443... -SSL established -<- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" -<- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=[FILTERED]&ccvv=[FILTERED]&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" --> "HTTP/1.1 200 OK\r\n" --> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" --> "Server: Apache\r\n" --> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" --> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" --> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" --> "Pragma: no-cache\r\n" --> "Vary: Accept-Encoding\r\n" --> "Content-Length: 691\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 691 bytes... --> "" --> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"[FILTERED]\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"[FILTERED]\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" -read 691 bytes -Conn close -opening connection to test.payu.in:443... -opened -starting SSL for test.payu.in:443... -SSL established -<- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" -<- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=[FILTERED]&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=[FILTERED]&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" --> "HTTP/1.1 200 OK\r\n" --> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" --> "Server: Apache\r\n" --> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" --> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" --> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" --> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" --> "Pragma: no-cache\r\n" --> "Vary: Accept-Encoding\r\n" --> "Content-Length: 1012\r\n" --> "Connection: close\r\n" --> "Content-Type: text/html; charset=UTF-8\r\n" --> "\r\n" -reading 1012 bytes... --> "" --> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=[FILTERED]\"}" -read 1012 bytes -Conn close + <<~POST_SCRUBBED + opening connection to test.payu.in:443... + opened + starting SSL for test.payu.in:443... + SSL established + <- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" + <- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=[FILTERED]&ccvv=[FILTERED]&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" + -> "Server: Apache\r\n" + -> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" + -> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" + -> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" + -> "Pragma: no-cache\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Content-Length: 691\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 691 bytes... + -> "" + -> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"[FILTERED]\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"[FILTERED]\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" + read 691 bytes + Conn close + opening connection to test.payu.in:443... + opened + starting SSL for test.payu.in:443... + SSL established + <- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" + <- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=[FILTERED]&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=[FILTERED]&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" + -> "Server: Apache\r\n" + -> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" + -> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" + -> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" + -> "Pragma: no-cache\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Content-Length: 1012\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "\r\n" + reading 1012 bytes... + -> "" + -> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=[FILTERED]\"}" + read 1012 bytes + Conn close POST_SCRUBBED end diff --git a/test/unit/gateways/payu_latam_test.rb b/test/unit/gateways/payu_latam_test.rb index 68e76b9ef2e..664db4e5490 100644 --- a/test/unit/gateways/payu_latam_test.rb +++ b/test/unit/gateways/payu_latam_test.rb @@ -12,10 +12,14 @@ def setup @pending_card = credit_card('4097440000000004', verification_value: '222', first_name: 'PENDING', last_name: '') @no_cvv_visa_card = credit_card('4097440000000004', verification_value: ' ') @no_cvv_amex_card = credit_card('4097440000000004', verification_value: ' ', brand: 'american_express') + @cabal_credit_card = credit_card('5896570000000004', verification_value: '123', first_name: 'APPROVED', last_name: '', brand: 'cabal') + @maestro_card = credit_card('6759000000000000005', verification_value: '123', first_name: 'APPROVED', brand: 'maestro') + @codensa_card = credit_card('5907120000000009', verification_value: '123', first_name: 'APPROVED', brand: 'maestro') @options = { dni_number: '5415668464654', dni_type: 'TI', + merchant_buyer_id: '1', currency: 'ARS', order_id: generate_unique_id, description: 'Active Merchant Transaction', @@ -51,11 +55,20 @@ def test_successful_purchase def test_successful_purchase_with_specified_language stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"language":"es"/, data) end.respond_with(successful_purchase_response) end + def test_successful_purchase_with_cabal_card + @gateway.expects(:ssl_post).returns(successful_purchase_with_cabal_response) + + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -65,6 +78,24 @@ def test_failed_purchase assert_equal 'DECLINED', response.params['transactionResponse']['state'] end + def test_failed_purchase_correct_message_when_payment_network_response_error_present + @gateway.expects(:ssl_post).returns(failed_purchase_response_when_payment_network_response_error_expected) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'CONTACT_THE_ENTITY | Contactar con entidad emisora', response.message + assert_equal '290', response.error_code + assert_equal 'Contactar con entidad emisora', response.params['transactionResponse']['paymentNetworkResponseErrorMessage'] + + @gateway.expects(:ssl_post).returns(failed_purchase_response_when_payment_network_response_error_not_expected) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'CONTACT_THE_ENTITY', response.message + assert_equal '51', response.error_code + assert_nil response.params['transactionResponse']['paymentNetworkResponseErrorMessage'] + end + def test_successful_authorize @gateway.expects(:ssl_post).returns(successful_authorize_response) @@ -77,11 +108,20 @@ def test_successful_authorize def test_successful_authorize_with_specified_language stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"language":"es"/, data) end.respond_with(successful_purchase_response) end + def test_successful_authorize_with_cabal_card + @gateway.expects(:ssl_post).returns(successful_authorize_with_cabal_response) + + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(pending_authorize_response) @@ -102,8 +142,19 @@ def test_pending_refund def test_pending_refund_with_specified_language stub_comms do @gateway.refund(@amount, '7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options.merge(language: 'es')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"language":"es"/, data) + assert_match(/"type":"REFUND"/, data) + end.respond_with(pending_refund_response) + end + + def test_partial_refund + stub_comms do + @gateway.refund(2000, '7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options.merge(partial_refund: true)) + end.check_request do |_endpoint, data, _headers| + assert_match(/"type":"PARTIAL_REFUND"/, data) + assert_match(/"TX_VALUE"/, data) + assert_match(/"value":"20.00"/, data) end.respond_with(pending_refund_response) end @@ -126,7 +177,7 @@ def test_successful_void def test_successful_void_with_specified_language stub_comms do @gateway.void('7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options.merge(language: 'es')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"language":"es"/, data) end.respond_with(successful_void_response) end @@ -142,11 +193,44 @@ def test_failed_void def test_successful_purchase_with_dni_number stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"dniNumber":"5415668464654"/, data) end.respond_with(successful_purchase_response) end + def test_successful_purchase_with_merchant_buyer_id + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"merchantBuyerId":"1"/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_phone_number + options = @options.merge(billing_address: {}, shipping_address: { phone_number: 5555555555 }) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_equal 5555555555, JSON.parse(data)['transaction']['order']['buyer']['contactPhone'] + end.respond_with(successful_purchase_response) + end + + def test_card_type_maestro_maps_to_mastercard + stub_comms do + @gateway.purchase(@amount, @maestro_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'MASTERCARD', JSON.parse(data)['transaction']['paymentMethod'] + end.respond_with(successful_purchase_response) + end + + def test_card_type_codensa + stub_comms do + @gateway.purchase(@amount, @codensa_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'CODENSA', JSON.parse(data)['transaction']['paymentMethod'] + end.respond_with(successful_purchase_response) + end + def test_verify_good_credentials @gateway.expects(:ssl_post).returns(credentials_are_legit_response) assert @gateway.verify_credentials @@ -158,8 +242,8 @@ def test_verify_bad_credentials end def test_request_using_visa_card_with_no_cvv - @gateway.expects(:ssl_post).with { |url, body, headers| - body.match '"securityCode":"000"' + @gateway.expects(:ssl_post).with { |_url, body, _headers| + body =~ /"securityCode":"000"/ body.match '"processWithoutCvv2":true' }.returns(successful_purchase_response) response = @gateway.purchase(@amount, @no_cvv_visa_card, @options) @@ -169,8 +253,8 @@ def test_request_using_visa_card_with_no_cvv end def test_request_using_amex_card_with_no_cvv - @gateway.expects(:ssl_post).with { |url, body, headers| - body.match '"securityCode":"0000"' + @gateway.expects(:ssl_post).with { |_url, body, _headers| + body =~ /"securityCode":"0000"/ body.match '"processWithoutCvv2":true' }.returns(successful_purchase_response) response = @gateway.purchase(@amount, @no_cvv_amex_card, @options) @@ -180,8 +264,8 @@ def test_request_using_amex_card_with_no_cvv end def test_request_passes_cvv_option - @gateway.expects(:ssl_post).with { |url, body, headers| - body.match '"securityCode":"777"' + @gateway.expects(:ssl_post).with { |_url, body, _headers| + body =~ /"securityCode":"777"/ !body.match '"processWithoutCvv2"' }.returns(successful_purchase_response) options = @options.merge(cvv: '777') @@ -202,7 +286,7 @@ def test_successful_capture def test_successful_capture_with_specified_language stub_comms do @gateway.capture(@amount, '4000|authorization', @options.merge(language: 'es')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/"language":"es"/, data) end.respond_with(successful_purchase_response) end @@ -210,7 +294,7 @@ def test_successful_capture_with_specified_language def test_successful_partial_capture stub_comms do @gateway.capture(@amount - 1, '4000|authorization', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_equal '39.99', JSON.parse(data)['transaction']['additionalValues']['TX_VALUE']['value'] end.respond_with(successful_purchase_response) end @@ -232,27 +316,56 @@ def test_partial_buyer_hash_info state: 'SP', country: 'BR', zip: '01019-030', - phone: '(11)756312345' + phone_number: '(11)756312345' ), buyer: { name: 'Jorge Borges', dni_number: '5415668464456', + merchant_buyer_id: '1', email: 'axaxaxas@mlo.org' } } stub_comms do @gateway.purchase(@amount, @credit_card, @options.update(options_buyer)) - end.check_request do |endpoint, data, headers| - assert_match(/\"buyer\":{\"fullName\":\"Jorge Borges\",\"dniNumber\":\"5415668464456\",\"dniType\":null,\"emailAddress\":\"axaxaxas@mlo.org\",\"contactPhone\":\"7563126\",\"shippingAddress\":{\"street1\":\"Calle 200\",\"street2\":\"N107\",\"city\":\"Sao Paulo\",\"state\":\"SP\",\"country\":\"BR\",\"postalCode\":\"01019-030\",\"phone\":\"\(11\)756312345\"}}/, data) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"buyer\":{\"fullName\":\"Jorge Borges\",\"dniNumber\":\"5415668464456\",\"dniType\":null,\"merchantBuyerId\":\"1\",\"emailAddress\":\"axaxaxas@mlo.org\",\"contactPhone\":\"7563126\",\"shippingAddress\":{\"street1\":\"Calle 200\",\"street2\":\"N107\",\"city\":\"Sao Paulo\",\"state\":\"SP\",\"country\":\"BR\",\"postalCode\":\"01019-030\",\"phone\":\"\(11\)756312345\"}}/, data) end.respond_with(successful_purchase_response) end def test_buyer_fields_default_to_payer stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_match(/\"buyer\":{\"fullName\":\"APPROVED\",\"dniNumber\":\"5415668464654\",\"dniType\":\"TI\",\"emailAddress\":\"username@domain.com\",\"contactPhone\":\"7563126\"/, data) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"buyer\":{\"fullName\":\"APPROVED\",\"dniNumber\":\"5415668464654\",\"dniType\":\"TI\",\"merchantBuyerId\":\"1\",\"emailAddress\":\"username@domain.com\",\"contactPhone\":\"7563126\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_request_with_blank_billing_address_fields + options = { + dni_number: '5415668464654', + dni_type: 'TI', + merchant_buyer_id: '1', + currency: 'ARS', + order_id: generate_unique_id, + description: 'Active Merchant Transaction', + billing_address: address( + address1: 'Viamonte', + address2: nil, + city: 'Plata', + state: 'Buenos Aires', + country: '', + zip: '64000', + phone: '7563126' + ) + } + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"merchantBuyerId":"1"/, data) + assert_match(/"street2":null/, data) + refute_match(/"country"/, data) end.respond_with(successful_purchase_response) end @@ -277,7 +390,7 @@ def test_brazil_required_fields state: 'SP', country: 'BR', zip: '01019-030', - phone: '(11)756312633' + phone_number: '(11)756312633' ), buyer: { cnpj: '32593371000110' @@ -286,7 +399,7 @@ def test_brazil_required_fields stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options.update(options_brazil)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\"cnpj\":\"32593371000110\"/, data) end.respond_with(successful_purchase_response) end @@ -312,7 +425,7 @@ def test_colombia_required_fields state: 'Bogota DC', country: 'CO', zip: '01019-030', - phone: '(11)756312633' + phone_number: '(11)756312633' ), tx_tax: '3193', tx_tax_return_base: '16806' @@ -320,7 +433,7 @@ def test_colombia_required_fields stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options.update(options_colombia)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\"additionalValues\":{\"TX_VALUE\":{\"value\":\"40.00\",\"currency\":\"COP\"},\"TX_TAX\":{\"value\":0,\"currency\":\"COP\"},\"TX_TAX_RETURN_BASE\":{\"value\":0,\"currency\":\"COP\"}}/, data) end.respond_with(successful_purchase_response) end @@ -346,18 +459,28 @@ def test_mexico_required_fields state: 'Jalisco', country: 'MX', zip: '01019-030', - phone: '(11)756312633' + phone_number: '(11)756312633' ), birth_date: '1985-05-25' } stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options.update(options_mexico)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\"birthdate\":\"1985-05-25\"/, data) end.respond_with(successful_purchase_response) end + def test_extra_parameters_fields + stub_comms(@gateway) do + @gateway.purchase(@amount, @credit_card, @options.merge({ extra_1: '123456', extra_2: 'abcdef', extra_3: 'testing' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/\"EXTRA1\":\"123456\"/, data) + assert_match(/\"EXTRA2\":\"abcdef\"/, data) + assert_match(/\"EXTRA3\":\"testing\"/, data) + end.respond_with(successful_purchase_response) + end + def test_scrub assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -451,6 +574,37 @@ def successful_purchase_response RESPONSE end + def successful_purchase_with_cabal_response + <<-RESPONSE + { + "code":"SUCCESS", + "error":null, + "transactionResponse": { + "orderId":846449068, + "transactionId":"34fa1616-f16c-4474-98dc-6163cb05f6d1", + "state":"APPROVED", + "paymentNetworkResponseCode":null, + "paymentNetworkResponseErrorMessage":null, + "trazabilityCode":"00000000", + "authorizationCode":"00000000", + "pendingReason":null, + "responseCode":"APPROVED", + "errorCode":null, + "responseMessage":null, + "transactionDate":null, + "transactionTime":null, + "operationDate":1567524354749, + "referenceQuestionnaire":null, + "extraParameters": { + "PAYMENT_WAY_ID":"28", + "BANK_REFERENCED_CODE":"DEBIT" + }, + "additionalInfo":null + } + } + RESPONSE + end + def failed_purchase_response <<-RESPONSE { @@ -478,6 +632,60 @@ def failed_purchase_response RESPONSE end + def failed_purchase_response_when_payment_network_response_error_expected + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 7354347, + "transactionId": "15b6cec0-9eec-4564-b6b9-c846b868203e", + "state": "DECLINED", + "paymentNetworkResponseCode": "290", + "paymentNetworkResponseErrorMessage": "Contactar con entidad emisora", + "trazabilityCode": null, + "authorizationCode": null, + "pendingReason": null, + "responseCode": "CONTACT_THE_ENTITY", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": null, + "referenceQuestionnaire": null, + "extraParameters": null + } + } + RESPONSE + end + + def failed_purchase_response_when_payment_network_response_error_not_expected + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 7354347, + "transactionId": "15b6cec0-9eec-4564-b6b9-c846b868203e", + "state": "DECLINED", + "paymentNetworkResponseCode": "51", + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": null, + "authorizationCode": null, + "pendingReason": null, + "responseCode": "CONTACT_THE_ENTITY", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": null, + "referenceQuestionnaire": null, + "extraParameters": null + } + } + RESPONSE + end + def successful_authorize_response <<-RESPONSE { @@ -504,6 +712,38 @@ def successful_authorize_response RESPONSE end + def successful_authorize_with_cabal_response + <<-RESPONSE + { + "code":"SUCCESS", + "error":null, + "transactionResponse": { + "orderId":846449155, + "transactionId":"c15e6015-87c2-4db9-9100-894bf5564330", + "state":"APPROVED", + "paymentNetworkResponseCode":null, + "paymentNetworkResponseErrorMessage":null, + "trazabilityCode":"00000000", + "authorizationCode":"00000000", + "pendingReason":null, + "responseCode":"APPROVED", + "errorCode":null, + "responseMessage":null, + "transactionDate":null, + "transactionTime":null, + "operationDate":1567524806987, + "referenceQuestionnaire":null, + "extraParameters": { + "PAYMENT_WAY_ID":"28", + "BANK_REFERENCED_CODE":"DEBIT" + }, + "additionalInfo":null + } + } + + RESPONSE + end + def pending_authorize_response <<-RESPONSE { diff --git a/test/unit/gateways/payway_dot_com_test.rb b/test/unit/gateways/payway_dot_com_test.rb new file mode 100644 index 00000000000..ee46f650366 --- /dev/null +++ b/test/unit/gateways/payway_dot_com_test.rb @@ -0,0 +1,1478 @@ +require 'test_helper' + +class PaywayDotComTest < Test::Unit::TestCase + def setup + @gateway = PaywayDotComGateway.new( + login: 'sprerestwsdev', + password: 'sprerestwsdev1!', + company_id: '3', + source_id: '67' + ) + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '5000', response.message[0, 4] + assert_equal '0987654321', response.params['cardTransaction']['authorizationCode'] + assert_equal '', response.error_code + assert response.test? + assert_equal 'Z', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + 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 '5035', response.message[0, 4] + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + auth_only = @gateway.authorize(103, @credit_card, @options) + assert_success auth_only + assert_equal '5000', auth_only.message[0, 4] + end + + def test_successful_authorize_and_capture + @gateway.expects(:ssl_request).returns(successful_authorize_and_capture_response) + + auth = @gateway.authorize(104, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_request).returns(successful_capture_response) + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal '5000', capture.message[0, 4] + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(105, @credit_card, @options) + assert_failure response + assert_equal '5035', response.message[0, 4] + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(106, '') + assert_failure response + assert_equal '5025', response.message[0, 4] + end + + def test_successful_credit + @gateway.expects(:ssl_request).returns(successful_credit_response) + + credit = @gateway.credit(107, @credit_card, @options) + assert_success credit + assert_equal '5000', credit.message[0, 4] + end + + def test_failed_credit + @gateway.expects(:ssl_request).returns(failed_credit_response) + + response = @gateway.credit(108, @credit_card, @options) + assert_failure response + assert_equal '5035', response.message[0, 4] + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_auth_for_void_response) + + auth = @gateway.authorize(109, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_request).returns(successful_void_auth_response) + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal '5025', response.message[0, 4] + end + + def test_successful_void_of_sale + @gateway.expects(:ssl_request).returns(successful_sale_for_void_response) + + sale = @gateway.purchase(110, @credit_card, @options) + assert_success sale + + @gateway.expects(:ssl_request).returns(successful_void_sale_response) + + assert void = @gateway.void(sale.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_successful_void_of_credit + @gateway.expects(:ssl_request).returns(successful_credit_for_void_response) + + credit = @gateway.credit(111, @credit_card, @options) + assert_success credit + + @gateway.expects(:ssl_request).returns(successful_credit_void_response) + + assert void = @gateway.void(credit.authorization, @options) + assert_success void + assert_equal '5000', void.message[0, 4] + end + + def test_invalid_login + gateway = PaywayDotComGateway.new(login: '', password: '', company_id: '', source_id: '') + gateway.expects(:ssl_request).returns(failed_invalid_login_response) + + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{5001}, response.message[0, 4] + end + + def test_missing_source_id + error = assert_raises(ArgumentError) { PaywayDotComGateway.new(login: '', password: '', company_id: '') } + assert_equal 'Missing required parameter: source_id', error.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + def test_scrub_failed_purchase + assert @gateway.supports_scrubbing? + assert_equal post_scrubbed_failed_purchase, @gateway.scrub(pre_scrubbed_failed_purchase) + end + + private + + def pre_scrubbed + %q( + opening connection to devedgilpayway.net:443... + opened + starting SSL for devedgilpayway.net:443... + SSL established, protocol: TLSv1.2, cipher: AES256-GCM-SHA384 + <- "POST /PaywayWS/Payment/CreditCard HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: devedgilpayway.net\r\nContent-Length: 423\r\n\r\n" + <- "{\"userName\":\"sprerestwsdev\",\"password\":\"sprerestwsdev1!\",\"companyId\":\"3\",\"accountInputMode\":\"primaryAccountNumber\",\"cardAccount\":{\"accountNumber\":\"4000100011112224\",\"fsv\":\"737\",\"expirationDate\":\"092022\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"address\":\"456 My Street Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"phone\":\"(555)555-5555\"},\"cardTransaction\":{\"amount\":\"100\",\"eciType\":\"1\",\"sourceId\":\"67\"},\"request\":\"sale\"}" + -> "HTTP/1.1 200 \r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials\r\n" + -> "Content-Encoding: application/json\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 2051\r\n" + -> "Date: Mon, 22 Mar 2021 19:06:00 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 2051 bytes... + -> "{\n \"cardAccount\": {\n \"accountNotes1\": \"\",\n \"accountNotes2\": \"\",\n \"accountNotes3\": \"\",\n \"accountNumber\": \"400010******2224\",\n \"account_number_masked\": \"400010******2224\",\n \"address\": \"456 My Street Apt 1\",\n \"auLastUpdate\": \"1999-01-01 00:00:00-05\",\n \"auUpdateType\": 0,\n \"cardType\": 1,\n \"city\": \"Ottawa\",\n \"commercialCardType\": 0,\n \"divisionId\": 7,\n \"email\": \"\",\n \"expirationDate\": \"0922\",\n \"firstFour\": \"4000\",\n \"firstName\": \"Jim\",\n \"fsv\": \"123\",\n \"inputMode\": 1,\n \"lastFour\": \"2224\",\n \"lastName\": \"Smith\",\n \"lastUsed\": \"1999-01-01 00:00:00-05\",\n \"middleName\": \"\",\n \"onlinePaymentCryptogram\": \"\",\n \"p2peInput\": \"\",\n \"paywayToken\": 10163736,\n \"phone\": \"5555555555\",\n \"state\": \"ON\",\n \"status\": 2,\n \"zip\": \"K1C2N6\"\n },\n \"cardTransaction\": {\n \"addressVerificationResults\": \"\",\n \"amount\": 100,\n \"authorizationCode\": \"0987654321\",\n \"authorizedTime\": \"2021-03-22 00:00:00-04\",\n \"capturedTime\": \"2021-03-22 15:06:00\",\n \"cbMode\": 2,\n \"eciType\": 1,\n \"fraudSecurityResults\": \"\",\n \"fsvIndicator\": \"\",\n \"name\": \"6720210322150600930144\",\n \"pfpstatus\": 3601,\n \"pfpstatusString\": \"PFP Not Enabled\",\n \"processorErrorMessage\": \"\",\n \"processorOrderId\": \"\",\n \"processorRecurringAdvice\": \"\",\n \"processorResponseDate\": \"\",\n \"processorResultCode\": \"\",\n \"processorSequenceNumber\": 0,\n \"processorSoftDescriptor\": \"\",\n \"referenceNumber\": \"123456\",\n \"resultCode\": 0,\n \"sessionToken_string\": \"0\",\n \"settledTime\": \"1999-01-01 00:00\",\n \"sourceId\": 67,\n \"status\": 4,\n \"tax\": 0,\n \"testResultAVS\": \"\",\n \"testResultFSV\": \"\",\n \"transactionNotes1\": \"\",\n \"transactionNotes2\": \"\",\n \"transactionNotes3\": \"\"\n },\n \"paywayCode\": \"5000\",\n \"paywayMessage\": \"\"\n}" + read 2051 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to devedgilpayway.net:443... + opened + starting SSL for devedgilpayway.net:443... + SSL established, protocol: TLSv1.2, cipher: AES256-GCM-SHA384 + <- "POST /PaywayWS/Payment/CreditCard HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: devedgilpayway.net\r\nContent-Length: 423\r\n\r\n" + <- "{\"userName\":\"sprerestwsdev\",\"password\":\"[FILTERED]\",\"companyId\":\"3\",\"accountInputMode\":\"primaryAccountNumber\",\"cardAccount\":{\"accountNumber\":\"[FILTERED]\",\"fsv\":\"[FILTERED]\",\"expirationDate\":\"092022\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"address\":\"456 My Street Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"phone\":\"(555)555-5555\"},\"cardTransaction\":{\"amount\":\"100\",\"eciType\":\"1\",\"sourceId\":\"67\"},\"request\":\"sale\"}" + -> "HTTP/1.1 200 \r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials\r\n" + -> "Content-Encoding: application/json\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 2051\r\n" + -> "Date: Mon, 22 Mar 2021 19:06:00 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 2051 bytes... + -> "{\n \"cardAccount\": {\n \"accountNotes1\": \"\",\n \"accountNotes2\": \"\",\n \"accountNotes3\": \"\",\n \"accountNumber\": \"[FILTERED]\",\n \"account_number_masked\": \"400010******2224\",\n \"address\": \"456 My Street Apt 1\",\n \"auLastUpdate\": \"1999-01-01 00:00:00-05\",\n \"auUpdateType\": 0,\n \"cardType\": 1,\n \"city\": \"Ottawa\",\n \"commercialCardType\": 0,\n \"divisionId\": 7,\n \"email\": \"\",\n \"expirationDate\": \"0922\",\n \"firstFour\": \"4000\",\n \"firstName\": \"Jim\",\n \"fsv\": \"[FILTERED]\",\n \"inputMode\": 1,\n \"lastFour\": \"2224\",\n \"lastName\": \"Smith\",\n \"lastUsed\": \"1999-01-01 00:00:00-05\",\n \"middleName\": \"\",\n \"onlinePaymentCryptogram\": \"\",\n \"p2peInput\": \"\",\n \"paywayToken\": 10163736,\n \"phone\": \"5555555555\",\n \"state\": \"ON\",\n \"status\": 2,\n \"zip\": \"K1C2N6\"\n },\n \"cardTransaction\": {\n \"addressVerificationResults\": \"\",\n \"amount\": 100,\n \"authorizationCode\": \"0987654321\",\n \"authorizedTime\": \"2021-03-22 00:00:00-04\",\n \"capturedTime\": \"2021-03-22 15:06:00\",\n \"cbMode\": 2,\n \"eciType\": 1,\n \"fraudSecurityResults\": \"\",\n \"fsvIndicator\": \"\",\n \"name\": \"6720210322150600930144\",\n \"pfpstatus\": 3601,\n \"pfpstatusString\": \"PFP Not Enabled\",\n \"processorErrorMessage\": \"\",\n \"processorOrderId\": \"\",\n \"processorRecurringAdvice\": \"\",\n \"processorResponseDate\": \"\",\n \"processorResultCode\": \"\",\n \"processorSequenceNumber\": 0,\n \"processorSoftDescriptor\": \"\",\n \"referenceNumber\": \"123456\",\n \"resultCode\": 0,\n \"sessionToken_string\": \"0\",\n \"settledTime\": \"1999-01-01 00:00\",\n \"sourceId\": 67,\n \"status\": 4,\n \"tax\": 0,\n \"testResultAVS\": \"\",\n \"testResultFSV\": \"\",\n \"transactionNotes1\": \"\",\n \"transactionNotes2\": \"\",\n \"transactionNotes3\": \"\"\n },\n \"paywayCode\": \"5000\",\n \"paywayMessage\": \"\"\n}" + read 2051 bytes + Conn close + ) + end + + def pre_scrubbed_failed_purchase + %q( + opening connection to devedgilpayway.net:443... + opened + starting SSL for devedgilpayway.net:443... + SSL established, protocol: TLSv1.2, cipher: AES256-GCM-SHA384 + <- "POST /PaywayWS/Payment/CreditCard HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: devedgilpayway.net\r\nContent-Length: 423\r\n\r\n" + <- "{\"userName\":\"sprerestwsdev\",\"password\":\"sprerestwsdev1!\",\"companyId\":\"3\",\"accountInputMode\":\"primaryAccountNumber\",\"cardAccount\":{\"accountNumber\":\"4000300011112221\",\"fsv\":\"123\",\"expirationDate\":\"092022\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"address\":\"456 My Street Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"phone\":\"(555)555-5555\"},\"cardTransaction\":{\"amount\":\"102\",\"eciType\":\"1\",\"sourceId\":\"67\"},\"request\":\"sale\"}" + -> "HTTP/1.1 200 \r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials\r\n" + -> "Content-Encoding: application/json\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 2013\r\n" + -> "Date: Tue, 23 Mar 2021 15:04:53 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 2013 bytes... + -> "{\n \"cardAccount\": {\n \"accountNotes1\": \"\",\n \"accountNotes2\": \"\",\n \"accountNotes3\": \"\",\n \"accountNumber\": \"400030******2221\",\n \"account_number_masked\": \"400030******2221\",\n \"address\": \"456 My Street Apt 1\",\n \"auLastUpdate\": \"1999-01-01 00:00\",\n \"auUpdateType\": 0,\n \"cardType\": 1,\n \"city\": \"Ottawa\",\n \"commercialCardType\": 0,\n \"divisionId\": 7,\n \"email\": \"\",\n \"expirationDate\": \"0922\",\n \"firstFour\": \"4000\",\n \"firstName\": \"Jim\",\n \"fsv\": \"123\",\n \"inputMode\": 1,\n \"lastFour\": \"2221\",\n \"lastName\": \"Smith\",\n \"lastUsed\": \"1999-01-01 00:00\",\n \"middleName\": \"\",\n \"onlinePaymentCryptogram\": \"\",\n \"p2peInput\": \"\",\n \"paywayToken\": 0,\n \"phone\": \"5555555555\",\n \"state\": \"ON\",\n \"status\": 2,\n \"zip\": \"K1C2N6\"\n },\n \"cardTransaction\": {\n \"addressVerificationResults\": \"\",\n \"amount\": 0,\n \"authorizationCode\": \"\",\n \"authorizedTime\": \"1999-01-01\",\n \"capturedTime\": \"1999-01-01\",\n \"cbMode\": 0,\n \"eciType\": 0,\n \"fraudSecurityResults\": \"\",\n \"fsvIndicator\": \"\",\n \"name\": \"\",\n \"pfpstatus\": 3601,\n \"pfpstatusString\": \"PFP Not Enabled\",\n \"processorErrorMessage\": \"\",\n \"processorOrderId\": \"\",\n \"processorRecurringAdvice\": \"\",\n \"processorResponseDate\": \"\",\n \"processorResultCode\": \"\",\n \"processorSequenceNumber\": 0,\n \"processorSoftDescriptor\": \"\",\n \"referenceNumber\": \"\",\n \"resultCode\": 1,\n \"sessionToken_string\": \"0\",\n \"settledTime\": \"1999-01-01 00:00\",\n \"sourceId\": 0,\n \"status\": 0,\n \"tax\": 0,\n \"testResultAVS\": \"\",\n \"testResultFSV\": \"\",\n \"transactionNotes1\": \"\",\n \"transactionNotes2\": \"\",\n \"transactionNotes3\": \"\"\n },\n \"paywayCode\": \"5035\",\n \"paywayMessage\": \"Invalid account number: 4000300011112221\"\n}" + read 2013 bytes + Conn close + ) + end + + def post_scrubbed_failed_purchase + %q( + opening connection to devedgilpayway.net:443... + opened + starting SSL for devedgilpayway.net:443... + SSL established, protocol: TLSv1.2, cipher: AES256-GCM-SHA384 + <- "POST /PaywayWS/Payment/CreditCard HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: devedgilpayway.net\r\nContent-Length: 423\r\n\r\n" + <- "{\"userName\":\"sprerestwsdev\",\"password\":\"[FILTERED]\",\"companyId\":\"3\",\"accountInputMode\":\"primaryAccountNumber\",\"cardAccount\":{\"accountNumber\":\"[FILTERED]\",\"fsv\":\"[FILTERED]\",\"expirationDate\":\"092022\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"address\":\"456 My Street Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"phone\":\"(555)555-5555\"},\"cardTransaction\":{\"amount\":\"102\",\"eciType\":\"1\",\"sourceId\":\"67\"},\"request\":\"sale\"}" + -> "HTTP/1.1 200 \r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials\r\n" + -> "Content-Encoding: application/json\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 2013\r\n" + -> "Date: Tue, 23 Mar 2021 15:04:53 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 2013 bytes... + -> "{\n \"cardAccount\": {\n \"accountNotes1\": \"\",\n \"accountNotes2\": \"\",\n \"accountNotes3\": \"\",\n \"accountNumber\": \"[FILTERED]\",\n \"account_number_masked\": \"400030******2221\",\n \"address\": \"456 My Street Apt 1\",\n \"auLastUpdate\": \"1999-01-01 00:00\",\n \"auUpdateType\": 0,\n \"cardType\": 1,\n \"city\": \"Ottawa\",\n \"commercialCardType\": 0,\n \"divisionId\": 7,\n \"email\": \"\",\n \"expirationDate\": \"0922\",\n \"firstFour\": \"4000\",\n \"firstName\": \"Jim\",\n \"fsv\": \"[FILTERED]\",\n \"inputMode\": 1,\n \"lastFour\": \"2221\",\n \"lastName\": \"Smith\",\n \"lastUsed\": \"1999-01-01 00:00\",\n \"middleName\": \"\",\n \"onlinePaymentCryptogram\": \"\",\n \"p2peInput\": \"\",\n \"paywayToken\": 0,\n \"phone\": \"5555555555\",\n \"state\": \"ON\",\n \"status\": 2,\n \"zip\": \"K1C2N6\"\n },\n \"cardTransaction\": {\n \"addressVerificationResults\": \"\",\n \"amount\": 0,\n \"authorizationCode\": \"\",\n \"authorizedTime\": \"1999-01-01\",\n \"capturedTime\": \"1999-01-01\",\n \"cbMode\": 0,\n \"eciType\": 0,\n \"fraudSecurityResults\": \"\",\n \"fsvIndicator\": \"\",\n \"name\": \"\",\n \"pfpstatus\": 3601,\n \"pfpstatusString\": \"PFP Not Enabled\",\n \"processorErrorMessage\": \"\",\n \"processorOrderId\": \"\",\n \"processorRecurringAdvice\": \"\",\n \"processorResponseDate\": \"\",\n \"processorResultCode\": \"\",\n \"processorSequenceNumber\": 0,\n \"processorSoftDescriptor\": \"\",\n \"referenceNumber\": \"\",\n \"resultCode\": 1,\n \"sessionToken_string\": \"0\",\n \"settledTime\": \"1999-01-01 00:00\",\n \"sourceId\": 0,\n \"status\": 0,\n \"tax\": 0,\n \"testResultAVS\": \"\",\n \"testResultFSV\": \"\",\n \"transactionNotes1\": \"\",\n \"transactionNotes2\": \"\",\n \"transactionNotes3\": \"\"\n },\n \"paywayCode\": \"5035\",\n \"paywayMessage\": \"Invalid account number: [FILTERED]\"\n}" + read 2013 bytes + Conn close + ) + end + + def successful_purchase_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "I4", + "amount": 100, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-08 00:00:00-05", + "capturedTime": "2021-02-08 18:17:49", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "M", + "fsvIndicator": "", + "name": "6720210208181749349115", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 4, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def failed_purchase_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400030******2221", + "account_number_masked": "400030******2221", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "123", + "inputMode": 1, + "lastFour": "2221", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5035", + "paywayMessage": "Invalid account number: 4000300011112221" + }' + end + + def successful_authorize_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "737", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 0, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 103, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09", + "capturedTime": "1999-01-01 00:00:00-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209084239789167", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 3, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_authorize_and_capture_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "737", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 0, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 104, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09", + "capturedTime": "1999-01-01 00:00:00-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209085526437200", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 3, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_capture_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 104, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 00:00:00-05", + "capturedTime": "2021-02-09 08:55:26", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209085526437200", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 4, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def failed_authorize_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400030******2221", + "account_number_masked": "400030******2221", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "123", + "inputMode": 1, + "lastFour": "2221", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5035", + "paywayMessage": "Invalid account number: 4000300011112221" + }' + end + + def failed_capture_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "", + "account_number_masked": "", + "address": "", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 8, + "city": "", + "commercialCardType": 0, + "divisionId": 0, + "email": "", + "expirationDate": "", + "firstFour": "", + "firstName": "", + "fsv": "", + "inputMode": 1, + "lastFour": "", + "lastName": "", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "", + "state": "", + "status": 0, + "zip": "" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5025", + "paywayMessage": "failed to read transaction with source 0 and name " + }' + end + + def successful_credit_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "737", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 0, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 107, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 13:09:23", + "capturedTime": "2021-02-09 13:09:23", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209130923241131", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 4, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def failed_credit_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400030******2221", + "account_number_masked": "400030******2221", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "123", + "inputMode": 1, + "lastFour": "2221", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5035", + "paywayMessage": "Invalid account number: 4000300011112221" + }' + end + + def successful_auth_for_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "737", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 0, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 108, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09", + "capturedTime": "1999-01-01 00:00:00-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209135306469560", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 3, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_void_auth_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 108, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 00:00:00-05", + "capturedTime": "1999-01-01 00:00:00-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209135306469560", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 6, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "", + "account_number_masked": "", + "address": "", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 8, + "city": "", + "commercialCardType": 0, + "divisionId": 0, + "email": "", + "expirationDate": "", + "firstFour": "", + "firstName": "", + "fsv": "", + "inputMode": 1, + "lastFour": "", + "lastName": "", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "", + "state": "", + "status": 0, + "zip": "" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5025", + "paywayMessage": "failed to read transaction with source 0 and name " + }' + end + + def failed_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "", + "account_number_masked": "", + "address": "", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 8, + "city": "", + "commercialCardType": 0, + "divisionId": 0, + "email": "", + "expirationDate": "", + "firstFour": "", + "firstName": "", + "fsv": "", + "inputMode": 1, + "lastFour": "", + "lastName": "", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 0, + "phone": "", + "state": "", + "status": 0, + "zip": "" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 0, + "authorizationCode": "", + "authorizedTime": "1999-01-01", + "capturedTime": "1999-01-01", + "cbMode": 0, + "eciType": 0, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "", + "resultCode": 1, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 0, + "status": 0, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5025", + "paywayMessage": "failed to read transaction with source 0 and name " + }' + end + + def successful_sale_for_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 109, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 00:00:00-05", + "capturedTime": "2021-02-09 13:00:48", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209130047957988", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 4, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_void_sale_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 109, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 00:00:00-05", + "capturedTime": "2021-02-09 13:00:48-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209130047957988", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 6, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_credit_for_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "737", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 0, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 110, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 13:06:33", + "capturedTime": "2021-02-09 13:06:33", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "6720210209130633236167", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 4, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def successful_credit_void_response + '{ + "cardAccount": { + "accountNotes1": "", + "accountNotes2": "", + "accountNotes3": "", + "accountNumber": "400010******2224", + "account_number_masked": "400010******2224", + "address": "456 My Street Apt 1", + "auLastUpdate": "1999-01-01 00:00:00-05", + "auUpdateType": 0, + "cardType": 1, + "city": "Ottawa", + "commercialCardType": 0, + "divisionId": 7, + "email": "", + "expirationDate": "0922", + "firstFour": "4000", + "firstName": "Jim", + "fsv": "", + "inputMode": 1, + "lastFour": "2224", + "lastName": "Smith", + "lastUsed": "1999-01-01 00:00:00-05", + "middleName": "", + "onlinePaymentCryptogram": "", + "p2peInput": "", + "paywayToken": 10163736, + "phone": "5555555555", + "state": "ON", + "status": 2, + "zip": "K1C2N6" + }, + "cardTransaction": { + "addressVerificationResults": "", + "amount": 110, + "authorizationCode": "0987654321", + "authorizedTime": "2021-02-09 14:02:51-05", + "capturedTime": "2021-02-09 14:02:51-05", + "cbMode": 2, + "eciType": 1, + "fraudSecurityResults": "", + "fsvIndicator": "", + "name": "672021020914025188146", + "pfpstatus": 3601, + "pfpstatusString": "PFP Not Enabled", + "processorErrorMessage": "", + "processorOrderId": "", + "processorRecurringAdvice": "", + "processorResponseDate": "", + "processorResultCode": "", + "processorSequenceNumber": 0, + "processorSoftDescriptor": "", + "referenceNumber": "123456", + "resultCode": 0, + "sessionToken_string": "0", + "settledTime": "1999-01-01 00:00", + "sourceId": 67, + "status": 6, + "tax": 0, + "testResultAVS": "", + "testResultFSV": "", + "transactionNotes1": "", + "transactionNotes2": "", + "transactionNotes3": "" + }, + "paywayCode": "5000", + "paywayMessage": "" + }' + end + + def failed_invalid_login_response + '{ + "paywayCode": "5001", + "paywayMessage": "Session timed out or other session error. Create new session" + }' + end +end diff --git a/test/unit/gateways/payway_test.rb b/test/unit/gateways/payway_test.rb index 42a1aa25321..f5959f9b0fd 100644 --- a/test/unit/gateways/payway_test.rb +++ b/test/unit/gateways/payway_test.rb @@ -1,28 +1,27 @@ require 'test_helper' class PaywayTest < Test::Unit::TestCase - def setup @gateway = PaywayGateway.new( - :username => '12341234', - :password => 'abcdabcd', - :pem => certificate + username: '12341234', + password: 'abcdabcd', + pem: certificate ) @amount = 1000 @credit_card = ActiveMerchant::Billing::CreditCard.new( - :number => 4564710000000004, - :month => 2, - :year => 2019, - :first_name => 'Bob', - :last_name => 'Smith', - :verification_value => '847', - :brand => 'visa' + number: '4564710000000004', + month: 2, + year: 2019, + first_name: 'Bob', + last_name: 'Smith', + verification_value: '847', + brand: 'visa' ) @options = { - :order_id => 'abc' + order_id: 'abc' } end @@ -209,7 +208,7 @@ def test_bad_merchant def test_store @gateway.stubs(:ssl_post).returns(successful_response_store) - response = @gateway.store(@credit_card, :billing_id => 84517) + response = @gateway.store(@credit_card, billing_id: 84517) assert_instance_of Response, response assert_success response diff --git a/test/unit/gateways/pin_test.rb b/test/unit/gateways/pin_test.rb index 1d448d2203b..bb3bb0b60c3 100644 --- a/test/unit/gateways/pin_test.rb +++ b/test/unit/gateways/pin_test.rb @@ -2,16 +2,30 @@ class PinTest < Test::Unit::TestCase def setup - @gateway = PinGateway.new(:api_key => 'I_THISISNOTAREALAPIKEY') + @gateway = PinGateway.new(api_key: 'I_THISISNOTAREALAPIKEY') @credit_card = credit_card @amount = 100 @options = { - :email => 'roland@pinpayments.com', - :billing_address => address, - :description => 'Store Purchase', - :ip => '127.0.0.1' + email: 'roland@pinpayments.com', + billing_address: address, + description: 'Store Purchase', + ip: '127.0.0.1' + } + + @three_d_secure_v1 = { + version: '1.0.2', + eci: '05', + cavv: '1234', + xid: '1234' + } + + @three_d_secure_v2 = { + version: '2.0.0', + eci: '06', + cavv: 'jEoEjMykRWFCBEAAAVOBSYAAAA=', + ds_transaction_id: 'f92a19e2-485f-4d21-81ea-69a7352f611e' } end @@ -38,11 +52,11 @@ def test_live_url end def test_supported_countries - assert_equal ['AU'], PinGateway.supported_countries + assert_equal %w(AU NZ), PinGateway.supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express], PinGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club discover jcb], PinGateway.supported_cardtypes end def test_display_name @@ -118,6 +132,26 @@ def test_unsuccessful_store assert response.test? end + def test_successful_unstore + token = 'cus_05p0n7UFPmcyCNjD8c6HdA' + @gateway.expects(:ssl_request).with(:delete, "https://test-api.pinpayments.com/1/customers/#{token}", instance_of(String), instance_of(Hash)).returns(nil) + + assert response = @gateway.unstore(token) + assert_success response + assert_nil response.message + assert response.test? + end + + def test_unsuccessful_unstore + token = 'cus_05p0n7UFPmcyCNjD8c6HdA' + @gateway.expects(:ssl_request).with(:delete, "https://test-api.pinpayments.com/1/customers/#{token}", instance_of(String), instance_of(Hash)).returns(failed_customer_unstore_response) + + assert response = @gateway.unstore(token) + assert_failure response + assert_equal 'The requested resource could not be found.', response.message + assert response.test? + end + def test_successful_update token = 'cus_05p0n7UFPmcyCNjD8c6HdA' @gateway.expects(:ssl_request).with(:put, "https://test-api.pinpayments.com/1/customers/#{token}", instance_of(String), instance_of(Hash)).returns(successful_customer_store_response) @@ -130,7 +164,7 @@ def test_successful_update def test_successful_refund token = 'ch_encBuMDf17qTabmVjDsQlg' - @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(successful_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", { amount: '100' }.to_json, instance_of(Hash)).returns(successful_refund_response) assert response = @gateway.refund(100, token) assert_equal 'rf_d2C7M6Mn4z2m3APqarNN6w', response.authorization @@ -140,7 +174,7 @@ def test_successful_refund def test_unsuccessful_refund token = 'ch_encBuMDf17qTabmVjDsQlg' - @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(failed_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", { amount: '100' }.to_json, instance_of(Hash)).returns(failed_refund_response) assert response = @gateway.refund(100, token) assert_failure response @@ -176,6 +210,34 @@ def test_successful_capture assert response.test? end + def test_succesful_purchase_with_3ds + post_data = {} + headers = {} + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:post, 'https://test-api.pinpayments.com/1/charges', post_data, headers).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure: @three_d_secure_v1)) + assert_success response + assert_equal 'ch_Kw_JxmVqMeSOQU19_krRdw', response.authorization + assert_equal JSON.parse(successful_purchase_response), response.params + assert response.test? + end + + def test_succesful_authorize_with_3ds + post_data = {} + headers = {} + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:post, 'https://test-api.pinpayments.com/1/charges', post_data, headers).returns(successful_purchase_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure: @three_d_secure_v1)) + assert_success response + assert_equal 'ch_Kw_JxmVqMeSOQU19_krRdw', response.authorization + assert_equal JSON.parse(successful_purchase_response), response.params + assert response.test? + end + def test_store_parameters @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) @@ -262,7 +324,7 @@ def test_add_capture @gateway.send(:add_capture, post, @options) assert_equal post[:capture], true - @gateway.send(:add_capture, post, :capture => false) + @gateway.send(:add_capture, post, capture: false) assert_equal post[:capture], false end @@ -291,6 +353,24 @@ def test_add_creditcard_with_customer_token assert_false post.has_key?(:card) end + def test_add_3ds_v1 + post = {} + @gateway.send(:add_3ds, post, @options.merge(three_d_secure: @three_d_secure_v1)) + assert_equal '1.0.2', post[:three_d_secure][:version] + assert_equal '05', post[:three_d_secure][:eci] + assert_equal '1234', post[:three_d_secure][:cavv] + assert_equal '1234', post[:three_d_secure][:transaction_id] + end + + def test_add_3ds_v2 + post = {} + @gateway.send(:add_3ds, post, @options.merge(three_d_secure: @three_d_secure_v2)) + assert_equal '2.0.0', post[:three_d_secure][:version] + assert_equal '06', post[:three_d_secure][:eci] + assert_equal 'jEoEjMykRWFCBEAAAVOBSYAAAA=', post[:three_d_secure][:cavv] + assert_equal 'f92a19e2-485f-4d21-81ea-69a7352f611e', post[:three_d_secure][:transaction_id] + end + def test_post_data post = {} @gateway.send(:add_creditcard, post, @credit_card) @@ -310,7 +390,7 @@ def test_headers expected_headers['X-Safe-Card'] = '1' @gateway.expects(:ssl_request).with(:post, anything, anything, expected_headers).returns(successful_purchase_response) - assert @gateway.purchase(@amount, @credit_card, :partner_key => 'MyPartnerKey', :safe_card => '1') + assert @gateway.purchase(@amount, @credit_card, partner_key: 'MyPartnerKey', safe_card: '1') end def test_transcript_scrubbing @@ -438,6 +518,13 @@ def failed_customer_store_response }' end + def failed_customer_unstore_response + '{ + "error": "not_found", + "error_description": "The requested resource could not be found." + }' + end + def successful_refund_response '{ "response":{ @@ -543,5 +630,4 @@ def scrubbed_transcript } }' end - end diff --git a/test/unit/gateways/plexo_test.rb b/test/unit/gateways/plexo_test.rb new file mode 100644 index 00000000000..d05db0f95cb --- /dev/null +++ b/test/unit/gateways/plexo_test.rb @@ -0,0 +1,884 @@ +require 'test_helper' + +class PlexoTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PlexoGateway.new(client_id: 'abcd', api_key: 'efgh', merchant_id: 'test090') + + @amount = 100 + @credit_card = credit_card('5555555555554444', month: '12', year: '2024', verification_value: '111', first_name: 'Santiago', last_name: 'Navatta') + @declined_card = credit_card('5555555555554445') + @options = { + email: 'snavatta@plexo.com.uy', + ip: '127.0.0.1', + items: [ + { + name: 'prueba', + description: 'prueba desc', + quantity: '1', + price: '100', + discount: '0' + } + ], + amount_details: { + tip_amount: '5' + }, + metadata: { + custom_one: 'test1', + test_a: 'abc' + }, + identification_type: '1', + identification_value: '123456', + billing_address: address + } + + @cancel_options = { + description: 'Test desc', + reason: 'requested by client' + } + 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 'You have been mocked', response.message + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal 'test090', request['MerchantId'] + assert_equal @credit_card.number, request['paymentMethod']['Card']['Number'] + assert_equal @credit_card.verification_value, request['paymentMethod']['Card']['Cvc'] + assert_equal @credit_card.first_name, request['paymentMethod']['Card']['Cardholder']['FirstName'] + assert_equal @options[:email], request['paymentMethod']['Card']['Cardholder']['Email'] + assert_equal @options[:identification_type], request['paymentMethod']['Card']['Cardholder']['Identification']['Type'] + assert_equal @options[:identification_value], request['paymentMethod']['Card']['Cardholder']['Identification']['Value'] + assert_equal @options[:billing_address][:city], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['City'] + assert_equal @options[:billing_address][:country], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['Country'] + assert_equal @options[:billing_address][:address1], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['Line1'] + assert_equal @options[:billing_address][:address2], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['Line2'] + assert_equal @options[:billing_address][:zip], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['PostalCode'] + assert_equal @options[:billing_address][:state], request['paymentMethod']['Card']['Cardholder']['BillingAddress']['State'] + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorize_with_items + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + request['Items'].each_with_index do |item, index| + assert_not_nil item['ReferenceId'] + assert_equal item['Name'], @options[:items][index][:name] if item['Name'] + assert_equal item['Description'], @options[:items][index][:description] if item['Description'] + assert_equal item['Quantity'], @options[:items][index][:quantity] if item['Quantity'] + assert_equal item['Price'], @options[:items][index][:price] if item['Price'] + assert_equal item['Discount'], @options[:items][index][:discount] if item['Discount'] + end + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorize_with_meta_fields + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + @options[:metadata].keys.each do |meta_key| + camel_key = meta_key.to_s.camelize + assert_equal request['Metadata'][camel_key], @options[:metadata][meta_key] + end + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorize_with_finger_print + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ finger_print: 'USABJHABSFASNJKN123532' })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['BrowserDetails']['DeviceFingerprint'], 'USABJHABSFASNJKN123532' + end.respond_with(successful_authorize_response) + end + + def test_successful_reordering_of_amount_in_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + original_response = JSON.parse(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal response.params['amount'], original_response['amount']['total'] + assert_equal response.params['currency'], original_response['amount']['currency'] + assert_equal response.params['amount_details'], original_response['amount']['details'] + end + + def test_successful_authorize_with_extra_options + other_fields = { + installments: '1', + statement_descriptor: 'Plexo * Test', + customer_id: 'customer1', + cardholder_birthdate: '1999-08-18T19:49:37.023Z' + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(other_fields)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['Installments'], other_fields[:installments] + assert_equal request['CustomerId'], other_fields[:customer_id] + assert_equal request['StatementDescriptor'], other_fields[:statement_descriptor] + assert_equal request['paymentMethod']['Card']['Cardholder']['Birthdate'], other_fields[:cardholder_birthdate] + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorize_with_amount_fields + amount_fields = { + taxed_amount: '100', + tip_amount: '32', + discount_amount: '10', + taxable_amount: '302', + tax: { + type: '17934', + amount: '22', + rate: '0.22' + } + } + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ amount_details: amount_fields, currency: 'USD' })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['Amount']['Currency'], 'USD' + assert_equal request['Amount']['Details']['TaxedAmount'], amount_fields[:taxed_amount] + assert_equal request['Amount']['Details']['TipAmount'], amount_fields[:tip_amount] + assert_equal request['Amount']['Details']['DiscountAmount'], amount_fields[:discount_amount] + assert_equal request['Amount']['Details']['TaxableAmount'], amount_fields[:taxable_amount] + assert_equal request['Amount']['Details']['Tax']['Type'], amount_fields[:tax][:type] + assert_equal request['Amount']['Details']['Tax']['Amount'], amount_fields[:tax][:amount] + assert_equal request['Amount']['Details']['Tax']['Rate'], amount_fields[:tax][:rate] + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, '123456abcdef', { reference_id: 'reference123' }) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['ReferenceId'], 'reference123' + assert_includes endpoint, '123456abcdef' + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_successful_refund + refund_options = { + reference_id: 'reference123', + refund_type: 'partial-refund', + description: 'my description', + reason: 'reason abc' + } + response = stub_comms do + @gateway.refund(@amount, '123456abcdef', refund_options) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['ReferenceId'], refund_options[:reference_id] + assert_equal request['Type'], refund_options[:refund_type] + assert_equal request['Description'], refund_options[:description] + assert_equal request['Reason'], refund_options[:reason] + assert_includes endpoint, '123456abcdef' + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_successful_void + void_options = { + reference_id: 'reference123', + description: 'my description', + reason: 'reason abc' + } + response = stub_comms do + @gateway.void('123456abcdef', void_options) + end.check_request do |endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['ReferenceId'], void_options[:reference_id] + assert_equal request['Description'], void_options[:description] + assert_equal request['Reason'], void_options[:reason] + assert_includes endpoint, '123456abcdef' + end.respond_with(successful_void_response) + + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void(@credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'You have been mocked.', response.message + end + + def test_successful_verify_with_custom_amount + stub_comms do + @gateway.verify(@credit_card, @options.merge({ verify_amount: '900' })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['Amount']['Total'], '9.00' + end.respond_with(successful_verify_response) + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<~PRE_SCRUBBED + opening connection to api.testing.plexo.com.uy:443... + opened + starting SSL for api.testing.plexo.com.uy:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- "POST /v1/payments/628b723aa450dab85ba2fa03/captures HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic MjIxOjlkZWZhZWVlYmMzOTQ1NDFhZmY2MzMyOTE4MmRkODQyNDA1MTJhYTI0NWE0NDY2MDkxZWQ3MGY2OTAxYjQ5NDc=\r\nX-Mock-Tokenization: true\r\nX-Mock-Switcher: true\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.testing.plexo.com.uy\r\nContent-Length: 66\r\n\r\n" + <- "{\"ReferenceId\":\"e6742109bb60458b1c5a7c69ffcc3f54\",\"Amount\":\"1.00\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 23 May 2022 11:38:35 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "X-MiniProfiler-Ids: [\"c6b2ce60-757c-4115-b802-e33a27c2e311\",\"e1533461-72dc-4693-97a6-deea47601ca4\",\"da8b919f-a1f8-4051-870d-3679f4c8ac6b\",\"4465311a-ab60-470d-8f69-e06eed35c271\",\"c4b23b7d-e824-4fd6-95b9-82fa4786f4a2\",\"c5fe47c7-6155-4eb7-b9f4-84cd7fae7acf\",\"80e2f132-1ac1-4b25-b030-5eaccd44a0db\",\"525c97a7-5df7-4dd5-b1da-4c6abe9a5995\",\"98694fd6-f3ff-497a-b6d4-477a50a093aa\",\"802b9242-97c6-4438-bd72-960dbdf2f752\",\"7aa9078c-12f1-41f4-bc57-c77fb8a9ecc8\",\"4890d7e1-22c9-4e9d-afe1-88149e743aa0\",\"cafed17f-08ce-49cc-91d0-d8d9865facc7\",\"98fea53d-ad00-44cb-8e82-0829e5c8aaee\",\"5730d4fa-1c70-4679-a097-d9c8b7156f2d\",\"ba7d9c5a-e2bc-461f-b87d-552ae9fabb65\",\"3b1dbbbe-8112-4293-9be3-c865741c5494\",\"3ab01bd5-a2b5-4d9c-84c7-9c743f1e9978\",\"d6e397a3-cf95-413c-b3c6-4729aa463d33\",\"fc9cb79e-ab22-42b0-b611-0b3f62a203bb\",\"b16fd902-f50a-43e2-8e82-cc0fe763b16b\",\"dc702114-866c-4b9a-bc07-291b0b0f8b73\"]\r\n" + -> "x-correlation-id: 24ebd1ee-a69a-4163-85cf-e5a1ab7fd26b\r\n" + -> "Strict-Transport-Security: max-age=15724800; includeSubDomains\r\n" + -> "\r\n" + -> "192\r\n" + reading 402 bytes... + -> "{\"id\":\"628b723ba450dab85ba2fa0a\",\"uniqueId\":\"978260656060936192\",\"parentId\":\"cf8ecc4a-b0ed-4a40-945e-0eaff39e66f9\",\"referenceId\":\"e6742109bb60458b1c5a7c69ffcc3f54\",\"type\":\"capture\",\"status\":\"approved\",\"createdAt\":\"2022-05-23T11:38:35.6091676Z\",\"processedAt\":\"2022-05-23T11:38:35.6091521Z\",\"resultCode\":\"0\",\"resultMessage\":\"You have been mocked.\",\"authorization\":\"12133\",\"ticket\":\"111111\",\"amount\":1.00}" + read 402 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<~POST_SCRUBBED + opening connection to api.testing.plexo.com.uy:443... + opened + starting SSL for api.testing.plexo.com.uy:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- "POST /v1/payments/628b723aa450dab85ba2fa03/captures HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]=\r\nX-Mock-Tokenization: true\r\nX-Mock-Switcher: true\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.testing.plexo.com.uy\r\nContent-Length: 66\r\n\r\n" + <- "{\"ReferenceId\":\"e6742109bb60458b1c5a7c69ffcc3f54",\"Amount\":\"1.00\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 23 May 2022 11:38:35 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "X-MiniProfiler-Ids: [\"c6b2ce60-757c-4115-b802-e33a27c2e311\",\"e1533461-72dc-4693-97a6-deea47601ca4\",\"da8b919f-a1f8-4051-870d-3679f4c8ac6b\",\"4465311a-ab60-470d-8f69-e06eed35c271\",\"c4b23b7d-e824-4fd6-95b9-82fa4786f4a2\",\"c5fe47c7-6155-4eb7-b9f4-84cd7fae7acf\",\"80e2f132-1ac1-4b25-b030-5eaccd44a0db\",\"525c97a7-5df7-4dd5-b1da-4c6abe9a5995\",\"98694fd6-f3ff-497a-b6d4-477a50a093aa\",\"802b9242-97c6-4438-bd72-960dbdf2f752\",\"7aa9078c-12f1-41f4-bc57-c77fb8a9ecc8\",\"4890d7e1-22c9-4e9d-afe1-88149e743aa0\",\"cafed17f-08ce-49cc-91d0-d8d9865facc7\",\"98fea53d-ad00-44cb-8e82-0829e5c8aaee\",\"5730d4fa-1c70-4679-a097-d9c8b7156f2d\",\"ba7d9c5a-e2bc-461f-b87d-552ae9fabb65\",\"3b1dbbbe-8112-4293-9be3-c865741c5494\",\"3ab01bd5-a2b5-4d9c-84c7-9c743f1e9978\",\"d6e397a3-cf95-413c-b3c6-4729aa463d33\",\"fc9cb79e-ab22-42b0-b611-0b3f62a203bb\",\"b16fd902-f50a-43e2-8e82-cc0fe763b16b\",\"dc702114-866c-4b9a-bc07-291b0b0f8b73\"]\r\n" + -> "x-correlation-id: 24ebd1ee-a69a-4163-85cf-e5a1ab7fd26b\r\n" + -> "Strict-Transport-Security: max-age=15724800; includeSubDomains\r\n" + -> "\r\n" + -> "192\r\n" + reading 402 bytes... + -> "{\"id\":\"628b723ba450dab85ba2fa0a\",\"uniqueId\":\"978260656060936192\",\"parentId\":\"cf8ecc4a-b0ed-4a40-945e-0eaff39e66f9\",\"referenceId\":\"e6742109bb60458b1c5a7c69ffcc3f54",\"type\":\"capture\",\"status\":\"approved\",\"createdAt\":\"2022-05-23T11:38:35.6091676Z\",\"processedAt\":\"2022-05-23T11:38:35.6091521Z\",\"resultCode\":\"0\",\"resultMessage\":\"You have been mocked.\",\"authorization\":\"12133\",\"ticket\":\"111111\",\"amount\":1.00}" + read 402 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def failed_purchase_response + <<~RESPONSE + { + "code": "merchant-not-found", + "message": "The requested Merchant was not found.", + "type": "invalid-request-error", + "status": 400 + } + RESPONSE + end + + def successful_authorize_response + <<~RESPONSE + { + "id": "62878b1fa450dab85ba2f983", + "token": "7c23b951-599f-462e-8a47-6bbbb4dc5ad0", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "status": "approved", + "processingMethod": "api", + "browserDetails": { + "DeviceFingerprint": "12345", + "IpAddress": "127.0.0.1" + }, + "createdAt": "2022-05-20T12:35:43.1389809Z", + "merchant": { + "id": 3243, + "name": "spreedly", + "settings": { + "id": 41363, + "issuerId": 4, + "issuer": { + "id": 4, + "code": "mastercard", + "name": "MASTERCARD", + "type": "online" + }, + "metadata": { + "ProviderCommerceNumber": "153289", + "TerminalNumber": "1K153289", + "SoftDescriptor": "VTEX-Testing", + "PaymentProcessorId": "oca" + }, + "paymentProcessor": { + "acquirer": "oca", + "settings": { + "commerce": { + "fields": [ + { + "name": "ProviderCommerceNumber", + "type": 2049 + }, + { + "name": "TerminalNumber", + "type": 2051 + } + ] + }, + "fingerprint": { + "name": "cybersource-oca" + }, + "fields": [ + { + "name": "Email", + "type": 261 + }, + { + "name": "FirstName", + "type": 271 + }, + { + "name": "LastName", + "type": 272 + }, + { + "name": "CVC", + "type": 33154 + } + ] + } + } + }, + "clientId": 221 + }, + "client": { + "id": 221, + "name": "Spreedly", + "tier": 2, + "sessionTimeInSeconds": 36000 + }, + "paymentMethod": { + "type": "card", + "card": { + "name": "555555XXXXXX4444", + "bin": "555555", + "last4": "4444", + "expMonth": 12, + "expYear": 24, + "cardholder": { + "firstName": "Santiago", + "lastName": "Navatta", + "email": "snavatta@plexo.com.uy" + }, + "fingerprint": "2cccefc7e6e54644b5f5540aaab7744b" + }, + "issuer": { + "id": "mastercard", + "name": "MasterCard", + "pictureUrl": "https://static.plexo.com.uy/issuers/4.svg", + "type": "online" + }, + "processor": { + "acquirer": "oca" + } + }, + "installments": 1, + "amount": { + "currency": "UYU", + "total": 147, + "details": { + "taxedAmount": 0 + } + }, + "items": [ + { + "referenceId": "7c34953392e84949ab511667db0ebef2", + "name": "prueba", + "description": "prueba desc", + "quantity": 1, + "price": 100, + "discount": 0 + } + ], + "capture": { + "method": "manual" + }, + "transactions": [ + { + "id": "62878b1fa450dab85ba2f987", + "uniqueId": "977187868889886720", + "parentId": "7c23b951-599f-462e-8a47-6bbbb4dc5ad0", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "authorization", + "status": "approved", + "createdAt": "2022-05-20T12:35:43.2161946Z", + "processedAt": "2022-05-20T12:35:43.2161798Z", + "resultCode": "0", + "resultMessage": "You have been mocked.", + "authorization": "12133", + "ticket": "111111", + "amount": 147 + } + ] + } + RESPONSE + end + + def successful_purchase_response + <<~RESPONSE + { + "id": "6305dd2d000d6ed5d1ecf79b", + "token": "82ae122c-d235-43bc-a454-fba16b2ae3a4", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "status": "approved", + "processingMethod": "api", + "createdAt": "2022-08-24T08:11:25.677Z", + "updatedAt": "2022-08-24T08:11:26.2893146Z", + "processedAt": "2022-08-24T08:11:26.2893146Z", + "merchant": { + "id": 3243, + "name": "spreedly", + "settings": { + "merchantIdentificationNumber": "98001456", + "paymentProcessor": { + "acquirer": "fiserv" + } + }, + "clientId": 221 + }, + "client": { + "id": 221, + "name": "Spreedly", + "tier": 2, + "sessionTimeInSeconds": 36000 + }, + "paymentMethod": { + "id": "mastercard", + "name": "MASTERCARD", + "type": "card", + "card": { + "name": "555555XXXXXX4444", + "bin": "555555", + "last4": "4444", + "expMonth": 12, + "expYear": 24, + "cardholder": { + "firstName": "Santiago", + "lastName": "Navatta", + "email": "snavatta@plexo.com.uy", + "identification": { + "type": 1, + "value": "123456" + }, + "billingAddress": { + "city": "Karachi", + "country": "Pakistan", + "line1": "street 4" + } + }, + "type": "credit", + "origin": "international", + "token": "03d43b25971546e0ab27e8b4698c9b7d" + }, + "issuer": { + "id": "mastercard", + "name": "MasterCard", + "pictureUrl": "https://static.plexo.com.uy/issuers/4.svg", + "type": "online" + }, + "processor": { + "id": 4, + "acquirer": "fiserv" + } + }, + "installments": 1, + "amount": { + "currency": "UYU", + "total": 147.0, + "details": { + "tax": { + "type": "17934", + "amount": 22.0 + }, + "taxedAmount": 100.0, + "tipAmount": 25.0, + "discountAmount": 0.0 + } + }, + "items": [ + { + "referenceId": "7c34953392e84949ab511667db0ebef2", + "name": "prueba", + "description": "prueba desc", + "quantity": 1, + "price": 100.0, + "discount": 0.0 + } + ], + "transactions": [ + { + "id": "6305dd2e000d6ed5d1ecf79f", + "uniqueId": "1011910592648278016", + "parentId": "82ae122c-d235-43bc-a454-fba16b2ae3a4", + "traceId": "cbf814cd-8b28-4145-ac0b-7381980015e8", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "purchase", + "status": "approved", + "createdAt": "2022-08-24T08:11:26.2893133Z", + "processedAt": "2022-08-24T08:11:26.2893129Z", + "resultCode": "0", + "resultMessage": "You have been mocked", + "authorization": "1234567890", + "ticket": "1234567890", + "amount": 147.0 + } + ] + } + RESPONSE + end + + def failed_authorize_response + <<~RESPONSE + { + "code": "merchant-not-found", + "message": "The requested Merchant was not found.", + "type": "invalid-request-error", + "status": 400 + } + RESPONSE + end + + def successful_capture_response + <<~RESPONSE + { + "id": "62878b1fa450dab85ba2f987", + "uniqueId": "977187868889886720", + "parentId": "7c23b951-599f-462e-8a47-6bbbb4dc5ad0", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "capture", + "status": "approved", + "createdAt": "2022-05-20T12:35:43.216Z", + "processedAt": "2022-05-20T12:35:43.216Z", + "resultCode": "0", + "resultMessage": "You have been mocked.", + "authorization": "12133", + "ticket": "111111", + "amount": 147 + } + RESPONSE + end + + def failed_capture_response + <<~RESPONSE + { + "code": "internal-error", + "message": "An internal error occurred. Contact support.", + "type": "api-error", + "status": 400 + } + RESPONSE + end + + def successful_refund_response + <<~RESPONSE + { + "id": "62878b1fa450dab85ba2f987", + "uniqueId": "977187868889886720", + "parentId": "7c23b951-599f-462e-8a47-6bbbb4dc5ad0", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "refund", + "status": "approved", + "resultCode": "0", + "resultMessage": "You have been mocked.", + "authorization": "12133", + "ticket": "111111", + "amount": 147, + "reason": "ClientRequest" + } + RESPONSE + end + + def failed_refund_response + <<~RESPONSE + { + "code": "internal-error", + "message": "An internal error occurred. Contact support.", + "type": "api-error", + "status": 400 + } + RESPONSE + end + + def successful_void_response + <<~RESPONSE + { + "id": "62878c0fa450dab85ba2f994", + "uniqueId": "977188875178913792", + "parentId": "49fe7306-d706-43e4-97cd-8de94683c9ae", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "cancellation", + "status": "approved", + "createdAt": "2022-05-20T12:39:43.134Z", + "processedAt": "2022-05-20T12:39:43.134Z", + "resultCode": "0", + "resultMessage": "You have been mocked.", + "authorization": "12133", + "ticket": "111111", + "amount": 147.0 + } + RESPONSE + end + + def failed_void_response + <<~RESPONSE + { + "code": "internal-error", + "message": "An internal error occurred. Contact support.", + "type": "api-error", + "status": 400 + } + RESPONSE + end + + def successful_verify_response + <<~RESPONSE + { + "id": "62ac2c5eaf353be57867f977", + "token": "7220c5cc-4b57-43e6-ae91-3fd3f3e8d49f", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "status": "approved", + "processingMethod": "api", + "browserDetails": { + "DeviceFingerprint": "12345", + "IpAddress": "127.0.0.1" + }, + "createdAt": "2022-06-17T07:25:18.1421498Z", + "merchant": { + "id": 3243, + "name": "spreedly", + "settings": { + "id": 41363, + "issuerId": 4, + "issuer": { + "id": 4, + "code": "mastercard", + "name": "MASTERCARD", + "type": "online" + }, + "metadata": { + "ProviderCommerceNumber": "153289", + "TerminalNumber": "1K153289", + "SoftDescriptor": "VTEX-Testing", + "PaymentProcessorId": "oca" + }, + "paymentProcessor": { + "acquirer": "oca", + "settings": { + "commerce": { + "fields": [ + { + "name": "ProviderCommerceNumber", + "type": 2049 + }, + { + "name": "TerminalNumber", + "type": 2051 + } + ] + }, + "fingerprint": { + "name": "cybersource-oca" + }, + "fields": [ + { + "name": "Email", + "type": 261 + }, + { + "name": "FirstName", + "type": 271 + }, + { + "name": "LastName", + "type": 272 + }, + { + "name": "CVC", + "type": 33154 + } + ] + } + } + }, + "clientId": 221 + }, + "client": { + "id": 221, + "name": "Spreedly", + "tier": 2, + "sessionTimeInSeconds": 36000 + }, + "paymentMethod": { + "type": "card", + "card": { + "name": "555555XXXXXX4444", + "bin": "555555", + "last4": "4444", + "expMonth": 12, + "expYear": 24, + "cardholder": { + "firstName": "Santiago", + "lastName": "Navatta", + "email": "snavatta@plexo.com.uy" + }, + "fingerprint": "36e2219cc4734a61af258905c1c59ba4" + }, + "issuer": { + "id": "mastercard", + "name": "MasterCard", + "pictureUrl": "https://static.plexo.com.uy/issuers/4.svg", + "type": "online" + }, + "processor": { + "acquirer": "oca" + } + }, + "installments": 1, + "amount": { + "currency": "UYU", + "total": 20 + }, + "items": [ + { + "referenceId": "997d4aafe29b4421ac52a3ddf5b28dfd", + "name": "card-verification", + "quantity": 1, + "price": 20 + } + ], + "capture": { + "method": "manual", + "delay": 0 + }, + "metadata": { + "One": "abc" + }, + "transactions": [ + { + "id": "62ac2c5eaf353be57867f97b", + "uniqueId": "987256610059481088", + "parentId": "7220c5cc-4b57-43e6-ae91-3fd3f3e8d49f", + "referenceId": "e7dbc06224f646ad8e63ec1c6e670a39", + "type": "authorization", + "status": "approved", + "createdAt": "2022-06-17T07:25:18.1796516Z", + "processedAt": "2022-06-17T07:25:18.1796366Z", + "resultCode": "0", + "resultMessage": "You have been mocked.", + "authorization": "12133", + "ticket": "111111", + "amount": 20 + } + ] + } + RESPONSE + end + + def failed_verify_response + <<~RESPONSE + { + "code": "invalid-transaction-state", + "message": "The selected payment state is not valid.", + "type": "api-error", + "status": 400 + } + RESPONSE + end +end diff --git a/test/unit/gateways/plugnpay_test.rb b/test/unit/gateways/plugnpay_test.rb index 4760eada3f6..da0170ceef0 100644 --- a/test/unit/gateways/plugnpay_test.rb +++ b/test/unit/gateways/plugnpay_test.rb @@ -1,19 +1,18 @@ require 'test_helper' class PlugnpayTest < Test::Unit::TestCase - def setup Base.mode = :test @gateway = PlugnpayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @credit_card = credit_card @options = { - :billing_address => address, - :description => 'Store purchase' + billing_address: address, + description: 'Store purchase' } @amount = 100 end @@ -43,7 +42,7 @@ def test_capture_partial_amount def test_capture_full_amount @gateway.expects(:ssl_post).with(anything, all_of(regexp_matches(/mode=mark/), regexp_matches(/card_amount=1.00/)), anything).returns('') - @gateway.expects(:parse).returns({'auth_msg' => 'Blah blah blah Transaction may not be reauthorized'}, {}) + @gateway.expects(:parse).returns({ 'auth_msg' => 'Blah blah blah Transaction may not be reauthorized' }, {}) @gateway.capture(@amount, @credit_card, @options) end @@ -70,7 +69,7 @@ def test_refund def test_add_address_outsite_north_america result = PlugnpayGateway::PlugnpayPostData.new - @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => 'Dortmund'}) + @gateway.send(:add_addresses, result, billing_address: { address1: '164 Waverley Street', country: 'DE', state: 'Dortmund' }) assert_equal result[:state], 'ZZ' assert_equal result[:province], 'Dortmund' @@ -85,7 +84,7 @@ def test_add_address_outsite_north_america def test_add_address result = PlugnpayGateway::PlugnpayPostData.new - @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) + @gateway.send(:add_addresses, result, billing_address: { address1: '164 Waverley Street', country: 'US', state: 'CO' }) assert_equal result[:card_state], 'CO' assert_equal result[:card_address1], '164 Waverley Street' diff --git a/test/unit/gateways/priority_test.rb b/test/unit/gateways/priority_test.rb new file mode 100644 index 00000000000..4d53c73417a --- /dev/null +++ b/test/unit/gateways/priority_test.rb @@ -0,0 +1,1352 @@ +require 'test_helper' +class PriorityTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PriorityGateway.new(api_key: 'sandbox_key', secret: 'secret', merchant_id: 'merchant_id') + @amount = 4 + @credit_card = credit_card + @invalid_credit_card = credit_card('4111') + @replay_id = rand(100...1000) + @approval_message = 'Approved or completed successfully. ' + @options = { billing_address: address } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal @approval_message, response.message + assert_equal 'Sale', response.params['type'] + assert response.test? + end + + def test_failed_purchase_invalid_credit_card + response = stub_comms do + @gateway.purchase(@amount, @invalid_credit_card, @options) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Declined', response.error_code + assert_equal 'Invalid card number', response.message + assert response.test? + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(333, @credit_card, @options) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal @approval_message, response.message + assert_equal 'Authorization', response.params['type'] + assert response.test? + end + + def test_failed_authorize_invalid_credit_card + response = stub_comms do + @gateway.purchase(@amount, @invalid_credit_card, @options) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Declined', response.error_code + assert_equal 'Invalid card number', response.message + assert_equal 'Authorization', response.params['type'] + assert response.test? + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, '10000001625060|PaQLIYLRdWtcFKl5VaKTdUVxMutXJ5Ru', @options) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal 'Approved', response.message + assert_equal '10000001625061|PaQLIYLRdWtcFKl5VaKTdUVxMutXJ5Ru', response.authorization + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(@amount, 'bogus_authorization', @options) + end.respond_with(failed_capture_response) + + assert_failure response + assert_equal 'Declined', response.error_code + assert_equal 'Original Transaction Not Found', response.message + assert_equal nil, response.authorization + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('bogus authorization') + assert_failure response + assert_equal 'Unauthorized', response.error_code + assert_equal 'Original Payment Not Found Or You Do Not Have Access.', response.message + end + + def test_successful_refund + authorization = '86044396|PTp2WxLTXEP9Ml4DfDzTAbDWRaEFLKEM' + response = stub_comms do + @gateway.refund(544, authorization, @options) + end.respond_with(successful_refund_response) + + assert_success response + assert_equal @approval_message, response.message + assert response.test? + end + + def test_failed_duplicate_refund + authorization = '86044396|PTp2WxLTXEP9Ml4DfDzTAbDWRaEFLKEM' + response = stub_comms do + @gateway.refund(544, authorization, @options) + end.respond_with(failed_duplicate_refund) + + assert_failure response + assert_equal 'Declined', response.error_code + assert_equal 'Payment already refunded', response.message + assert response.test? + end + + def test_failed_get_payment_status + @gateway.expects(:ssl_get).returns('Not Found') + + batch_check = @gateway.get_payment_status(123456) + + assert_failure batch_check + assert_includes batch_check.message, 'Invalid JSON response' + assert_includes batch_check.message, 'Not Found' + end + + def test_purchase_passes_shipping_data + options_with_shipping = @options.merge({ ship_to_country: 'USA', ship_to_zip: 27703, ship_amount: 0.01 }) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_shipping) + end.check_request do |_endpoint, data, _headers| + assert_match(/shipAmount\":0.01/, data) + assert_match(/shipToZip\":27703/, data) + assert_match(/shipToCountry\":\"USA/, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_purchases_data + purchases_data = { + 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 + } + ] + } + options_with_purchases = @options.merge(purchases_data) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_purchases) + end.check_request do |_endpoint, data, _headers| + purchase_item = purchases_data[:purchases].first + purchase_object = JSON.parse(data)['purchases'].first + + assert_equal(purchase_item[:name], purchase_object['name']) + assert_equal(purchase_item[:description], purchase_object['description']) + assert_equal(purchase_item[:unit_price], purchase_object['unitPrice']) + assert_equal(purchase_item[:quantity], purchase_object['quantity']) + assert_equal(purchase_item[:tax_amount], purchase_object['taxAmount']) + assert_equal(purchase_item[:discount_rate], purchase_object['discountRate']) + assert_equal(purchase_item[:discount_amount], purchase_object['discountAmount']) + assert_equal(purchase_item[:extended_amount], purchase_object['extendedAmount']) + assert_equal(purchase_item[:line_item_id], purchase_object['lineItemId']) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_pos_data + custom_pos_data = { + 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' + } + } + options_with_custom_pos_data = @options.merge(custom_pos_data) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_custom_pos_data) + end.check_request do |_endpoint, data, _headers| + pos_data_object = JSON.parse(data)['posData'] + assert_equal(custom_pos_data[:pos_data][:cardholder_presence], pos_data_object['cardholderPresence']) + assert_equal(custom_pos_data[:pos_data][:device_attendance], pos_data_object['deviceAttendance']) + assert_equal(custom_pos_data[:pos_data][:device_input_capability], pos_data_object['deviceInputCapability']) + assert_equal(custom_pos_data[:pos_data][:device_location], pos_data_object['deviceLocation']) + assert_equal(custom_pos_data[:pos_data][:pan_capture_method], pos_data_object['panCaptureMethod']) + assert_equal(custom_pos_data[:pos_data][:partial_approval_support], pos_data_object['partialApprovalSupport']) + assert_equal(custom_pos_data[:pos_data][:pin_capture_capability], pos_data_object['pinCaptureCapability']) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_duplicate_replay_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: @replay_id)) + end.check_request do |_endpoint, data, _headers| + assert_equal @replay_id, JSON.parse(data)['replayId'] + end.respond_with(successful_purchase_response_with_replay_id) + assert_success response + + duplicate_response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: response.params['replayId'])) + end.check_request do |_endpoint, data, _headers| + assert_equal response.params['replayId'], JSON.parse(data)['replayId'] + end.respond_with(successful_purchase_response_with_replay_id) + assert_success duplicate_response + + assert_equal response.params['id'], duplicate_response.params['id'] + end + + def test_failed_purchase_with_duplicate_replay_id + response = stub_comms do + @gateway.purchase(@amount, @invalid_credit_card, @options.merge(replay_id: @replay_id)) + end.respond_with(failed_purchase_response_with_replay_id) + assert_failure response + + duplicate_response = stub_comms do + @gateway.purchase(@amount, @invalid_credit_card, @options.merge(replay_id: response.params['replayId'])) + end.respond_with(failed_purchase_response_with_replay_id) + assert_failure duplicate_response + + assert_equal response.params['id'], duplicate_response.params['id'] + end + + def test_successful_settled_purchase_recalled_with_replay_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: '10101010101010101')) + end.respond_with(successful_purchase_response_with_settled_transaction) + assert_success response + end + + def test_successful_voided_purchase_recalled_with_replay_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(replay_id: '333333')) + end.respond_with(successful_response_with_voided_transaction) + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.respond_with(successful_credit_response) + + assert_success response + assert_equal @approval_message, response.message + assert_equal 'Return', response.params['type'] + assert response.test? + end + + def test_failed_credit_invalid_credit_card_month + response = stub_comms do + @gateway.credit(@amount, @invalid_credit_card, @options) + end.respond_with(failed_credit_response) + + assert_failure response + assert_equal 'ValidationError', response.error_code + assert_equal 'Year, Month, and Day parameters describe an un-representable DateTime.', response.message + assert response.test? + end + + def successful_refund_response + %( + { + "created": "2021-08-03T04:11:24.51Z", + "paymentToken": "PdSp5zrZBr0Jwx34gbEGoZHkPzWRxXBJ", + "originalId": 10000001625073, + "id": 10000001625074, + "creatorName": "tester-api", + "isDuplicate": false, + "merchantId": 12345678, + "batch": "0001", + "batchId": 10000000227764, + "tenderType": "Card", + "currency": "USD", + "amount": "-3.21", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "PdSp5zrZBr0Jwx34gbEGoZHkPzWRxXBJ", + "expiryMonth": "02", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPSe8b", + "status": "Approved", + "risk": {}, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully. ", + "availableAuthAmount": "0", + "reference": "121504000047", + "tax": "0.04", + "invoice": "V00KCLJT", + "customerCode": "PTHHV00KCLJT", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "3.17", + "quantity": 1, + "taxRate": "0.0126182965299684542586750789", + "taxAmount": "0.04", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "3.21", + "lineItemId": 0 + } + ], + "clientReference": "PTHHV00KCLJT", + "type": "Return", + "taxExempt": false, + "reviewIndicator": 0, + "source": "Tester", + "shouldGetCreditCardLevel": false + } + ) + end + + def failed_void_response + '{ + "errorCode": "Unauthorized", + "message": "Unauthorized", + "details": [ + "Original Payment Not Found Or You Do Not Have Access." + ], + "responseCode": "eENKmhrToV9UYxsXAh7iGAQ" + }' + end + + def transcript + '{ + "amount":"100", + "currency":"USD", + "email":"john@trexle.com", + "ip_address":"66.249.79.118", + "description":"Store Purchase 1437598192", + "card":{ + "number":"5555555555554444", + "expiry_month":9, + "expiry_year":2017, + "cvc":"123", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + + def scrubbed_transcript + '{ + "amount":"100", + "currency":"USD", + "email":"john@trexle.com", + "ip_address":"66.249.79.118", + "description":"Store Purchase 1437598192", + "card":{ + "number":"[FILTERED]", + "expiry_month":9, + "expiry_year":2017, + "cvc":"[FILTERED]", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + + def successful_purchase_response + %( + { + "created": "2021-07-25T12:54:37.327Z", + "paymentToken": "Px0NmeG5uPe4xb9wQHq5WWHasBtIYloZ", + "id": 10000001620265, + "creatorName": "Mike B", + "isDuplicate": false, + "shouldVaultCard": true, + "merchantId": 12345678, + "batch": "0009", + "batchId": 10000000227516, + "tenderType": "Card", + "currency": "USD", + "amount": "9.87", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "Px0NmeG5uPe4xb9wQHq5WWHasBtIYloZ", + "expiryMonth": "02", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPSe7b", + "status": "Approved", + "risk": { + "cvvResponseCode": "M", + "cvvResponse": "Match", + "cvvMatch": true, + "avsResponse": "No Response from AVS", + "avsAddressMatch": false, + "avsZipMatch": false + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully. ", + "availableAuthAmount": "0", + "reference": "120612000096", + "tax": "0.12", + "invoice": "V00554CJ", + "customerCode": "PTHGV00554CJ", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "9.75", + "quantity": 1, + "taxRate": "0.0123076923076923076923076923", + "taxAmount": "0.12", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "9.87", + "lineItemId": 0 + } + ], + "clientReference": "PTHGV00554CJ", + "type": "Sale", + "taxExempt": false, + "reviewIndicator": 1, + "source": "Tester1", + "shouldGetCreditCardLevel": false + } +) + end + + def successful_purchase_response_with_replay_id + %( + { + "created": "2022-03-07T16:04:45.103Z", + "paymentToken": "PuUfnYT8Tt8YlNmIce1wkQamcjmJymuB", + "id": 86560202, + "creatorName": "API Key", + "replayId": #{@replay_id}, + "isDuplicate": false, + "shouldVaultCard": false, + "merchantId": 1000003310, + "batch": "0032", + "batchId": 10000000271187, + "tenderType": "Card", + "currency": "USD", + "amount": "0.02", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "PuUfnYT8Tt8YlNmIce1wkQamcjmJymuB", + "expiryMonth": "01", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPS9f4", + "status": "Approved", + "risk": { + "cvvResponseCode": "N", + "cvvResponse": "No Match", + "cvvMatch": false, + "avsResponseCode": "D", + "avsAddressMatch": true, + "avsZipMatch": true + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully", + "availableAuthAmount": "0", + "reference": "206616004772", + "shipAmount": "0.01", + "shipToZip": "55667", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Anita", + "description": "Dump", + "unitPrice": "0", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "0", + "lineItemId": 0 + }, + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Old Peculier", + "description": "Beer", + "unitPrice": "0", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "0", + "lineItemId": 0 + } + ], + "type": "Sale", + "taxExempt": false, + "reviewIndicator": 1, + "source": "API", + "shouldGetCreditCardLevel": false + } + ) + end + + def successful_purchase_response_with_settled_transaction + %( + { + "created": "2022-04-12T17:25:16.48Z", + "paymentToken": "P9NTT6JORP0kQsEW1mQOpQG2sWUwrZJq", + "id": 86816543, + "creatorName": "API Key", + "replayId": 10101010101010101, + "isDuplicate": false, + "shouldVaultCard": false, + "merchantId": 1000003310, + "batch": "0022", + "batchId": 10000000272639, + "tenderType": "Card", + "currency": "USD", + "amount": "0.02", + "cardAccount": { + "cardType": "MasterCard", + "entryMode": "Keyed", + "last4": "0008", + "cardId": "59n0xFxuRB77138B0zc3wZwljA8f", + "token": "P9NTT6JORP0kQsEW1mQOpQG2sWUwrZJq", + "expiryMonth": "12", + "expiryYear": "22", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPS4a2", + "status": "Settled", + "risk": { + "cvvResponseCode": "M", + "cvvResponse": "Match", + "cvvMatch": false, + "avsResponseCode": "X", + "avsAddressMatch": true, + "avsZipMatch": true + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "settledDate": "2022-04-12T17:38:26.283", + "cardPresent": false, + "authMessage": "Approved or completed successfully", + "availableAuthAmount": "0", + "reference": "210217004823", + "shipAmount": "0.01", + "shipToZip": "27705", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Cat Poster", + "description": "A sleeping cat", + "unitPrice": "0", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "0", + "lineItemId": 0 + } + ], + "type": "Sale", + "taxExempt": false, + "reviewIndicator": 0, + "source": "API", + "shouldGetCreditCardLevel": false + } +) + end + + def successful_response_with_voided_transaction + %( + { + "created": "2022-04-13T20:18:58.357Z", + "paymentToken": "PWagbvmFarc7V3vhN5llPE1pA11phsqB", + "id": 86823831, + "creatorName": "API Key", + "replayId": 333333, + "isDuplicate": false, + "merchantId": 1000003310, + "batch": "0029", + "batchId": 10000000272684, + "tenderType": "Card", + "currency": "USD", + "amount": "0.02", + "cardAccount": + { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "4242", + "cardId": "ESkW1RwQPcSW12HOH4wdBllGQMsf", + "token": "PWagbvmFarc7V3vhN5llPE1pA11phsqB", + "expiryMonth": "09", + "expiryYear": "23", + "hasContract": false, + "cardPresent": false + }, + "authOnly": false, + "authCode": "PPS42e", + "status": "Voided", + "risk": + { + "cvvResponseCode": "N", + "cvvResponse": "No Match", + "cvvMatch": false, + "avsResponseCode": "D", + "avsAddressMatch": true, + "avsZipMatch": true + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully", + "originalAmount": "0.02", + "availableAuthAmount": "0", + "reference": "210320005533", + "shipToZip": "K1C2N6", + "shipToCountry": "CA", + "type": "Sale", + "taxExempt": false, + "reviewIndicator": 1, + "source": "API", + "shouldGetCreditCardLevel": false + } +) + end + + def failed_purchase_response + %( + { + "created": "2021-07-25T14:59:46.617Z", + "paymentToken": "P3AmSeSyXQDRM0ioGlP05Q6ykRXXVaGx", + "id": 10000001620267, + "creatorName": "tester-api", + "isDuplicate": false, + "shouldVaultCard": true, + "merchantId": 12345678, + "batch": "0009", + "batchId": 10000000227516, + "tenderType": "Card", + "currency": "USD", + "amount": "411", + "cardAccount": { + "entryMode": "Keyed", + "cardId": "B6R6ItScfvnUDwHWjP6ea1OUVX0f", + "token": "P3AmSeSyXQDRM0ioGlP05Q6ykRXXVaGx", + "expiryMonth": "01", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "status": "Declined", + "risk": { + "avsResponse": "No Response from AVS", + "avsAddressMatch": false, + "avsZipMatch": false + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Invalid card number", + "availableAuthAmount": "0", + "reference": "120614000100", + "tax": "0.05", + "invoice": "V009M2JZ", + "customerCode": "PTHGV009M2JZ", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "4.06", + "quantity": 1, + "taxRate": "0.0123152709359605911330049261", + "taxAmount": "0.05", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "411", + "lineItemId": 0 + } + ], + "clientReference": "PTHGV009M2JZ", + "type": "Sale", + "taxExempt": false, + "source": "Tester", + "shouldGetCreditCardLevel": false + } +) + end + + def failed_purchase_response_with_replay_id + %( + { + "created": "2022-03-07T17:41:29.1Z", + "paymentToken": "PKWMpiNtZ1mlUk4E4d95UWirHfQDNLwv", + "id": 86560811, + "creatorName": "API Key", + "replayId": #{@replay_id}, + "isDuplicate": false, + "shouldVaultCard": false, + "merchantId": 1000003310, + "batch": "0050", + "batchId": 10000000271205, + "tenderType": "Card", + "currency": "USD", + "amount": "0.02", + "cardAccount": { + "entryMode": "Keyed", + "cardId": "B6R6ItScfvnUDwHWjP6ea1OUVX0f", + "token": "PKWMpiNtZ1mlUk4E4d95UWirHfQDNLwv", + "expiryMonth": "01", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "status": "Declined", + "risk": { + "avsResponse": "No Response from AVS", + "avsAddressMatch": false, + "avsZipMatch": false + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Invalid card number", + "availableAuthAmount": "0", + "reference": "206617005381", + "shipAmount": "0.01", + "shipToZip": "55667", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Anita", + "description": "Dump", + "unitPrice": "0", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "0", + "lineItemId": 0 + }, + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Old Peculier", + "description": "Beer", + "unitPrice": "0", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "0", + "lineItemId": 0 + } + ], + "type": "Sale", + "taxExempt": false, + "source": "API", + "shouldGetCreditCardLevel": false + } + ) + end + + def successful_authorize_response + %( + { + "created": "2021-07-25T17:58:07.263Z", + "paymentToken": "PkEcPvkJ9DloiT26r5u6GmXV8yIevwcp", + "id": 10000001620268, + "creatorName": "Mike B", + "isDuplicate": false, + "shouldVaultCard": true, + "merchantId": 12345678, + "tenderType": "Card", + "currency": "USD", + "amount": "3.32", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "PkEcPvkJ9DloiT26r5u6GmXV8yIevwcp", + "expiryMonth": "02", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": true, + "authCode": "PPS72f", + "status": "Approved", + "risk": { + "cvvResponseCode": "M", + "cvvResponse": "Match", + "cvvMatch": true, + "avsResponse": "No Response from AVS", + "avsAddressMatch": false, + "avsZipMatch": false + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully. ", + "availableAuthAmount": "3.32", + "reference": "120617000104", + "invoice": "V00FZF87", + "customerCode": "PTHGV00FZF87", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "3.32", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "3.32", + "lineItemId": 0 + } + ], + "clientReference": "PTHGV00FZF87", + "type": "Authorization", + "taxExempt": false, + "reviewIndicator": 1, + "source": "Tester1", + "shouldGetCreditCardLevel": false + } + ) + end + + def failed_authorize_response + %( + { + "created": "2021-07-25T20:32:47.84Z", + "paymentToken": "PyzLzQBl8xAgjKYyrDfbA0Dbs39mopvN", + "id": 10000001620269, + "creatorName": "tester-api", + "isDuplicate": false, + "shouldVaultCard": true, + "merchantId": 12345678, + "tenderType": "Card", + "currency": "USD", + "amount": "411", + "cardAccount": { + "entryMode": "Keyed", + "cardId": "B6R6ItScfvnUDwHWjP6ea1OUVX0f", + "token": "PyzLzQBl8xAgjKYyrDfbA0Dbs39mopvN", + "expiryMonth": "01", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": true, + "status": "Declined", + "risk": { + "avsResponse": "No Response from AVS", + "avsAddressMatch": false, + "avsZipMatch": false + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Invalid card number", + "availableAuthAmount": "411", + "reference": "120620000107", + "invoice": "V00LIC5Y", + "customerCode": "PTHGV00LIC5Y", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "411", + "quantity": 1, + "taxRate": "0", + "taxAmount": "0", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "411", + "lineItemId": 0 + } + ], + "clientReference": "PTHGV00LIC5Y", + "type": "Authorization", + "taxExempt": false, + "source": "Tester", + "shouldGetCreditCardLevel": false + } + ) + end + + def successful_capture_response + %( + { + "created": "2021-08-03T03:10:38.543Z", + "paymentToken": "PaQLIYLRdWtcFKl5VaKTdUVxMutXJ5Ru", + "originalId": 10000001625060, + "id": 10000001625061, + "creatorName": "tester-api", + "isDuplicate": false, + "merchantId": 12345678, + "batch": "0016", + "batchId": 10000000227758, + "tenderType": "Card", + "currency": "USD", + "amount": "7.99", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "PaQLIYLRdWtcFKl5VaKTdUVxMutXJ5Ru", + "expiryMonth": "01", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPSbf7", + "status": "Approved", + "risk": {}, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved", + "availableAuthAmount": "0", + "reference": "121503000033", + "tax": "0.1", + "invoice": "V00ICCMR", + "customerCode": "PTHHV00ICLFZ", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "7.89", + "quantity": 1, + "taxRate": "0.0126742712294043092522179975", + "taxAmount": "0.1", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "7.99", + "lineItemId": 0 + } + ], + "clientReference": "PTHHV00ICLFZ", + "type": "SaleCompletion", + "reviewIndicator": 0, + "source": "Tester", + "shouldGetCreditCardLevel": false + } + ) + end + + def failed_capture_response + %( + {"created":"2022-04-06T16:54:08.9Z","paymentToken":"PHubmbgcqEPVUI2HmOAr2sF7Vl33MnuJ","id":86777943,"creatorName":"API Key","isDuplicate":false,"merchantId":12345678,"batch":"0028","batchId":10000000272426,"tenderType":"Card","currency":"USD","amount":"0.02","cardAccount":{"token":"PHubmbgcqEPVUI2HmOAr2sF7Vl33MnuJ","hasContract":false,"cardPresent":false},"posData":{"panCaptureMethod":"Manual"},"authOnly":false,"status":"Declined","risk":{},"requireSignature":false,"settledAmount":"0","settledCurrency":"USD","cardPresent":false,"authMessage":"Original Transaction Not Found","availableAuthAmount":"0","reference":"209616004816","type":"Sale","source":"API","shouldGetCreditCardLevel":false} + ) + end + + def successful_void_response + %( + # + ) + end + + def successful_refund_purchase_response + %( + { + "created": "2021-07-27T02:14:55.477Z", + "paymentToken": "PU2QSwaBlKx5OEzBKavi1L0Dy9yIMSEx", + "originalId": 10000001620800, + "id": 10000001620801, + "creatorName": "Mike B", + "isDuplicate": false, + "merchantId": 12345678, + "batch": "0001", + "batchId": 10000000227556, + "tenderType": "Card", + "currency": "USD", + "amount": "-14.14", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "PU2QSwaBlKx5OEzBKavi1L0Dy9yIMSEx", + "expiryMonth": "02", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPS39c", + "status": "Approved", + "risk": {}, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully. ", + "availableAuthAmount": "0", + "reference": "120802000004", + "tax": "0.17", + "invoice": "Z00C02TD", + "customerCode": "PTHGZ00C02TD", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 11042381, + "transactionIId": 0, + "transactionId": "10000001620800", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "13.97", + "quantity": 1, + "taxRate": "0.01", + "taxAmount": "0.17", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "14.14", + "lineItemId": 0 + } + ], + "clientReference": "PTHGZ00C02TD", + "type": "Return", + "taxExempt": false, + "reviewIndicator": 0, + "source": "Tester", + "shouldGetCreditCardLevel": false + } + ) + end + + def failed_duplicate_refund + %( + { + "created": "2021-07-27T04:35:58.397Z", + "paymentToken": "P9cjoRNccieQXBmDxEmXi2NjLKWtVF9A", + "originalId": 10000001620798, + "id": 10000001620802, + "creatorName": "tester-api", + "isDuplicate": false, + "merchantId": 12345678, + "batch": "0001", + "batchId": 10000000227556, + "tenderType": "Card", + "currency": "USD", + "amount": "-14.14", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "1111", + "cardId": "y15QvOteHZGBm7LH3GNIlTWbA1If", + "token": "P9cjoRNccieQXBmDxEmXi2NjLKWtVF9A", + "expiryMonth": "02", + "expiryYear": "29", + "hasContract": false, + "cardPresent": false, + "isDebit": false, + "isCorp": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPSdda", + "status": "Declined", + "risk": {}, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Payment already refunded", + "availableAuthAmount": "0", + "reference": "120804000007", + "tax": "0.17", + "invoice": "Z001MFP5", + "customerCode": "PTHGZ001MFP5", + "shipToCountry": "USA", + "purchases": [ + { + "dateCreated": "0001-01-01T00:00:00", + "iId": 0, + "transactionIId": 0, + "transactionId": "0", + "name": "Miscellaneous", + "description": "Miscellaneous", + "code": "MISC", + "unitOfMeasure": "EA", + "unitPrice": "13.97", + "quantity": 1, + "taxRate": "0.0121689334287759484609878311", + "taxAmount": "0.17", + "discountRate": "0", + "discountAmount": "0", + "extendedAmount": "14.14", + "lineItemId": 0 + } + ], + "clientReference": "PTHGZ001MFP5", + "type": "Return", + "taxExempt": false, + "source": "Tester", + "shouldGetCreditCardLevel": false + } + ) + end + + def pre_scrubbed + %( + {\"achIndicator\":null,\"amount\":2.11,\"authCode\":null,\"authOnly\":false,\"bankAccount\":null,\"cardAccount\":{\"avsStreet\":\"1\",\"avsZip\":\"88888\",\"cvv\":\"123\",\"entryMode\":\"Keyed\",\"expiryDate\":\"01/29\",\"expiryMonth\":\"01\",\"expiryYear\":\"29\",\"last4\":null,\"magstripe\":null,\"number\":\"4111111111111111\"},\"cardPresent\":false,\"cardPresentType\":\"CardNotPresent\",\"isAuth\":true,\"isSettleFunds\":true,\"isTicket\":false,\"merchantId\":12345678,\"mxAdvantageEnabled\":false,\"mxAdvantageFeeLabel\":\"\",\"paymentType\":\"Sale\",\"purchases\":[{\"taxRate\":\"0.0000\",\"additionalTaxRate\":null,\"discountRate\":null}],\"shouldGetCreditCardLevel\":true,\"shouldVaultCard\":true,\"source\":\"Tester\",\"sourceZip\":\"K1C2N6\",\"taxExempt\":false,\"tenderType\":\"Card\",\"terminals\":[]} + ) + end + + def post_scrubbed + %( + {\"achIndicator\":null,\"amount\":2.11,\"authCode\":null,\"authOnly\":false,\"bankAccount\":null,\"cardAccount\":{\"avsStreet\":\"1\",\"avsZip\":\"88888\",\"cvv\":\"[FILTERED]\",\"entryMode\":\"Keyed\",\"expiryDate\":\"01/29\",\"expiryMonth\":\"01\",\"expiryYear\":\"29\",\"last4\":null,\"magstripe\":null,\"number\":\"[FILTERED]\"},\"cardPresent\":false,\"cardPresentType\":\"CardNotPresent\",\"isAuth\":true,\"isSettleFunds\":true,\"isTicket\":false,\"merchantId\":12345678,\"mxAdvantageEnabled\":false,\"mxAdvantageFeeLabel\":\"\",\"paymentType\":\"Sale\",\"purchases\":[{\"taxRate\":\"0.0000\",\"additionalTaxRate\":null,\"discountRate\":null}],\"shouldGetCreditCardLevel\":true,\"shouldVaultCard\":true,\"source\":\"Tester\",\"sourceZip\":\"K1C2N6\",\"taxExempt\":false,\"tenderType\":\"Card\",\"terminals\":[]} + ) + end + + def successful_credit_response + %( + { + "created": "2022-07-21T15:12:48.543Z", + "paymentToken": "PfUCohlRQpcR1cKarXkKFHV4pjcX2RJl", + "id": 87523059, + "creatorName": "spreedlyprapi", + "replayId": 16584146247154989, + "isDuplicate": false, + "shouldVaultCard": true, + "merchantId": 1000003310, + "batch": "0015", + "batchId": 10000000275553, + "tenderType": "Card", + "currency": "USD", + "amount": "-26.34", + "meta": "I like beer", + "cardAccount": { + "cardType": "Visa", + "entryMode": "Keyed", + "last4": "4242", + "cardId": "ESkW1RwQPcSW12HOH4wdBllGQMsf", + "token": "PfUCohlRQpcR1cKarXkKFHV4pjcX2RJl", + "expiryMonth": "09", + "expiryYear": "23", + "hasContract": false, + "cardPresent": false + }, + "posData": { + "panCaptureMethod": "Manual" + }, + "authOnly": false, + "authCode": "PPS6bf", + "status": "Approved", + "risk": { + "cvvResponseCode": "N", + "cvvResponse": "No Match", + "cvvMatch": false, + "avsResponseCode": "D", + "avsAddressMatch": true, + "avsZipMatch": true + }, + "requireSignature": false, + "settledAmount": "0", + "settledCurrency": "USD", + "cardPresent": false, + "authMessage": "Approved or completed successfully. ", + "availableAuthAmount": "0", + "reference": "220215004077", + "tax": "0", + "invoice": "12345", + "type": "Return", + "taxExempt": false, + "reviewIndicator": 1, + "source": "Spreedly", + "shouldGetCreditCardLevel": false + } + ) + end + + def failed_credit_response + %( + { + "errorCode": "ValidationError", + "message": "Validation error happened", + "details": [ + "Year, Month, and Day parameters describe an un-representable DateTime." + ], + "responseCode": "eSD7row8WL3JkUkOymB3FlQ" + } + ) + end +end diff --git a/test/unit/gateways/pro_pay_test.rb b/test/unit/gateways/pro_pay_test.rb index 3064da173bd..f96b6f0a474 100644 --- a/test/unit/gateways/pro_pay_test.rb +++ b/test/unit/gateways/pro_pay_test.rb @@ -89,7 +89,7 @@ def test_failed_refund def test_successful_void response = stub_comms do @gateway.void('auth', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(07), data) end.respond_with(successful_void_response) @@ -99,7 +99,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('invalid-auth', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r(07), data) end.respond_with(failed_void_response) @@ -157,7 +157,7 @@ def test_does_not_send_dashed_zip_code stub_comms do @gateway.purchase(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/123453456\n\n 5ab9cddef2e4911b77e0c4ffb70f03\n partner\n \n 100\n USD\n 4747474747474747\n 0918\n 999\n Longbob Longsen\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n 32287391\n 04\n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" --> "Pragma: no-cache\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Encoding: gzip\r\n" --> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" --> "Vary: Accept-Encoding\r\n" --> "Server: Microsoft-IIS/7.5\r\n" --> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" --> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" --> "Connection: close\r\n" --> "Content-Length: 343\r\n" --> "\r\n" -reading 343 bytes... --> "" -read 343 bytes -Conn close + <<~RESPONSE + opening connection to xmltest.propay.com:443... + opened + starting SSL for xmltest.propay.com:443... + SSL established + <- "POST /API/PropayAPI.aspx HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: xmltest.propay.com\r\nContent-Length: 547\r\n\r\n" + <- "\n\n 5ab9cddef2e4911b77e0c4ffb70f03\n partner\n \n 100\n USD\n 4747474747474747\n 0918\n 999\n Longbob Longsen\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n 32287391\n 04\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" + -> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 343\r\n" + -> "\r\n" + reading 343 bytes... + -> "" + read 343 bytes + Conn close RESPONSE end def post_scrubbed - <<-POST_SCRUBBED -opening connection to xmltest.propay.com:443... -opened -starting SSL for xmltest.propay.com:443... -SSL established -<- "POST /API/PropayAPI.aspx HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: xmltest.propay.com\r\nContent-Length: 547\r\n\r\n" -<- "\n\n [FILTERED]\n partner\n \n 100\n USD\n [FILTERED]\n 0918\n [FILTERED]\n Longbob Longsen\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n 32287391\n 04\n \n\n" --> "HTTP/1.1 200 OK\r\n" --> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" --> "Pragma: no-cache\r\n" --> "Content-Type: text/xml; charset=utf-8\r\n" --> "Content-Encoding: gzip\r\n" --> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" --> "Vary: Accept-Encoding\r\n" --> "Server: Microsoft-IIS/7.5\r\n" --> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" --> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" --> "Connection: close\r\n" --> "Content-Length: 343\r\n" --> "\r\n" -reading 343 bytes... --> "" -read 343 bytes -Conn close + <<~POST_SCRUBBED + opening connection to xmltest.propay.com:443... + opened + starting SSL for xmltest.propay.com:443... + SSL established + <- "POST /API/PropayAPI.aspx HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: xmltest.propay.com\r\nContent-Length: 547\r\n\r\n" + <- "\n\n [FILTERED]\n partner\n \n 100\n USD\n [FILTERED]\n 0918\n [FILTERED]\n Longbob Longsen\n 456 My Street\n Apt 1\n Ottawa\n ON\n K1C2N6\n 32287391\n 04\n \n\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" + -> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 343\r\n" + -> "\r\n" + reading 343 bytes... + -> "" + read 343 bytes + Conn close POST_SCRUBBED end diff --git a/test/unit/gateways/psigate_test.rb b/test/unit/gateways/psigate_test.rb index 1bcb49689fb..a8561b40256 100644 --- a/test/unit/gateways/psigate_test.rb +++ b/test/unit/gateways/psigate_test.rb @@ -3,13 +3,13 @@ class PsigateTest < Test::Unit::TestCase def setup @gateway = PsigateGateway.new( - :login => 'teststore', - :password => 'psigate1234' + login: 'teststore', + password: 'psigate1234' ) @amount = 100 @credit_card = credit_card('4111111111111111') - @options = { :order_id => '1', :billing_address => address } + @options = { order_id: '1', billing_address: address } end def test_successful_authorization @@ -70,7 +70,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express], PsigateGateway.supported_cardtypes + assert_equal %i[visa master american_express], PsigateGateway.supported_cardtypes end def test_avs_result @@ -95,92 +95,92 @@ def test_scrub private def successful_authorization_response - <<-RESPONSE - - - Sun Jan 06 23:10:53 EST 2008 - 1000 - PREAUTH - APPROVED - Y:123456:0abcdef:M:X:NNN - - 0.00 - 0.00 - 24.00 - 24.00 - CC - ......4242 - 1bdde305d7658367 - M - X - 123456 - 0abcdef - VISA - NNN - UN - UNKNOWN - UNKNOWN - + <<~RESPONSE + + + Sun Jan 06 23:10:53 EST 2008 + 1000 + PREAUTH + APPROVED + Y:123456:0abcdef:M:X:NNN + + 0.00 + 0.00 + 24.00 + 24.00 + CC + ......4242 + 1bdde305d7658367 + M + X + 123456 + 0abcdef + VISA + NNN + UN + UNKNOWN + UNKNOWN + RESPONSE end def successful_purchase_response - <<-RESPONSE - - - Sun Jan 06 23:15:30 EST 2008 - 1000 - SALE - APPROVED - Y:123456:0abcdef:M:X:NNN - - 0.00 - 0.00 - 24.00 - 24.00 - CC - ......4242 - 1bdde305da3ee234 - M - X - 123456 - 0abcdef - VISA - NNN - UN - UNKNOWN - UNKNOWN - + <<~RESPONSE + + + Sun Jan 06 23:15:30 EST 2008 + 1000 + SALE + APPROVED + Y:123456:0abcdef:M:X:NNN + + 0.00 + 0.00 + 24.00 + 24.00 + CC + ......4242 + 1bdde305da3ee234 + M + X + 123456 + 0abcdef + VISA + NNN + UN + UNKNOWN + UNKNOWN + RESPONSE end def failed_purchase_response - <<-RESPONSE - - - Sun Jan 06 23:24:29 EST 2008 - b3dca49e3ec77e42ab80a0f0f590fff0 - SALE - DECLINED - N:TESTDECLINE - - 0.00 - 0.00 - 24.00 - 24.00 - CC - ......4242 - 1bdde305df991f89 - M - X - TEST - TESTTRANS - VISA - NNN - UN - UNKNOWN - UNKNOWN - + <<~RESPONSE + + + Sun Jan 06 23:24:29 EST 2008 + b3dca49e3ec77e42ab80a0f0f590fff0 + SALE + DECLINED + N:TESTDECLINE + + 0.00 + 0.00 + 24.00 + 24.00 + CC + ......4242 + 1bdde305df991f89 + M + X + TEST + TESTTRANS + VISA + NNN + UN + UNKNOWN + UNKNOWN + RESPONSE end @@ -193,14 +193,14 @@ def xml_capture_fixture end def pre_scrubbed - <<-PRE_SCRUBBED + <<~PRE_SCRUBBED teststorepsigate12341b7b4b36bf61e972a9e6a6be8fff15d8jack@example.comCC024.00424242424242424209141123Jim Smith1234 My StreetApt 1OttawaONK1C2N6CAWidgets Inc ......4242 PRE_SCRUBBED end def post_scrubbed - <<-POST_SCRUBBED + <<~POST_SCRUBBED teststore[FILTERED]1b7b4b36bf61e972a9e6a6be8fff15d8jack@example.comCC024.00[FILTERED]09141[FILTERED]Jim Smith1234 My StreetApt 1OttawaONK1C2N6CAWidgets Inc [FILTERED] POST_SCRUBBED diff --git a/test/unit/gateways/psl_card_test.rb b/test/unit/gateways/psl_card_test.rb index 6a971d1815e..64de15284b2 100644 --- a/test/unit/gateways/psl_card_test.rb +++ b/test/unit/gateways/psl_card_test.rb @@ -1,17 +1,16 @@ require 'test_helper' class PslCardTest < Test::Unit::TestCase - def setup @gateway = PslCardGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' - ) + login: 'LOGIN', + password: 'PASSWORD' + ) @credit_card = credit_card @options = { - :billing_address => address, - :description => 'Store purchase' + billing_address: address, + description: 'Store purchase' } @amount = 100 end @@ -36,7 +35,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :diners_club, :jcb, :maestro ], PslCardGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club jcb maestro], PslCardGateway.supported_cardtypes end def test_avs_result diff --git a/test/unit/gateways/qbms_test.rb b/test/unit/gateways/qbms_test.rb index 894a3e0571d..f526c67865c 100644 --- a/test/unit/gateways/qbms_test.rb +++ b/test/unit/gateways/qbms_test.rb @@ -5,9 +5,10 @@ def setup Base.mode = :test @gateway = QbmsGateway.new( - :login => 'test', - :ticket => 'abc123', - :pem => 'PEM') + login: 'test', + ticket: 'abc123', + pem: 'PEM' + ) @amount = 100 @card = credit_card('4111111111111111') @@ -45,7 +46,7 @@ def test_truncated_address_is_sent with(anything, regexp_matches(/12345 Ridiculously Lengthy Roa\<.*445566778\ address.update(:address1 => '12345 Ridiculously Lengthy Road Name', :zip => '4455667788') } + options = { billing_address: address.update(address1: '12345 Ridiculously Lengthy Road Name', zip: '4455667788') } assert response = @gateway.purchase(@amount, @card, options) assert_success response end @@ -53,7 +54,7 @@ def test_truncated_address_is_sent def test_partial_address_is_ok @gateway.expects(:ssl_post).returns(charge_response) - options = { :billing_address => address.update(:address1 => nil, :zip => nil) } + options = { billing_address: address.update(address1: nil, zip: nil) } assert response = @gateway.purchase(@amount, @card, options) assert_success response end @@ -90,17 +91,17 @@ def test_avs_result assert_equal 'Y', response.avs_result['street_match'] assert_equal 'Y', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => 'Fail')) + @gateway.expects(:ssl_post).returns(authorization_response(avs_street: 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.avs_result['street_match'] assert_equal 'Y', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_zip => 'Fail')) + @gateway.expects(:ssl_post).returns(authorization_response(avs_zip: 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'Y', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => 'Fail', :avs_zip => 'Fail')) + @gateway.expects(:ssl_post).returns(authorization_response(avs_street: 'Fail', avs_zip: 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] @@ -111,11 +112,11 @@ def test_cvv_result assert response = @gateway.authorize(@amount, @card) assert_equal 'M', response.cvv_result['code'] - @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => 'Fail')) + @gateway.expects(:ssl_post).returns(authorization_response(card_security_code_match: 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.cvv_result['code'] - @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => 'NotAvailable')) + @gateway.expects(:ssl_post).returns(authorization_response(card_security_code_match: 'NotAvailable')) assert response = @gateway.authorize(@amount, @card) assert_equal 'P', response.cvv_result['code'] end @@ -129,7 +130,7 @@ def test_successful_query end def test_failed_signon - @gateway.expects(:ssl_post).returns(query_response(:signon_status_code => 2000)) + @gateway.expects(:ssl_post).returns(query_response(signon_status_code: 2000)) assert response = @gateway.query() assert_instance_of Response, response @@ -137,7 +138,7 @@ def test_failed_signon end def test_failed_authorization - @gateway.expects(:ssl_post).returns(authorization_response(:status_code => 10301)) + @gateway.expects(:ssl_post).returns(authorization_response(status_code: 10301)) assert response = @gateway.authorize(@amount, @card) assert_instance_of Response, response @@ -147,7 +148,7 @@ def test_failed_authorization def test_use_test_url_when_overwriting_with_test_option ActiveMerchant::Billing::Base.mode = :production - @gateway = QbmsGateway.new(:login => 'test', :ticket => 'abc123', :test => true) + @gateway = QbmsGateway.new(login: 'test', ticket: 'abc123', test: true) @gateway.stubs(:parse).returns({}) @gateway.expects(:ssl_post).with(QbmsGateway.test_url, anything, anything).returns(authorization_response) @gateway.authorize(@amount, @card) @@ -173,9 +174,9 @@ def query_response(opts = {}) def authorization_response(opts = {}) opts = { - :avs_street => 'Pass', - :avs_zip => 'Pass', - :card_security_code_match => 'Pass', + avs_street: 'Pass', + avs_zip: 'Pass', + card_security_code_match: 'Pass' }.merge(opts) wrap 'CustomerCreditCardAuth', opts, <<-"XML" @@ -200,9 +201,9 @@ def capture_response(opts = {}) def charge_response(opts = {}) opts = { - :avs_street => 'Pass', - :avs_zip => 'Pass', - :card_security_code_match => 'Pass', + avs_street: 'Pass', + avs_zip: 'Pass', + card_security_code_match: 'Pass' }.merge(opts) wrap 'CustomerCreditCardCharge', opts, <<-"XML" @@ -236,9 +237,9 @@ def credit_response(opts = {}) def wrap(type, opts, xml) opts = { - :signon_status_code => 0, - :request_id => 'x', - :status_code => 0, + signon_status_code: 0, + request_id: 'x', + status_code: 0 }.merge(opts) <<-"XML" @@ -257,7 +258,7 @@ def wrap(type, opts, xml) - XML + XML end def pre_scrubbed diff --git a/test/unit/gateways/quantum_test.rb b/test/unit/gateways/quantum_test.rb index 10bb11ccaf4..3af7c0ae3f7 100644 --- a/test/unit/gateways/quantum_test.rb +++ b/test/unit/gateways/quantum_test.rb @@ -3,16 +3,16 @@ class QuantumTest < Test::Unit::TestCase def setup @gateway = QuantumGateway.new( - :login => '', - :password => '' - ) + login: '', + password: '' + ) @credit_card = credit_card @amount = 100 @options = { - :billing_address => address, - :description => 'Store Purchase' + billing_address: address, + description: 'Store Purchase' } end diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb index 7d59f157ba5..7e48cce44ef 100644 --- a/test/unit/gateways/quickbooks_test.rb +++ b/test/unit/gateways/quickbooks_test.rb @@ -4,7 +4,7 @@ class QuickBooksTest < Test::Unit::TestCase include CommStub def setup - @gateway = QuickbooksGateway.new( + @oauth_1_gateway = QuickbooksGateway.new( consumer_key: 'consumer_key', consumer_secret: 'consumer_secret', access_token: 'access_token', @@ -12,6 +12,13 @@ def setup realm: 'realm_ID' ) + @oauth_2_gateway = QuickbooksGateway.new( + client_id: 'client_id', + client_secret: 'client_secret', + access_token: 'access_token', + refresh_token: 'refresh_token' + ) + @credit_card = credit_card @amount = 100 @@ -21,105 +28,232 @@ def setup description: 'Store Purchase' } - @authorization = 'ECZ7U0SO423E' + @authorization = 'ECZ7U0SO423E|d40f8a8007ba1af90a656d7f6371f641' + @authorization_no_request_id = 'ECZ7U0SO423E' end def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - - assert_equal 'EF1IQ9GGXS2D', response.authorization - assert response.test? + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_purchase_response) + response = gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_match(/EF1IQ9GGXS2D|/, response.authorization) + assert response.test? + end end def test_failed_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(failed_purchase_response) - response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end end def test_successful_authorize - @gateway.expects(:ssl_post).returns(successful_authorize_response) - response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - - assert_equal @authorization, response.authorization - assert response.test? + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_authorize_response) + response = gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_match(/ECZ7U0SO423E|/, response.authorization) + assert response.test? + end end def test_failed_authorize - @gateway.expects(:ssl_post).returns(failed_authorize_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(failed_authorize_response) - response = @gateway.authorize(@amount, @credit_card, @options) - assert_failure response - assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + response = gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end end def test_successful_capture - @gateway.expects(:ssl_post).returns(successful_capture_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_capture_response) - response = @gateway.capture(@amount, @authorization) - assert_success response + response = gateway.capture(@amount, @authorization) + assert_success response + end + end + + def test_successful_capture_when_authorization_does_not_include_request_id + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_capture_response) + + response = gateway.capture(@amount, @authorization_no_request_id) + assert_success response + end end def test_failed_capture - @gateway.expects(:ssl_post).returns(failed_capture_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(failed_capture_response) - response = @gateway.capture(@amount, @authorization) - assert_failure response + response = gateway.capture(@amount, @authorization) + assert_failure response + end end def test_successful_refund - @gateway.expects(:ssl_post).returns(successful_refund_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_refund_response) - response = @gateway.refund(@amount, @authorization) - assert_success response + response = gateway.refund(@amount, @authorization) + assert_success response + end + end + + def test_successful_refund_when_authorization_does_not_include_request_id + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(successful_refund_response) + + response = gateway.refund(@amount, @authorization_no_request_id) + assert_success response + end end def test_failed_refund - @gateway.expects(:ssl_post).returns(failed_refund_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + gateway.expects(:ssl_post).returns(failed_refund_response) - response = @gateway.refund(@amount, @authorization) - assert_failure response + response = gateway.refund(@amount, @authorization) + assert_failure response + end end def test_successful_verify - response = stub_comms do - @gateway.verify(@credit_card) - end.respond_with(successful_authorize_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + response = stub_comms(gateway) do + gateway.verify(@credit_card) + end.respond_with(successful_authorize_response) - assert_success response + assert_success response + end end def test_failed_verify - response = stub_comms do - @gateway.verify(@credit_card, @options) - end.respond_with(failed_authorize_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + response = stub_comms(gateway) do + gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_not_nil response.message + end + end - assert_failure response - assert_not_nil response.message + def test_successful_void + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + response = stub_comms(gateway) do + gateway.void(@authorization) + end.respond_with(successful_void_response) + + assert_success response + end + end + + def test_failed_void + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + response = stub_comms(gateway) do + gateway.void(@authorization) + end.respond_with(failed_void_response) + + assert_failure response + end end - def test_scrub - assert @gateway.supports_scrubbing? - assert_equal @gateway.send(:scrub, pre_scrubbed), post_scrubbed + def test_scrub_oauth_1 + assert @oauth_1_gateway.supports_scrubbing? + assert_equal @oauth_1_gateway.send(:scrub, pre_scrubbed), post_scrubbed + end + + def test_scrub_oauth_2 + assert @oauth_2_gateway.supports_scrubbing? + assert_equal @oauth_2_gateway.send(:scrub, pre_scrubbed_oauth_2), post_scrubbed_oauth_2 end def test_scrub_with_small_json - assert_equal @gateway.scrub(pre_scrubbed_small_json), post_scrubbed_small_json + assert_equal @oauth_1_gateway.scrub(pre_scrubbed_small_json), post_scrubbed_small_json end def test_default_context - stub_comms do - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |_endpoint, data, _headers| - json = JSON.parse(data) - refute json.fetch('context').fetch('mobile') - assert json.fetch('context').fetch('isEcommerce') - end.respond_with(successful_purchase_response) + [@oauth_1_gateway, @oauth_2_gateway].each do |gateway| + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + json = JSON.parse(data) + refute json.fetch('context').fetch('mobile') + assert json.fetch('context').fetch('isEcommerce') + end.respond_with(successful_purchase_response) + end + end + + def test_refresh_does_not_occur_for_oauth_1 + @oauth_1_gateway.expects(:ssl_post).with( + anything, + Not(regexp_matches(%r{grant_type=refresh_token})), + anything + ).returns(successful_purchase_response) + + response = @oauth_1_gateway.purchase(@amount, @credit_card, @options.merge(allow_refresh: true)) + + assert_success response + + assert_match(/EF1IQ9GGXS2D|/, response.authorization) + assert response.test? + end + + def test_refresh_does_not_occur_when_token_valid_for_oauth_2 + @oauth_2_gateway.expects(:ssl_post).with( + anything, + Not(regexp_matches(%r{grant_type=refresh_token})), + has_entries('Authorization' => 'Bearer access_token') + ).returns(successful_purchase_response) + + response = @oauth_2_gateway.purchase(@amount, @credit_card, @options.merge(allow_refresh: true)) + assert_success response + end + + def test_refresh_does_occur_when_token_invalid_for_oauth_2 + @oauth_2_gateway.expects(:ssl_post).with( + anything, + anything, + has_entries('Authorization' => 'Bearer access_token') + ).returns(authentication_failed_oauth_2_response) + + @oauth_2_gateway.expects(:ssl_post).with( + anything, + all_of(regexp_matches(%r{grant_type=refresh_token})), + anything + ).returns(successful_refresh_token_response) + + @oauth_2_gateway.expects(:ssl_post).with( + anything, + anything, + has_entries('Authorization' => 'Bearer new_access_token') + ).returns(successful_purchase_response) + + response = @oauth_2_gateway.purchase(@amount, @credit_card, @options.merge(allow_refresh: true)) + assert_success response + + assert_match(/EF1IQ9GGXS2D|/, response.authorization) + assert response.test? + end + + def test_authorization_failed_code_results_in_failure + @oauth_2_gateway.expects(:ssl_post).returns(authorization_failed_oauth_2_response) + + response = @oauth_2_gateway.authorize(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'AuthorizationFailed', response.error_code end private @@ -333,6 +467,42 @@ def failed_void_response RESPONSE end + def authentication_failed_oauth_2_response + <<-RESPONSE + { + "code": "AuthenticationFailed", + "type": "INPUT", + "message": null, + "detail": null, + "moreInfo": null + } + RESPONSE + end + + def authorization_failed_oauth_2_response + <<-RESPONSE + { + "code": "AuthorizationFailed", + "type": "INPUT", + "message": null, + "detail": null, + "moreInfo": null + } + RESPONSE + end + + def successful_refresh_token_response + <<-RESPONSE + { + "x_refresh_token_expires_in": 8719040, + "refresh_token": "refresh_token", + "access_token": "new_access_token", + "token_type": "bearer", + "expires_in": 3600 + } + RESPONSE + end + def pre_scrubbed <<-PRE_SCRUBBED opening connection to sandbox.api.intuit.com:443... @@ -394,4 +564,150 @@ def post_scrubbed Conn close POST_SCRUBBED end + + def pre_scrubbed_oauth_2 + %q( + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: 3b098cc41f53562ec0f36a0fc7071ff8\r\nAccept: application/json\r\nAuthorization: Bearer eyabcd9ewjie0w9fj9jkewjaiojfiew0..rEVIND9few90zsg.CyFO4k9gR-t5yJsc0lxGrPPLGeO-JRa_5MZ_vG_H5AMlObrPpfhBRK51jUukhh0QOUjgkGm2jJb8c_haieKnkb3nY_W7giZyIG6d5g5XPqRZLhDnMCVVFHZyLIbBT_TDvZWROeOGY10xrDnUY5O05LYnOZc8gq7k_VTHHDrrmyeon3EmerAGjDUhnpp1DJRvR7SLUWgZQOuR997OuaP31_ZesKACzdVSw5QBJAhBeRqGl8LaNjjveQMo1c20CjWr_-c0EWbp0frMAA_UYaxtuzgRRs_opnMr4_PD7axQQevAzftSR1cQfUDAu_uybV5lyiUTfX80B3NBlLihWLiqCD9yWiYmup4TpNbapTL4x9CQz_WobicwWbhIJ7P1IrnxeJh2pW3ijjrBhbgLCCZ-6tcNUsD697ywn3YknT-iTSH-BIpGE_43bEOHyUtwZcIZIeb-6KtZIjQ_fjHfkRz66IrpP0V-XZ7_N5hJ7UIuQ34gOiuxdFJtbiMSUW1GnanJ9aRH8Fbzk_UzrWyuSs.XnsOxzQ\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 310\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"4000100011112224\",\"expMonth\":\"09\",\"expYear\":2020,\"cvc\":\"123\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"context\":{\"mobile\":false,\"isEcommerce\":true},\"capture\":\"true\"}" + -> "HTTP/1.1 401 Unauthorized\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:43 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 91\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "intuit_tid: adca0516-0af2-48cd-a704-095529fe615c\r\n" + -> "WWW-Authenticate: Bearer realm=\"Intuit\", error=\"invalid_token\"\r\n" + -> "\r\n" + reading 91 bytes... + -> "{\"code\":\"AuthenticationFailed\",\"type\":\"INPUT\",\"message\":null,\"detail\":null,\"moreInfo\":null}" + read 91 bytes + Conn close + opening connection to oauth.platform.intuit.com:443... + opened + starting SSL for oauth.platform.intuit.com:443... + SSL established + <- "POST /oauth2/v1/tokens/bearer HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nAuthorization: Basic QUI3QWFkWGZYRWZyRE1WN0k2a3RFYXoyT2hCeHdhVkdtZUU5N3pmeGdjSllPUU40Qmo6ZEVJcms2bHozclVvQ05wRXFSbFV6bFd6STBYRUtyeDBYcDdoYVd3RQ==\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: oauth.platform.intuit.com\r\nContent-Length: 89\r\n\r\n" + <- "grant_type=refresh_token&refresh_token=DE123456780s7AvBrjWjfiowji9IIKDU4zE237CmbGO" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:43 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1007\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=15552000\r\n" + -> "intuit_tid: c9aff174-410e-4683-8a35-2591c659550f\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "\r\n" + reading 1007 bytes... + -> "{\"x_refresh_token_expires_in\":8719040,\"refresh_token\":\"DE987654s7AvBrjWOCJYWsifjewaifjeiowja4zE237CmbGO\",\"access_token\":\"abcifejwiajJJJIDJFIEJQ0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..DIoIqgR5MP51jw.SK_1VawNWV1SC9ZSRu278imQXb-Fsn4K6gJK_IuEcG2p5xf9bj5fO6p8M8cN2HOw8D9TNuqR3u4POypw-QR4xfjiewjaifeDzc_L1D9cR_Zypcss0CWlk3Wl5Sm-Yel6Ej6DZPdMRYDVzFQIy-ugvlcbBMs_TBhPWuidiL7Gdy61iMW-CUG80iy0VN8TrOTTxI7oRlrsKeVF_htYbwfafeYxUnMIMnjz8BxsWHCj2Dj3Osx1d1RScHPlrzQhO8t9s0MpGbpO0Ygiu5H3-E5KC5ihnDtgTFeyyHFx8hPiG_ScbdnYgXQPqJiJIJ47Us9Jv0kXA1YxQr35-vL2IGHa6haofByqLJjXIKlYi-suu1Xl6wlCCZufXvELBcfhdkG4iCKGO3KXOozUkZOav9IqPM7qjGskTzbmR4zMzCmf0ypQbmk-4NXQT3N1Z_mxTX4ebCfjF7h0LjX3sgFcwYtNKS_iLsygU8mPZScCthBH67bO2ce35ZjHr2kHYKKxAYS-wXmiMpFM7NvEkVjoWJarrMF-Q4DB7eLKezmEKuRMDr6Q6_gDEbeyHqqCauEczBriq61LnWlDuqJtySL-amSrADFU7SU8fmD4DhgxU.f0o4123vdcxH_zvzfaewa7Q\",\"token_type\":\"bearer\",\"expires_in\":3600}" + read 1007 bytes + Conn close + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: da14d01c3608a0a036c4e7298cb5d56a\r\nAccept: application/json\r\nAuthorization: Bearer abcifejwiajJJJIDJFIEJQ0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..DIoIqgR5MP51jw.SK_1VawNWV1SC9ZSRu278imQXb-Fsn4K6gJK_IuEcG2p5xf9bj5fO6p8M8cN2HOw8D9TNuqR3u4POypw-QR4xfjiewjaifeDzc_L1D9cR_Zypcss0CWlk3Wl5Sm-Yel6Ej6DZPdMRYDVzFQIy-ugvlcbBMs_TBhPWuidiL7Gdy61iMW-CUG80iy0VN8TrOTTxI7oRlrsKeVF_htYbwfafeYxUnMIMnjz8BxsWHCj2Dj3Osx1d1RScHPlrzQhO8t9s0MpGbpO0Ygiu5H3-E5KC5ihnDtgTFeyyHFx8hPiG_ScbdnYgXQPqJiJIJ47Us9Jv0kXA1YxQr35-vL2IGHa6haofByqLJjXIKlYi-suu1Xl6wlCCZufXvELBcfhdkG4iCKGO3KXOozUkZOav9IqPM7qjGskTzbmR4zMzCmf0ypQbmk-4NXQT3N1Z_mxTX4ebCfjF7h0LjX3sgFcwYtNKS_iLsygU8mPZScCthBH67bO2ce35ZjHr2kHYKKxAYS-wXmiMpFM7NvEkVjoWJarrMF-Q4DB7eLKezmEKuRMDr6Q6_gDEbeyHqqCauEczBriq61LnWlDuqJtySL-amSrADFU7SU8fmD4DhgxU.f0o4123vdcxH_zvzfaewa7Q\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 310\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"4000100011112224\",\"expMonth\":\"09\",\"expYear\":2020,\"cvc\":\"123\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"context\":{\"mobile\":false,\"isEcommerce\":true},\"capture\":\"true\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:44 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=15552000\r\n" + -> "intuit_tid: 09ce7b7f-e19a-4567-8d7f-cf6ce81a9c75\r\n" + -> "\r\n" + -> "213\r\n" + reading 531 bytes... + -> "{\"created\":\"2019-10-17T15:40:44Z\",\"status\":\"CAPTURED\",\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"xxxxxxxxxxxx2224\",\"name\":\"Longbob Longsen\",\"address\":{\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"streetAddress\":\"456 My Street\",\"postalCode\":\"90210\"},\"cardType\":\"Visa\",\"expMonth\":\"09\",\"expYear\":\"2020\",\"cvc\":\"xxx\"},\"capture\":true,\"avsStreet\":\"Pass\",\"avsZip\":\"Pass\",\"cardSecurityCodeMatch\":\"NotAvailable\",\"id\":\"ES2Q849Y8KQ9\",\"context\":{\"mobile\":false,\"deviceInfo\":{},\"recurring\":false,\"isEcommerce\":true},\"authCode\":\"574943\"}" + read 531 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed_oauth_2 + %q( + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: 3b098cc41f53562ec0f36a0fc7071ff8\r\nAccept: 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\nUser-Agent: Ruby\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 310\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"[FILTERED]\",\"expMonth\":\"09\",\"expYear\":2020,\"cvc\":\"[FILTERED]\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"context\":{\"mobile\":false,\"isEcommerce\":true},\"capture\":\"true\"}" + -> "HTTP/1.1 401 Unauthorized\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:43 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 91\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "intuit_tid: adca0516-0af2-48cd-a704-095529fe615c\r\n" + -> "WWW-Authenticate: Bearer realm=\"Intuit\", error=\"invalid_token\"\r\n" + -> "\r\n" + reading 91 bytes... + -> "{\"code\":\"AuthenticationFailed\",\"type\":\"INPUT\",\"message\":null,\"detail\":null,\"moreInfo\":null}" + read 91 bytes + Conn close + opening connection to oauth.platform.intuit.com:443... + opened + starting SSL for oauth.platform.intuit.com:443... + SSL established + <- "POST /oauth2/v1/tokens/bearer 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: oauth.platform.intuit.com\r\nContent-Length: 89\r\n\r\n" + <- "grant_type=refresh_token&refresh_token=[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:43 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1007\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=15552000\r\n" + -> "intuit_tid: c9aff174-410e-4683-8a35-2591c659550f\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "\r\n" + reading 1007 bytes... + -> "{\"x_refresh_token_expires_in\":8719040,\"refresh_token\":\"[FILTERED]\",\"access_token\":\"[FILTERED]\",\"token_type\":\"bearer\",\"expires_in\":3600}" + read 1007 bytes + Conn close + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: da14d01c3608a0a036c4e7298cb5d56a\r\nAccept: 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\nUser-Agent: Ruby\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 310\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"[FILTERED]\",\"expMonth\":\"09\",\"expYear\":2020,\"cvc\":\"[FILTERED]\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"context\":{\"mobile\":false,\"isEcommerce\":true},\"capture\":\"true\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 17 Oct 2019 15:40:44 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=15552000\r\n" + -> "intuit_tid: 09ce7b7f-e19a-4567-8d7f-cf6ce81a9c75\r\n" + -> "\r\n" + -> "213\r\n" + reading 531 bytes... + -> "{\"created\":\"2019-10-17T15:40:44Z\",\"status\":\"CAPTURED\",\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"xxxxxxxxxxxx2224\",\"name\":\"Longbob Longsen\",\"address\":{\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"streetAddress\":\"456 My Street\",\"postalCode\":\"90210\"},\"cardType\":\"Visa\",\"expMonth\":\"09\",\"expYear\":\"2020\",\"cvc\":\"xxx\"},\"capture\":true,\"avsStreet\":\"Pass\",\"avsZip\":\"Pass\",\"cardSecurityCodeMatch\":\"NotAvailable\",\"id\":\"ES2Q849Y8KQ9\",\"context\":{\"mobile\":false,\"deviceInfo\":{},\"recurring\":false,\"isEcommerce\":true},\"authCode\":\"574943\"}" + read 531 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end end diff --git a/test/unit/gateways/quickpay_test.rb b/test/unit/gateways/quickpay_test.rb index f9f2e2d28cd..8209f95c965 100644 --- a/test/unit/gateways/quickpay_test.rb +++ b/test/unit/gateways/quickpay_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class QuickpayTest < Test::Unit::TestCase - def test_error_without_login_option assert_raise ArgumentError do QuickpayGateway.new @@ -9,13 +8,12 @@ def test_error_without_login_option end def test_v4to7 - gateway = QuickpayGateway.new(:login => 50000000, :password => 'secret') + gateway = QuickpayGateway.new(login: 50000000, password: 'secret') assert_instance_of QuickpayV4to7Gateway, gateway end def test_v10 - gateway = QuickpayGateway.new(:login => 100, :api_key => 'APIKEY') + gateway = QuickpayGateway.new(login: 100, api_key: 'APIKEY') assert_instance_of QuickpayV10Gateway, gateway end - end diff --git a/test/unit/gateways/quickpay_v10_test.rb b/test/unit/gateways/quickpay_v10_test.rb index f07f13cc03e..fccafc60268 100644 --- a/test/unit/gateways/quickpay_v10_test.rb +++ b/test/unit/gateways/quickpay_v10_test.rb @@ -4,10 +4,10 @@ class QuickpayV10Test < Test::Unit::TestCase include CommStub def setup - @gateway = QuickpayV10Gateway.new(:api_key => 'APIKEY') + @gateway = QuickpayV10Gateway.new(api_key: 'APIKEY') @credit_card = credit_card('4242424242424242') @amount = 100 - @options = { :order_id => '1', :billing_address => address, :customer_ip => '1.1.1.1' } + @options = { order_id: '1', billing_address: address, customer_ip: '1.1.1.1' } end def parse(body) @@ -28,7 +28,7 @@ def test_successful_purchase assert_success response assert_equal '1145', response.authorization assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| parsed = parse(data) if parsed['order_id'] assert_match %r{/payments}, endpoint @@ -47,7 +47,7 @@ def test_successful_authorization assert_success response assert_equal '1145', response.authorization assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| parsed_data = parse(data) if parsed_data['order_id'] assert_match %r{/payments}, endpoint @@ -71,7 +71,7 @@ def test_successful_authorization_with_3ds assert_success response assert_equal '1145', response.authorization assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, data, _headers| parsed_data = parse(data) if parsed_data['order_id'] assert_match %r{/payments}, endpoint @@ -87,9 +87,9 @@ def test_successful_void assert response = @gateway.void(1145) assert_success response assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_match %r{/payments/1145/cancel}, endpoint - end.respond_with({'id' => 1145}.to_json) + end.respond_with({ 'id' => 1145 }.to_json) end def test_failed_authorization @@ -115,7 +115,7 @@ def test_successful_store assert response = @gateway.store(@credit_card, @options) assert_success response assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_match %r{/card}, endpoint end.respond_with(successful_store_response, successful_sauthorize_response) end @@ -125,9 +125,9 @@ def test_successful_unstore assert response = @gateway.unstore('123') assert_success response assert response.test? - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_match %r{/cards/\d+/cancel}, endpoint - end.respond_with({'id' => '123'}.to_json) + end.respond_with({ 'id' => '123' }.to_json) end def test_successful_verify @@ -141,19 +141,19 @@ def test_successful_verify def test_failed_verify response = stub_comms do @gateway.verify(@credit_card, @options) - end.respond_with(failed_authorization_response, {'id' => 1145}.to_json) + end.respond_with(failed_authorization_response, { 'id' => 1145 }.to_json) assert_failure response assert_equal 'Validation error', response.message end def test_supported_countries klass = @gateway.class - assert_equal ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'], klass.supported_countries + assert_equal %w[DE DK ES FI FR FO GB IS NO SE], klass.supported_countries end def test_supported_card_types klass = @gateway.class - assert_equal [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro ], klass.supported_cardtypes + assert_equal %i[dankort forbrugsforeningen visa master american_express diners_club jcb maestro], klass.supported_cardtypes end def test_successful_capture @@ -170,90 +170,90 @@ def test_transcript_scrubbing def successful_payment_response { - 'id' =>1145, - 'order_id' =>'310f59c57a', - 'accepted' =>false, - 'test_mode' =>false, - 'branding_id' =>nil, - 'variables' =>{}, - 'acquirer' =>nil, - 'operations' =>[], - 'metadata' =>{}, - 'created_at' =>'2015-03-30T16:56:17Z', - 'balance' =>0, - 'currency' =>'DKK' + 'id' => 1145, + 'order_id' => '310f59c57a', + 'accepted' => false, + 'test_mode' => false, + 'branding_id' => nil, + 'variables' => {}, + 'acquirer' => nil, + 'operations' => [], + 'metadata' => {}, + 'created_at' => '2015-03-30T16:56:17Z', + 'balance' => 0, + 'currency' => 'DKK' }.to_json end def successful_authorization_response { - 'id' => 1145, - 'order_id' => '310f59c57a', - 'accepted' => false, - 'test_mode' => true, - 'branding_id' => nil, - 'variables' => {}, - 'acquirer' => 'clearhaus', - 'operations' => [], - 'metadata' => { - 'type' =>'card', - 'brand' =>'quickpay-test-card', - 'last4' =>'0008', - 'exp_month' =>9, - 'exp_year' =>2016, - 'country' =>'DK', - 'is_3d_secure' =>false, - 'customer_ip' =>nil, - 'customer_country' =>nil - }, - 'created_at' => '2015-03-30T16:56:17Z', - 'balance' => 0, - 'currency' => 'DKK' + 'id' => 1145, + 'order_id' => '310f59c57a', + 'accepted' => false, + 'test_mode' => true, + 'branding_id' => nil, + 'variables' => {}, + 'acquirer' => 'clearhaus', + 'operations' => [], + 'metadata' => { + 'type' => 'card', + 'brand' => 'quickpay-test-card', + 'last4' => '0008', + 'exp_month' => 9, + 'exp_year' => 2016, + 'country' => 'DK', + 'is_3d_secure' => false, + 'customer_ip' => nil, + 'customer_country' => nil + }, + 'created_at' => '2015-03-30T16:56:17Z', + 'balance' => 0, + 'currency' => 'DKK' }.to_json end def successful_capture_response { - 'id' =>1145, - 'order_id' =>'310f59c57a', - 'accepted' =>true, - 'test_mode' =>true, - 'branding_id' =>nil, - 'variables' =>{}, - 'acquirer' =>'clearhaus', - 'operations' =>[], - 'metadata' =>{'type'=>'card', 'brand'=>'quickpay-test-card', 'last4'=>'0008', 'exp_month'=>9, 'exp_year'=>2016, 'country'=>'DK', 'is_3d_secure'=>false, 'customer_ip'=>nil, 'customer_country'=>nil}, - 'created_at' =>'2015-03-30T16:56:17Z', - 'balance' =>0, - 'currency' =>'DKK' + 'id' => 1145, + 'order_id' => '310f59c57a', + 'accepted' => true, + 'test_mode' => true, + 'branding_id' => nil, + 'variables' => {}, + 'acquirer' => 'clearhaus', + 'operations' => [], + 'metadata' => { 'type' => 'card', 'brand' => 'quickpay-test-card', 'last4' => '0008', 'exp_month' => 9, 'exp_year' => 2016, 'country' => 'DK', 'is_3d_secure' => false, 'customer_ip' => nil, 'customer_country' => nil }, + 'created_at' => '2015-03-30T16:56:17Z', + 'balance' => 0, + 'currency' => 'DKK' }.to_json end def succesful_refund_response { - 'id' =>1145, - 'order_id' =>'310f59c57a', - 'accepted' =>true, - 'test_mode' =>true, - 'branding_id' =>nil, - 'variables' =>{}, - 'acquirer' =>'clearhaus', - 'operations' =>[], - 'metadata'=>{ - 'type' =>'card', - 'brand' =>'quickpay-test-card', - 'last4' =>'0008', - 'exp_month' =>9, - 'exp_year' =>2016, - 'country' =>'DK', - 'is_3d_secure' =>false, - 'customer_ip' =>nil, - 'customer_country' =>nil - }, - 'created_at' =>'2015-03-30T16:56:17Z', - 'balance' =>100, - 'currency' =>'DKK' - }.to_json + 'id' => 1145, + 'order_id' => '310f59c57a', + 'accepted' => true, + 'test_mode' => true, + 'branding_id' => nil, + 'variables' => {}, + 'acquirer' => 'clearhaus', + 'operations' => [], + 'metadata' => { + 'type' => 'card', + 'brand' => 'quickpay-test-card', + 'last4' => '0008', + 'exp_month' => 9, + 'exp_year' => 2016, + 'country' => 'DK', + 'is_3d_secure' => false, + 'customer_ip' => nil, + 'customer_country' => nil + }, + 'created_at' => '2015-03-30T16:56:17Z', + 'balance' => 100, + 'currency' => 'DKK' + }.to_json end def failed_authorization_response diff --git a/test/unit/gateways/quickpay_v4to7_test.rb b/test/unit/gateways/quickpay_v4to7_test.rb index 74e395980ca..e4beffd6571 100644 --- a/test/unit/gateways/quickpay_v4to7_test.rb +++ b/test/unit/gateways/quickpay_v4to7_test.rb @@ -9,14 +9,14 @@ def merchant_id def setup @gateway = QuickpayGateway.new( - :login => merchant_id, - :password => 'PASSWORD', - :version => 7 + login: merchant_id, + password: 'PASSWORD', + version: 7 ) @credit_card = credit_card('4242424242424242') @amount = 100 - @options = { :order_id => '1', :billing_address => address } + @options = { order_id: '1', billing_address: address } end def test_successful_purchase @@ -39,15 +39,15 @@ def test_successful_authorization def test_successful_store_for_v6 @gateway = QuickpayGateway.new( - :login => merchant_id, - :password => 'PASSWORD', - :version => 6 + login: merchant_id, + password: 'PASSWORD', + version: 6 ) @gateway.expects(:generate_check_hash).returns(mock_md5_hash) response = stub_comms do - @gateway.store(@credit_card, {:order_id => 'fa73664073e23597bbdd', :description => 'Storing Card'}) - end.check_request do |endpoint, data, headers| + @gateway.store(@credit_card, { order_id: 'fa73664073e23597bbdd', description: 'Storing Card' }) + end.check_request do |_endpoint, data, _headers| assert_equal(expected_store_parameters_v6, CGI::parse(data)) end.respond_with(successful_store_response_v6) @@ -61,8 +61,8 @@ def test_successful_store_for_v7 @gateway.expects(:generate_check_hash).returns(mock_md5_hash) response = stub_comms do - @gateway.store(@credit_card, {:order_id => 'ed7546cb4ceb8f017ea4', :description => 'Storing Card'}) - end.check_request do |endpoint, data, headers| + @gateway.store(@credit_card, { order_id: 'ed7546cb4ceb8f017ea4', description: 'Storing Card' }) + end.check_request do |_endpoint, data, _headers| assert_equal(expected_store_parameters_v7, CGI::parse(data)) end.respond_with(successful_store_response_v7) @@ -124,16 +124,16 @@ def test_parsing_successful_response def test_supported_countries klass = @gateway.class - assert_equal ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'], klass.supported_countries + assert_equal %w[DE DK ES FI FR FO GB IS NO SE], klass.supported_countries end def test_supported_card_types klass = @gateway.class - assert_equal [ :dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro ], klass.supported_cardtypes + assert_equal %i[dankort forbrugsforeningen visa master american_express diners_club jcb maestro], klass.supported_cardtypes end def test_add_testmode_does_not_add_testmode_if_transaction_id_present - post_hash = {:transaction => '12345'} + post_hash = { transaction: '12345' } @gateway.send(:add_testmode, post_hash) assert_equal nil, post_hash[:testmode] end @@ -147,7 +147,7 @@ def test_add_testmode_adds_a_testmode_param_if_transaction_id_not_present def test_finalize_is_disabled_by_default stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, '12345') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /finalize=0/ end.respond_with(successful_capture_response) end @@ -155,7 +155,7 @@ def test_finalize_is_disabled_by_default def test_finalize_is_enabled stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, '12345', finalize: true) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /finalize=1/ end.respond_with(successful_capture_response) end @@ -192,33 +192,33 @@ def failed_authorization_response def expected_store_parameters_v6 { - 'cardnumber'=>['4242424242424242'], - 'cvd'=>['123'], - 'expirationdate'=>[expected_expiration_date], - 'ordernumber'=>['fa73664073e23597bbdd'], - 'description'=>['Storing Card'], - 'testmode'=>['1'], - 'protocol'=>['6'], - 'msgtype'=>['subscribe'], - 'merchant'=>[merchant_id], - 'md5check'=>[mock_md5_hash] + 'cardnumber' => ['4242424242424242'], + 'cvd' => ['123'], + 'expirationdate' => [expected_expiration_date], + 'ordernumber' => ['fa73664073e23597bbdd'], + 'description' => ['Storing Card'], + 'testmode' => ['1'], + 'protocol' => ['6'], + 'msgtype' => ['subscribe'], + 'merchant' => [merchant_id], + 'md5check' => [mock_md5_hash] } end def expected_store_parameters_v7 { - 'amount'=>['0'], - 'currency'=>['DKK'], - 'cardnumber'=>['4242424242424242'], - 'cvd'=>['123'], - 'expirationdate'=>[expected_expiration_date], - 'ordernumber'=>['ed7546cb4ceb8f017ea4'], - 'description'=>['Storing Card'], - 'testmode'=>['1'], - 'protocol'=>['7'], - 'msgtype'=>['subscribe'], - 'merchant'=>[merchant_id], - 'md5check'=>[mock_md5_hash] + 'amount' => ['0'], + 'currency' => ['DKK'], + 'cardnumber' => ['4242424242424242'], + 'cvd' => ['123'], + 'expirationdate' => [expected_expiration_date], + 'ordernumber' => ['ed7546cb4ceb8f017ea4'], + 'description' => ['Storing Card'], + 'testmode' => ['1'], + 'protocol' => ['7'], + 'msgtype' => ['subscribe'], + 'merchant' => [merchant_id], + 'md5check' => [mock_md5_hash] } end diff --git a/test/unit/gateways/qvalent_test.rb b/test/unit/gateways/qvalent_test.rb index 1582e39388c..ebf25c7fc28 100644 --- a/test/unit/gateways/qvalent_test.rb +++ b/test/unit/gateways/qvalent_test.rb @@ -16,6 +16,12 @@ def setup @amount = 100 end + def test_successful_gateway_creation_without_pem_password + gateway = QvalentGateway.new(username: 'username', password: 'password', merchant: 'merchant', pem: 'pem') + + assert_instance_of QvalentGateway, gateway + end + def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) @@ -91,7 +97,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{5d53a33d960c46d00f5dc061947d998c}, data end.respond_with(successful_refund_response) @@ -177,8 +183,8 @@ def test_empty_response_fails def test_3d_secure_fields response = stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, {xid: '123', cavv: '456', eci: '5'}) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, { xid: '123', cavv: '456', eci: '5' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/xid=123/, data) assert_match(/cavv=456/, data) assert_match(/ECI=5/, data) @@ -187,6 +193,71 @@ def test_3d_secure_fields assert_success response end + def test_stored_credential_fields_initial + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { stored_credential: { initial_transaction: true, reason_type: 'unscheduled', initiator: 'merchant' } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/posEntryMode=MANUAL/, data) + assert_match(/storedCredentialUsage=UNSCHEDULED_MIT/, data) + assert_match(/ECI=SSL/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_recurring + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { stored_credential: { reason_type: 'recurring', initiator: 'merchant', network_transaction_id: '7890' } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + assert_match(/storedCredentialUsage=RECURRING/, data) + assert_match(/ECI=REC/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_unscheduled + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { stored_credential: { reason_type: 'unscheduled', initiator: 'merchant', network_transaction_id: '7890' } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + assert_match(/storedCredentialUsage=UNSCHEDULED/, data) + assert_match(/ECI=MTO/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_cardholder_initiated + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { stored_credential: { reason_type: 'unscheduled', initiator: 'cardholder', network_transaction_id: '7890' } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + refute_match(/storedCredentialUsage/, data) + assert_match(/ECI=MTO/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_mastercard + @credit_card.brand = 'master' + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { stored_credential: { reason_type: 'recurring', initiator: 'merchant', network_transaction_id: '7890' } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + refute_match(/storedCredentialUsage/, data) + assert_match(/ECI=REC/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_cvv_result response = stub_comms do @gateway.purchase(@amount, @credit_card) @@ -200,6 +271,37 @@ def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end + def test_default_add_card_reference_number + post = {} + options = {} + options[:order_id] = 1234534 + @gateway.send(:add_card_reference, post, options) + assert_equal post['customer.customerReferenceNumber'], 1234534 + end + + def test_add_card_reference_number + post = {} + options = {} + options[:order_id] = 1234 + options[:customer_reference_number] = 4321 + @gateway.send(:add_card_reference, post, options) + assert_equal post['customer.customerReferenceNumber'], 4321 + end + + def test_default_add_customer_reference_number + post = {} + @gateway.send(:add_customer_reference, post, {}) + assert_nil post['customer.customerReferenceNumber'] + end + + def test_add_customer_reference_number + post = {} + options = {} + options[:customer_reference_number] = 4321 + @gateway.send(:add_customer_reference, post, options) + assert_equal post['customer.customerReferenceNumber'], 4321 + end + private def successful_purchase_response diff --git a/test/unit/gateways/rapyd_test.rb b/test/unit/gateways/rapyd_test.rb new file mode 100644 index 00000000000..efebb6976e0 --- /dev/null +++ b/test/unit/gateways/rapyd_test.rb @@ -0,0 +1,604 @@ +require 'test_helper' + +class RapydTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = RapydGateway.new(secret_key: 'secret_key', access_key: 'access_key') + @credit_card = credit_card + @check = check + @amount = 100 + @authorization = 'cus_9e1b5a357b2b7f25f8dd98827fbc4f22|card_cf105df9e77462deb34ffef33c3e3d05' + + @options = { + pm_type: 'in_amex_card', + currency: 'USD', + complete_payment_url: 'www.google.com', + error_payment_url: 'www.google.com', + description: 'Describe this transaction', + statement_descriptor: 'Statement Descriptor', + email: 'test@example.com', + billing_address: address(name: 'Jim Reynolds'), + order_id: '987654321' + } + + @metadata = { + 'array_of_objects': [ + { 'name': 'John Doe' }, + { 'type': 'customer' } + ], + 'array_of_strings': %w[ + color + size + ], + 'number': 1234567890, + 'object': { + 'string': 'person' + }, + 'string': 'preferred', + 'Boolean': true + } + + @ewallet_id = 'ewallet_1a867a32b47158b30a8c17d42f12f3f1' + + @address_object = address(line_1: '123 State Street', line_2: 'Apt. 34', phone_number: '12125559999') + end + + def test_successful_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: address(name: 'Joe John-ston'))) + end.check_request do |_method, _endpoint, data, _headers| + assert_equal JSON.parse(data)['address']['name'], 'Joe John-ston' + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'payment_716ce0efc63aa8d91579e873d29d9d5e', response.authorization.split('|')[0] + end + + def test_successful_purchase_without_cvv + @credit_card.verification_value = nil + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"number":"4242424242424242","expiration_month":"9","expiration_year":"2024","name":"Longbob Longsen/, data) + end.respond_with(successful_purchase_response) + assert_success response + assert_equal 'payment_716ce0efc63aa8d91579e873d29d9d5e', response.authorization.split('|')[0] + end + + def test_successful_purchase_with_ach + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @check, @options.merge(billing_address: address(name: 'Joe John-ston'))) + end.check_request do |_method, _endpoint, data, _headers| + assert_nil JSON.parse(data)['capture'] + end.respond_with(successful_ach_purchase_response) + + assert_success response + assert_equal 'ACT', response.params['data']['status'] + end + + def test_successful_purchase_with_token + @options[:customer_id] = 'cus_9e1b5a357b2b7f25f8dd98827fbc4f22' + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @authorization, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method'], @authorization.split('|')[1] + assert_equal request['customer'], @options[:customer_id] + end.respond_with(successful_purchase_with_options_response) + + assert_success response + assert_equal @metadata, response.params['data']['metadata'].deep_transform_keys(&:to_sym) + end + + def test_successful_purchase_with_payment_options + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"complete_payment_url":"www.google.com"/, data) + assert_match(/"error_payment_url":"www.google.com"/, data) + assert_match(/"description":"Describe this transaction"/, data) + assert_match(/"statement_descriptor":"Statement Descriptor"/, data) + assert_match(/"merchant_reference_id":"987654321"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_purchase_with_explicit_merchant_reference_id + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({ merchant_reference_id: '99988877776' })) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"complete_payment_url":"www.google.com"/, data) + assert_match(/"error_payment_url":"www.google.com"/, data) + assert_match(/"description":"Describe this transaction"/, data) + assert_match(/"statement_descriptor":"Statement Descriptor"/, data) + assert_match(/"merchant_reference_id":"99988877776"/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_purchase_with_stored_credential + @options[:stored_credential] = { + reason_type: 'recurring', + network_transaction_id: '12345' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method']['fields']['network_reference_id'], @options[:stored_credential][:network_transaction_id] + assert_equal request['initiation_type'], @options[:stored_credential][:reason_type] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_network_transaction_id_and_initiation_type_fields + @options[:network_transaction_id] = '54321' + @options[:initiation_type] = 'customer_present' + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method']['fields']['network_reference_id'], @options[:network_transaction_id] + assert_equal request['initiation_type'], @options[:initiation_type] + end.respond_with(successful_purchase_response) + end + + def test_success_purchase_with_recurrence_type + @options[:recurrence_type] = 'recurring' + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method']['fields']['recurrence_type'], @options[:recurrence_type] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_3ds_global + @options[:three_d_secure] = { + required: true, + version: '2.1.0' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method_options']['3d_required'], true + assert_equal request['payment_method_options']['3d_version'], '2.1.0' + assert request['complete_payment_url'] + assert request['error_payment_url'] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_3ds_gateway_specific + @options.merge!(execute_threed: true, force_3d_secure: true) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['payment_method_options']['3d_required'], true + assert_nil request['payment_method_options']['3d_version'] + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'ERROR_PROCESSING_CARD - [05]', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + 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 'Do Not Honor', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + transaction_id = 'payment_e0979a1c6843e5d7bf0c18335794cccb' + + response = @gateway.capture(@amount, transaction_id, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_equal 'The request tried to retrieve a payment, but the payment was not found. The request was rejected. Corrective action: Use a valid payment ID.', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + transaction_id = 'refund_2a575991bee3b010f44e438f7f6a6d5f' + + response = @gateway.refund(@amount, transaction_id, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal 'The request tried to retrieve a payment, but the payment was not found. The request was rejected. Corrective action: Use a valid payment ID.', response.message + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + authorization = 'payment_a29a73f09d6f55defddc779dbb2d1089' + + response = @gateway.void(authorization) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'UNAUTHORIZED_API_CALL', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'Do Not Honor', response.message + end + + def test_successful_store_and_unstore + @gateway.expects(:ssl_request).twice.returns(successful_store_response, successful_unstore_response) + + store = @gateway.store(@credit_card, @options) + assert_success store + assert customer_id = store.params.dig('data', 'id') + + unstore = @gateway.unstore(store.authorization) + assert_success unstore + assert_equal true, unstore.params.dig('data', 'deleted') + assert_equal customer_id, unstore.params.dig('data', 'id') + end + + def test_failed_purchase_without_customer_object + @options[:pm_type] = 'us_debit_visa_card' + @gateway.expects(:ssl_request).returns(failed_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'ERROR_PROCESSING_CARD - [05]', response.params['status']['error_code'] + end + + def test_successful_purchase_with_customer_object + stub_comms(@gateway, :ssl_request) do + @options[:pm_type] = 'us_debit_mastercard_card' + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + assert_match(/"name":"Jim Reynolds"/, data) + assert_match(/"email":"test@example.com"/, data) + assert_match(/"phone_number":"5555555555"/, data) + assert_match(/"customer":/, data) + end + end + + def test_successful_purchase_with_billing_address_phone_variations + stub_comms(@gateway, :ssl_request) do + @options[:pm_type] = 'us_debit_mastercard_card' + @gateway.purchase(@amount, @credit_card, { billing_address: { phone_number: '919.123.1234' } }) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + assert_match(/"phone_number":"9191231234"/, data) + end + + stub_comms(@gateway, :ssl_request) do + @options[:pm_type] = 'us_debit_mastercard_card' + @gateway.purchase(@amount, @credit_card, { billing_address: { phone: '919.123.1234' } }) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + assert_match(/"phone_number":"9191231234"/, data) + end + end + + def test_successful_store_with_customer_object + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"name":"Jim Reynolds"/, data) + assert_match(/"email":"test@example.com"/, data) + assert_match(/"phone_number":"5555555555"/, data) + end.respond_with(successful_store_response) + + assert_success response + end + + def test_payment_urls_correctly_nested_by_operation + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request_body = JSON.parse(data) + assert_equal @options[:complete_payment_url], request_body['payment_method']['complete_payment_url'] + assert_equal @options[:error_payment_url], request_body['payment_method']['error_payment_url'] + end.respond_with(successful_store_response) + + assert_success response + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + request_body = JSON.parse(data) + assert_equal @options[:complete_payment_url], request_body['complete_payment_url'] + assert_equal @options[:error_payment_url], request_body['error_payment_url'] + end.respond_with(successful_store_response) + + assert_success response + end + + def test_purchase_with_customer_and_card_id + store = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options) + end.respond_with(successful_store_response) + + assert customer_id = store.params.dig('data', 'id') + assert card_id = store.params.dig('data', 'default_payment_method') + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, store.authorization, @options) + end.check_request do |_method, _endpoint, data, _headers| + request_body = JSON.parse(data) + assert_equal request_body['customer'], customer_id + assert_equal request_body['payment_method'], card_id + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure + options = { + three_d_secure: { + cavv: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + eci: '5', + xid: 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/"cavv":"EHuWW9PiBkWvqE5juRwDzAUFBAk="/, data) + assert_match(/"eci":"5"/, data) + assert_match(/"xid":"TTBCSkVTa1ZpbDI1bjRxbGk5ODE="/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_not_send_cvv_with_empty_value + @credit_card.verification_value = '' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['payment_method']['fields']['cvv'] + end + end + + def test_not_send_cvv_with_nil_value + @credit_card.verification_value = nil + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['payment_method']['fields']['cvv'] + end + end + + def test_not_send_cvv_for_recurring_transactions + @options[:stored_credential] = { + reason_type: 'recurring', + network_transaction_id: '12345' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['payment_method']['fields']['cvv'] + end + end + + def test_not_send_network_reference_id_for_recurring_transactions + @options[:stored_credential] = { + reason_type: 'recurring', + network_transaction_id: nil + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['payment_method']['fields']['network_reference_id'] + end + end + + def test_not_send_customer_object_for_recurring_transactions + @options[:stored_credential] = { + reason_type: 'recurring', + network_transaction_id: '12345' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_method, _endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['customer'] + end + end + + private + + def pre_scrubbed + ' + opening connection to sandboxapi.rapyd.net:443... + opened + starting SSL for sandboxapi.rapyd.net:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json\r\nAccess_key: A6E93651174B48E0EF1E\r\nSalt: +3mM6dOjHsOwF/VQ\r\nTimestamp: 1647870006\r\nSignature: YjY4NTA1NDY3ZTUxMWUyNzk0NjFkOTJhZjIwYWUzZTA5YzYyMzUzZDE1ZjY2NWFmM2NhZTlmZDY2ZDZjNjEwYQ==\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: sandboxapi.rapyd.net\r\nContent-Length: 212\r\n\r\n" + <- "{\"amount\":\"1.0\",\"currency\":\"USD\",\"payment_method\":{\"type\":\"in_amex_card\",\"fields\":{\"number\":\"4111111111111111\",\"expiration_month\":\"12\",\"expiration_year\":\"2035\",\"cvv\":\"345\",\"name\":\"Ryan Reynolds\"}},\"capture\":true}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 21 Mar 2022 13:40:08 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: X-HTTP-Method-Override, Accept-Encoding\r\n" + -> "Strict-Transport-Security: max-age=8640000; includeSubDomains\r\n" + -> "ETag: W/\"7d1-tsdr4eAZn2y+2my4kMxz2w\"\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "a\r\n" + -> "0\r\n" + -> "\r\n" + Conn close + ' + end + + def post_scrubbed + ' + opening connection to sandboxapi.rapyd.net:443... + opened + starting SSL for sandboxapi.rapyd.net:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/json\r\nAccess_key: [FILTERED]\r\nSalt: +3mM6dOjHsOwF/VQ\r\nTimestamp: 1647870006\r\nSignature: YjY4NTA1NDY3ZTUxMWUyNzk0NjFkOTJhZjIwYWUzZTA5YzYyMzUzZDE1ZjY2NWFmM2NhZTlmZDY2ZDZjNjEwYQ==\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: sandboxapi.rapyd.net\r\nContent-Length: 212\r\n\r\n" + <- "{\"amount\":\"1.0\",\"currency\":\"USD\",\"payment_method\":{\"type\":\"in_amex_card\",\"fields\":{\"number\":\"[FILTERED]\",\"expiration_month\":\"12\",\"expiration_year\":\"2035\",\"cvv\":\"[FILTERED]\",\"name\":\"Ryan Reynolds\"}},\"capture\":true}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 21 Mar 2022 13:40:08 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: X-HTTP-Method-Override, Accept-Encoding\r\n" + -> "Strict-Transport-Security: max-age=8640000; includeSubDomains\r\n" + -> "ETag: W/\"7d1-tsdr4eAZn2y+2my4kMxz2w\"\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "a\r\n" + -> "0\r\n" + -> "\r\n" + Conn close + ' + end + + def successful_purchase_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"99571e34-f236-4f86-9040-5e0f256d6f64"},"data":{"id":"payment_716ce0efc63aa8d91579e873d29d9d5e","amount":1,"original_amount":1,"is_partial":false,"currency_code":"USD","country_code":"in","status":"CLO","description":"","merchant_reference_id":"","customer_token":"cus_f991c9a9f0cc7abdad64f9f7aea13f31","payment_method":"card_652d9fef3ec0089689fcaf0154340c64","payment_method_data":{"id":"card_652d9fef3ec0089689fcaf0154340c64","type":"in_amex_card","category":"card","metadata":null,"image":"","webhook_url":"","supporting_documentation":"","next_action":"not_applicable","name":"Ryan Reynolds","last4":"1111","acs_check":"unchecked","cvv_check":"unchecked","bin_details":{"type":null,"brand":null,"country":null,"bin_number":"411111"},"expiration_year":"35","expiration_month":"12","fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e"},"expiration":1648237659,"captured":true,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1647632859,"metadata":{},"failure_code":"","failure_message":"","paid":true,"paid_at":1647632859,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":{},"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":1,"percent":100,"refunded_amount":0}],"payment_method_options":{},"payment_method_type":"in_amex_card","payment_method_type_category":"card","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"not_applicable","error_code":"","remitter_information":{}}} + ) + end + + def successful_purchase_with_options_response + %( + {"status":{"error_code":"", "status":"SUCCESS", "message":"", "response_code":"", "operation_id":"2852540b-ffa4-4547-9260-26f101f649ad"}, "data":{"id":"payment_6b00756cfefb0fdf6fb295fa507594d3", "amount":1000, "original_amount":1000, "is_partial":false, "currency_code":"USD", "country_code":"US", "status":"CLO", "description":"", "merchant_reference_id":"", "customer_token":"cus_9cb7908aec8a75a95846f1b3759ad1ef", "payment_method":"card_a838c23ef7be1ece86aa27a330167737", "payment_method_data":{"id":"card_a838c23ef7be1ece86aa27a330167737", "type":"us_visa_card", "category":"card", "metadata":null, "image":"", "webhook_url":"", "supporting_documentation":"", "next_action":"not_applicable", "name":"Ryan Reynolds", "last4":"1111", "acs_check":"unchecked", "cvv_check":"unchecked", "bin_details":{"type":null, "brand":null, "country":null, "bin_number":"411111"}, "expiration_year":"35", "expiration_month":"12", "fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e"}, "expiration":1649955834, "captured":true, "refunded":false, "refunded_amount":0, "receipt_email":"", "redirect_url":"", "complete_payment_url":"", "error_payment_url":"", "receipt_number":"", "flow_type":"", "address":null, "statement_descriptor":"N/A", "transaction_id":"", "created_at":1649351034, "metadata":{"number":1234567890, "object":{"string":"person"}, "string":"preferred", "Boolean":true, "array_of_objects":[{"name":"John Doe"}, {"type":"customer"}], "array_of_strings":["color", "size"]}, "failure_code":"", "failure_message":"", "paid":true, "paid_at":1649351034, "dispute":null, "refunds":null, "order":null, "outcome":null, "visual_codes":{}, "textual_codes":{}, "instructions":[], "ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77", "ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77", "amount":1000, "percent":100, "refunded_amount":0}], "payment_method_options":{}, "payment_method_type":"us_visa_card", "payment_method_type_category":"card", "fx_rate":1, "merchant_requested_currency":null, "merchant_requested_amount":null, "fixed_side":"", "payment_fees":null, "invoice":"", "escrow":null, "group_payment":"", "cancel_reason":null, "initiation_type":"customer_present", "mid":"", "next_action":"not_applicable", "error_code":"", "remitter_information":{}}} + ) + end + + def successful_ach_purchase_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"7362425c-06ef-4a31-b50c-234e84352bb9"},"data":{"id":"payment_59daaa8786d9120a8487dc0b86d32a9e","amount":0,"original_amount":2100,"is_partial":false,"currency_code":"USD","country_code":"US","status":"ACT","description":"","merchant_reference_id":"","customer_token":"cus_99ed3308f30dd5b14c2f2cde40fac98e","payment_method":"other_73b2e0fcd0ddb3200c1fcc5a4aeaeebf","payment_method_data":{"id":"other_73b2e0fcd0ddb3200c1fcc5a4aeaeebf","type":"us_ach_bank","category":"bank_transfer","metadata":{},"image":"","webhook_url":"","supporting_documentation":"","next_action":"not_applicable","last_name":"Smith","first_name":"Jim","account_number":"15378535","routing_number":"244183602","payment_purpose":"Testing Purpose","proof_of_authorization":true},"expiration":1649093215,"captured":true,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1647883616,"metadata":{},"failure_code":"","failure_message":"","paid":false,"paid_at":0,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":{"name":"instructions","steps":[{"step1":"Provide your routing and account number to process the transaction"},{"step2":"Once completed, the transaction will take approximately 2-3 days to process"}]},"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":2100,"percent":100,"refunded_amount":0}],"payment_method_options":{},"payment_method_type":"us_ach_bank","payment_method_type_category":"bank_transfer","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"pending_confirmation","error_code":"","remitter_information":{}}} + ) + end + + def failed_purchase_response + %( + {"status":{"error_code":"ERROR_PROCESSING_CARD - [05]","status":"ERROR","message":"Do Not Honor","response_code":"ERROR_PROCESSING_CARD - [05]","operation_id":"5486c9f2-2c11-47eb-adec-993fc3a8c302"}} + ) + end + + def successful_authorize_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"4ac3438d-8afe-4fdd-bc93-38f78a2a52ba"},"data":{"id":"payment_e0979a1c6843e5d7bf0c18335794cccb","amount":0,"original_amount":1,"is_partial":false,"currency_code":"USD","country_code":"in","status":"ACT","description":"","merchant_reference_id":"","customer_token":"cus_bcf45118ae3e8bf45abf01aaae8bfd5b","payment_method":"card_23db2eb985533e23cf56de4a46cee312","payment_method_data":{"id":"card_23db2eb985533e23cf56de4a46cee312","type":"in_amex_card","category":"card","metadata":null,"image":"","webhook_url":"","supporting_documentation":"","next_action":"not_applicable","name":"Ryan Reynolds","last4":"1111","acs_check":"unchecked","cvv_check":"unchecked","bin_details":{"type":null,"brand":null,"country":null,"bin_number":"411111"},"expiration_year":"35","expiration_month":"12","fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e"},"expiration":1648242162,"captured":false,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1647637362,"metadata":{},"failure_code":"","failure_message":"","paid":false,"paid_at":0,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":{},"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":1,"percent":100,"refunded_amount":0}],"payment_method_options":{},"payment_method_type":"in_amex_card","payment_method_type_category":"card","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"pending_capture","error_code":"","remitter_information":{}}} + ) + end + + def failed_authorize_response + %( + {"status":{"error_code":"ERROR_PROCESSING_CARD - [05]","status":"ERROR","message":"Do Not Honor","response_code":"ERROR_PROCESSING_CARD - [05]","operation_id":"410488ba-523f-480a-a497-053ca2327866"}} + ) + end + + def successful_capture_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"015c41d0-f11a-4a91-9518-dc4117d8017b"},"data":{"id":"payment_e0979a1c6843e5d7bf0c18335794cccb","amount":1,"original_amount":1,"is_partial":false,"currency_code":"USD","country_code":"in","status":"CLO","description":"","merchant_reference_id":"","customer_token":"cus_bcf45118ae3e8bf45abf01aaae8bfd5b","payment_method":"card_23db2eb985533e23cf56de4a46cee312","payment_method_data":{"id":"card_23db2eb985533e23cf56de4a46cee312","type":"in_amex_card","category":"card","metadata":null,"image":"","webhook_url":"","supporting_documentation":"","next_action":"not_applicable","name":"Ryan Reynolds","last4":"1111","acs_check":"unchecked","cvv_check":"unchecked","bin_details":{"type":null,"brand":null,"country":null,"bin_number":"411111"},"expiration_year":"35","expiration_month":"12","fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e"},"expiration":1648242162,"captured":true,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1647637362,"metadata":{},"failure_code":"","failure_message":"","paid":true,"paid_at":1647637363,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":{},"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":1,"percent":100,"refunded_amount":0}],"payment_method_options":{},"payment_method_type":"in_amex_card","payment_method_type_category":"card","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"not_applicable","error_code":"","remitter_information":{}}} + ) + end + + def failed_capture_response + %( + {"status":{"error_code":"ERROR_GET_PAYMENT","status":"ERROR","message":"The request tried to retrieve a payment, but the payment was not found. The request was rejected. Corrective action: Use a valid payment ID.","response_code":"ERROR_GET_PAYMENT","operation_id":"a836ca9b-def8-4e4e-a4e8-a249b0c0e0ff"}} + ) + end + + def successful_refund_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"5bfa30c5-c698-4e43-861c-e6fe5e82b324"},"data":{"id":"refund_2a575991bee3b010f44e438f7f6a6d5f","amount":1,"payment":"payment_c861474086bd50305f51ee7855d65eb5","currency":"USD","failure_reason":"","metadata":{},"reason":"","status":"Completed","receipt_number":0,"created_at":1647637499,"updated_at":1647637499,"merchant_reference_id":"","payment_created_at":1647637498,"payment_method_type":"in_amex_card","ewallets":[{"ewallet":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":1}],"proportional_refund":true,"merchant_debited_amount":null,"merchant_debited_currency":null,"fx_rate":null,"fixed_side":null}} + ) + end + + def failed_refund_response + %( + {"status":{"error_code":"ERROR_GET_PAYMENT","status":"ERROR","message":"The request tried to retrieve a payment, but the payment was not found. The request was rejected. Corrective action: Use a valid payment ID.","response_code":"ERROR_GET_PAYMENT","operation_id":"29a59e7c-8e82-4abe-ad9e-bf47eb72f6c1"}} + ) + end + + def successful_void_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"af46ead1-8b34-48c5-903d-07eefef6cbbd"},"data":{"id":"payment_a29a73f09d6f55defddc779dbb2d1089","amount":0,"original_amount":1,"is_partial":false,"currency_code":"USD","country_code":"in","status":"CAN","description":"","merchant_reference_id":"","customer_token":"cus_256d8f8a97f252f32210a27a97b855a5","payment_method":"card_e42d1d0bdca84661f0b62640330c4c65","payment_method_data":{},"expiration":1648242349,"captured":false,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1647637549,"metadata":{},"failure_code":"","failure_message":"","paid":false,"paid_at":0,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":{},"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","ewallets":[{"ewallet_id":"ewallet_1936682fdca7a188c49eb9f9817ade77","amount":1,"percent":100,"refunded_amount":0}],"payment_method_options":{},"payment_method_type":"in_amex_card","payment_method_type_category":"card","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"not_applicable","error_code":"","remitter_information":{}}} + ) + end + + def failed_void_response + %( + {"status":{"error_code":"UNAUTHORIZED_API_CALL","status":"ERROR","message":"","response_code":"UNAUTHORIZED_API_CALL","operation_id":"12e59804-b742-44eb-aa49-4b722629faa8"}} + ) + end + + def successful_verify_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"27385814-fc69-46fc-bbcc-2a5e0aac442d"},"data":{"id":"payment_2736748fec92a96c7c1280f7e46e2876","amount":0,"original_amount":0,"is_partial":false,"currency_code":"USD","country_code":"US","status":"ACT","description":"","merchant_reference_id":"","customer_token":"cus_c99aab5dae41102b0bb4276ab32e7777","payment_method":"card_5a07af7ff5c038eef4802ffb200fffa6","payment_method_data":{"id":"card_5a07af7ff5c038eef4802ffb200fffa6","type":"us_visa_card","category":"card","metadata":null,"image":"","webhook_url":"","supporting_documentation":"","next_action":"3d_verification","name":"Ryan Reynolds","last4":"1111","acs_check":"unchecked","cvv_check":"unchecked","bin_details":{"type":null,"brand":null,"level":null,"country":null,"bin_number":"411111"},"expiration_year":"35","expiration_month":"12","fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e"},"expiration":1653942478,"captured":false,"refunded":false,"refunded_amount":0,"receipt_email":"","redirect_url":"https://sandboxcheckout.rapyd.net/3ds-payment?token=payment_2736748fec92a96c7c1280f7e46e2876","complete_payment_url":"","error_payment_url":"","receipt_number":"","flow_type":"","address":null,"statement_descriptor":"N/A","transaction_id":"","created_at":1653337678,"metadata":{},"failure_code":"","failure_message":"","paid":false,"paid_at":0,"dispute":null,"refunds":null,"order":null,"outcome":null,"visual_codes":{},"textual_codes":{},"instructions":[],"ewallet_id":null,"ewallets":[],"payment_method_options":{},"payment_method_type":"us_visa_card","payment_method_type_category":"card","fx_rate":1,"merchant_requested_currency":null,"merchant_requested_amount":null,"fixed_side":"","payment_fees":null,"invoice":"","escrow":null,"group_payment":"","cancel_reason":null,"initiation_type":"customer_present","mid":"","next_action":"3d_verification","error_code":"","remitter_information":{}}} + ) + end + + def successful_store_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"47e8bbbc-baa5-43c6-9395-df8a01645e91"},"data":{"id":"cus_4d8509d0997c7ce8aa1f63c19c1b6870","delinquent":false,"discount":null,"name":"Ryan Reynolds","default_payment_method":"card_94a3a70510109163a4eb438f06d82f78","description":"","email":"","phone_number":"","invoice_prefix":"","addresses":[],"payment_methods":{"data":[{"id":"card_94a3a70510109163a4eb438f06d82f78","type":"us_visa_card","category":"card","metadata":null,"image":"https://iconslib.rapyd.net/checkout/us_visa_card.png","webhook_url":"","supporting_documentation":"","next_action":"3d_verification","name":"Ryan Reynolds","last4":"1111","acs_check":"unchecked","cvv_check":"unchecked","bin_details":{"type":null,"brand":null,"level":null,"country":null,"bin_number":"411111"},"expiration_year":"35","expiration_month":"12","fingerprint_token":"ocfp_eb9edd24a3f3f59651aee0bd3d16201e","redirect_url":"https://sandboxcheckout.rapyd.net/3ds-payment?token=payment_f4ab1b25a09cbd769df05b30a29f71a4"}],"has_more":false,"total_count":1,"url":"/v1/customers/cus_4d8509d0997c7ce8aa1f63c19c1b6870/payment_methods"},"subscriptions":null,"created_at":1653487824,"metadata":{},"business_vat_id":"","ewallet":""}} + ) + end + + def successful_unstore_response + %( + {"status":{"error_code":"","status":"SUCCESS","message":"","response_code":"","operation_id":"6f7857f4-e063-4edb-ab93-da60c8563c52"},"data":{"deleted":true,"id":"cus_4d8509d0997c7ce8aa1f63c19c1b6870"}} + ) + end +end diff --git a/test/unit/gateways/reach_test.rb b/test/unit/gateways/reach_test.rb new file mode 100644 index 00000000000..6a86450cccb --- /dev/null +++ b/test/unit/gateways/reach_test.rb @@ -0,0 +1,258 @@ +require 'test_helper' + +class ReachTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ReachGateway.new(fixtures(:reach)) + @credit_card = credit_card + @amount = 100 + + @options = { + email: 'johndoe@reach.com', + order_id: '123', + currency: 'USD', + billing_address: { + address1: '1670', + address2: '1670 NW 82ND AVE', + city: 'Miami', + state: 'FL', + zip: '32191', + country: 'US' + } + } + end + + def test_required_merchant_id_and_secret + error = assert_raises(ArgumentError) { ReachGateway.new } + assert_equal 'Missing required parameter: merchant_id', error.message + end + + def test_supported_card_types + assert_equal ReachGateway.supported_cardtypes, %i[visa diners_club american_express jcb master discover maestro] + end + + def test_should_be_able_format_a_request + post = { + request: { someId: 'abc123' }, + card: { number: '12132323', name: 'John doe' } + } + + formatted = @gateway.send :format_and_sign, post + + refute_empty formatted[:signature] + assert_kind_of String, formatted[:request] + assert_kind_of String, formatted[:card] + + assert_equal 'abc123', JSON.parse(formatted[:request])['someId'] + assert_equal '12132323', JSON.parse(formatted[:card])['number'] + assert formatted[:signature].present? + end + + def test_properly_format_on_zero_decilmal + @options[:currency] = 'BYR' + stub_comms do + @gateway.authorize(1000, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_equal '10', request['Items'].first['ConsumerPrice'] + end.respond_with(successful_purchase_response) + end + + def test_successfully_build_a_purchase + stub_comms do + @gateway.authorize(1250, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + card = JSON.parse(URI.decode_www_form(data)[1][1]) + + # request + assert_equal request['ReferenceId'], @options[:order_id] + assert_equal request['Consumer']['Email'], @options[:email] + assert_equal request['ConsumerCurrency'], @options[:currency] + assert_equal request['Capture'], false + assert_equal '12.50', request['Items'].first['ConsumerPrice'] + + # card + assert_equal card['Number'], @credit_card.number + assert_equal card['Name'], @credit_card.name + assert_equal card['VerificationCode'], @credit_card.verification_value + end.respond_with(successful_purchase_response) + end + + def test_successfully_build_a_purchase_with_fingerprint + stub_comms do + @options[:device_fingerprint] = '54fd66c2-b5b5-4dbd-ab89-12a8b6177347' + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_equal request['DeviceFingerprint'], @options[:device_fingerprint] + end.respond_with(successful_purchase_response) + end + + def test_properly_set_capture_flag_on_purchase + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_equal true, request['Capture'] + end.respond_with(successful_purchase_response) + end + + def test_sending_item_sku_and_item_price + @options[:item_sku] = '1212121212' + @options[:item_quantity] = 250 + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + + # request + assert_equal request['Items'].first['Sku'], @options[:item_sku] + assert_equal request['Items'].first['Quantity'], @options[:item_quantity] + end.respond_with(successful_purchase_response) + end + + def test_successfull_retrieve_error_message + response = { 'response' => { 'Error' => { 'ReasonCode' => 'is an error' } } } + + message = @gateway.send(:message_from, response) + assert_equal 'is an error', message + end + + def test_safe_retrieve_error_message + response = { 'response' => { 'Error' => { 'Code' => 'is an error' } } } + + message = @gateway.send(:message_from, response) + assert_nil message + end + + def test_sucess_from_on_sucess_result + response = { 'response' => { OrderId: '' } } + + assert @gateway.send(:success_from, response) + end + + def test_sucess_from_on_failure + response = { 'response' => { 'Error' => 'is an error' } } + + refute @gateway.send(:success_from, response) + end + + def test_stored_credential + cases = + [ + { { initial_transaction: true, initiator: 'cardholder', reason_type: 'installment' } => 'CIT-Setup-Scheduled' }, + { { initial_transaction: true, initiator: 'cardholder', reason_type: 'unscheduled' } => 'CIT-Setup-Unscheduled-MIT' }, + { { initial_transaction: true, initiator: 'cardholder', reason_type: 'recurring' } => 'CIT-Setup-Unscheduled' }, + { { initial_transaction: false, initiator: 'cardholder', reason_type: 'unscheduled' } => 'CIT-Subsequent-Unscheduled' }, + { { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' } => 'MIT-Subsequent-Scheduled' }, + { { initial_transaction: false, initiator: 'merchant', reason_type: 'unscheduled' } => 'MIT-Subsequent-Unscheduled' } + ] + + cases.each do |stored_credential_case| + stored_credential_options = stored_credential_case.keys[0] + expected = stored_credential_case[stored_credential_options] + @options[:stored_credential] = stored_credential_options + stub_comms do + @gateway.expects(:ssl_request).returns(succesful_query_response) + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_equal expected, request['PaymentModel'] + end.respond_with(successful_purchase_response) + end + end + + def test_stored_credential_with_no_store_credential_parameters + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_equal 'CIT-One-Time', request['PaymentModel'] + end.respond_with(successful_purchase_response) + end + + def test_stored_credential_with_wrong_combination_stored_credential_paramaters + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: true, reason_type: 'unscheduled' } + @gateway.expects(:get_network_payment_reference).returns(stub(message: 'abc123', "success?": true)) + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_empty request['PaymentModel'] + end.respond_with(successful_purchase_response) + end + + def test_stored_credential_with_at_lest_one_stored_credential_paramaters_nil + @options[:stored_credential] = { initiator: 'merchant', initial_transaction: true, reason_type: nil } + @gateway.expects(:get_network_payment_reference).returns(stub(message: 'abc123', "success?": true)) + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(URI.decode_www_form(data)[0][1]) + assert_empty request['PaymentModel'] + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def successful_purchase_response + 'response=%7B%22OrderId%22%3A%22e8f8c529-15c7-46c1-b28b-9d43bb5efe92%22%2C%22UnderReview%22%3Afalse%2C%22Expiry%22%3A%222022-11-03T12%3A47%3A21Z%22%2C%22Authorized%22%3Atrue%2C%22Completed%22%3Afalse%2C%22Captured%22%3Afalse%7D&signature=JqLa7Y68OYRgRcA5ALHOZwXXzdZFeNzqHma2RT2JWAg%3D' + end + + def succesful_query_response + 'response=%7B%22Meta%22%3A%20null%2C%20%22Rate%22%3A%201.000000000000%2C%20%22Items%22%3A%20%5B%7B%22Sku%22%3A%20%22RLaP7OsSZjbR2pJK%22%2C%20%22Quantity%22%3A%201%2C%20%22ConsumerPrice%22%3A%20100.00%2C%20%22MerchantPrice%22%3A%20100.00%7D%5D%2C%20%22Store%22%3A%20null%2C%20%22Times%22%3A%20%7B%22Created%22%3A%20%222022-12-05T17%3A48%3A18.830991Z%22%2C%20%22Processed%22%3A%20null%2C%20%22Authorized%22%3A%20%222022-12-05T17%3A48%3A19.855608Z%22%7D%2C%20%22Action%22%3A%20null%2C%20%22Expiry%22%3A%20%222022-12-12T17%3A48%3A19.855608Z%22%2C%20%22Reason%22%3A%20null%2C%20%22Charges%22%3A%20null%2C%20%22OrderId%22%3A%20%226ec68268-a4a5-44dd-8997-e76df4aa9c97%22%2C%20%22Payment%22%3A%20%7B%22Class%22%3A%20%22Card%22%2C%20%22Expiry%22%3A%20%222030-03%22%2C%20%22Method%22%3A%20%22VISA%22%2C%20%22AccountIdentifier%22%3A%20%22444433******1111%22%2C%20%22NetworkPaymentReference%22%3A%20%22546646904394415%22%7D%2C%20%22Refunds%22%3A%20%5B%5D%2C%20%22Consumer%22%3A%20%7B%22City%22%3A%20%22Miami%22%2C%20%22Name%22%3A%20%22Longbob%20Longsen%22%2C%20%22Email%22%3A%20%22johndoe%40reach.com%22%2C%20%22Address%22%3A%20%221670%22%2C%20%22Country%22%3A%20%22US%22%2C%20%22EffectiveIpAddress%22%3A%20%22181.78.14.203%22%7D%2C%20%22Shipping%22%3A%20null%2C%20%22Consignee%22%3A%20null%2C%20%22Discounts%22%3A%20null%2C%20%22Financing%22%3A%20null%2C%20%22Chargeback%22%3A%20false%2C%20%22ContractId%22%3A%20null%2C%20%22MerchantId%22%3A%20%22testMerchantId%22%2C%20%22OrderState%22%3A%20%22PaymentAuthorized%22%2C%20%22RateOfferId%22%3A%20%22c754012f-e0fc-4630-9cb5-11c3450f462e%22%2C%20%22ReferenceId%22%3A%20%22123%22%2C%20%22UnderReview%22%3A%20false%2C%20%22ConsumerTotal%22%3A%20100.00%2C%20%22MerchantTotal%22%3A%20100.00%2C%20%22TransactionId%22%3A%20%22e08f6501-2607-4be1-9dba-97d6780dfe9a%22%2C%20%22ConsumerCurrency%22%3A%20%22USD%22%7D&signature=no%2BEojgxrO5JK4wt4EWtbuY9M7h1eVQ9SLezu10X%2Bn4%3D' + end + + def pre_scrubbed + <<-PRE_SCRUBBED + <- "POST /v2.21/checkout HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: checkout.rch.how\r\nContent-Length: 756\r\n\r\n" + <- "request=%7B%22MerchantId%22%3A%22Some-30value-4for3-9test35-f93086cd7crednet1%22%2C%22ReferenceId%22%3A%22123%22%2C%22ConsumerCurrency%22%3A%22USD%22%2C%22Capture%22%3Atrue%2C%22PaymentMethod%22%3A%22VISA%22%2C%22Items%22%3A%5B%7B%22Sku%22%3A%22d99oJA8rkwgQANFJ%22%2C%22ConsumerPrice%22%3A100%2C%22Quantity%22%3A1%7D%5D%2C%22ViaAgent%22%3Atrue%2C%22Consumer%22%3A%7B%22Name%22%3A%22Longbob+Longsen%22%2C%22Email%22%3A%22johndoe%40reach.com%22%2C%22Address%22%3A%221670%22%2C%22City%22%3A%22Miami%22%2C%22Country%22%3A%22US%22%7D%7D&card=%7B%22Name%22%3A%22Longbob+Longsen%22%2C%22Number%22%3A%224444333322221111%22%2C%22Expiry%22%3A%7B%22Month%22%3A3%2C%22Year%22%3A2030%7D%2C%22VerificationCode%22%3A737%7D&signature=5nimSignatUre%3D" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 03 Nov 2022 23:04:01 GMT\r\n" + -> "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n" + -> "Content-Length: 235\r\n" + -> "Connection: close\r\n" + -> "Server: ipCheckoutApi/unreleased ibiHttpServer\r\n" + -> "Strict-Transport-Security: max-age=60000\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "\r\n" + reading 235 bytes... + -> "response=%7B%22OrderId%22%3A%22621a0c76-69fb-4c05-854a-e7e731759ad3%22%2C%22UnderReview%22%3Afalse%2C%22Authorized%22%3Atrue%2C%22Completed%22%3Afalse%2C%22Captured%22%3Afalse%7D&signature=23475signature23123%3D" + read 235 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SRCUBBED + <- "POST /v2.21/checkout HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: checkout.rch.how\r\nContent-Length: 756\r\n\r\n" + <- "request=%7B%22MerchantId%22%3A%22[FILTERED]%22%2C%22ReferenceId%22%3A%22123%22%2C%22ConsumerCurrency%22%3A%22USD%22%2C%22Capture%22%3Atrue%2C%22PaymentMethod%22%3A%22VISA%22%2C%22Items%22%3A%5B%7B%22Sku%22%3A%22d99oJA8rkwgQANFJ%22%2C%22ConsumerPrice%22%3A100%2C%22Quantity%22%3A1%7D%5D%2C%22ViaAgent%22%3Atrue%2C%22Consumer%22%3A%7B%22Name%22%3A%22Longbob+Longsen%22%2C%22Email%22%3A%22johndoe%40reach.com%22%2C%22Address%22%3A%221670%22%2C%22City%22%3A%22Miami%22%2C%22Country%22%3A%22US%22%7D%7D&card=%7B%22Name%22%3A%22Longbob+Longsen%22%2C%22Number%22%3A%22[FILTERED]%22%2C%22Expiry%22%3A%7B%22Month%22%3A3%2C%22Year%22%3A2030%7D%2C%22VerificationCode%22%3A[FILTERED]%7D&signature=[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 03 Nov 2022 23:04:01 GMT\r\n" + -> "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n" + -> "Content-Length: 235\r\n" + -> "Connection: close\r\n" + -> "Server: ipCheckoutApi/unreleased ibiHttpServer\r\n" + -> "Strict-Transport-Security: max-age=60000\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "\r\n" + reading 235 bytes... + -> "response=%7B%22OrderId%22%3A%22621a0c76-69fb-4c05-854a-e7e731759ad3%22%2C%22UnderReview%22%3Afalse%2C%22Authorized%22%3Atrue%2C%22Completed%22%3Afalse%2C%22Captured%22%3Afalse%7D&signature=[FILTERED]" + read 235 bytes + Conn close + POST_SRCUBBED + end +end diff --git a/test/unit/gateways/realex_test.rb b/test/unit/gateways/realex_test.rb index b0b5e92859b..9fc3358c256 100644 --- a/test/unit/gateways/realex_test.rb +++ b/test/unit/gateways/realex_test.rb @@ -1,10 +1,11 @@ require 'test_helper' class RealexTest < Test::Unit::TestCase + include CommStub + class ActiveMerchant::Billing::RealexGateway # For the purposes of testing, lets redefine some protected methods as public. - public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request, - :build_capture_request, :build_verify_request + public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request, :build_capture_request, :build_verify_request, :build_credit_request end def setup @@ -12,52 +13,90 @@ def setup @password = 'your_secret' @account = 'your_account' @rebate_secret = 'your_rebate_secret' + @refund_secret = 'your_refund_secret' @gateway = RealexGateway.new( - :login => @login, - :password => @password, - :account => @account + login: @login, + password: @password, + account: @account ) @gateway_with_account = RealexGateway.new( - :login => @login, - :password => @password, - :account => 'bill_web_cengal' + login: @login, + password: @password, + account: 'bill_web_cengal' ) @credit_card = CreditCard.new( - :number => '4263971921001307', - :month => 8, - :year => 2008, - :first_name => 'Longbob', - :last_name => 'Longsen', - :brand => 'visa' + number: '4263971921001307', + month: 8, + year: 2008, + first_name: 'Longbob', + last_name: 'Longsen', + brand: 'visa' ) @options = { - :order_id => '1' + order_id: '1' } @address = { - :name => 'Longbob Longsen', - :address1 => '123 Fake Street', - :city => 'Belfast', - :state => 'Antrim', - :country => 'Northern Ireland', - :zip => 'BT2 8XX' + name: 'Longbob Longsen', + address1: '123 Fake Street', + city: 'Belfast', + state: 'Antrim', + country: 'Northern Ireland', + zip: 'BT2 8XX' } @amount = 100 end + def test_initialize_sets_refund_and_credit_hashes + refund_secret = 'refund' + rebate_secret = 'rebate' + + gateway = RealexGateway.new( + login: @login, + password: @password, + rebate_secret: rebate_secret, + refund_secret: refund_secret + ) + + assert gateway.options[:refund_hash] == Digest::SHA1.hexdigest(rebate_secret) + assert gateway.options[:credit_hash] == Digest::SHA1.hexdigest(refund_secret) + end + + def test_initialize_with_nil_refund_and_rebate_secrets + gateway = RealexGateway.new( + login: @login, + password: @password, + rebate_secret: nil, + refund_secret: nil + ) + + assert_false gateway.options.key?(:refund_hash) + assert_false gateway.options.key?(:credit_hash) + end + + def test_initialize_without_refund_and_rebate_secrets + gateway = RealexGateway.new( + login: @login, + password: @password + ) + + assert_false gateway.options.key?(:refund_hash) + assert_false gateway.options.key?(:credit_hash) + end + def test_hash gateway = RealexGateway.new( - :login => 'thestore', - :password => 'mysecret' + login: 'thestore', + password: 'mysecret' ) Time.stubs(:now).returns(Time.new(2001, 4, 3, 12, 32, 45)) gateway.expects(:ssl_post).with(anything, regexp_matches(/9af7064afd307c9f988e8dfc271f9257f1fc02f6/)).returns(successful_purchase_response) - gateway.purchase(29900, credit_card('5105105105105100'), :order_id => 'ORD453-11') + gateway.purchase(29900, credit_card('5105105105105100'), order_id: 'ORD453-11') end def test_successful_purchase @@ -78,6 +117,28 @@ def test_unsuccessful_purchase assert response.test? end + def test_purchase_passes_stored_credential + options = @options.merge({ + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + }) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + stored_credential_params = Nokogiri::XML.parse(data).xpath('//storedcredential') + + assert_equal stored_credential_params.xpath('type').text, 'oneoff' + assert_equal stored_credential_params.xpath('initiator').text, 'cardholder' + assert_equal stored_credential_params.xpath('sequence').text, 'first' + assert_equal stored_credential_params.xpath('srd').text, '' + end.respond_with(successful_purchase_response) + end + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_refund_response) assert_success @gateway.refund(@amount, '1234;1234;1234') @@ -88,19 +149,22 @@ def test_unsuccessful_refund assert_failure @gateway.refund(@amount, '1234;1234;1234') end - def test_deprecated_credit - @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do - assert_success @gateway.credit(@amount, '1234;1234;1234') - end + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + assert_success @gateway.credit(@amount, @credit_card, @options) + end + + def test_unsuccessful_credit + @gateway.expects(:ssl_post).returns(unsuccessful_credit_response) + assert_failure @gateway.credit(@amount, @credit_card, @options) end def test_supported_countries - assert_equal ['IE', 'GB', 'FR', 'BE', 'NL', 'LU', 'IT', 'US', 'CA', 'ES'], RealexGateway.supported_countries + assert_equal %w[IE GB FR BE NL LU IT US CA ES], RealexGateway.supported_countries end def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :diners_club ], RealexGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club], RealexGateway.supported_cardtypes end def test_avs_result_not_supported @@ -130,49 +194,86 @@ def test_malformed_xml def test_capture_xml @gateway.expects(:new_timestamp).returns('20090824160201') - valid_capture_xml = <<-SRC - - your_merchant_id - your_account - 100 - 1 - 4321 - 1234 - ef0a6c485452f3f94aff336fa90c6c62993056ca - -SRC + valid_capture_xml = <<~SRC + + your_merchant_id + your_account + 100 + 1 + 4321 + 1234 + ef0a6c485452f3f94aff336fa90c6c62993056ca + + SRC assert_xml_equal valid_capture_xml, @gateway.build_capture_request(@amount, '1;4321;1234', {}) end def test_purchase_xml options = { - :order_id => '1' + order_id: '1', + ip: '123.456.789.0' } @gateway.expects(:new_timestamp).returns('20090824160201') - valid_purchase_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - 100 - - 4263971921001307 - 0808 - Longbob Longsen - VISA - - - - - - - - 3499d7bc8dbacdcfba2286bd74916d026bae630f - -SRC + valid_purchase_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 3499d7bc8dbacdcfba2286bd74916d026bae630f + + 123.456.789.0 + + + SRC + + assert_xml_equal valid_purchase_request_xml, @gateway.build_purchase_or_authorization_request(:purchase, @amount, @credit_card, options) + end + + def test_purchase_xml_with_ipv6 + options = { + order_id: '1', + ip: '2a02:c7d:da18:ac00:6d10:4f13:1795:4890' + } + + @gateway.expects(:new_timestamp).returns('20090824160201') + + valid_purchase_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 3499d7bc8dbacdcfba2286bd74916d026bae630f + + SRC assert_xml_equal valid_purchase_request_xml, @gateway.build_purchase_or_authorization_request(:purchase, @amount, @credit_card, options) end @@ -180,77 +281,77 @@ def test_purchase_xml def test_void_xml @gateway.expects(:new_timestamp).returns('20090824160201') - valid_void_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - 4321 - 1234 - 4132600f1dc70333b943fc292bd0ca7d8e722f6e - -SRC + valid_void_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 4321 + 1234 + 4132600f1dc70333b943fc292bd0ca7d8e722f6e + + SRC assert_xml_equal valid_void_request_xml, @gateway.build_void_request('1;4321;1234', {}) end def test_verify_xml options = { - :order_id => '1' + order_id: '1' } @gateway.expects(:new_timestamp).returns('20181026114304') - valid_verify_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - - 4263971921001307 - 0808 - Longbob Longsen - VISA - - - - - - - d53aebf1eaee4c3ff4c30f83f27b80ce99ba5644 - -SRC + valid_verify_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + d53aebf1eaee4c3ff4c30f83f27b80ce99ba5644 + + SRC assert_xml_equal valid_verify_request_xml, @gateway.build_verify_request(@credit_card, options) end def test_auth_xml options = { - :order_id => '1' + order_id: '1' } @gateway.expects(:new_timestamp).returns('20090824160201') - valid_auth_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - 100 - - 4263971921001307 - 0808 - Longbob Longsen - VISA - - - - - - - - 3499d7bc8dbacdcfba2286bd74916d026bae630f - -SRC + valid_auth_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 3499d7bc8dbacdcfba2286bd74916d026bae630f + + SRC assert_xml_equal valid_auth_request_xml, @gateway.build_purchase_or_authorization_request(:authorization, @amount, @credit_card, options) end @@ -258,51 +359,114 @@ def test_auth_xml def test_refund_xml @gateway.expects(:new_timestamp).returns('20090824160201') - valid_refund_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - 4321 - 1234 - 100 - - ef0a6c485452f3f94aff336fa90c6c62993056ca - -SRC + valid_refund_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 4321 + 1234 + 100 + + ef0a6c485452f3f94aff336fa90c6c62993056ca + + SRC assert_xml_equal valid_refund_request_xml, @gateway.build_refund_request(@amount, '1;4321;1234', {}) end def test_refund_with_rebate_secret_xml - gateway = RealexGateway.new(:login => @login, :password => @password, :account => @account, :rebate_secret => @rebate_secret) + gateway = RealexGateway.new(login: @login, password: @password, account: @account, rebate_secret: @rebate_secret) gateway.expects(:new_timestamp).returns('20090824160201') - valid_refund_request_xml = <<-SRC - - your_merchant_id - your_account - 1 - 4321 - 1234 - 100 - f94ff2a7c125a8ad87e5683114ba1e384889240e - - ef0a6c485452f3f94aff336fa90c6c62993056ca - -SRC + valid_refund_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 4321 + 1234 + 100 + f94ff2a7c125a8ad87e5683114ba1e384889240e + + ef0a6c485452f3f94aff336fa90c6c62993056ca + + SRC assert_xml_equal valid_refund_request_xml, gateway.build_refund_request(@amount, '1;4321;1234', {}) end + def test_credit_xml + options = { + order_id: '1' + } + + @gateway.expects(:new_timestamp).returns('20190717161006') + + valid_credit_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 73ff566dcfc3a73bebf1a2d387316162111f030e + + SRC + + assert_xml_equal valid_credit_request_xml, @gateway.build_credit_request(@amount, @credit_card, options) + end + + def test_credit_with_refund_secret_xml + gateway = RealexGateway.new(login: @login, password: @password, account: @account, refund_secret: @refund_secret) + + gateway.expects(:new_timestamp).returns('20190717161006') + + valid_credit_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + bbc192c6eac0132a039c23eae8550a22907c6796 + + 73ff566dcfc3a73bebf1a2d387316162111f030e + + SRC + + assert_xml_equal valid_credit_request_xml, gateway.build_credit_request(@amount, @credit_card, @options) + end + def test_auth_with_address @gateway.expects(:ssl_post).returns(successful_purchase_response) options = { - :order_id => '1', - :billing_address => @address, - :shipping_address => @address + order_id: '1', + billing_address: @address, + shipping_address: @address } @gateway.expects(:new_timestamp).returns('20090824160201') @@ -317,8 +481,8 @@ def test_zip_in_shipping_address @gateway.expects(:ssl_post).with(anything, regexp_matches(/28\|123<\/code>/)).returns(successful_purchase_response) options = { - :order_id => '1', - :shipping_address => @address + order_id: '1', + shipping_address: @address } @gateway.authorize(@amount, @credit_card, options) @@ -328,8 +492,8 @@ def test_zip_in_billing_address @gateway.expects(:ssl_post).with(anything, regexp_matches(/28\|123<\/code>/)).returns(successful_purchase_response) options = { - :order_id => '1', - :billing_address => @address + order_id: '1', + billing_address: @address } @gateway.authorize(@amount, @credit_card, options) @@ -339,128 +503,270 @@ def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end + def test_three_d_secure_1 + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234', + version: '1.0.2' + } + } + + response = @gateway.authorize(@amount, @credit_card, options) + assert_equal 'M', response.cvv_result['code'] + end + + def test_auth_xml_with_three_d_secure_1 + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234', + version: '1.0.2' + } + } + + @gateway.expects(:new_timestamp).returns('20090824160201') + + valid_auth_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 3499d7bc8dbacdcfba2286bd74916d026bae630f + + 1234 + 1234 + 1234 + 1 + + + SRC + + assert_xml_equal valid_auth_request_xml, @gateway.build_purchase_or_authorization_request(:authorization, @amount, @credit_card, options) + end + + def test_auth_xml_with_three_d_secure_2 + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + ds_transaction_id: '1234', + version: '2.1.0' + } + } + + @gateway.expects(:new_timestamp).returns('20090824160201') + + valid_auth_request_xml = <<~SRC + + your_merchant_id + your_account + 1 + 100 + + 4263971921001307 + 0808 + Longbob Longsen + VISA + + + + + + + + 3499d7bc8dbacdcfba2286bd74916d026bae630f + + 1234 + 1234 + 1234 + 2.1.0 + + + SRC + + assert_xml_equal valid_auth_request_xml, @gateway.build_purchase_or_authorization_request(:authorization, @amount, @credit_card, options) + end + private def successful_purchase_response - <<-RESPONSE - - your merchant id - account to use - order id from request - authcode received - 00 - [ test system ] message returned from system - realex payments reference - M - batch id for this transaction (if any) - - Issuing Bank Name - Issuing Bank Country - Issuing Bank Country Code - Issuing Bank Region - - - 89 - 9 - 9 - - 7384ae67....ac7d7d - 34e7....a77d -" + <<~RESPONSE + + your merchant id + account to use + order id from request + authcode received + 00 + [ test system ] message returned from system + realex payments reference + M + batch id for this transaction (if any) + + Issuing Bank Name + Issuing Bank Country + Issuing Bank Country Code + Issuing Bank Region + + + 89 + 9 + 9 + + 7384ae67....ac7d7d + 34e7....a77d + " RESPONSE end def unsuccessful_purchase_response - <<-RESPONSE - - your merchant id - account to use - order id from request - authcode received - 01 - [ test system ] message returned from system - realex payments reference - M - batch id for this transaction (if any) - - Issuing Bank Name - Issuing Bank Country - Issuing Bank Country Code - Issuing Bank Region - - - 89 - 9 - 9 - - 7384ae67....ac7d7d - 34e7....a77d -" + <<~RESPONSE + + your merchant id + account to use + order id from request + authcode received + 01 + [ test system ] message returned from system + realex payments reference + M + batch id for this transaction (if any) + + Issuing Bank Name + Issuing Bank Country + Issuing Bank Country Code + Issuing Bank Region + + + 89 + 9 + 9 + + 7384ae67....ac7d7d + 34e7....a77d + " RESPONSE end def malformed_unsuccessful_purchase_response - <<-RESPONSE - - your merchant id - account to use - order id from request - authcode received - 01 - [ test system ] This is & not awesome - realex payments reference - M - batch id for this transaction (if any) - - Issuing Bank Name - Issuing Bank Country - Issuing Bank Country Code - Issuing Bank Region - - - 89 - 9 - 9 - - 7384ae67....ac7d7d - 34e7....a77d -" + <<~RESPONSE + + your merchant id + account to use + order id from request + authcode received + 01 + [ test system ] This is & not awesome + realex payments reference + M + batch id for this transaction (if any) + + Issuing Bank Name + Issuing Bank Country + Issuing Bank Country Code + Issuing Bank Region + + + 89 + 9 + 9 + + 7384ae67....ac7d7d + 34e7....a77d + " RESPONSE end def successful_refund_response - <<-RESPONSE - - your merchant id - account to use - order id from request - authcode received - 00 - [ test system ] message returned from system - realex payments reference - M - batch id for this transaction (if any) - 7384ae67....ac7d7d - 34e7....a77d -" + <<~RESPONSE + + your merchant id + account to use + order id from request + authcode received + 00 + [ test system ] message returned from system + realex payments reference + M + batch id for this transaction (if any) + 7384ae67....ac7d7d + 34e7....a77d + " RESPONSE end def unsuccessful_refund_response + <<~RESPONSE + + your merchant id + account to use + order id from request + authcode received + 508 + [ test system ] You may only rebate up to 115% of the original amount. + realex payments reference + M + batch id for this transaction (if any) + 7384ae67....ac7d7d + 34e7....a77d + " + RESPONSE + end + + def successful_credit_response <<-RESPONSE - - your merchant id - account to use - order id from request - authcode received - 508 - [ test system ] You may only rebate up to 115% of the original amount. - realex payments reference - M - batch id for this transaction (if any) - 7384ae67....ac7d7d - 34e7....a77d -" + + spreedly + internet + 57a861e97273371e6f1b1737a9bc5710 + 005030 + 00 + U + U + U + 674655 + AUTH CODE: 005030 + 15633930303644971 + 0 + 0 + + AIB BANK + IRELAND + IE + EUR + + 6d2fc...67814 + " + RESPONSE + end + + def unsuccessful_credit_response + <<-RESPONSE + + 502 + Refund Hash not present. + _refund_fd4ea2d10b339011bdba89f580c5b207 + " RESPONSE end @@ -525,10 +831,9 @@ def scrubbed_transcript
- REQUEST + REQUEST end - require 'nokogiri' def assert_xml_equal(expected, actual) assert_xml_equal_recursive(Nokogiri::XML(expected).root, Nokogiri::XML(actual).root) end diff --git a/test/unit/gateways/redsys_sha256_test.rb b/test/unit/gateways/redsys_sha256_test.rb index f1fbac3c70f..66dae34a2c0 100644 --- a/test/unit/gateways/redsys_sha256_test.rb +++ b/test/unit/gateways/redsys_sha256_test.rb @@ -6,12 +6,13 @@ class RedsysSHA256Test < Test::Unit::TestCase def setup Base.mode = :test @credentials = { - :login => '091952713', - :secret_key => 'QIK77hYl6UFcoCYFKcj+ZjJg8Q6I93Dx', - :signature_algorithm => 'sha256' + login: '091952713', + secret_key: 'QIK77hYl6UFcoCYFKcj+ZjJg8Q6I93Dx', + signature_algorithm: 'sha256' } @gateway = RedsysGateway.new(@credentials) @credit_card = credit_card('4548812049400004') + @threeds2_credit_card = credit_card('4918019199883839') @headers = { 'Content-Type' => 'application/x-www-form-urlencoded' } @@ -22,17 +23,17 @@ def test_purchase_payload @credit_card.month = 9 @credit_card.year = 2017 @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request, @headers).returns(successful_purchase_response) - @gateway.purchase(100, @credit_card, :order_id => '144742736014') + @gateway.purchase(100, @credit_card, order_id: '144742736014') end def test_purchase_payload_with_credit_card_token @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request_with_credit_card_token, @headers).returns(successful_purchase_response) - @gateway.purchase(100, '3126bb8b80a79e66eb1ecc39e305288b60075f86', :order_id => '144742884282') + @gateway.purchase(100, '3126bb8b80a79e66eb1ecc39e305288b60075f86', order_id: '144742884282') end def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - res = @gateway.purchase(100, credit_card, :order_id => '144742736014') + res = @gateway.purchase(100, credit_card, order_id: '144742736014') assert_success res assert_equal 'Transaction Approved', res.message assert_equal '144742736014|100|978', res.authorization @@ -42,7 +43,7 @@ def test_successful_purchase # This one is being werid... def test_successful_purchase_requesting_credit_card_token @gateway.expects(:ssl_post).returns(successful_purchase_response_with_credit_card_token) - res = @gateway.purchase(100, 'e55e1d0ef338e281baf1d0b5b68be433260ddea0', :order_id => '144742955848') + res = @gateway.purchase(100, 'e55e1d0ef338e281baf1d0b5b68be433260ddea0', order_id: '144742955848') assert_success res assert_equal 'Transaction Approved', res.message assert_equal '144742955848|100|978', res.authorization @@ -52,7 +53,7 @@ def test_successful_purchase_requesting_credit_card_token def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - res = @gateway.purchase(100, credit_card, :order_id => '144743314659') + res = @gateway.purchase(100, credit_card, order_id: '144743314659') assert_failure res assert_equal 'SIS0093 ERROR', res.message end @@ -65,7 +66,7 @@ def test_purchase_without_order_id def test_error_purchase @gateway.expects(:ssl_post).returns(error_purchase_response) - res = @gateway.purchase(100, credit_card, :order_id => '123') + res = @gateway.purchase(100, credit_card, order_id: '123') assert_failure res assert_equal 'SIS0051 ERROR', res.message end @@ -104,7 +105,7 @@ def test_authorize ), anything ).returns(successful_authorize_response) - response = @gateway.authorize(100, credit_card, :order_id => '144743367273') + response = @gateway.authorize(100, credit_card, order_id: '144743367273') assert_success response end @@ -114,10 +115,203 @@ def test_authorize_without_order_id end end + def test_successful_authorize_with_3ds + @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) + response = @gateway.authorize(100, credit_card, { execute_threed: true, order_id: '156270437866' }) + assert response.test? + assert response.params['ds_emv3ds'] + assert_equal response.message, 'CardConfiguration' + assert_equal response.authorization, '156270437866||' + end + + def test_successful_purchase_with_3ds + @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) + response = @gateway.purchase(100, credit_card, { execute_threed: true, order_id: '156270437866' }) + assert response.test? + assert response.params['ds_emv3ds'] + assert_equal response.message, 'CardConfiguration' + assert_equal response.authorization, '156270437866||' + end + + def test_successful_purchase_with_3ds2 + @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) + response = @gateway.purchase(100, @threeds2_credit_card, { execute_threed: true, order_id: '156270437866' }) + assert response.test? + assert response.params['ds_emv3ds'] + assert_equal response.message, 'CardConfiguration' + assert_equal response.authorization, '156270437866||' + end + + def test_successful_purchase_with_3ds2_and_mit_exemption + @gateway.expects(:ssl_post).returns(successful_purchase_with_3ds2_and_mit_exemption_response) + response = @gateway.purchase(100, @threeds2_credit_card, { execute_threed: true, order_id: '161608782525', sca_exemption: 'MIT', sca_exemption_direct_payment_enabled: true }) + assert response.test? + assert response.params['ds_emv3ds'] + + assert_equal response.message, 'CardConfiguration' + assert_equal response.authorization, '161608782525||' + + assert response.params['ds_card_psd2'] + assert_equal '2.1.0', JSON.parse(response.params['ds_emv3ds'])['protocolVersion'] + assert_equal 'Y', response.params['ds_card_psd2'] + assert_equal 'CardConfiguration', response.message + end + + def test_3ds_data_passed + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { execute_threed: true, order_id: '156270437866', terminal: 12, sca_exemption: 'LWV' }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/iniciaPeticion/, data) + assert_match(/12<\/DS_MERCHANT_TERMINAL>/, data) + assert_match(/\"threeDSInfo\":\"CardData\"/, data) + + # as per docs on Inicia Peticion Y must be passed + assert_match(/Y<\/DS_MERCHANT_EXCEP_SCA>/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_3ds_data_with_special_characters_properly_escaped + @credit_card.first_name = 'Julián' + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, @credit_card, { execute_threed: true, order_id: '156270437866', terminal: 12, sca_exemption: 'LWV', description: 'esta es la descripción' }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/iniciaPeticion/, data) + assert_match(/12<\/DS_MERCHANT_TERMINAL>/, data) + assert_match(/\"threeDSInfo\":\"CardData\"/, data) + + # as per docs on Inicia Peticion Y must be passed + assert_match(/Y<\/DS_MERCHANT_EXCEP_SCA>/, data) + assert_match(/Juli%C3%A1n/, data) + assert_match(/descripci%C3%B3n/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_3ds1_data_passed_as_mpi + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { order_id: '156270437866', description: 'esta es la descripción', three_d_secure: { version: '1.0.2', xid: 'xid', ds_transaction_id: 'ds_transaction_id', cavv: 'cavv', eci: '02' } }) + end.check_request do |_method, _endpoint, encdata, _headers| + data = CGI.unescape(encdata) + assert_match(//, data) + assert_match(%r("TXID":"xid"), data) + assert_match(%r("CAVV":"cavv"), data) + assert_match(%r("ECI":"02"), data) + + assert_not_match(%r("authenticacionMethod"), data) + assert_not_match(%r("authenticacionType"), data) + assert_not_match(%r("authenticacionFlow"), data) + + assert_not_match(%r("protocolVersion":"2.1.0"), data) + assert_match(/descripción/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_3ds2_data_passed_as_mpi + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { order_id: '156270437866', description: 'esta es la descripción', three_d_secure: { version: '2.1.0', three_ds_server_trans_id: 'three_ds_server_trans_id', ds_transaction_id: 'ds_transaction_id', cavv: 'cavv', eci: '02' } }) + end.check_request do |_method, _endpoint, encdata, _headers| + data = CGI.unescape(encdata) + assert_match(//, data) + assert_match(%r("threeDSServerTransID":"three_ds_server_trans_id"), data) + assert_match(%r("dsTransID":"ds_transaction_id"), data) + assert_match(%r("authenticacionValue":"cavv"), data) + assert_match(%r("Eci":"02"), data) + + assert_not_match(%r("authenticacionMethod"), data) + assert_not_match(%r("authenticacionType"), data) + assert_not_match(%r("authenticacionFlow"), data) + + assert_match(%r("protocolVersion":"2.1.0"), data) + assert_match(/descripción/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_3ds2_data_passed_as_mpi_with_optional_values + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { order_id: '156270437866', description: 'esta es la descripción', three_d_secure: { version: '2.1.0', three_ds_server_trans_id: 'three_ds_server_trans_id', ds_transaction_id: 'ds_transaction_id', cavv: 'cavv', eci: '02' }, + authentication_method: '01', + authentication_type: 'anything', + authentication_flow: 'F' }) + end.check_request do |_method, _endpoint, encdata, _headers| + data = CGI.unescape(encdata) + assert_match(//, data) + assert_match(%r("threeDSServerTransID":"three_ds_server_trans_id"), data) + assert_match(%r("dsTransID":"ds_transaction_id"), data) + assert_match(%r("authenticacionValue":"cavv"), data) + assert_match(%r("Eci":"02"), data) + + assert_match(%r("authenticacionMethod":"01"), data) + assert_match(%r("authenticacionType":"anything"), data) + assert_match(%r("authenticacionFlow":"F"), data) + + assert_match(%r("protocolVersion":"2.1.0"), data) + assert_match(/descripción/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_3ds2_data_as_mpi_with_special_characters_properly_escaped + @credit_card.first_name = 'Julián' + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, @credit_card, { order_id: '156270437866', terminal: 12, description: 'esta es la descripción', three_d_secure: { version: '2.1.0', xid: 'xid', ds_transaction_id: 'ds_transaction_id', cavv: 'cavv' } }) + end.check_request do |_method, _endpoint, encdata, _headers| + assert_match(/Juli%C3%A1n/, encdata) + assert_match(%r(descripci%C3%B3n), encdata) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_mit_exemption_sets_direct_payment + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(100, @threeds2_credit_card, { order_id: '161608782525', terminal: 12, sca_exemption: 'MIT' }) + end.check_request do |_method, _endpoint, encdata, _headers| + assert_match(/true/, encdata) + end.respond_with(successful_non_3ds_purchase_with_mit_exemption_response) + end + + def test_mit_exemption_hits_webservice_endpoint + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(100, @threeds2_credit_card, { order_id: '161608782525', terminal: 12, sca_exemption: 'MIT' }) + end.check_request do |_method, endpoint, _encdata, _headers| + assert_match(/\/sis\/services\/SerClsWSEntradaV2/, endpoint) + end.respond_with(successful_non_3ds_purchase_with_mit_exemption_response) + end + + def test_webservice_endpoint_override_hits_webservice_endpoint + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, @credit_card, { order_id: '156270437866', use_webservice_endpoint: true }) + end.check_request do |_method, endpoint, _encdata, _headers| + assert_match(/\/sis\/services\/SerClsWSEntradaV2/, endpoint) + end.respond_with(successful_non_3ds_purchase_with_mit_exemption_response) + end + + def test_webservice_endpoint_requests_escapes_special_characters_in_card_name_and_description + @credit_card.first_name = 'Julián' + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, @credit_card, { order_id: '156270437866', description: 'esta es la descripción', use_webservice_endpoint: true }) + end.check_request do |_method, _endpoint, encdata, _headers| + assert_match(/Juli%C3%A1n/, encdata) + assert_match(%r(descripci%C3%B3n), encdata) + end.respond_with(successful_non_3ds_purchase_with_mit_exemption_response) + end + + def test_moto_flag_passed + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { order_id: '156270437866', moto: true, metadata: { manual_entry: true } }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/DS_MERCHANT_DIRECTPAYMENT%3Emoto%3C%2FDS_MERCHANT_DIRECTPAYMENT/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + + def test_moto_flag_not_passed_if_not_explicitly_requested + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, { order_id: '156270437866', metadata: { manual_entry: true } }) + end.check_request do |_method, _endpoint, data, _headers| + refute_match(/DS_MERCHANT_DIRECTPAYMENT%3Emoto%3C%2FDS_MERCHANT_DIRECTPAYMENT/, data) + end.respond_with(successful_authorize_with_3ds_response) + end + def test_bad_order_id_format stub_comms(@gateway, :ssl_request) do @gateway.authorize(100, credit_card, order_id: 'Una#cce-ptable44Format') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/MERCHANT_ORDER%3E\d\d\d\dUnaccept%3C/, data) end.respond_with(successful_authorize_response) end @@ -125,7 +319,7 @@ def test_bad_order_id_format def test_order_id_numeric_start_but_too_long stub_comms(@gateway, :ssl_request) do @gateway.authorize(100, credit_card, order_id: '1234ThisIs]FineButTooLong') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/MERCHANT_ORDER%3E1234ThisIsFi%3C/, data) end.respond_with(successful_authorize_response) end @@ -163,25 +357,25 @@ def test_override_currency includes(CGI.escape('840')), anything ).returns(successful_purchase_response) - @gateway.authorize(100, credit_card, :order_id => '1001', :currency => 'USD') + @gateway.authorize(100, credit_card, order_id: '1001', currency: 'USD') end def test_successful_verify @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response) - response = @gateway.verify(credit_card, :order_id => '144743367273') + response = @gateway.verify(credit_card, order_id: '144743367273') assert_success response end def test_successful_verify_with_failed_void @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response) - response = @gateway.verify(credit_card, :order_id => '144743367273') + response = @gateway.verify(credit_card, order_id: '144743367273') assert_success response assert_equal 'Transaction Approved', response.message end def test_unsuccessful_verify @gateway.expects(:ssl_post).returns(failed_authorize_response) - response = @gateway.verify(credit_card, :order_id => '141278225678') + response = @gateway.verify(credit_card, order_id: '141278225678') assert_failure response assert_equal 'SIS0093 ERROR', response.message end @@ -197,11 +391,11 @@ def test_default_currency end def test_supported_countries - assert_equal ['ES'], RedsysGateway.supported_countries + assert_equal %w[ES FR GB IT PL PT], RedsysGateway.supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :jcb, :diners_club], RedsysGateway.supported_cardtypes + assert_equal %i[visa master american_express jcb diners_club unionpay], RedsysGateway.supported_cardtypes end def test_using_test_mode @@ -212,10 +406,10 @@ def test_using_test_mode def test_overriding_options Base.mode = :production gw = RedsysGateway.new( - :terminal => 1, - :login => '1234', - :secret_key => '12345', - :test => true + terminal: 1, + login: '1234', + secret_key: '12345', + test: true ) assert gw.test? assert_equal RedsysGateway.test_url, gw.send(:url) @@ -224,9 +418,9 @@ def test_overriding_options def test_production_mode Base.mode = :production gw = RedsysGateway.new( - :terminal => 1, - :login => '1234', - :secret_key => '12345' + terminal: 1, + login: '1234', + secret_key: '12345' ) assert !gw.test? assert_equal RedsysGateway.live_url, gw.send(:url) @@ -240,6 +434,10 @@ def test_failed_transaction_transcript_scrubbing assert_equal failed_transaction_post_scrubbed, @gateway.scrub(failed_transaction_pre_scrubbed) end + def test_failed_3ds_transaction_transcript_scrubbing + assert_equal failed_3ds_transaction_post_scrubbed, @gateway.scrub(failed_3ds_transaction_pre_scrubbed) + end + def test_nil_cvv_transcript_scrubbing assert_equal nil_cvv_post_scrubbed, @gateway.scrub(nil_cvv_pre_scrubbed) end @@ -262,11 +460,11 @@ def generate_order_id # one with card and another without. def purchase_request - 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742736014%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%3CDS_MERCHANT_EXPIRYDATE%3E1709%3C%2FDS_MERCHANT_EXPIRYDATE%3E%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3Eq9QH2P%2B4qm8w%2FS85KRPVaepWOrOT2RXlEmyPUce5XRM%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' + 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742736014%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3E0%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%3CDS_MERCHANT_EXPIRYDATE%3E1709%3C%2FDS_MERCHANT_EXPIRYDATE%3E%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3Ef46TQxKLJJ6SjcETDp%2Bul92Qsb5kVve2QzGnZMj8JkI%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' end def purchase_request_with_credit_card_token - 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742884282%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_IDENTIFIER%3E3126bb8b80a79e66eb1ecc39e305288b60075f86%3C%2FDS_MERCHANT_IDENTIFIER%3E%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3EN0tYMrHGf1PmmJ7WIiRONdqbIGmyhaV%2BhP4acTyfJYE%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' + 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742884282%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3E0%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_IDENTIFIER%3E3126bb8b80a79e66eb1ecc39e305288b60075f86%3C%2FDS_MERCHANT_IDENTIFIER%3E%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3Eeozf9m%2FmDx7JKtcJSPvUa%2FdCZQmzzEAU2nrOVD84fp4%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' end def successful_purchase_response @@ -289,6 +487,18 @@ def successful_authorize_response "00.110097814474336727329qv8K/6k3P1zyk5F+ZYmMel0uuOzC58kXCgp5rcnhI=09195271310000399957101724\n" end + def successful_authorize_with_3ds_response + '<RETORNOXML><CODIGO>0</CODIGO><INFOTARJETA><Ds_Order>156270437866</Ds_Order><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_TransactionType>0</Ds_TransactionType><Ds_EMV3DS>{"protocolVersion":"NO_3DS_v2","threeDSInfo":"CardConfiguration"}</Ds_EMV3DS><Ds_Signature>LIWUaQh+lwsE0DBNpv2EOYALCY6ZxHDQ6gLvOcWiSB4=</Ds_Signature></INFOTARJETA></RETORNOXML>' + end + + def successful_purchase_with_3ds2_and_mit_exemption_response + '<RETORNOXML><CODIGO>0</CODIGO><INFOTARJETA><Ds_Order>161608782525</Ds_Order><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>12</Ds_Terminal><Ds_TransactionType>0</Ds_TransactionType><Ds_EMV3DS>{"protocolVersion":"2.1.0","threeDSServerTransID":"65120b61-28a3-476a-9aac-7b78c63a907a","threeDSInfo":"CardConfiguration","threeDSMethodURL":"https://sis-d.redsys.es/sis-simulador-web/threeDsMethod.jsp"}</Ds_EMV3DS><Ds_Card_PSD2>Y</Ds_Card_PSD2><Ds_Signature>q4ija0q0x48NBb3O6EFLwEavCUMbtUWR/U38Iv0qSn0=</Ds_Signature></INFOTARJETA></RETORNOXML>' + end + + def successful_non_3ds_purchase_with_mit_exemption_response + '<RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>162068064777</Ds_Order><Ds_Signature>axkiHGkNxpGp/JZFGC5/S0+u2n5r/S7jj6FY+F9eZ6k=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>363732</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country><Ds_Card_Brand>1</Ds_Card_Brand><Ds_ProcessedPayMethod>3</Ds_ProcessedPayMethod></OPERACION></RETORNOXML>' + end + def failed_authorize_response "SIS0093\n 0.1\n 978\n 100\n 141278225678\n 1\n 1\n 91952713\n 1c34699589507802f800b929ea314dc143b0b8a5\n Longbob Longsen\n 4242424242424242\n 1509\n 123\n\n" end @@ -345,6 +555,18 @@ def failed_transaction_post_scrubbed ) end + def failed_3ds_transaction_pre_scrubbed + %q( +<RETORNOXML><CODIGO>SIS0571</CODIGO><RECIBIDO>\n <REQUEST><DATOSENTRADA><DS_Version>0.1</DS_Version><DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY><DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT><DS_MERCHANT_ORDER>82973d604ba1</DS_MERCHANT_ORDER><DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE><DS_MERCHANT_PRODUCTDESCRIPTION/><DS_MERCHANT_TERMINAL>12</DS_MERCHANT_TERMINAL><DS_MERCHANT_MERCHANTCODE>091952713</DS_MERCHANT_MERCHANTCODE><DS_MERCHANT_TITULAR>Jane Doe</DS_MERCHANT_TITULAR><DS_MERCHANT_PAN>4548812049400004</DS_MERCHANT_PAN><DS_MERCHANT_EXPIRYDATE>2012</DS_MERCHANT_EXPIRYDATE><DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2><DS_MERCHANT_EMV3DS>{"threeDSInfo":"AuthenticationData","browserAcceptHeader":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","browserUserAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"}</DS_MERCHANT_EMV3DS></DATOSENTRADA><DS_SIGNATUREVERSION>HMAC_SHA256_V1</DS_SIGNATUREVERSION><DS_SIGNATURE>ips3TqR6upMAEbC0D6vmzV9tldU5224MSR63dpWPBT0=</DS_SIGNATURE></REQUEST>\n </RECIBIDO></RETORNOXML> + ) + end + + def failed_3ds_transaction_post_scrubbed + %q( +<RETORNOXML><CODIGO>SIS0571</CODIGO><RECIBIDO>\n <REQUEST><DATOSENTRADA><DS_Version>0.1</DS_Version><DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY><DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT><DS_MERCHANT_ORDER>82973d604ba1</DS_MERCHANT_ORDER><DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE><DS_MERCHANT_PRODUCTDESCRIPTION/><DS_MERCHANT_TERMINAL>12</DS_MERCHANT_TERMINAL><DS_MERCHANT_MERCHANTCODE>091952713</DS_MERCHANT_MERCHANTCODE><DS_MERCHANT_TITULAR>Jane Doe</DS_MERCHANT_TITULAR><DS_MERCHANT_PAN>[FILTERED]</DS_MERCHANT_PAN><DS_MERCHANT_EXPIRYDATE>2012</DS_MERCHANT_EXPIRYDATE><DS_MERCHANT_CVV2>[FILTERED]</DS_MERCHANT_CVV2><DS_MERCHANT_EMV3DS>{"threeDSInfo":"AuthenticationData","browserAcceptHeader":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","browserUserAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"}</DS_MERCHANT_EMV3DS></DATOSENTRADA><DS_SIGNATUREVERSION>HMAC_SHA256_V1</DS_SIGNATUREVERSION><DS_SIGNATURE>ips3TqR6upMAEbC0D6vmzV9tldU5224MSR63dpWPBT0=</DS_SIGNATURE></REQUEST>\n </RECIBIDO></RETORNOXML> + ) + end + def nil_cvv_pre_scrubbed <<-PRE_SCRUBBED entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%2F%3E%0A%3C%2FDATOSENTRADA%3E%0A @@ -377,13 +599,13 @@ def whitespace_string_cvv_pre_scrubbed <<-PRE_SCRUBBED entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E+++%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A 00.110097813521401409897FBF7E648015AC8AFCA107CD67A1F600FBE96119195271310000701841A01724 - PRE_SCRUBBED + PRE_SCRUBBED end def whitespace_string_cvv_post_scrubbed <<-PRE_SCRUBBED entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A 00.110097813521401409897FBF7E648015AC8AFCA107CD67A1F600FBE96119195271310000701841A01724 - PRE_SCRUBBED + PRE_SCRUBBED end end diff --git a/test/unit/gateways/redsys_test.rb b/test/unit/gateways/redsys_test.rb index 741b5ae9eed..605d077e38a 100644 --- a/test/unit/gateways/redsys_test.rb +++ b/test/unit/gateways/redsys_test.rb @@ -6,9 +6,9 @@ class RedsysTest < Test::Unit::TestCase def setup Base.mode = :test @credentials = { - :login => '091952713', - :secret_key => 'qwertyasdf0123456789', - :terminal => '1', + login: '091952713', + secret_key: 'qwertyasdf0123456789', + terminal: '1' } @gateway = RedsysGateway.new(@credentials) @headers = { @@ -20,12 +20,12 @@ def setup def test_purchase_payload @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request, @headers).returns(successful_purchase_response) - @gateway.purchase(123, credit_card, :order_id => '1001') + @gateway.purchase(123, credit_card, order_id: '1001') end def test_purchase_payload_with_credit_card_token @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request_with_credit_card_token, @headers).returns(successful_purchase_response) - @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', :order_id => '1001') + @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', order_id: '1001') end def test_successful_purchase @@ -47,6 +47,157 @@ def test_successful_purchase_requesting_credit_card_token assert_equal '77bff3a969d6f97b2ec815448cdcff453971f573', res.params['ds_merchant_identifier'] end + def test_successful_purchase_with_stored_credentials + @gateway.expects(:ssl_post).returns(successful_purchase_initial_stored_credential_response) + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_res = @gateway.purchase(123, credit_card, initial_options) + assert_success initial_res + assert_equal 'Transaction Approved', initial_res.message + assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_res.params['ds_merchant_cof_txnid'] + + @gateway.expects(:ssl_post).returns(successful_purchase_used_stored_credential_response) + used_options = { + order_id: '1002', + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + network_transaction_id: network_transaction_id + } + } + res = @gateway.purchase(123, credit_card, used_options) + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '561350', res.params['ds_authorisationcode'] + end + + def test_successful_purchase_with_stored_credentials_for_merchant_initiated_transactions + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes(CGI.escape('0')), + includes(CGI.escape('1001')), + includes(CGI.escape('123')), + includes(CGI.escape('S')), + includes(CGI.escape('R')), + includes(CGI.escape('4242424242424242')), + includes(CGI.escape('2409')), + includes(CGI.escape('123')), + includes(CGI.escape('false')), + Not(includes(CGI.escape(''))), + Not(includes(CGI.escape(''))) + ), + anything + ).returns(successful_purchase_initial_stored_credential_response) + + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_res = @gateway.purchase(123, credit_card, initial_options) + assert_success initial_res + assert_equal 'Transaction Approved', initial_res.message + assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_res.params['ds_merchant_cof_txnid'] + + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes('0'), + includes('1002'), + includes('123'), + includes('N'), + includes('R'), + includes('4242424242424242'), + includes('2409'), + includes('123'), + includes('true'), + includes('MIT'), + includes("#{network_transaction_id}") + ), + anything + ).returns(successful_purchase_used_stored_credential_response) + used_options = { + order_id: '1002', + sca_exemption: 'MIT', + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: network_transaction_id + } + } + res = @gateway.purchase(123, credit_card, used_options) + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '561350', res.params['ds_authorisationcode'] + end + + def test_successful_purchase_with_stored_credentials_for_merchant_initiated_transactions_with_card_tokens + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes(CGI.escape('0')), + includes(CGI.escape('1001')), + includes(CGI.escape('123')), + includes(CGI.escape('S')), + includes(CGI.escape('R')), + includes(CGI.escape('77bff3a969d6f97b2ec815448cdcff453971f573')), + includes(CGI.escape('false')), + Not(includes(CGI.escape(''))), + Not(includes(CGI.escape(''))) + ), + anything + ).returns(successful_purchase_initial_stored_credential_response) + + initial_options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring' + } + ) + initial_res = @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', initial_options) + assert_success initial_res + assert_equal 'Transaction Approved', initial_res.message + assert_equal '2012102122020', initial_res.params['ds_merchant_cof_txnid'] + network_transaction_id = initial_res.params['ds_merchant_cof_txnid'] + + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes('0'), + includes('1002'), + includes('123'), + includes('N'), + includes('R'), + includes('77bff3a969d6f97b2ec815448cdcff453971f573'), + includes('true'), + includes('MIT'), + includes("#{network_transaction_id}") + ), + anything + ).returns(successful_purchase_used_stored_credential_response) + used_options = { + order_id: '1002', + sca_exemption: 'MIT', + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + network_transaction_id: network_transaction_id + } + } + res = @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', used_options) + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '561350', res.params['ds_authorisationcode'] + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) res = @gateway.purchase(123, credit_card, @options) @@ -115,7 +266,7 @@ def test_authorize_without_order_id def test_bad_order_id_format stub_comms(@gateway, :ssl_request) do @gateway.authorize(123, credit_card, order_id: 'Una#cce-ptable44Format') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/MERCHANT_ORDER%3E\d\d\d\dUnaccept%3C/, data) end.respond_with(successful_authorize_response) end @@ -123,7 +274,7 @@ def test_bad_order_id_format def test_order_id_numeric_start_but_too_long stub_comms(@gateway, :ssl_request) do @gateway.authorize(123, credit_card, order_id: '1234ThisIs]FineButTooLong') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/MERCHANT_ORDER%3E1234ThisIsFi%3C/, data) end.respond_with(successful_authorize_response) end @@ -161,7 +312,7 @@ def test_override_currency includes(CGI.escape('840')), anything ).returns(successful_purchase_response) - @gateway.authorize(123, credit_card, :order_id => '1001', :currency => 'USD') + @gateway.authorize(123, credit_card, order_id: '1001', currency: 'USD') end def test_successful_verify @@ -195,11 +346,11 @@ def test_default_currency end def test_supported_countries - assert_equal ['ES'], RedsysGateway.supported_countries + assert_equal %w[ES FR GB IT PL PT], RedsysGateway.supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express, :jcb, :diners_club], RedsysGateway.supported_cardtypes + assert_equal %i[visa master american_express jcb diners_club unionpay], RedsysGateway.supported_cardtypes end def test_using_test_mode @@ -210,10 +361,10 @@ def test_using_test_mode def test_overriding_options Base.mode = :production gw = RedsysGateway.new( - :terminal => 1, - :login => '1234', - :secret_key => '12345', - :test => true + terminal: 1, + login: '1234', + secret_key: '12345', + test: true ) assert gw.test? assert_equal RedsysGateway.test_url, gw.send(:url) @@ -222,9 +373,9 @@ def test_overriding_options def test_production_mode Base.mode = :production gw = RedsysGateway.new( - :terminal => 1, - :login => '1234', - :secret_key => '12345' + terminal: 1, + login: '1234', + secret_key: '12345' ) assert !gw.test? assert_equal RedsysGateway.live_url, gw.send(:url) @@ -256,11 +407,11 @@ def test_whitespace_string_cvv_transcript_scrubbing # one with card and another without. def purchase_request - "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Eb98b606a6a588d8c45c239f244160efbbe30b4a8%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4242424242424242%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E#{(Time.now.year + 1).to_s.slice(2, 2)}09%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A" + "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3E0%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E0b930082f7905d7dba3d83be4d4331b8acd57624%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4242424242424242%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E#{(Time.now.year + 1).to_s.slice(2, 2)}09%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A" end def purchase_request_with_credit_card_token - 'entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Ecbcc0dee5724cd3fff08bbd4371946a0599c7fb9%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_IDENTIFIER%3E77bff3a969d6f97b2ec815448cdcff453971f573%3C%2FDS_MERCHANT_IDENTIFIER%3E%0A++%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%0A%3C%2FDATOSENTRADA%3E%0A' + 'entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3E0%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E0c56bb3edd0cae65ef16c96c61c5ecd306973d2f%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_IDENTIFIER%3E77bff3a969d6f97b2ec815448cdcff453971f573%3C%2FDS_MERCHANT_IDENTIFIER%3E%0A++%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%0A%3C%2FDATOSENTRADA%3E%0A' end def successful_purchase_response @@ -271,6 +422,14 @@ def successful_purchase_response_with_credit_card_token "00.1100978141661632759C65E11D80534B432042ABAA47DCA54F5AFEC23ED32723468820000341129A0177bff3a969d6f97b2ec815448cdcff453971f573724" end + def successful_purchase_initial_stored_credential_response + "00.11239781001989D357BCC9EF0962A456C51422C4FAF4BF4399F9195271310000561350A0177bff3a969d6f97b2ec815448cdcff453971f573724201210212202013" + end + + def successful_purchase_used_stored_credential_response + "00.11239781001989D357BCC9EF0962A456C51422C4FAF4BF4399F9195271310000561350A0172413" + end + def failed_purchase_response "00.1123978100280D5D1BE64777946519C4E633EE5498C6187747B919527131190561350A01724" end @@ -371,13 +530,13 @@ def whitespace_string_cvv_pre_scrubbed <<-PRE_SCRUBBED entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E+++%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A 00.110097813521401409897FBF7E648015AC8AFCA107CD67A1F600FBE96119195271310000701841A01724 - PRE_SCRUBBED + PRE_SCRUBBED end def whitespace_string_cvv_post_scrubbed <<-PRE_SCRUBBED entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A 00.110097813521401409897FBF7E648015AC8AFCA107CD67A1F600FBE96119195271310000701841A01724 - PRE_SCRUBBED + PRE_SCRUBBED end end diff --git a/test/unit/gateways/s5_test.rb b/test/unit/gateways/s5_test.rb index 5a858c1edf9..aebbd977634 100644 --- a/test/unit/gateways/s5_test.rb +++ b/test/unit/gateways/s5_test.rb @@ -34,7 +34,7 @@ def test_successful_purchase def test_successful_purchase_with_recurring_flag response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(recurring: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/Recurrence.*REPEATED/, data) end.respond_with(successful_purchase_response) diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb index 94d310907c6..23a579e569f 100644 --- a/test/unit/gateways/safe_charge_test.rb +++ b/test/unit/gateways/safe_charge_test.rb @@ -8,18 +8,48 @@ def setup @credit_card = credit_card @three_ds_enrolled_card = credit_card('4012 0010 3749 0014') @amount = 100 + @network_token_credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + brand: 'Visa', + payment_cryptogram: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + number: '4012001037490014', + source: :network_token, + month: '12', + year: 2020 + }) @options = { order_id: '1', billing_address: address, description: 'Store Purchase' } + @merchant_options = @options.merge( merchant_descriptor: 'Test Descriptor', merchant_phone_number: '(555)555-5555', - merchant_name: 'Test Merchant' + merchant_name: 'Test Merchant', + product_id: 'Test Product' ) + @three_ds_options = @options.merge(three_d_secure: true) + + @mpi_options_3ds1 = @options.merge({ + three_d_secure: { + eci: '05', + cavv: 'Vk83Y2t0cHRzRFZzRlZlR0JIQXo=', + xid: '00000000000000000501' + } + }) + + @mpi_options_3ds2 = @options.merge({ + three_d_secure: { + version: '2.1.0', + eci: '05', + cavv: 'Vk83Y2t0cHRzRFZzRlZlR0JIQXo=', + xid: '00000000000000000501', + ds_transaction_id: 'c5b808e7-1de1-4069-a17b-f70d3b3b1645', + challenge_preference: 'NoPreference' + } + }) end def test_successful_purchase @@ -37,7 +67,7 @@ def test_successful_purchase def test_successful_purchase_with_merchant_options purchase = stub_comms do @gateway.purchase(@amount, @credit_card, @merchant_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/sg_Descriptor/, data) assert_match(/sg_MerchantPhoneNumber/, data) assert_match(/sg_MerchantName/, data) @@ -53,7 +83,7 @@ def test_successful_purchase_with_merchant_options def test_successful_purchase_with_truthy_stored_credential_mode purchase = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential_mode: true)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/sg_StoredCredentialMode=1/, data) end.respond_with(successful_purchase_response) @@ -67,7 +97,7 @@ def test_successful_purchase_with_truthy_stored_credential_mode def test_successful_purchase_with_falsey_stored_credential_mode purchase = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential_mode: false)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/sg_StoredCredentialMode=0/, data) end.respond_with(successful_purchase_response) @@ -98,6 +128,35 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_with_not_use_cvv + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ not_use_cvv: true })) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_NotUseCVV=1/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ not_use_cvv: 'true' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_NotUseCVV=1/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ not_use_cvv: false })) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_NotUseCVV=0/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ not_use_cvv: 'false' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_NotUseCVV=0/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert response.test? + end + def test_failed_authorize @gateway.expects(:ssl_post).returns(failed_authorize_response) @@ -118,6 +177,16 @@ def test_successful_capture assert response.test? end + def test_successful_capture_with_options + capture = stub_comms do + @gateway.capture(@amount, 'auth|transaction_id|token|month|year|amount|currency', @options.merge(email: 'slowturtle86@aol.com')) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_Email/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + def test_failed_capture @gateway.expects(:ssl_post).returns(failed_capture_response) @@ -134,6 +203,46 @@ def test_successful_refund assert_equal 'Success', response.message end + def test_successful_unreferenced_refund + refund = stub_comms do + @gateway.refund(@amount, 'auth|transaction_id|token|month|year|amount|currency', @options.merge(unreferenced_refund: true)) + end.check_request do |_endpoint, data, _headers| + assert_equal(data.split('&').include?('sg_TransactionID=transaction_id'), false) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_successful_refund_without_unreferenced_refund + refund = stub_comms do + @gateway.refund(@amount, 'auth|transaction_id|token|month|year|amount|currency', @options) + end.check_request do |_endpoint, data, _headers| + assert_equal(data.split('&').include?('sg_TransactionID=transaction_id'), true) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_successful_credit_with_unreferenced_refund + credit = stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(unreferenced_refund: true)) + end.check_request do |_endpoint, data, _headers| + assert_equal(data.split('&').include?('sg_CreditType=2'), true) + end.respond_with(successful_credit_response) + + assert_success credit + end + + def test_successful_credit_without_unreferenced_refund + credit = stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal(data.split('&').include?('sg_CreditType=1'), true) + end.respond_with(successful_credit_response) + + assert_success credit + end + def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) @@ -150,6 +259,16 @@ def test_successful_credit assert_equal 'Success', response.message end + def test_credit_sends_addtional_info + stub_comms do + @gateway.credit(@amount, @credit_card, @options.merge(email: 'test@example.com')) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_FirstName=Longbob/, data) + assert_match(/sg_LastName=Longsen/, data) + assert_match(/sg_Email/, data) + end.respond_with(successful_credit_response) + end + def test_failed_credit @gateway.expects(:ssl_post).returns(failed_credit_response) @@ -179,31 +298,13 @@ def test_failed_void assert response.test? end - def test_successful_verify - @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) - - response = @gateway.verify(@credit_card, @options) - assert_success response - - assert_equal '111534|101508189855|MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAW' \ - 'wBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAE' \ - 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization - assert response.test? - end - - def test_successful_verify_with_failed_void - @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) - - response = @gateway.verify(@credit_card, @options) - assert_success response - end - - def test_failed_verify - @gateway.expects(:ssl_post).returns(failed_authorize_response) - - response = @gateway.verify(@credit_card, @options) - assert_failure response - assert_equal '0', response.error_code + def test_verify_sends_zero_amount + stub_comms do + @gateway.verify(@credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/sg_TransType=Auth/, data) + assert_match(/sg_Amount=0.00/, data) + end.respond_with(successful_authorize_response) end def test_scrub @@ -214,7 +315,7 @@ def test_scrub def test_3ds_response purchase = stub_comms do @gateway.purchase(@amount, @three_ds_enrolled_card, @three_ds_options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/Sale3D/, data) assert_match(/sg_APIType/, data) end.respond_with(successful_3ds_purchase_response) @@ -225,6 +326,66 @@ def test_3ds_response assert_equal 'https://pit.3dsecure.net/VbVTestSuiteService/pit1/acsService/paReq?summary=MjRlZGYwY2EtZTk5Zi00NDJjLTljOTAtNWUxZmRhMjEwODg3', purchase.params['acsurl'] end + def test_mpi_response_fail + purchase = stub_comms do + @gateway.purchase(@amount, @three_ds_enrolled_card, @mpi_options_3ds1) + end.check_request do |_, data, _| + assert_match(/sg_ECI/, data) + assert_match(/sg_CAVV/, data) + assert_match(/sg_IsExternalMPI/, data) + end.respond_with(failed_mpi_response) + + assert_failure purchase + assert_equal 'DECLINED', purchase.params['status'] + end + + def test_mpi_response_success_3ds1 + purchase = stub_comms do + @gateway.purchase(@amount, @three_ds_enrolled_card, @mpi_options_3ds1) + end.check_request do |_, data, _| + assert_match(/sg_ECI/, data) + assert_match(/sg_CAVV/, data) + assert_match(/sg_IsExternalMPI/, data) + assert_match(/sg_threeDSProtocolVersion=1/, data) + assert_match(/sg_Xid/, data) + end.respond_with(successful_mpi_response) + + assert_success purchase + assert_equal 'APPROVED', purchase.params['status'] + end + + def test_mpi_response_success_3ds2 + purchase = stub_comms do + @gateway.purchase(@amount, @three_ds_enrolled_card, @mpi_options_3ds2) + end.check_request do |_, data, _| + assert_match(/sg_ECI/, data) + assert_match(/sg_CAVV/, data) + assert_match(/sg_IsExternalMPI/, data) + assert_match(/sg_dsTransID/, data) + assert_match(/sg_threeDSProtocolVersion=2/, data) + assert_match(/sg_challengePreference/, data) + refute_match(/sg_xid/, data) + end.respond_with(successful_mpi_response) + + assert_success purchase + assert_equal 'APPROVED', purchase.params['status'] + end + + def test_network_tokenization_success + purchase = stub_comms do + @gateway.purchase(@amount, @network_token_credit_card, @mpi_options_3ds2) + end.check_request do |_, data, _| + assert_match(/sg_CAVV/, data) + assert_match(/sg_ECI/, data) + assert_match(/sg_IsExternalMPI/, data) + assert_match(/sg_CardNumber/, data) + assert_match(/sg_challengePreference/, data) + end.respond_with(successful_network_token_response) + + assert_success purchase + assert_equal 'APPROVED', purchase.params['status'] + end + private def pre_scrubbed @@ -360,4 +521,76 @@ def successful_3ds_purchase_response 4.1.0SpreedlyManTestTRX98bd80c8c9534088311153ad6a67d108101510108310APPROVED00ZQBpAFAAMwBTAEcAMQBZAHcASQA4ADoAPQBlACQAZAB3ACMAWwAyAFoAWQBLAFUAPwBTAHYAKQAnAHQAUAA2AHYAYwAoAG0ARgBNAEEAcAAlAGEAMwA=YeJxVUdtuwjAM/ZWK95GYgijIjVTWaUNTGdqQ4DUKFq2gF9J0A75+SVcuixTF59g+sY5xlWqi+ItUo0lgQnUtd+Rl27BXyScYAQce+MB7ApfRJx0FfpOus7IQ0Of9AbIrtK1apbIwAqU6zuYLMQSY8ABZBzEnPY8FfzhjGCH7o7GQOYlIq9J4K6qNd5VD1mZQlU1h9FkEQ47sCrDRB5EaU00ZO5RKHtKyth2ORXYfaNm4qLYqp2wrkjj6ud8XSFbRKYl3F/uGyFwFbqUhMeAwBvC5B6Opz6c+IGt5lLn73hlgR+kAVu6PqMu4xCOB1l1NhTqLydg6ckNIp6osyFZYJ28xsvvAz2/OT2WsRa+bdf2+X6cXtd9oHxZNPks+ojB0DrcFTi2zrkDAJ62cA8icBOuWx7oF2+jf4n8B000000000000715https://pit.3dsecure.net/VbVTestSuiteService/pit1/acsService/paReq?summary=MjRlZGYwY2EtZTk5Zi00NDJjLTljOTAtNWUxZmRhMjEwODg3MDAwMDAwMDAwMDE1MTAxMDgzMTA=19Visa Production Support Client Bid 1usrDNDlh6XR8R6CVdGQyqDkZzdqE0=10Debit01EUR1EUR ) end + + def successful_mpi_response + %( + 4.1.0SpreedlyTestTRX27822c1132eba4c731ebe24b6190646f1110000000009330260APPROVED11144700UQBzAFEAdABvAG0ATgA5AEwAagBHAGwAPwA7AF0ANgA1AD4AfABOADUAdAA/AD4AZQA3AEcAXQBnAGgAQQA4AG4APABNACUARABFADgAMQBrAFIAMwA=5Vk83Y2t0cHRzRFZzRlZlR0JIQXo=00000000000000000501c5b808e7-1de1-4069-a17b-f70d3b3b164519Visa Production Support Client Bid 1gbrDNDlh6XR8R6CVdGQyqDkZzdqE0=0Debit01EUR1EURAccept + ) + end + + def successful_network_token_response + %( + + 4.1.0 + SpreedlyTestTRX + 27822c1132eba4c731ebe24b6190646f + 1110000000009330260 + APPROVED + + + + + + + 0 + 0 + UQBzAFEAdABvAG0ATgA5AEwAagBHAGwAPwA7AF0ANgA1AD4AfABOADUAdAA/AD4AZQA3AEcAXQBnAGgAQQA4AG4APABNACUARABFADgAMQBrAFIAMwA= + + + + + 5 + Vk83Y2t0cHRzRFZzRlZlR0JIQXo= + + 00000000000000000501 + + + + + c5b808e7-1de1-4069-a17b-f70d3b3b1645 + + + 19 + Visa Production Support Client Bid 1 + gb + + + + rDNDlh6XR8R6CVdGQyqDkZzdqE0= + + + 0 + Debit + + + + 0 + + 1 + EUR + 1 + EUR + + + + + + ) + end + + def failed_mpi_response + %( + 4.1.0SpreedlyTestTRX040b37ca7af949daeb38a8cff0a16f1b1110000000009330310DECLINEDDecline-10UQBLAEcAdABRAE8AWABPADUANgBCADAAcABGAEUANwArADgAewBTACcAcwAlAF8APABQAEEAXgBVACUAYQBLACMALwBWAEUANQApAD4ARQBqADsAMwA=5Vk83Y2t0cHRzRFZzRlZlR0JIQXo=00000000000000000501c5b808e7-1de1-4069-a17b-f70d3b3b164519GyueFkuQqW+UL38d57fuA5/RqfQ=001EUR1EURAccept + ) + end end diff --git a/test/unit/gateways/sage_pay_test.rb b/test/unit/gateways/sage_pay_test.rb index 034d563e448..8f915a1f6c4 100644 --- a/test/unit/gateways/sage_pay_test.rb +++ b/test/unit/gateways/sage_pay_test.rb @@ -6,23 +6,23 @@ class SagePayTest < Test::Unit::TestCase def setup @gateway = SagePayGateway.new(login: 'X') - @credit_card = credit_card('4242424242424242', :brand => 'visa') - @electron_credit_card = credit_card('4245190000000000', :brand => 'visa') + @credit_card = credit_card('4242424242424242', brand: 'visa') + @electron_credit_card = credit_card('4245190000000000', brand: 'visa') @options = { - :billing_address => { - :name => 'Tekin Suleyman', - :address1 => 'Flat 10 Lapwing Court', - :address2 => 'West Didsbury', - :city => 'Manchester', - :county => 'Greater Manchester', - :country => 'GB', - :zip => 'M20 2PS' + billing_address: { + name: 'Tekin Suleyman', + address1: 'Flat 10 Lapwing Court', + address2: 'West Didsbury', + city: 'Manchester', + county: 'Greater Manchester', + country: 'GB', + zip: 'M20 2PS' }, - :order_id => '1', - :description => 'Store purchase', - :ip => '86.150.65.37', - :email => 'tekin@tekin.co.uk', - :phone => '0161 123 4567' + order_id: '1', + description: 'Store purchase', + ip: '86.150.65.37', + email: 'tekin@tekin.co.uk', + phone: '0161 123 4567' } @amount = 100 end @@ -92,18 +92,18 @@ def test_not_matched_cvv_result end def test_dont_send_fractional_amount_for_chinese_yen - @amount = 100_00 # 100 YEN + @amount = 100_00 # 100 YEN @options[:currency] = 'JPY' - @gateway.expects(:add_pair).with({}, :Amount, '100', :required => true) - @gateway.expects(:add_pair).with({}, :Currency, 'JPY', :required => true) + @gateway.expects(:add_pair).with({}, :Amount, '100', required: true) + @gateway.expects(:add_pair).with({}, :Currency, 'JPY', required: true) @gateway.send(:add_amount, {}, @amount, @options) end def test_send_fractional_amount_for_british_pounds - @gateway.expects(:add_pair).with({}, :Amount, '1.00', :required => true) - @gateway.expects(:add_pair).with({}, :Currency, 'GBP', :required => true) + @gateway.expects(:add_pair).with({}, :Amount, '1.00', required: true) + @gateway.expects(:add_pair).with({}, :Currency, 'GBP', required: true) @gateway.send(:add_amount, {}, @amount, @options) end @@ -111,7 +111,7 @@ def test_send_fractional_amount_for_british_pounds def test_paypal_callback_url_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(paypal_callback_url: 'callback.com') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/PayPalCallbackURL=callback\.com/, data) end.respond_with(successful_purchase_response) end @@ -119,7 +119,7 @@ def test_paypal_callback_url_is_submitted def test_basket_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(basket: 'A1.2 Basket section') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/Basket=A1\.2\+Basket\+section/, data) end.respond_with(successful_purchase_response) end @@ -127,7 +127,7 @@ def test_basket_is_submitted def test_gift_aid_payment_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(gift_aid_payment: 1) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/GiftAidPayment=1/, data) end.respond_with(successful_purchase_response) end @@ -135,7 +135,7 @@ def test_gift_aid_payment_is_submitted def test_apply_avscv2_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(apply_avscv2: 1) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ApplyAVSCV2=1/, data) end.respond_with(successful_purchase_response) end @@ -143,7 +143,7 @@ def test_apply_avscv2_is_submitted def test_disable_3d_security_flag_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(apply_3d_secure: 1) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/Apply3DSecure=1/, data) end.respond_with(successful_purchase_response) end @@ -151,7 +151,7 @@ def test_disable_3d_security_flag_is_submitted def test_account_type_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(account_type: 'M') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/AccountType=M/, data) end.respond_with(successful_purchase_response) end @@ -159,7 +159,7 @@ def test_account_type_is_submitted def test_billing_agreement_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(billing_agreement: 1) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/BillingAgreement=1/, data) end.respond_with(successful_purchase_response) end @@ -167,7 +167,7 @@ def test_billing_agreement_is_submitted def test_store_token_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(store: true) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/CreateToken=1/, data) end.respond_with(successful_purchase_response) end @@ -175,7 +175,7 @@ def test_store_token_is_submitted def test_basket_xml_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(basket_xml: 'A1.3 BasketXML section') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/BasketXML=A1\.3\+BasketXML\+section/, data) end.respond_with(successful_purchase_response) end @@ -183,7 +183,7 @@ def test_basket_xml_is_submitted def test_customer_xml_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(customer_xml: 'A1.4 CustomerXML section') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/CustomerXML=A1\.4\+CustomerXML\+section/, data) end.respond_with(successful_purchase_response) end @@ -191,7 +191,7 @@ def test_customer_xml_is_submitted def test_surcharge_xml_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(surcharge_xml: 'A1.1 SurchargeXML section') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/SurchargeXML=A1\.1\+SurchargeXML\+section/, data) end.respond_with(successful_purchase_response) end @@ -199,7 +199,7 @@ def test_surcharge_xml_is_submitted def test_vendor_data_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(vendor_data: 'any data') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/VendorData=any\+data/, data) end.respond_with(successful_purchase_response) end @@ -207,7 +207,7 @@ def test_vendor_data_is_submitted def test_language_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(language: 'FR') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/Language=FR/, data) end.respond_with(successful_purchase_response) end @@ -215,7 +215,7 @@ def test_language_is_submitted def test_website_is_submitted stub_comms(@gateway, :ssl_request) do purchase_with_options(website: 'transaction-origin.com') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/Website=transaction-origin\.com/, data) end.respond_with(successful_purchase_response) end @@ -225,7 +225,7 @@ def test_FIxxxx_optional_fields_are_submitted purchase_with_options(recipient_account_number: '1234567890', recipient_surname: 'Withnail', recipient_postcode: 'AB11AB', recipient_dob: '19701223') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/FIRecipientAcctNumber=1234567890/, data) assert_match(/FIRecipientSurname=Withnail/, data) assert_match(/FIRecipientPostcode=AB11AB/, data) @@ -237,7 +237,7 @@ def test_description_is_truncated huge_description = 'SagePay transactions fail if the déscription is more than 100 characters. Therefore, we truncate it to 100 characters.' + ' Lots more text ' * 1000 stub_comms(@gateway, :ssl_request) do purchase_with_options(description: huge_description) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/&Description=SagePay\+transactions\+fail\+if\+the\+d%C3%A9scription\+is\+more\+than\+100\+characters.\+Therefore%2C\+we\+trunc&/, data) end.respond_with(successful_purchase_response) end @@ -247,7 +247,7 @@ def test_protocol_version_is_honoured stub_comms(gateway, :ssl_request) do gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/VPSProtocol=2.23/, data) end.respond_with(successful_purchase_response) end @@ -256,7 +256,7 @@ def test_referrer_id_is_added_to_post_data_parameters ActiveMerchant::Billing::SagePayGateway.application_id = '00000000-0000-0000-0000-000000000001' stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data.include?('ReferrerID=00000000-0000-0000-0000-000000000001') end.respond_with(successful_purchase_response) ensure @@ -266,7 +266,7 @@ def test_referrer_id_is_added_to_post_data_parameters def test_successful_store response = stub_comms(@gateway, :ssl_request) do @gateway.store(@credit_card) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/TxType=TOKEN/, data) end.respond_with(successful_purchase_response) @@ -316,10 +316,7 @@ def test_successful_authorization_and_capture_and_refund assert_success capture refund = stub_comms do - @gateway.refund(@amount, capture.authorization, - order_id: generate_unique_id, - description: 'Refund txn' - ) + @gateway.refund(@amount, capture.authorization, order_id: generate_unique_id, description: 'Refund txn') end.respond_with(successful_refund_response) assert_success refund end @@ -327,12 +324,21 @@ def test_successful_authorization_and_capture_and_refund def test_repeat_purchase_with_reference_token stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, '1455548a8d178beecd88fe6a285f50ff;{0D2ACAF0-FA64-6DFF-3869-7ADDDC1E0474};15353766;BS231FNE14;purchase', @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/RelatedVPSTxId=%7B0D2ACAF0-FA64-6DFF-3869-7ADDDC1E0474%/, data) assert_match(/TxType=REPEAT/, data) end.respond_with(successful_purchase_response) end + def test_repeat_purchase_from_reference_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, '9a3c5a71ef733ce56a9b03754763da2c;{4B98024C-5D40-4F5C-4E19-A8D07EBFC5AD};14575233;7NJB98CZSG;repeat', @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/RelatedVPSTxId=%7B4B98024C-5D40-4F5C-4E19-A8D07EBFC5AD%/, data) + assert_match(/TxType=REPEAT/, data) + end.respond_with(successful_purchase_response) + end + private def purchase_with_options(optional) @@ -340,156 +346,156 @@ def purchase_with_options(optional) end def successful_purchase_response - <<-RESP -VPSProtocol=2.23 -Status=OK -StatusDetail=0000 : The Authorisation was Successful. -VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 -SecurityKey=OHMETD7DFK -TxAuthNo=4193753 -AVSCV2=NO DATA MATCHES -AddressResult=NOTMATCHED -PostCodeResult=MATCHED -CV2Result=NOTMATCHED -3DSecureStatus=NOTCHECKED -Token=1 + <<~RESP + VPSProtocol=2.23 + Status=OK + StatusDetail=0000 : The Authorisation was Successful. + VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 + SecurityKey=OHMETD7DFK + TxAuthNo=4193753 + AVSCV2=NO DATA MATCHES + AddressResult=NOTMATCHED + PostCodeResult=MATCHED + CV2Result=NOTMATCHED + 3DSecureStatus=NOTCHECKED + Token=1 RESP end def unsuccessful_purchase_response - <<-RESP -VPSProtocol=2.23 -Status=NOTAUTHED -StatusDetail=VSP Direct transaction from VSP Simulator. -VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 -SecurityKey=DKDYLDYLXV -AVSCV2=ALL MATCH -AddressResult=MATCHED -PostCodeResult=MATCHED -CV2Result=MATCHED + <<~RESP + VPSProtocol=2.23 + Status=NOTAUTHED + StatusDetail=VSP Direct transaction from VSP Simulator. + VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 + SecurityKey=DKDYLDYLXV + AVSCV2=ALL MATCH + AddressResult=MATCHED + PostCodeResult=MATCHED + CV2Result=MATCHED RESP end def successful_authorize_response - <<-RESP -VPSProtocol=2.23 -Status=OK -StatusDetail=0000 : The Authorisation was Successful. -VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 -SecurityKey=OHMETD7DFK -TxAuthNo=4193753 -AVSCV2=NO DATA MATCHES -AddressResult=NOTMATCHED -PostCodeResult=MATCHED -CV2Result=NOTMATCHED -3DSecureStatus=NOTCHECKED -Token=1 + <<~RESP + VPSProtocol=2.23 + Status=OK + StatusDetail=0000 : The Authorisation was Successful. + VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 + SecurityKey=OHMETD7DFK + TxAuthNo=4193753 + AVSCV2=NO DATA MATCHES + AddressResult=NOTMATCHED + PostCodeResult=MATCHED + CV2Result=NOTMATCHED + 3DSecureStatus=NOTCHECKED + Token=1 RESP end def successful_refund_response - <<-RESP -VPSProtocol=3.00 -Status=OK -StatusDetail=0000 : The Authorisation was Successful. -SecurityKey=KUMJBP02HM -TxAuthNo=15282432 -VPSTxId={08C870A9-1E53-3852-BA44-CBC91612CBCA} + <<~RESP + VPSProtocol=3.00 + Status=OK + StatusDetail=0000 : The Authorisation was Successful. + SecurityKey=KUMJBP02HM + TxAuthNo=15282432 + VPSTxId={08C870A9-1E53-3852-BA44-CBC91612CBCA} RESP end def successful_capture_response - <<-RESP -VPSProtocol=3.00 -Status=OK -StatusDetail=2004 : The Release was Successful. + <<~RESP + VPSProtocol=3.00 + Status=OK + StatusDetail=2004 : The Release was Successful. RESP end def unsuccessful_authorize_response - <<-RESP -VPSProtocol=2.23 -Status=NOTAUTHED -StatusDetail=VSP Direct transaction from VSP Simulator. -VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 -SecurityKey=DKDYLDYLXV -AVSCV2=ALL MATCH -AddressResult=MATCHED -PostCodeResult=MATCHED -CV2Result=MATCHED + <<~RESP + VPSProtocol=2.23 + Status=NOTAUTHED + StatusDetail=VSP Direct transaction from VSP Simulator. + VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 + SecurityKey=DKDYLDYLXV + AVSCV2=ALL MATCH + AddressResult=MATCHED + PostCodeResult=MATCHED + CV2Result=MATCHED RESP end def successful_void_response - <<-RESP -VPSProtocol=2.23 -Status=OK -StatusDetail=2006 : The Abort was Successful. -VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 -SecurityKey=OHMETD7DFK -TxAuthNo=4193753 -AVSCV2=NO DATA MATCHES -AddressResult=NOTMATCHED -PostCodeResult=MATCHED -CV2Result=NOTMATCHED -3DSecureStatus=NOTCHECKED -Token=1 + <<~RESP + VPSProtocol=2.23 + Status=OK + StatusDetail=2006 : The Abort was Successful. + VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 + SecurityKey=OHMETD7DFK + TxAuthNo=4193753 + AVSCV2=NO DATA MATCHES + AddressResult=NOTMATCHED + PostCodeResult=MATCHED + CV2Result=NOTMATCHED + 3DSecureStatus=NOTCHECKED + Token=1 RESP end def unsuccessful_void_response - <<-RESP -VPSProtocol=2.23 -Status=MALFORMED -StatusDetail=3046 : The VPSTxId field is missing. -VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 -SecurityKey=DKDYLDYLXV -AVSCV2=ALL MATCH -AddressResult=MATCHED -PostCodeResult=MATCHED -CV2Result=MATCHED + <<~RESP + VPSProtocol=2.23 + Status=MALFORMED + StatusDetail=3046 : The VPSTxId field is missing. + VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 + SecurityKey=DKDYLDYLXV + AVSCV2=ALL MATCH + AddressResult=MATCHED + PostCodeResult=MATCHED + CV2Result=MATCHED RESP end def transcript - <<-TRANSCRIPT - Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=4929000000006&ExpiryDate=0616&CardType=VISA&CV2=123&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 -I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) -D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 -Status=OK -StatusDetail=0000 : The Authorisation was Successful. -VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} -SecurityKey=7OYK4OHM7Y -TxAuthNo=8769237 -AVSCV2=DATA NOT CHECKED -AddressResult=NOTPROVIDED -PostCodeResult=NOTPROVIDED -CV2Result=NOTPROVIDED -3DSecureStatus=NOTCHECKED -DeclineCode=00 -ExpiryDate=0616 -BankAuthCode=999777 - TRANSCRIPT + <<~TRANSCRIPT + Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=4929000000006&ExpiryDate=0616&CardType=VISA&CV2=123&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 + I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) + D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 + Status=OK + StatusDetail=0000 : The Authorisation was Successful. + VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} + SecurityKey=7OYK4OHM7Y + TxAuthNo=8769237 + AVSCV2=DATA NOT CHECKED + AddressResult=NOTPROVIDED + PostCodeResult=NOTPROVIDED + CV2Result=NOTPROVIDED + 3DSecureStatus=NOTCHECKED + DeclineCode=00 + ExpiryDate=0616 + BankAuthCode=999777 + TRANSCRIPT end def scrubbed_transcript - <<-TRANSCRIPT - Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=[FILTERED]&ExpiryDate=0616&CardType=VISA&CV2=[FILTERED]&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 -I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) -D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 -Status=OK -StatusDetail=0000 : The Authorisation was Successful. -VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} -SecurityKey=7OYK4OHM7Y -TxAuthNo=8769237 -AVSCV2=DATA NOT CHECKED -AddressResult=NOTPROVIDED -PostCodeResult=NOTPROVIDED -CV2Result=NOTPROVIDED -3DSecureStatus=NOTCHECKED -DeclineCode=00 -ExpiryDate=0616 -BankAuthCode=999777 - TRANSCRIPT + <<~TRANSCRIPT + Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=[FILTERED]&ExpiryDate=0616&CardType=VISA&CV2=[FILTERED]&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 + I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) + D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 + Status=OK + StatusDetail=0000 : The Authorisation was Successful. + VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} + SecurityKey=7OYK4OHM7Y + TxAuthNo=8769237 + AVSCV2=DATA NOT CHECKED + AddressResult=NOTPROVIDED + PostCodeResult=NOTPROVIDED + CV2Result=NOTPROVIDED + 3DSecureStatus=NOTCHECKED + DeclineCode=00 + ExpiryDate=0616 + BankAuthCode=999777 + TRANSCRIPT end end diff --git a/test/unit/gateways/sage_test.rb b/test/unit/gateways/sage_test.rb index b6faa1761c6..69d3e98d876 100644 --- a/test/unit/gateways/sage_test.rb +++ b/test/unit/gateways/sage_test.rb @@ -5,29 +5,29 @@ class SageGatewayTest < Test::Unit::TestCase def setup @gateway = SageGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @check = check @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } @check_options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :email => 'longbob@example.com', - :drivers_license_state => 'CA', - :drivers_license_number => '12345689', - :date_of_birth => Date.new(1978, 8, 11), - :ssn => '078051120' + order_id: generate_unique_id, + billing_address: address, + shipping_address: address, + email: 'longbob@example.com', + drivers_license_state: 'CA', + drivers_license_number: '12345689', + date_of_birth: Date.new(1978, 8, 11), + ssn: '078051120' } end @@ -157,16 +157,16 @@ def test_invalid_login def test_include_customer_number_for_numeric_values stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge({:customer => '123'})) - end.check_request do |method, data| + @gateway.purchase(@amount, @credit_card, @options.merge({ customer: '123' })) + end.check_request do |_method, data| assert data =~ /T_customer_number=123/ end.respond_with(successful_authorization_response) end def test_dont_include_customer_number_for_numeric_values stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge({:customer => 'bob@test.com'})) - end.check_request do |method, data| + @gateway.purchase(@amount, @credit_card, @options.merge({ customer: 'bob@test.com' })) + end.check_request do |_method, data| assert data !~ /T_customer_number/ end.respond_with(successful_authorization_response) end @@ -188,7 +188,7 @@ def test_cvv_result def test_address_with_state post = {} options = { - :billing_address => { :country => 'US', :state => 'CA'} + billing_address: { country: 'US', state: 'CA' } } @gateway.send(:add_addresses, post, options) @@ -199,7 +199,7 @@ def test_address_with_state def test_address_without_state post = {} options = { - :billing_address => { :country => 'NZ', :state => ''} + billing_address: { country: 'NZ', state: '' } } @gateway.send(:add_addresses, post, options) @@ -249,7 +249,7 @@ def test_declined_check_purchase def test_successful_store response = stub_comms do @gateway.store(@credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, headers| assert_match(/login<\/ns1:M_ID>/, data) assert_match(/password<\/ns1:M_KEY>/, data) assert_match(/#{credit_card.number}<\/ns1:CARDNUMBER>/, data) @@ -267,7 +267,7 @@ def test_successful_store def test_failed_store response = stub_comms do @gateway.store(@credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, headers| assert_match(/login<\/ns1:M_ID>/, data) assert_match(/password<\/ns1:M_KEY>/, data) assert_match(/#{credit_card.number}<\/ns1:CARDNUMBER>/, data) @@ -285,7 +285,7 @@ def test_failed_store def test_successful_unstore response = stub_comms do @gateway.unstore('1234', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, headers| assert_match(/login<\/ns1:M_ID>/, data) assert_match(/password<\/ns1:M_KEY>/, data) assert_match(/1234<\/ns1:GUID>/, data) @@ -301,7 +301,7 @@ def test_successful_unstore def test_failed_unstore response = stub_comms do @gateway.unstore('1234', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, headers| assert_match(/login<\/ns1:M_ID>/, data) assert_match(/password<\/ns1:M_KEY>/, data) assert_match(/1234<\/ns1:GUID>/, data) @@ -366,175 +366,175 @@ def expected_expiration_date end def successful_store_response - <<-XML - - - - - - - - - - true - 66234d2dfec24efe9fdcd4b751578c11 - SUCCESS - - - - - - - + <<~XML + + + + + + + + + + true + 66234d2dfec24efe9fdcd4b751578c11 + SUCCESS + + + + + + + XML end def failed_store_response - <<-XML - - - - - - - - - - false - - UNABLE TO VERIFY VAULT SERVICE - - - - - - - + <<~XML + + + + + + + + + + false + + UNABLE TO VERIFY VAULT SERVICE + + + + + + + XML end def successful_unstore_response - <<-XML - - - - - true - - - + <<~XML + + + + + true + + + XML end def failed_unstore_response - <<-XML - - - - - false - - - + <<~XML + + + + + false + + + XML end def pre_scrubbed - <<-PRE_SCRUBBED -opening connection to www.sagepayments.net:443... -opened -starting SSL for www.sagepayments.net:443... -SSL established -<- "POST /cgi-bin/eftBankcard.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 444\r\n\r\n" -<- "C_name=Longbob+Longsen&C_cardnumber=4111111111111111&C_exp=0917&C_cvv=123&T_amt=1.00&T_ordernum=1741a24e00a5a5f11653&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=214282982451&M_key=Z5W2S8J7X8T5&T_code=01" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: text/html\r\n" --> "Content-Encoding: gzip\r\n" --> "Vary: Accept-Encoding\r\n" --> "Server: \r\n" --> "X-AspNet-Version: \r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" --> "Connection: close\r\n" --> "Content-Length: 185\r\n" --> "\r\n" -reading 185 bytes... --> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x1D<\xBB\xC7/_\xBE\xFA\xF2'O\x9F\xA6\xF4\a\xFD\x99v\x9F\xDD\x9D/\xE8\xAB\xCF?}\xF3\xF9\x17\xEF\xCE\xBF;\xDF\xF9\x9Dv\x1F\xEC\xEFf{\xFB\xF9\xCENv?\xBB\x7F\xBE\xBB\xFB\xE9\xFD{\xBF\xD3\xCE\xEF\xF4k\xFF?XI\x04rQ\x00\x00\x00" -read 185 bytes -Conn close + <<~PRE_SCRUBBED + opening connection to www.sagepayments.net:443... + opened + starting SSL for www.sagepayments.net:443... + SSL established + <- "POST /cgi-bin/eftBankcard.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 444\r\n\r\n" + <- "C_name=Longbob+Longsen&C_cardnumber=4111111111111111&C_exp=0917&C_cvv=123&T_amt=1.00&T_ordernum=1741a24e00a5a5f11653&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=214282982451&M_key=Z5W2S8J7X8T5&T_code=01" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Server: \r\n" + -> "X-AspNet-Version: \r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 185\r\n" + -> "\r\n" + reading 185 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x1D<\xBB\xC7/_\xBE\xFA\xF2'O\x9F\xA6\xF4\a\xFD\x99v\x9F\xDD\x9D/\xE8\xAB\xCF?}\xF3\xF9\x17\xEF\xCE\xBF;\xDF\xF9\x9Dv\x1F\xEC\xEFf{\xFB\xF9\xCENv?\xBB\x7F\xBE\xBB\xFB\xE9\xFD{\xBF\xD3\xCE\xEF\xF4k\xFF?XI\x04rQ\x00\x00\x00" + read 185 bytes + Conn close PRE_SCRUBBED end def post_scrubbed - <<-POST_SCRUBBED -opening connection to www.sagepayments.net:443... -opened -starting SSL for www.sagepayments.net:443... -SSL established -<- "POST /cgi-bin/eftBankcard.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 444\r\n\r\n" -<- "C_name=Longbob+Longsen&C_cardnumber=[FILTERED]&C_exp=0917&C_cvv=[FILTERED]&T_amt=1.00&T_ordernum=1741a24e00a5a5f11653&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=[FILTERED]&M_key=[FILTERED]&T_code=01" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: text/html\r\n" --> "Content-Encoding: gzip\r\n" --> "Vary: Accept-Encoding\r\n" --> "Server: \r\n" --> "X-AspNet-Version: \r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" --> "Connection: close\r\n" --> "Content-Length: 185\r\n" --> "\r\n" -reading 185 bytes... --> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~??\u001D "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Server: \r\n" + -> "X-AspNet-Version: \r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Length: 185\r\n" + -> "\r\n" + reading 185 bytes... + -> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~??\u001D "HTTP/1.1 200 OK\r\n" --> "Cache-Control: no-cache\r\n" --> "Pragma: no-cache\r\n" --> "Transfer-Encoding: chunked\r\n" --> "Content-Type: text/html; charset=us-ascii\r\n" --> "Content-Encoding: gzip\r\n" --> "Expires: -1\r\n" --> "Vary: Accept-Encoding\r\n" --> "Server: Microsoft-IIS/7.5\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n" --> "Connection: close\r\n" --> "\r\n" --> "ac\r\n" -reading 172 bytes... --> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x94\x9F\xE3\x93\x93\xD3\x97oN\x9F\xD2\xAF\xD1gg\xE7\xD9\x93\xBDo?\xFC\x89\xAF\x16\xF7v~\xA7\x9Dl\xFA\xE9\xF9l\xF7\xFC\xC1~\xF6\xF0`\x96?\xDC\x9F\x9C?\xFC\x9Dv~\xA7\x17_\xBE8\xA5\x1F\xBF" -read 172 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "b\r\n" -reading 11 bytes... --> "\xF6\xFF\x03\x90\xEB\x1E T\x00\x00\x00" -read 11 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close + <<~PRE_SCRUBBED + opening connection to www.sagepayments.net:443... + opened + starting SSL for www.sagepayments.net:443... + SSL established + <- "POST /cgi-bin/eftVirtualCheck.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 562\r\n\r\n" + <- "C_first_name=Jim&C_last_name=Smith&C_rte=244183602&C_acct=15378535&C_check_number=1&C_acct_type=DDA&C_customer_type=WEB&C_originator_id=&T_addenda=&C_ssn=&C_dl_state_code=&C_dl_number=&C_dob=&T_amt=1.00&T_ordernum=0ac6fd1f74a98de94bf9&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=562313162894&M_key=J6U9B3G2F6L3&T_code=01" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: text/html; charset=us-ascii\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Expires: -1\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + -> "ac\r\n" + reading 172 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x94\x9F\xE3\x93\x93\xD3\x97oN\x9F\xD2\xAF\xD1gg\xE7\xD9\x93\xBDo?\xFC\x89\xAF\x16\xF7v~\xA7\x9Dl\xFA\xE9\xF9l\xF7\xFC\xC1~\xF6\xF0`\x96?\xDC\x9F\x9C?\xFC\x9Dv~\xA7\x17_\xBE8\xA5\x1F\xBF" + read 172 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "b\r\n" + reading 11 bytes... + -> "\xF6\xFF\x03\x90\xEB\x1E T\x00\x00\x00" + read 11 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close PRE_SCRUBBED end def post_scrubbed_echeck - <<-POST_SCRUBBED -opening connection to www.sagepayments.net:443...\nopened\nstarting SSL for www.sagepayments.net:443...\nSSL established\n<- \"POST /cgi-bin/eftVirtualCheck.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 562\r\n\r\n\"\n<- \"C_first_name=Jim&C_last_name=Smith&C_rte=[FILTERED]&C_acct=[FILTERED]&C_check_number=1&C_acct_type=DDA&C_customer_type=WEB&C_originator_id=&T_addenda=&C_ssn=[FILTERED]&C_dl_state_code=&C_dl_number=&C_dob=&T_amt=1.00&T_ordernum=0ac6fd1f74a98de94bf9&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=[FILTERED]&M_key=[FILTERED]&T_code=01\"\n-> \"HTTP/1.1 200 OK\r\n\"\n-> \"Cache-Control: no-cache\r\n\"\n-> \"Pragma: no-cache\r\n\"\n-> \"Transfer-Encoding: chunked\r\n\"\n-> \"Content-Type: text/html; charset=us-ascii\r\n\"\n-> \"Content-Encoding: gzip\r\n\"\n-> \"Expires: -1\r\n\"\n-> \"Vary: Accept-Encoding\r\n\"\n-> \"Server: Microsoft-IIS/7.5\r\n\"\n-> \"X-Powered-By: ASP.NET\r\n\"\n-> \"Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n\"\n-> \"Connection: close\r\n\"\n-> \"\r\n\"\n-> \"ac\r\n\"\nreading 172 bytes...\n-> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~????oN???gg???o????\u0016?v~??l???l???~??`???????v~?\u0017_?8?\u001F?\"\nread 172 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"b\r\n\"\nreading 11 bytes...\n-> \"??\u0003??\u001E T\u0000\u0000\u0000\"\nread 11 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"0\r\n\"\n-> \"\r\n\"\nConn close + <<~POST_SCRUBBED + opening connection to www.sagepayments.net:443...\nopened\nstarting SSL for www.sagepayments.net:443...\nSSL established\n<- \"POST /cgi-bin/eftVirtualCheck.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 562\r\n\r\n\"\n<- \"C_first_name=Jim&C_last_name=Smith&C_rte=[FILTERED]&C_acct=[FILTERED]&C_check_number=1&C_acct_type=DDA&C_customer_type=WEB&C_originator_id=&T_addenda=&C_ssn=[FILTERED]&C_dl_state_code=&C_dl_number=&C_dob=&T_amt=1.00&T_ordernum=0ac6fd1f74a98de94bf9&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=[FILTERED]&M_key=[FILTERED]&T_code=01\"\n-> \"HTTP/1.1 200 OK\r\n\"\n-> \"Cache-Control: no-cache\r\n\"\n-> \"Pragma: no-cache\r\n\"\n-> \"Transfer-Encoding: chunked\r\n\"\n-> \"Content-Type: text/html; charset=us-ascii\r\n\"\n-> \"Content-Encoding: gzip\r\n\"\n-> \"Expires: -1\r\n\"\n-> \"Vary: Accept-Encoding\r\n\"\n-> \"Server: Microsoft-IIS/7.5\r\n\"\n-> \"X-Powered-By: ASP.NET\r\n\"\n-> \"Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n\"\n-> \"Connection: close\r\n\"\n-> \"\r\n\"\n-> \"ac\r\n\"\nreading 172 bytes...\n-> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~????oN???gg???o????\u0016?v~??l???l???~??`???????v~?\u0017_?8?\u001F?\"\nread 172 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"b\r\n\"\nreading 11 bytes...\n-> \"??\u0003??\u001E T\u0000\u0000\u0000\"\nread 11 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"0\r\n\"\n-> \"\r\n\"\nConn close POST_SCRUBBED end end diff --git a/test/unit/gateways/sallie_mae_test.rb b/test/unit/gateways/sallie_mae_test.rb index 40194a47164..b47bb928923 100644 --- a/test/unit/gateways/sallie_mae_test.rb +++ b/test/unit/gateways/sallie_mae_test.rb @@ -3,16 +3,16 @@ class SallieMaeTest < Test::Unit::TestCase def setup @gateway = SallieMaeGateway.new( - :login => 'FAKEACCOUNT' - ) + login: 'FAKEACCOUNT' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -35,7 +35,7 @@ def test_non_test_account end def test_test_account - gateway = SallieMaeGateway.new(:login => 'TEST0') + gateway = SallieMaeGateway.new(login: 'TEST0') assert gateway.test? end diff --git a/test/unit/gateways/secure_net_test.rb b/test/unit/gateways/secure_net_test.rb index 01c9b55c70d..76baf7edfac 100644 --- a/test/unit/gateways/secure_net_test.rb +++ b/test/unit/gateways/secure_net_test.rb @@ -5,17 +5,17 @@ class SecureNetTest < Test::Unit::TestCase def setup @gateway = SecureNetGateway.new( - :login => 'X', - :password => 'Y' - ) + login: 'X', + password: 'Y' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -118,7 +118,7 @@ def test_order_id_is_truncated order_id = "SecureNet doesn't like order_ids greater than 25 characters." stub_comms do @gateway.purchase(@amount, @credit_card, order_id: order_id) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/ORDERID>SecureNet doesn't like orGood Stuff<}, data) assert_match(%r{INVOICEDESC>Sweet Invoice<}, data) assert_match(%r{INVOICENUM>48<}, data) @@ -143,7 +143,7 @@ def test_passes_optional_fields def test_only_passes_optional_fields_if_specified stub_comms do @gateway.purchase(@amount, @credit_card, {}) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{NOTE}, data) assert_no_match(%r{INVOICEDESC}, data) assert_no_match(%r{INVOICENUM}, data) @@ -153,7 +153,7 @@ def test_only_passes_optional_fields_if_specified def test_passes_with_no_developer_id stub_comms do @gateway.purchase(@amount, @credit_card, {}) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match(%r{DEVELOPERID}, data) end.respond_with(successful_purchase_response) end @@ -161,7 +161,7 @@ def test_passes_with_no_developer_id def test_passes_with_developer_id stub_comms do @gateway.purchase(@amount, @credit_card, developer_id: '1234') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{DEVELOPERID}, data) end.respond_with(successful_purchase_response) end @@ -169,7 +169,7 @@ def test_passes_with_developer_id def test_passes_with_test_mode stub_comms do @gateway.purchase(@amount, @credit_card, test_mode: false) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{FALSE}, data) end.respond_with(successful_purchase_response) end @@ -177,7 +177,7 @@ def test_passes_with_test_mode def test_passes_without_test_mode stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{TRUE}, data) end.respond_with(successful_purchase_response) end @@ -231,48 +231,48 @@ def failed_refund_response end def pre_scrubbed - <<-EOS -opening connection to certify.securenet.com:443... -opened -starting SSL for certify.securenet.com:443... -SSL established -<- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" -<- "1.00123400010001111222409190100
456 My Street
OttawaWidgets IncCALongbobLongsen(555)555-5555ONK1C2N6
010BI8gL8HO1dKP7001218CCStore Purchase151992186896260900TRUE00
" --> "HTTP/1.1 200 OK\r\n" --> "Content-Length: 2547\r\n" --> "Content-Type: application/xml; charset=utf-8\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" --> "Connection: close\r\n" --> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" --> "\r\n" -reading 2547 bytes... --> "10000Approved0JUJQLQ1.00Y0LongbobLongsenVIM000100
456 My Street
OttawaWidgets IncCAFALSELongbobLongsen(555)555-5555ONK1C2N6
09190P2224FALSEFALSECCStore Purchase151992186896260970012181.000301201811310101.0003012018113101116186071
" -read 2547 bytes -Conn close - EOS + <<~REQUEST + opening connection to certify.securenet.com:443... + opened + starting SSL for certify.securenet.com:443... + SSL established + <- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" + <- "1.00123400010001111222409190100
456 My Street
OttawaWidgets IncCALongbobLongsen(555)555-5555ONK1C2N6
010BI8gL8HO1dKP7001218CCStore Purchase151992186896260900TRUE00
" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Length: 2547\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" + -> "\r\n" + reading 2547 bytes... + -> "10000Approved0JUJQLQ1.00Y0LongbobLongsenVIM000100
456 My Street
OttawaWidgets IncCAFALSELongbobLongsen(555)555-5555ONK1C2N6
09190P2224FALSEFALSECCStore Purchase151992186896260970012181.000301201811310101.0003012018113101116186071
" + read 2547 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to certify.securenet.com:443... -opened -starting SSL for certify.securenet.com:443... -SSL established -<- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" -<- "1.00[FILTERED][FILTERED]09190100
456 My Street
OttawaWidgets IncCALongbobLongsen(555)555-5555ONK1C2N6
010[FILTERED]7001218CCStore Purchase151992186896260900TRUE00
" --> "HTTP/1.1 200 OK\r\n" --> "Content-Length: 2547\r\n" --> "Content-Type: application/xml; charset=utf-8\r\n" --> "X-Powered-By: ASP.NET\r\n" --> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" --> "Connection: close\r\n" --> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" --> "\r\n" -reading 2547 bytes... --> "10000Approved0JUJQLQ1.00Y0LongbobLongsenVIM000100
456 My Street
OttawaWidgets IncCAFALSELongbobLongsen(555)555-5555ONK1C2N6
09190P2224FALSEFALSECCStore Purchase151992186896260970012181.000301201811310101.0003012018113101116186071
" -read 2547 bytes -Conn close - EOS + <<~REQUEST + opening connection to certify.securenet.com:443... + opened + starting SSL for certify.securenet.com:443... + SSL established + <- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" + <- "1.00[FILTERED][FILTERED]09190100
456 My Street
OttawaWidgets IncCALongbobLongsen(555)555-5555ONK1C2N6
010[FILTERED]7001218CCStore Purchase151992186896260900TRUE00
" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Length: 2547\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" + -> "\r\n" + reading 2547 bytes... + -> "10000Approved0JUJQLQ1.00Y0LongbobLongsenVIM000100
456 My Street
OttawaWidgets IncCAFALSELongbobLongsen(555)555-5555ONK1C2N6
09190P2224FALSEFALSECCStore Purchase151992186896260970012181.000301201811310101.0003012018113101116186071
" + read 2547 bytes + Conn close + REQUEST end end diff --git a/test/unit/gateways/secure_pay_au_test.rb b/test/unit/gateways/secure_pay_au_test.rb index 5d995aab6f8..4bbd0213712 100644 --- a/test/unit/gateways/secure_pay_au_test.rb +++ b/test/unit/gateways/secure_pay_au_test.rb @@ -5,17 +5,17 @@ class SecurePayAuTest < Test::Unit::TestCase def setup @gateway = SecurePayAuGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -24,7 +24,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :jcb], SecurePayAuGateway.supported_cardtypes + assert_equal %i[visa master american_express diners_club jcb], SecurePayAuGateway.supported_cardtypes end def test_successful_purchase_with_live_data @@ -51,14 +51,14 @@ def test_successful_purchase def test_localized_currency stub_comms do - @gateway.purchase(100, @credit_card, @options.merge(:currency => 'CAD')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, @credit_card, @options.merge(currency: 'CAD')) + end.check_request do |_endpoint, data, _headers| assert_match %r{100<\/amount>}, data end.respond_with(successful_purchase_response) stub_comms do - @gateway.purchase(100, @credit_card, @options.merge(:currency => 'JPY')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(100, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |_endpoint, data, _headers| assert_match %r{1<\/amount>}, data end.respond_with(successful_purchase_response) end @@ -166,7 +166,7 @@ def test_failed_login def test_successful_store @gateway.expects(:ssl_post).returns(successful_store_response) - assert response = @gateway.store(@credit_card, {:billing_id => 'test3', :amount => 123}) + assert response = @gateway.store(@credit_card, { billing_id: 'test3', amount: 123 }) assert_instance_of Response, response assert_equal 'Successful', response.message assert_equal 'test3', response.params['client_id'] diff --git a/test/unit/gateways/secure_pay_tech_test.rb b/test/unit/gateways/secure_pay_tech_test.rb index 87293a7f817..6ebe85049ff 100644 --- a/test/unit/gateways/secure_pay_tech_test.rb +++ b/test/unit/gateways/secure_pay_tech_test.rb @@ -3,14 +3,14 @@ class SecurePayTechTest < Test::Unit::TestCase def setup @gateway = SecurePayTechGateway.new( - :login => 'x', - :password => 'y' - ) + login: 'x', + password: 'y' + ) @amount = 100 @credit_card = credit_card('4987654321098769') @options = { - :billing_address => address + billing_address: address } end diff --git a/test/unit/gateways/secure_pay_test.rb b/test/unit/gateways/secure_pay_test.rb index 0fdc3b08493..71cd4e7fa00 100644 --- a/test/unit/gateways/secure_pay_test.rb +++ b/test/unit/gateways/secure_pay_test.rb @@ -3,16 +3,16 @@ class SecurePayTest < Test::Unit::TestCase def setup @gateway = SecurePayGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) @credit_card = credit_card @options = { - :order_id => generate_unique_id, - :description => 'Store purchase', - :billing_address => address + order_id: generate_unique_id, + description: 'Store purchase', + billing_address: address } @amount = 100 diff --git a/test/unit/gateways/securion_pay_test.rb b/test/unit/gateways/securion_pay_test.rb index 06fbe0ff622..b37509b5f66 100644 --- a/test/unit/gateways/securion_pay_test.rb +++ b/test/unit/gateways/securion_pay_test.rb @@ -18,6 +18,15 @@ def setup billing_address: address, description: 'Store Purchase' } + + @three_ds_secure = { + version: '1.0.2', + cavv: '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', + eci: '05', + xid: 'ODUzNTYzOTcwODU5NzY3Qw==', + enrolled: 'true', + authentication_response_status: 'Y' + } end def test_successful_store @@ -27,7 +36,6 @@ 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'] @@ -35,7 +43,8 @@ def test_successful_store @gateway.expects(:ssl_post).returns(successful_authorize_response) @gateway.expects(:ssl_post).returns(successful_void_response) - @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'] @@ -63,10 +72,93 @@ def test_successful_purchase assert response.test? end + def test_three_ds_v1_object_construction + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_external_three_ds, post, @options) + assert post[:threeDSecure] + ds_data = post[:threeDSecure][:external] + ds_options = @options[:three_d_secure] + assert_equal ds_options[:version], ds_data[:version] + assert_equal ds_options[:cavv], ds_data[:authenticationValue] + assert_equal ds_options[:eci], ds_data[:eci] + assert_equal ds_options[:xid], ds_data[:xid] + assert_equal nil, ds_data[:ds_transaction_id] + assert_equal ds_options[:authentication_response_status], ds_data[:status] + end + + def test_three_ds_v2_object_construction + post = {} + @three_ds_secure[:ds_transaction_id] = 'ODUzNTYzOTcwODU5NzY3Qw==' + @three_ds_secure[:version] = '2.2.0' + @options[:three_d_secure] = @three_ds_secure + + @gateway.send(:add_external_three_ds, post, @options) + + assert post[:threeDSecure] + ds_data = post[:threeDSecure][:external] + ds_options = @options[:three_d_secure] + + assert_equal ds_options[:version], ds_data[:version] + assert_equal ds_options[:cavv], ds_data[:authenticationValue] + assert_equal ds_options[:eci], ds_data[:eci] + assert_equal nil, ds_data[:xid] + assert_equal ds_options[:ds_transaction_id], ds_data[:dsTransactionId] + assert_equal ds_options[:authentication_response_status], ds_data[:status] + end + + def test_three_ds_version_validation + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_external_three_ds, post, @options) + resp = @gateway.send(:validate_three_ds_params, @three_ds_secure) + + assert_equal nil, resp + post[:threeDSecure][:external][:version] = '4.0' + resp = @gateway.send(:validate_three_ds_params, post[:threeDSecure]) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'ThreeDs version not supported', resp.params['three_ds_version'] + end + + def test_three_ds_auth_response_validation + post = {} + @options[:three_d_secure] = @three_ds_secure + @gateway.send(:add_external_three_ds, post, @options) + post[:threeDSecure][:external][:status] = 'P' + resp = @gateway.send(:validate_three_ds_params, post[:threeDSecure][:external]) + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'Authentication response value not supported', resp.params['auth_response'] + end + + def test_purchase_with_three_ds + @options[:three_d_secure] = @three_ds_secure + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + three_ds_params = JSON.parse(data)['three_dsecure'] + assert_equal '1.0', three_ds_params['three_dsecure_version'] + assert_equal '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=', three_ds_params['cavv'] + assert_equal '05', three_ds_params['eci'] + assert_equal 'ODUzNTYzOTcwODU5NzY3Qw==', three_ds_params['xid'] + assert_equal 'Y', three_ds_params['enrollment_response'] + assert_equal 'Y', three_ds_params['authentication_response'] + end + end + + def test_unsuccessfully_purchase_with_wrong_three_ds_data + @three_ds_secure.delete(:version) + @options[:three_d_secure] = @three_ds_secure + resp = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal 'ThreeDs data is invalid', resp.message + assert_equal 'ThreeDs version not supported', resp.params['three_ds_version'] + end + def test_successful_purchase_with_token response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, 'tok_xxx') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/card=tok_xxx/, data) refute_match(/card\[number\]/, data) end.respond_with(successful_purchase_response) @@ -88,7 +180,7 @@ def test_client_data_submitted_with_purchase stub_comms(@gateway, :ssl_request) do updated_options = @options.merge({ description: 'test charge', ip: '127.127.127.127', user_agent: 'browser XXX', referrer: 'http://www.foobar.com', email: 'foo@bar.com' }) @gateway.purchase(@amount, @credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/description=test\+charge/, data) assert_match(/ip=127\.127\.127\.127/, data) assert_match(/user_agent=browser\+XXX/, data) @@ -101,7 +193,7 @@ def test_client_data_submitted_with_purchase_without_email_or_order stub_comms(@gateway, :ssl_request) do updated_options = @options.merge({ description: 'test charge', ip: '127.127.127.127', user_agent: 'browser XXX', referrer: 'http://www.foobar.com' }) @gateway.purchase(@amount, @credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/description=test\+charge/, data) assert_match(/ip=127\.127\.127\.127/, data) assert_match(/user_agent=browser\+XXX/, data) @@ -122,7 +214,7 @@ def test_successful_authorization end def test_add_address - post = { card: { } } + post = { card: {} } @gateway.send(:add_address, post, @options) assert_equal @options[:billing_address][:zip], post[:card][:addressZip] assert_equal @options[:billing_address][:state], post[:card][:addressState] @@ -139,7 +231,7 @@ def test_ensure_does_not_respond_to_credit def test_address_is_included_with_card_data stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[addressLine1\]/ end.respond_with(successful_purchase_response) end @@ -170,7 +262,7 @@ def test_failed_authorize response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code - assert_nil response.authorization + assert_equal 'char_mApucpvVbCJgo7x09Je4n9gC', response.authorization assert response.test? end @@ -302,6 +394,15 @@ def test_declined_request assert_equal 'char_mApucpvVbCJgo7x09Je4n9gC', response.params['error']['chargeId'] end + def test_amount_currency_gets_downcased + @options[:currency] = 'USD' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'usd', CGI.parse(data)['currency'].first + end.respond_with(successful_purchase_response) + end + private def pre_scrubbed diff --git a/test/unit/gateways/shift4_test.rb b/test/unit/gateways/shift4_test.rb new file mode 100644 index 00000000000..267de4254c9 --- /dev/null +++ b/test/unit/gateways/shift4_test.rb @@ -0,0 +1,1072 @@ +require 'test_helper' + +class Shift4Test < Test::Unit::TestCase + include CommStub + def setup + @gateway = Shift4Gateway.new(client_guid: '123456', auth_token: 'abcder123') + @credit_card = credit_card('4000100011112224', verification_value: '333', first_name: 'John', last_name: 'Doe') + @amount = 5 + @options = {} + @extra_options = { + clerk_id: '1576', + notes: 'test notes', + tax: '2', + customer_reference: 'D019D09309F2', + destination_postal_code: '94719', + product_descriptors: %w(Hamburger Fries Soda Cookie), + order_id: '123456' + } + @customer_address = { + address1: '123 Street', + zip: '94901' + } + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, '1111g66gw3ryke06', @options) + end.check_request do |_endpoint, data, headers| + request = JSON.parse(data) + assert_nil request['card']['present'], 'N' + assert_nil request['card']['entryMode'] + assert_nil headers['Invoice'] + end.respond_with(successful_capture_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + assert_equal response_result(response)['card']['token']['value'].present?, true + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + assert_equal response_result(response)['card']['token']['value'].present?, true + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, '1111g66gw3ryke06', @options) + end.respond_with(successful_purchase_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + assert_equal response_result(response)['card']['token']['value'].present?, true + end + + def test_successful_purchase_with_extra_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(@extra_options)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['clerk']['numericId'], @extra_options[:clerk_id] + assert_equal request['transaction']['notes'], @extra_options[:notes] + assert_equal request['transaction']['vendorReference'], @extra_options[:order_id] + assert_equal request['amount']['tax'], @extra_options[:tax].to_f + assert_equal request['amount']['total'], (@amount / 100.0).to_s + assert_equal request['transaction']['purchaseCard']['customerReference'], @extra_options[:customer_reference] + assert_equal request['transaction']['purchaseCard']['destinationPostalCode'], @extra_options[:destination_postal_code] + assert_equal request['transaction']['purchaseCard']['productDescriptors'], @extra_options[:product_descriptors] + end.respond_with(successful_purchase_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + end + + def test_successful_purchase_with_customer_details + customer = { billing_address: @customer_address, ip: '127.0.0.1', email: 'test@test.com' } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(customer)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['customer']['addressLine1'], @customer_address[:address1] + assert_equal request['customer']['postalCode'], @customer_address[:zip] + assert_equal request['customer']['emailAddress'], customer[:email] + assert_equal request['customer']['ipAddress'], customer[:ip] + assert_equal request['customer']['firstName'], @credit_card.first_name + assert_equal request['customer']['lastName'], @credit_card.last_name + end.respond_with(successful_purchase_response) + + customer[:billing_address][:zip] = nil + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(customer)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['customer']['postalCode'] + end.respond_with(successful_purchase_response) + + customer[:billing_address][:zip] = '' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(customer)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['customer']['postalCode'] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_stored_credential_framework + stored_credential_options = { + initial_transaction: true, + reason_type: 'recurring' + } + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], '01' + assert_equal request['cardOnFile']['indicator'], '01' + assert_equal request['cardOnFile']['scheduledIndicator'], '01' + assert_nil request['cardOnFile']['transactionId'] + end.respond_with(successful_purchase_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + + stored_credential_options = { + reason_type: 'recurring', + network_transaction_id: '123abcdefg' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ stored_credential: stored_credential_options })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], '02' + assert_equal request['cardOnFile']['indicator'], '01' + assert_equal request['cardOnFile']['scheduledIndicator'], '01' + assert_equal request['cardOnFile']['transactionId'], stored_credential_options[:network_transaction_id] + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_card_on_file_fields + card_on_file_fields = { + usage_indicator: '01', + indicator: '02', + scheduled_indicator: '01' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(card_on_file_fields)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], card_on_file_fields[:usage_indicator] + assert_equal request['cardOnFile']['indicator'], card_on_file_fields[:indicator] + assert_equal request['cardOnFile']['scheduledIndicator'], card_on_file_fields[:scheduled_indicator] + assert_nil request['cardOnFile']['transactionId'] + end.respond_with(successful_purchase_response) + + card_on_file_fields = { + usage_indicator: '02', + indicator: '01', + scheduled_indicator: '02', + transaction_id: 'TXID00001293' + } + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(card_on_file_fields)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], card_on_file_fields[:usage_indicator] + assert_equal request['cardOnFile']['indicator'], card_on_file_fields[:indicator] + assert_equal request['cardOnFile']['scheduledIndicator'], card_on_file_fields[:scheduled_indicator] + assert_equal request['cardOnFile']['transactionId'], card_on_file_fields[:transaction_id] + end.respond_with(successful_purchase_response) + end + + def test_card_on_file_fields_and_stored_credential_framework_combined + card_on_file_fields = { + usage_indicator: '02', + indicator: '02', + scheduled_indicator: '02' + } + stored_credential_options = { + initial_transaction: true, + reason_type: 'recurring' + } + @options[:stored_credential] = stored_credential_options + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(card_on_file_fields)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], card_on_file_fields[:usage_indicator] + assert_equal request['cardOnFile']['indicator'], card_on_file_fields[:indicator] + assert_equal request['cardOnFile']['scheduledIndicator'], card_on_file_fields[:scheduled_indicator] + assert_nil request['cardOnFile']['transactionId'] + end.respond_with(successful_purchase_response) + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @options.merge(@extra_options.except(:tax))) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_nil request['card']['entryMode'] + assert_nil request['clerk'] + end.respond_with(successful_store_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '1111g66gw3ryke06', @options.merge!(invoice: '4666309473', expiration_date: '1235')) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['card']['present'], 'N' + assert_equal request['card']['expirationDate'], '1235' + assert_nil request['card']['entryMode'] + assert_nil request['customer'] + end.respond_with(successful_refund_response) + + assert response.success? + assert_equal response.message, 'Transaction successful' + end + + def test_successful_credit + stub_comms do + @gateway.refund(@amount, @credit_card, @options.merge!(invoice: '4666309473', expiration_date: '1235')) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['card']['present'], 'N' + assert_equal request['card']['expirationDate'], '0924' + assert_nil request['card']['entryMode'] + assert_nil request['customer'] + end.respond_with(successful_refund_response) + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + response = @gateway.void('123') + + assert response.success? + assert_equal response.message, 'Transaction successful' + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, 'abc', @options) + assert_failure response + assert_nil response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_nil response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'abc', @options) + assert_failure response + assert_nil response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'abc', @options) + assert_failure response + assert_nil response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('', @options) + assert_failure response + assert_nil response.authorization + assert response.test? + end + + def test_successful_verify_fields + card_on_file_fields = { + usage_indicator: '02', + indicator: '01', + scheduled_indicator: '02', + transaction_id: 'TXID00001293' + } + response = stub_comms do + @gateway.verify(@credit_card, @options.merge(card_on_file_fields)) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['transaction']['cardOnFile']['usageIndicator'], card_on_file_fields[:usage_indicator] + assert_equal request['transaction']['cardOnFile']['indicator'], card_on_file_fields[:indicator] + assert_equal request['transaction']['cardOnFile']['scheduledIndicator'], card_on_file_fields[:scheduled_indicator] + assert_equal request['transaction']['cardOnFile']['transactionId'], card_on_file_fields[:transaction_id] + assert_not_nil request['dateTime'] + assert !request['customer'].nil? && !request['customer'].empty? + assert_nil request['card']['entryMode'] + end.respond_with(successful_verify_response) + + assert_success response + end + + def test_successful_verify_with_stored_credential_framework + stored_credential_options = { + reason_type: 'recurring', + network_transaction_id: '123abcdefg' + } + stub_comms do + @gateway.verify(@credit_card, @options.merge({ stored_credential: stored_credential_options })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data)['transaction'] + assert_equal request['cardOnFile']['usageIndicator'], '02' + assert_equal request['cardOnFile']['indicator'], '01' + assert_equal request['cardOnFile']['scheduledIndicator'], '01' + assert_equal request['cardOnFile']['transactionId'], stored_credential_options[:network_transaction_id] + end.respond_with(successful_verify_response) + end + + def test_card_present_field + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['card']['present'], 'N' + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({ card_present: 'Y' })) + end.check_request do |_endpoint, data, _headers| + request = JSON.parse(data) + assert_equal request['card']['present'], 'Y' + end.respond_with(successful_purchase_response) + end + + def test_successful_header_fields + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, _data, headers| + assert_equal headers['CompanyName'], 'Spreedly' + assert_equal headers['InterfaceVersion'], '1' + assert_equal headers['InterfaceName'], 'Spreedly' + end.respond_with(successful_purchase_response) + end + + def test_successful_time_zone_offset + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge!(merchant_time_zone: 'EST')) + end.check_request do |_endpoint, data, _headers| + assert_equal DateTime.parse(JSON.parse(data)['dateTime']).formatted_offset, Time.now.in_time_zone(@options[:merchant_time_zone]).formatted_offset + end.respond_with(successful_purchase_response) + end + + def test_support_scrub + assert @gateway.supports_scrubbing? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_setup_access_token_should_rise_an_exception_under_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_auth_response) + + error = assert_raises(ActiveMerchant::OAuthResponseError) do + @gateway.setup_access_token + end + + assert_match(/Failed with AuthToken not valid ENGINE22CE/, error.message) + end + + def test_setup_access_token_should_successfully_extract_the_token_from_response + @gateway.expects(:ssl_post).returns(sucess_auth_response) + + assert_equal 'abc123', @gateway.setup_access_token + end + + private + + def response_result(response) + response.params['result'].first + end + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to utgapi.shift4test.com:443... + opened + starting SSL for utgapi.shift4test.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /api/rest/v1/transactions/authorization HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nCompanyname: Spreedly\r\nAccesstoken: 4902FAD2-E88F-4A8D-98C2-EED2A73DBBE2\r\nInvoice: 1\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: utgapi.shift4test.com\r\nContent-Length: 498\r\n\r\n" + <- "{\"dateTime\":\"2022-06-09T14:03:36.413505000+14:03\",\"amount\":{\"total\":5.0,\"tax\":1.0},\"clerk\":{\"numericId\":24},\"transaction\":{\"invoice\":\"1\",\"purchaseCard\":{\"customerReference\":\"457\",\"destinationPostalCode\":\"89123\",\"productDescriptors\":[\"Potential\",\"Wrong\"]}},\"card\":{\"expirationDate\":\"0923\",\"number\":\"4000100011112224\",\"entryMode\":null,\"present\":null,\"securityCode\":{\"indicator\":1,\"value\":\"4444\"}},\"customer\":{\"addressLine1\":\"89 Main Street\",\"firstName\":\"XYZ\",\"lastName\":\"RON\",\"postalCode\":\"89000\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/json; charset=ISO-8859-1\r\n" + -> "Content-Length: 1074\r\n" + -> "Date: Thu, 09 Jun 2022 09:03:40 GMT\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: deny\r\n" + -> "Content-Security-Policy: default-src 'none';base-uri 'none';frame-ancestors 'none';object-src 'none';sandbox;\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Permitted-Cross-Domain-Policies: none\r\n" + -> "Referrer-Policy: no-referrer\r\n" + -> "X-Powered-By: Electricity\r\n" + -> "Expires: 0\r\n" + -> "Cache-Control: private, no-cache, no-store, max-age=0, no-transform\r\n" + -> "Server: DatasnapHTTPService/2011\r\n" + -> "\r\n" + reading 1074 bytes... + -> "" + -> "{\"result\":[{\"dateTime\":\"2022-06-09T14:03:36.000-07:00\",\"receiptColumns\":30,\"amount\":{\"tax\":1,\"total\":5},\"card\":{\"type\":\"VS\",\"entryMode\":\"M\",\"number\":\"XXXXXXXXXXXX2224\",\"present\":\"Y\",\"securityCode\":{\"result\":\"N\",\"valid\":\"N\"},\"token\":{\"value\":\"8042728003772224\"}},\"clerk\":{\"numericId\":24},\"customer\":{\"addressLine1\":\"89 Main Street\",\"firstName\":\"XYZ\",\"lastName\":\"RON\",\"postalCode\":\"89000\"},\"device\":{\"capability\":{\"magstripe\":\"Y\",\"manualEntry\":\"Y\"}},\"merchant\":{\"mid\":8504672,\"name\":\"Zippin - Retail\"},\"receipt\":[{\"key\":\"MaskedPAN\",\"printValue\":\"XXXXXXXXXXXX2224\"},{\"key\":\"CardEntryMode\",\"printName\":\"ENTRY METHOD\",\"printValue\":\"KEYED\"},{\"key\":\"SignatureRequired\",\"printValue\":\"N\"}],\"server\":{\"name\":\"UTGAPI05CE\"},\"transaction\":{\"authSource\":\"E\",\"avs\":{\"postalCodeVerified\":\"Y\",\"result\":\"Y\",\"streetVerified\":\"Y\",\"valid\":\"Y\"},\"invoice\":\"0000000001\",\"purchaseCard\":{\"customerReference\":\"457\",\"destinationPostalCode\":\"89123\",\"productDescriptors\":[\"Potential\",\"Wrong\"]},\"responseCode\":\"D\",\"saleFlag\":\"S\"},\"universalToken\":{\"value\":\"400010-2F1AA405-001AA4-000026B7-1766C44E9E8\"}}]}" + read 1074 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to utgapi.shift4test.com:443... + opened + starting SSL for utgapi.shift4test.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /api/rest/v1/transactions/authorization HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nCompanyname: Spreedly\r\nAccesstoken: 4902FAD2-E88F-4A8D-98C2-EED2A73DBBE2\r\nInvoice: 1\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: utgapi.shift4test.com\r\nContent-Length: 498\r\n\r\n" + <- "{\"dateTime\":\"2022-06-09T14:03:36.413505000+14:03\",\"amount\":{\"total\":5.0,\"tax\":1.0},\"clerk\":{\"numericId\":24},\"transaction\":{\"invoice\":\"1\",\"purchaseCard\":{\"customerReference\":\"457\",\"destinationPostalCode\":\"89123\",\"productDescriptors\":[\"Potential\",\"Wrong\"]}},\"card\":{\"expirationDate\":\"[FILTERED]",\"number\":\"[FILTERED]",\"entryMode\":null,\"present\":null,\"securityCode\":{\"indicator\":1,\"value\":\"[FILTERED]\"}},\"customer\":{\"addressLine1\":\"89 Main Street\",\"firstName\":\"[FILTERED]",\"lastName\":\"[FILTERED]",\"postalCode\":\"89000\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/json; charset=ISO-8859-1\r\n" + -> "Content-Length: 1074\r\n" + -> "Date: Thu, 09 Jun 2022 09:03:40 GMT\r\n" + -> "Pragma: no-cache\r\n" + -> "X-Frame-Options: deny\r\n" + -> "Content-Security-Policy: default-src 'none';base-uri 'none';frame-ancestors 'none';object-src 'none';sandbox;\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Permitted-Cross-Domain-Policies: none\r\n" + -> "Referrer-Policy: no-referrer\r\n" + -> "X-Powered-By: Electricity\r\n" + -> "Expires: 0\r\n" + -> "Cache-Control: private, no-cache, no-store, max-age=0, no-transform\r\n" + -> "Server: DatasnapHTTPService/2011\r\n" + -> "\r\n" + reading 1074 bytes... + -> "" + -> "{\"result\":[{\"dateTime\":\"2022-06-09T14:03:36.000-07:00\",\"receiptColumns\":30,\"amount\":{\"tax\":1,\"total\":5},\"card\":{\"type\":\"VS\",\"entryMode\":\"M\",\"number\":\"[FILTERED]",\"present\":\"Y\",\"securityCode\":{\"result\":\"N\",\"valid\":\"N\"},\"token\":{\"value\":\"8042728003772224\"}},\"clerk\":{\"numericId\":24},\"customer\":{\"addressLine1\":\"89 Main Street\",\"firstName\":\"[FILTERED]",\"lastName\":\"[FILTERED]",\"postalCode\":\"89000\"},\"device\":{\"capability\":{\"magstripe\":\"Y\",\"manualEntry\":\"Y\"}},\"merchant\":{\"mid\":8504672,\"name\":\"Zippin - Retail\"},\"receipt\":[{\"key\":\"MaskedPAN\",\"printValue\":\"XXXXXXXXXXXX2224\"},{\"key\":\"CardEntryMode\",\"printName\":\"ENTRY METHOD\",\"printValue\":\"KEYED\"},{\"key\":\"SignatureRequired\",\"printValue\":\"N\"}],\"server\":{\"name\":\"UTGAPI05CE\"},\"transaction\":{\"authSource\":\"E\",\"avs\":{\"postalCodeVerified\":\"Y\",\"result\":\"Y\",\"streetVerified\":\"Y\",\"valid\":\"Y\"},\"invoice\":\"0000000001\",\"purchaseCard\":{\"customerReference\":\"457\",\"destinationPostalCode\":\"89123\",\"productDescriptors\":[\"Potential\",\"Wrong\"]},\"responseCode\":\"D\",\"saleFlag\":\"S\"},\"universalToken\":{\"value\":\"400010-2F1AA405-001AA4-000026B7-1766C44E9E8\"}}]}" + read 1074 bytes + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-02-09T05:11:54.000-08:00", + "receiptColumns": 30, + "amount": { + "total": 5 + }, + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX1111", + "present": "N", + "securityCode": { + "result": "N", + "valid": "N" + }, + "token": { + "value": "8042714004661111" + } + }, + "clerk": { + "numericId": 16 + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "mid": 8585812, + "name": "RealtimePOS - Retail" + }, + "receipt": [ + { + "key": "MaskedPAN", + "printValue": "XXXXXXXXXXXX1111" + }, + { + "key": "CardEntryMode", + "printName": "ENTRY METHOD", + "printValue": "KEYED" + }, + { + "key": "SignatureRequired", + "printValue": "N" + } + ], + "server": { + "name": "UTGAPI12CE" + }, + "transaction": { + "authSource": "E", + "invoice": "4666309473", + "purchaseCard": { + "customerReference": "1234567", + "destinationPostalCode": "89123", + "productDescriptors": [ + "Test" + ] + }, + "responseCode": "A", + "saleFlag": "S" + }, + "universalToken": { + "value": "444433-2D5C1A5C-001624-00001621-16BAAF4ACC6" + } + } + ] + } + RESPONSE + end + + def successful_store_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-06-27T13:06:07.000-07:00", + "receiptColumns": 30, + "card": { + "type": "VS", + "number": "XXXXXXXXXXXX2224", + "securityCode": {}, + "token": { + "value": "22243v5f0vkezpej" + } + }, + "merchant": { + "mid": 8628968 + }, + "server": { + "name": "UTGAPI11CE" + }, + "universalToken": { + "value": "400010-2F1AA405-001AA4-000026B7-1766C44E9E8" + } + } + ] + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-05-02T02:19:38.000-07:00", + "receiptColumns": 30, + "amount": { + "total": 5 + }, + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX1111", + "present": "N", + "securityCode": {}, + "token": { + "value": "8042677003331111" + } + }, + "clerk": { + "numericId": 24 + }, + "customer": { + "addressLine1": "89 Main Street", + "firstName": "XYZ", + "lastName": "RON", + "postalCode": "89000" + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "mid": 8504672 + }, + "receipt": [ + { + "key": "MaskedPAN", + "printValue": "XXXXXXXXXXXX1111" + }, + { + "key": "CardEntryMode", + "printName": "ENTRY METHOD", + "printValue": "KEYED" + }, + { + "key": "SignatureRequired", + "printValue": "Y" + } + ], + "server": { + "name": "UTGAPI12CE" + }, + "transaction": { + "authorizationCode": "OK168Z", + "authSource": "E", + "invoice": "3333333309", + "purchaseCard": { + "customerReference": "457", + "destinationPostalCode": "89123", + "productDescriptors": [ + "Potential", + "Wrong" + ] + }, + "responseCode": "A", + "saleFlag": "S" + }, + "universalToken": { + "value": "444433-2D5C1A5C-001624-00001621-16BAAF4ACC6" + } + } + ] + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-05-08T01:18:22.000-07:00", + "receiptColumns": 30, + "amount": { + "total": 5 + }, + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX1111", + "present": "N", + "token": { + "value": "1111x19h4cryk231" + } + }, + "clerk": { + "numericId": 24 + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "mid": 8628968 + }, + "receipt": [ + { + "key": "MaskedPAN", + "printValue": "XXXXXXXXXXXX1111" + }, + { + "key": "CardEntryMode", + "printName": "ENTRY METHOD", + "printValue": "KEYED" + }, + { + "key": "SignatureRequired", + "printValue": "Y" + } + ], + "server": { + "name": "UTGAPI03CE" + }, + "transaction": { + "authorizationCode": "OK207Z", + "authSource": "E", + "invoice": "3333333309", + "responseCode": "A", + "saleFlag": "S" + }, + "universalToken": { + "value": "444433-2D5C1A5C-001624-00001621-16BAAF4ACC6" + } + } + ] + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-02-09T05:11:54.000-08:00", + "receiptColumns": 30, + "amount": { + "total": 5 + }, + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX1111", + "present": "N", + "securityCode": { + "result": "N", + "valid": "N" + }, + "token": { + "value": "8042714004661111" + } + }, + "clerk": { + "numericId": 16 + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "mid": 8585812, + "name": "RealtimePOS - Retail" + }, + "receipt": [ + { + "key": "MaskedPAN", + "printValue": "XXXXXXXXXXXX1111" + }, + { + "key": "CardEntryMode", + "printName": "ENTRY METHOD", + "printValue": "KEYED" + }, + { + "key": "SignatureRequired", + "printValue": "N" + } + ], + "server": { + "name": "UTGAPI12CE" + }, + "transaction": { + "authSource": "E", + "invoice": "4666309473", + "purchaseCard": { + "customerReference": "1234567", + "destinationPostalCode": "89123", + "productDescriptors": [ + "Test" + ] + }, + "responseCode": "A", + "saleFlag": "S" + }, + "universalToken": { + "value": "444433-2D5C1A5C-001624-00001621-16BAAF4ACC6" + } + } + ] + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-05-16T14:59:54.000-07:00", + "receiptColumns": 30, + "amount": { + "total": 5 + }, + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX2224", + "token": { + "value": "2224kz7vybyv1gs3" + } + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "mid": 8628968 + }, + "receipt": [ + { + "key": "TransactionResponse", + "printName": "Response", + "printValue": "SALE CORRECTION" + }, + { + "key": "MaskedPAN", + "printValue": "XXXXXXXXXXXX2224" + }, + { + "key": "CardEntryMode", + "printName": "ENTRY METHOD", + "printValue": "KEYED" + }, + { + "key": "SignatureRequired", + "printValue": "N" + } + ], + "server": { + "name": "UTGAPI07CE" + }, + "transaction": { + "authSource": "E", + "invoice": "0000000001", + "responseCode": "A", + "saleFlag": "S" + } + } + ] + } + RESPONSE + end + + def successful_verify_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-09-16T01:40:51.000-07:00", + "card": { + "type": "VS", + "entryMode": "M", + "number": "XXXXXXXXXXXX2224", + "present": "N", + "securityCode": { + "result": "M", + "valid": "Y" + }, + "token": { + "value": "2224xzsetmjksx13" + } + }, + "customer": { + "firstName": "John", + "lastName": "Smith" + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "merchant": { + "name": "Spreedly - ECom" + }, + "server": { + "name": "UTGAPI12CE" + }, + "transaction": { + "authorizationCode": "OK684Z", + "authSource": "E", + "responseCode": "A", + "saleFlag": "S" + }, + "universalToken": { + "value": "400010-2F1AA405-001AA4-000026B7-1766C44E9E8" + } + } + ] + } + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "GTV Msg: ERROR{0} 20018: no default category found, UC, Mod10=N TOKEN01CE ENGINE29CE", + "primaryCode": 9100, + "shortText": "SYSTEM ERROR" + }, + "server": { + "name": "UTGAPI12CE" + } + } + ] + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "Token contains invalid characters UTGAPI08CE", + "primaryCode": 9864, + "shortText": "Invalid Token" + }, + "server": { + "name": "UTGAPI08CE" + } + } + ] + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "INTERNET FAILURE: Timeout waiting for response across the Internet UTGAPI05CE", + "primaryCode": 9961, + "shortText": "INTERNET FAILURE" + }, + "server": { + "name": "UTGAPI05CE" + } + } + ] + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "record not posted ENGINE21CE", + "primaryCode": 9844, + "shortText": "I/O ERROR" + }, + "server": { + "name": "UTGAPI05CE" + } + } + ] + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "Invoice Not Found 00000000kl 0008628968 ENGINE29CE", + "primaryCode": 9815, + "shortText": "NO INV" + }, + "server": { + "name": "UTGAPI13CE" + } + } + ] + } + RESPONSE + end + + def successful_access_token_response + <<-RESPONSE + { + "result": [ + { + "dateTime": "2022-06-22T15:27:51.000-07:00", + "receiptColumns": 30, + "credential": { + "accessToken": "3F6A334E-01E5-4EDB-B4CE-0B1BEFC13518" + }, + "device": { + "capability": { + "magstripe": "Y", + "manualEntry": "Y" + } + }, + "server": { + "name": "UTGAPI09CE" + } + } + ] + } + RESPONSE + end + + def failed_auth_response + <<-RESPONSE + { + "result": [ + { + "error": { + "longText": "AuthToken not valid ENGINE22CE", + "primaryCode": 9862, + "secondaryCode": 4, + "shortText ": "AuthToken" + }, + "server": { + "name": "UTGAPI03CE" + } + } + ] + } + RESPONSE + end + + def failed_auth_response_no_message + <<-RESPONSE + { + "result": [ + { + "error": { + "secondaryCode": 4, + "shortText ": "AuthToken" + }, + "server": { + "name": "UTGAPI03CE" + } + } + ] + } + RESPONSE + end + + def sucess_auth_response + <<-RESPONSE + { + "result": [ + { + "credential": { + "accessToken": "abc123" + } + } + ] + } + RESPONSE + end +end diff --git a/test/unit/gateways/shift4_v2_test.rb b/test/unit/gateways/shift4_v2_test.rb new file mode 100644 index 00000000000..7bc4747aff3 --- /dev/null +++ b/test/unit/gateways/shift4_v2_test.rb @@ -0,0 +1,91 @@ +require 'test_helper' +require_relative 'securion_pay_test' + +class Shift4V2Test < SecurionPayTest + include CommStub + + def setup + super + @gateway = Shift4V2Gateway.new( + secret_key: 'pr_test_random_key' + ) + end + + def test_invalid_raw_response + @gateway.expects(:ssl_request).returns(invalid_json_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/^Invalid response received from the Shift4 V2 API/, response.message) + end + + def test_amount_gets_upcased_if_needed + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_equal 'USD', CGI.parse(data)['currency'].first + end.respond_with(successful_purchase_response) + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.shift4.com:443... + opened + starting SSL for api.shift4.com:443... + SSL established + <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic cHJfdGVzdF9xWk40VlZJS0N5U2ZDZVhDQm9ITzlEQmU6\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n" + <- "amount=2000¤cy=usd&card[number]=4242424242424242&card[expMonth]=9&card[expYear]=2016&card[cvc]=123&card[cardholderName]=Longbob+Longsen&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n" + -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n" + -> "\r\n" + -> "1f4\r\n" + reading 500 bytes... + -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}" + read 500 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.shift4.com:443... + opened + starting SSL for api.shift4.com:443... + SSL established + <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.shift4.com\r\nContent-Length: 214\r\n\r\n" + <- "amount=2000¤cy=usd&card[number]=[FILTERED]&card[expMonth]=[FILTERED]&card[expYear]=[FILTERED]&card[cvc]=[FILTERED]&card[cardholderName]=[FILTERED]&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n" + -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n" + -> "\r\n" + -> "1f4\r\n" + reading 500 bytes... + -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}" + read 500 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/simetrik_test.rb b/test/unit/gateways/simetrik_test.rb new file mode 100644 index 00000000000..f47a31203a9 --- /dev/null +++ b/test/unit/gateways/simetrik_test.rb @@ -0,0 +1,970 @@ +require 'test_helper' + +class SimetrikTest < Test::Unit::TestCase + def setup + @token_acquirer = 'ea890fd1-49f3-4a34-a150-192bf9a59205' + @datetime = Time.new.strftime('%Y-%m-%dT%H:%M:%S.%L%:z') + @gateway = SimetrikGateway.new( + client_id: 'client_id', + client_secret: 'client_secret_key', + audience: 'audience_url', + access_token: { expires_at: Time.new.to_i } + ) + @credit_card = CreditCard.new( + first_name: 'sergiod', + last_name: 'lobob', + number: '4551478422045511', + month: 12, + year: 2029, + verification_value: '111' + ) + @amount = 1000 + @trace_id = SecureRandom.uuid + @order_id = SecureRandom.uuid[0..7] + + @sub_merchant = { + address: 'string', + extra_params: {}, + mcc: 'string', + merchant_id: 'string', + name: 'string', + phone_number: 'string', + postal_code: 'string', + url: 'string' + } + + @authorize_capture_options = { + acquire_extra_options: {}, + trace_id: @trace_id, + user: { + id: '123', + email: 's@example.com' + }, + order: { + id: @order_id, + datetime_local_transaction: @datetime, + description: 'a popsicle', + installments: 1 + }, + currency: 'USD', + vat: 190, + three_ds_fields: { + version: '2.1.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA', + ds_transaction_id: '97267598-FAE6-48F2-8083-C23433990FBC', + acs_transaction_id: '13c701a3-5a88-4c45-89e9-ef65e50a8bf9', + xid: '00000000000000000501', + enrolled: 'string', + cavv_algorithm: '1', + directory_response_status: 'Y', + authentication_response_status: 'Y', + three_ds_server_trans_id: '24f701e3-9a85-4d45-89e9-af67e70d8fg8' + }, + sub_merchant: @sub_merchant, + token_acquirer: @token_acquirer + } + + @authorize_capture_expected_body = { + "forward_route": { + "trace_id": @trace_id, + "psp_extra_fields": {} + }, + "forward_payload": { + "user": { + "id": '123', + "email": 's@example.com' + }, + "order": { + "id": @order_id, + "description": 'a popsicle', + "installments": 1, + "datetime_local_transaction": @datetime, + "amount": { + "total_amount": 10.0, + "currency": 'USD', + "vat": 1.9 + } + }, + "payment_method": { + "card": { + "number": '4551478422045511', + "exp_month": 12, + "exp_year": 2029, + "security_code": '111', + "type": 'visa', + "holder_first_name": 'sergiod', + "holder_last_name": 'lobob' + } + }, + "authentication": { + "three_ds_fields": { + "version": '2.1.0', + "eci": '02', + "cavv": 'jJ81HADVRtXfCBATEp01CJUAAAA', + "ds_transaction_id": '97267598-FAE6-48F2-8083-C23433990FBC', + "acs_transaction_id": '13c701a3-5a88-4c45-89e9-ef65e50a8bf9', + "xid": '00000000000000000501', + "enrolled": 'string', + "cavv_algorithm": '1', + "directory_response_status": 'Y', + "authentication_response_status": 'Y', + "three_ds_server_trans_id": '24f701e3-9a85-4d45-89e9-af67e70d8fg8' + } + }, + "sub_merchant": { + "merchant_id": 'string', + "extra_params": {}, + "mcc": 'string', + "name": 'string', + "address": 'string', + "postal_code": 'string', + "url": 'string', + "phone_number": 'string' + }, + "acquire_extra_options": {} + } + }.to_json.to_s + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response_body) + + response = @gateway.purchase(@amount, @credit_card, @authorize_capture_options) + assert_success response + assert_instance_of Response, response + + assert_equal response.message, 'successful charge' + assert_equal response.error_code, nil, 'Should expected error code equal to nil ' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_success_purchase_with_billing_address + expected_body = JSON.parse(@authorize_capture_expected_body.dup) + expected_body['forward_payload']['payment_method']['card']['billing_address'] = address + + @gateway.expects(:ssl_request).returns(successful_purchase_response_body) + + options = @authorize_capture_options.clone() + options[:billing_address] = address + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_instance_of Response, response + + assert_equal response.message, 'successful charge' + assert_equal response.error_code, nil, 'Should expected error code equal to nil ' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_fetch_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, 401, 'Unauthorized')) + @gateway.send(:fetch_access_token) + end + + assert_match(/Failed with 401 Unauthorized/, error.message) + end + + def test_success_purchase_with_shipping_address + expected_body = JSON.parse(@authorize_capture_expected_body.dup) + expected_body['forward_payload']['order']['shipping_address'] = address + + @gateway.expects(:ssl_request).returns(successful_purchase_response_body) + + options = @authorize_capture_options.clone() + options[:shipping_address] = address + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_instance_of Response, response + + assert_equal response.message, 'successful charge' + assert_equal response.error_code, nil, 'Should expected error code equal to nil ' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response_body) + + response = @gateway.purchase(@amount, @credit_card, @authorize_capture_options) + assert_failure response + assert_instance_of Response, response + assert response.test? + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert_equal response.error_code, 'incorrect_number' + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response_body) + + response = @gateway.authorize(@amount, @credit_card, @authorize_capture_options) + assert_success response + assert_instance_of Response, response + assert_equal response.message, 'successful authorize' + assert_equal response.error_code, nil, 'Should expected error code equal to nil ' + assert_equal response.avs_result['code'], 'G' + assert_equal response.cvv_result['code'], 'P' + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response_body) + + response = @gateway.authorize(@amount, @credit_card, @authorize_capture_options) + assert_failure response + assert_instance_of Response, response + assert response.test? + + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert_equal response.error_code, 'incorrect_number' + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response_body) + + response = @gateway.capture(@amount, 'fdb52e6a0e794b039de097e815a982fd', { + vat: @authorize_capture_options[:vat], + currency: 'USD', + transaction_id: 'fdb52e6a0e794b039de097e815a982fd', + token_acquirer: @token_acquirer, + trace_id: @trace_id + }) + + assert_success response + assert_instance_of Response, response + assert response.test? + assert_equal 'successful capture', response.message + assert_equal response.message, 'successful capture' + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response_body) + + response = @gateway.capture(@amount, 'SI-226', { + vat: 190, + currency: 'USD', + token_acquirer: @token_acquirer, + trace_id: @trace_id + }) + + assert_failure response + assert_instance_of Response, response + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert_equal response.error_code, 'processing_error' + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response_body) + + response = @gateway.refund(@amount, 'SI-226', { + amount: { + currency: 'USD' + }, + token_acquirer: @token_acquirer, + comment: 'A Comment', + acquire_extra_options: { + ruc: 123 + }, + trace_id: @trace_id + }) + + assert_success response + assert_instance_of Response, response + assert response.test? + + assert_equal 'successful refund', response.message + assert_equal response.message, 'successful refund' + assert_equal response.error_code, nil, 'Should expected error code equal to nil' + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response_body) + response = @gateway.refund(@amount, 'SI-226', { + amount: { + currency: 'USD' + }, + token_acquirer: @token_acquirer, + comment: 'A Comment', + trace_id: @trace_id + }) + assert_failure response + assert_instance_of Response, response + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert_equal response.error_code, 'processing_error' + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response_body) + response = @gateway.void('a17f70f9-82de-4c47-8d9c-7743dac6a561', { + token_acquirer: @token_acquirer, + trace_id: @trace_id + + }) + + assert_success response + assert_instance_of Response, response + assert response.test? + assert_equal 'successful void', response.message + assert_equal response.message, 'successful void' + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response_body) + response = @gateway.void('a17f70f9-82de-4c47-8d9c-7743dac6a561', { + token_acquirer: @token_acquirer, + trace_id: @trace_id + }) + assert_failure response + assert_instance_of Response, response + assert_equal response.avs_result['code'], 'I' + assert_equal response.cvv_result['code'], 'P' + assert_equal response.error_code, 'processing_error' + assert response.test? + end + + # no assertion for client_secret because it is included in a private method, if there + # is refactoring of sign_access_token method ensure that it is properly scrubbed + def test_scrub + transcript = @gateway.scrub(pre_scrubbed()) + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_scrubbed('4551478422045511', transcript) + end + + private + + def pre_scrubbed + <<-PRESCRUBBED + opening connection to payments.sta.simetrik.com:443... + opened + starting SSL for payments.sta.simetrik.com:443... + SSL established + <- "POST /v1/bc4c0f26-a357-4294-9b9e-a90e6c868c6e/charge HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InYwZ0ZNM1QwY1BpZ3J2OTBrS1dEZSJ9. eyJpc3MiOiJodHRwczovL3RlbmFudC1wYXltZW50cy1kZXYudXMuYXV0aDAuY29tLyIsInN1YiI6IndOaEpCZHJLRGszdlRta1FNQVdpNXpXTjd5MjFhZE8zQGNsaWVudHMiL CJhdWQiOiJodHRwczovL3RlbmFudC1wYXltZW50cy1kZXYudXMuYXV0aDAuY29tL2FwaS92Mi8iLCJpYXQiOjE2NTExNjk1OTYsImV4cCI6MTY1MTI1NTk5NiwiYXpwIjoid0 5oSkJkcktEazN2VG1rUU1BV2k1eldON3kyMWFkTzMiLCJzY29wZSI6InJlYWQ6Y2xpZW50X2dyYW50cyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9. mAaWcAiq0t_UnXQGMv2uHcOfFoxclfPBU9Wa_Tmzmps3jIZnCggGxptAjaxn_Hj7Mteni4u9t7QVDUA6pQ1nVT4hfuFbFiC3CcvB8AKb6_PgIYLHuZ1i0VKyS6VtdB04_Sl8u kbBcnXXt2GrRps23OPwwBjdJOKzXhz0pLeiDeBA_0SkF6LXqmbMuFB5PPGC2hyQUOOlkrqBjXH8meIMfnBh4GooF3AnsuhDT3hSu8t0gpQAVmQatxqPQwce8WXkD6qnCM6Q81 LnZCBjfzyF2T_LN4q9GmFkcy3NGkEXXNC1UigPxqbgDmf448biCKiMv1NnXyMhfknxH_kR2f6QdQ\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: payments.sta.simetrik.com\r\nContent-Length: 720\r\n\r\n" + <- "{\"forward_route\":{\"trace_id\":\"ce4091cf-3656-4c78-b835-f9fcf2b2cb11\",\"psp_extra_fields\":{}},\"forward_payload\":{\"user\": {\"id\":\"123\",\"email\":\"s@example.com\"},\"order\":{\"id\":\"191885304068\",\"description\":\"apopsicle\",\"installments\":1, \"datetime_local_transaction\":\"2022-04-28T14:13:16.117-04:00\",\"amount\":{\"total_amount\":1.0,\"currency\":\"USD\",\"vat\":19.0}} ,\"payment_method\":{\"card\":{\"number\":\"4551708161768059\",\"exp_month\":7,\"exp_year\":2022,\"security_code\":\"111\", \"type\":\"visa\",\"holder_first_name\":\"Joe\",\"holder_last_name\":\"Doe\"}},\"sub_merchant\":{\"merchant_id\":\"400000008\", \"extra_params\":{},\"mcc\":\"5816\",\"name\":\"885.519.237\",\"address\":\"None\",\"postal_code\":\"None\",\"url\":\"string\", \"phone_number\":\"3434343\"},\"acquire_extra_options\":{}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Thu, 28 Apr 2022 18:13:34 GMT\r\n" + -> "Content-Type: text/plain; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Apigw-Requestid: RTbhkj7GoAMETbA=\r\n" + -> "Via: 1.1 reverse-proxy-02-797bd8c84-8jv96\r\n" + -> "access-control-allow-origin: *\r\n" + -> "VGS-Request-Id: 8cdb14abe5f9fb04b3d3a5690930a418\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "235\r\n" + reading 565 bytes... + read 565 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRESCRUBBED + end + + def post_scrubbed + <<-POSTSCRUBBED + opening connection to payments.sta.simetrik.com:443... + opened + starting SSL for payments.sta.simetrik.com:443... + SSL established + <- "POST /v1/bc4c0f26-a357-4294-9b9e-a90e6c868c6e/charge HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\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: payments.sta.simetrik.com\r\nContent-Length: 720\r\n\r\n" + <- "{\"forward_route\":{\"trace_id\":\"ce4091cf-3656-4c78-b835-f9fcf2b2cb11\",\"psp_extra_fields\":{}},\"forward_payload\":{\"user\": {\"id\":\"123\",\"email\":\"s@example.com\"},\"order\":{\"id\":\"191885304068\",\"description\":\"apopsicle\",\"installments\":1, \"datetime_local_transaction\":\"2022-04-28T14:13:16.117-04:00\",\"amount\":{\"total_amount\":1.0,\"currency\":\"USD\",\"vat\":19.0}} ,\"payment_method\":{\"card\":{\"number\":\"[FILTERED]\",\"exp_month\":7,\"exp_year\":2022,\"security_code\":\"[FILTERED]\", \"type\":\"visa\",\"holder_first_name\":\"Joe\",\"holder_last_name\":\"Doe\"}},\"sub_merchant\":{\"merchant_id\":\"400000008\", \"extra_params\":{},\"mcc\":\"5816\",\"name\":\"885.519.237\",\"address\":\"None\",\"postal_code\":\"None\",\"url\":\"string\", \"phone_number\":\"3434343\"},\"acquire_extra_options\":{}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Thu, 28 Apr 2022 18:13:34 GMT\r\n" + -> "Content-Type: text/plain; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Apigw-Requestid: RTbhkj7GoAMETbA=\r\n" + -> "Via: 1.1 reverse-proxy-02-797bd8c84-8jv96\r\n" + -> "access-control-allow-origin: *\r\n" + -> "VGS-Request-Id: 8cdb14abe5f9fb04b3d3a5690930a418\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "235\r\n" + reading 565 bytes... + read 565 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POSTSCRUBBED + end + + def successful_purchase_response_body + '{ + "code": "S001", + "message": "successful charge", + "acquirer_body": { + "dataMap": { + "ACTION_CODE": "000", + "MERCHANT": "400000008", + "STATUS": "Authorized", + "CARD": "455170******8059", + "INSTALLMENTS_INFO": "03000000000", + "QUOTA_NUMBER": "03", + "QUOTA_AMOUNT": "0.00", + "QUOTA_DEFERRED": "0" + }, + "order": { + "purchaseNumber": "56700001", + "amount": 1000.0, + "currency": "USD", + "authorizedAmount": 1000.0, + "authorizationCode": "105604", + "actionCode": "000", + "traceNumber": "75763", + "transactionId": "984220460014549", + "transactionDate": "220215105559" + }, + "fulfillment": { + "merchantId": "400000008", + "captureType": "manual", + "countable": false, + "signature": "6168ebd4-9798-477c-80d4-b80971820b51" + } + }, + "avs_result": "G", + "cvv_result": "P", + "simetrik_authorization_id": "a870eeca1b1c46b39b6fd76fde7c32b6", + "trace_id": "00866583c3c24a36b0270f1e38568c77" + }' + end + + def failed_purchase_response_body + '{ + "trace_id": 50300, + "code": "R101", + "message": "incorrect_number", + "simetrik_authorization_id": "S-1205", + "avs_result": "I", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def successful_authorize_response_body + '{ + "trace_id": 50300, + "code": "S001", + "message": "successful authorize", + "simetrik_authorization_id": "S-1205", + "avs_result": "G", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def failed_authorize_response_body + '{ + "trace_id": 50300, + "code": "R101", + "message": "incorrect_number", + "simetrik_authorization_id": "S-1205", + "avs_result": "I", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def successful_capture_response_body + '{"code": "S001", "message": "successful capture", "acquirer_body": {"dataMap": {"ACTION_CODE": "000", "AUTHORIZATION_CODE": "121742", "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", "ID_UNICO": "984221100087087", "MERCHANT": "400000008", "STATUS": "Confirmed", "TRACE_NUMBER": "134626", "ACTION_DESCRIPTION": "Aprobado y completado con exito"}, "order": {"purchaseNumber": "112226091072", "amount": 1.0, "currency": "USD", "authorizedAmount": 1.0, "authorizationCode": "121742", "actionCode": "000", "traceNumber": "134626", "transactionId": "984221100087087", "transactionDate": "220420121813"}}, "simetrik_authorization_id": "0ef9f1f07e304bd7969d8282d230f072", "trace_id": "5273214580359436090"}' + end + + def failed_capture_response_body + '{ + "trace_id": 50300, + "code": "R302", + "message": "processing_error", + "simetrik_authorization_id": "S-1205", + "avs_result": "I", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def successful_refund_response_body + '{ + "trace_id": 50300, + "code": "S001", + "message": "successful refund", + "simetrik_authorization_id": "S-1205", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def failed_refund_response_body + '{ + "trace_id": 50300, + "code": "R302", + "message": "processing_error", + "simetrik_authorization_id": "S-1205", + "avs_result": "I", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end + + def successful_void_response_body + '{ + "trace_id": 50300, + "code": "S001", + "message": "successful void", + "simetrik_authorization_id": "S-1205", + "acquirer_body": {} + }' + end + + def failed_void_response_body + '{ + "trace_id": 50300, + "code": "R302", + "message": "processing_error", + "simetrik_authorization_id": "S-1205", + "avs_result": "I", + "cvv_result": "P", + "acquirer_body": { + "header": { + "ecoreTransactionUUID": "8d2dfc73-ec1f-43af-aa0f-b2c123fd25ea", + "ecoreTransactionDate": 1603054418778, + "millis": 4752 + }, + "fulfillment": { + "channel": "web", + "merchantId": "341198210", + "terminalId": "1", + "captureType": "manual", + "countable": true, + "fastPayment": false, + "signature": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73" + }, + "order": { + "tokenId": "99E9BF92C69A4799A9BF92C69AF79979", + "purchaseNumber": "2020100901", + "amount": 10.5, + "installment": 2, + "currency": "PEN", + "authorizedAmount": 10.5, + "authorizationCode": "173424", + "actionCode": "000", + "traceNumber": "177159", + "transactionDate": "201010173430", + "transactionId": "993202840246052" + }, + "token": { + "tokenId": "7000010038706267", + "ownerId": "abc@mail.com", + "expireOn": "240702123548" + }, + "dataMap": { + "TERMINAL": "00000001", + "TRACE_NUMBER": "177159", + "ECI_DESCRIPTION": "Transaccion no autenticada pero enviada en canal seguro", + "SIGNATURE": "2e2cba40-a914-4e79-b4d3-8a2f2737eb73", + "CARD": "455170******8329", + "MERCHANT": "341198210", + "STATUS": "Authorized", + "INSTALLMENTS_INFO": "02000000000", + "ACTION_DESCRIPTION": "Aprobado y completado con exito", + "ID_UNICO": "993202840246052", + "AMOUNT": "10.50", + "QUOTA_NUMBER": "02", + "AUTHORIZATION_CODE": "173424", + "CURRENCY": "0604", + "TRANSACTION_DATE": "201010173430", + "ACTION_CODE": "000", + "CARD_TOKEN": "7000010038706267", + "ECI": "07", + "BRAND": "visa", + "ADQUIRENTE": "570002", + "QUOTA_AMOUNT": "0.00", + "PROCESS_CODE": "000000", + "VAULT_BLOCK": "abc@mail.com", + "TRANSACTION_ID": "993202840246052", + "QUOTA_DEFERRED": "0" + } + } + }' + end +end diff --git a/test/unit/gateways/skip_jack_test.rb b/test/unit/gateways/skip_jack_test.rb index 6df67c6a94e..362efffb520 100644 --- a/test/unit/gateways/skip_jack_test.rb +++ b/test/unit/gateways/skip_jack_test.rb @@ -1,38 +1,37 @@ require 'test_helper' class SkipJackTest < Test::Unit::TestCase - def setup Base.mode = :test - @gateway = SkipJackGateway.new(:login => 'X', :password => 'Y') + @gateway = SkipJackGateway.new(login: 'X', password: 'Y') @credit_card = credit_card('4242424242424242') @billing_address = { - :address1 => '123 Any St.', - :address2 => 'Apt. B', - :city => 'Anytown', - :state => 'ST', - :country => 'US', - :zip => '51511-1234', - :phone => '616-555-1212', - :fax => '616-555-2121' + address1: '123 Any St.', + address2: 'Apt. B', + city: 'Anytown', + state: 'ST', + country: 'US', + zip: '51511-1234', + phone: '616-555-1212', + fax: '616-555-2121' } @shipping_address = { - :name => 'Stew Packman', - :address1 => 'Company', - :address2 => '321 No RD', - :city => 'Nowhereton', - :state => 'ZC', - :country => 'MX', - :phone => '0123231212' + name: 'Stew Packman', + address1: 'Company', + address2: '321 No RD', + city: 'Nowhereton', + state: 'ZC', + country: 'MX', + phone: '0123231212' } @options = { - :order_id => 1, - :email => 'cody@example.com' + order_id: 1, + email: 'cody@example.com' } @amount = 100 @@ -103,10 +102,10 @@ def test_split_line end def test_turn_authorizeapi_response_into_hash - body = <<-EOS -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" -"000067","999888777666","1900","","N","Card authorized, exact address match with 5 digit zipcode.","1","000067","1","","","1","10138083786558.009","" - EOS + body = <<~RESPONSE + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" + "000067","999888777666","1900","","N","Card authorized, exact address match with 5 digit zipcode.","1","000067","1","","","1","10138083786558.009","" + RESPONSE map = @gateway.send(:authorize_response_map, body) @@ -194,7 +193,7 @@ def test_paymentech_authorization_failure end def test_serial_number_is_added_before_developer_serial_number_for_authorization - expected ="Year=#{Time.now.year + 1}&TransactionAmount=1.00&ShipToPhone=&SerialNumber=X&SJName=Longbob+Longsen&OrderString=1~None~0.00~0~N~%7C%7C&OrderNumber=1&OrderDescription=&Month=9&InvoiceNumber=&Email=cody%40example.com&DeveloperSerialNumber=Y&CustomerCode=&CVV2=123&AccountNumber=4242424242424242" + expected = "Year=#{Time.now.year + 1}&TransactionAmount=1.00&ShipToPhone=&SerialNumber=X&SJName=Longbob+Longsen&OrderString=1~None~0.00~0~N~%7C%7C&OrderNumber=1&OrderDescription=&Month=9&InvoiceNumber=&Email=cody%40example.com&DeveloperSerialNumber=Y&CustomerCode=&CVV2=123&AccountNumber=4242424242424242" expected = expected.gsub('~', '%7E') if RUBY_VERSION < '2.5.0' @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI', expected).returns(successful_authorization_response) @@ -215,7 +214,7 @@ def test_successful_partial_capture response = @gateway.authorize(@amount, @credit_card, @options) @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', "szTransactionId=#{response.authorization}&szSerialNumber=X&szForceSettlement=0&szDeveloperSerialNumber=Y&szDesiredStatus=SETTLE&szAmount=1.00").returns(successful_capture_response) - response = @gateway.capture(@amount/2, response.authorization) + response = @gateway.capture(@amount / 2, response.authorization) assert_equal '1.0000', response.params['TransactionAmount'] end @@ -226,8 +225,8 @@ def test_dont_send_blank_state @options[:shipping_address] = @shipping_address @gateway.expects(:ssl_post).with do |url, params| url == 'https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI' && - CGI.parse(params)['State'].first == 'XX' && - CGI.parse(params)['ShipToState'].first == 'XX' + CGI.parse(params)['State'].first == 'XX' && + CGI.parse(params)['ShipToState'].first == 'XX' end.returns(successful_authorization_response) @gateway.authorize(@amount, @credit_card, @options) @@ -236,43 +235,43 @@ def test_dont_send_blank_state private def successful_authorization_response - <<-CSV -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" -"TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" + <<~CSV + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" + "TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" CSV end def successful_capture_response - <<-CSV -"000386891209","0","1","","","","","","","","","" -"000386891209","1.0000","SETTLE","SUCCESSFUL","Valid","618844630c5fad658e95abfd5e1d4e22","9802853156029.022" + <<~CSV + "000386891209","0","1","","","","","","","","","" + "000386891209","1.0000","SETTLE","SUCCESSFUL","Valid","618844630c5fad658e95abfd5e1d4e22","9802853156029.022" CSV end def successful_refund_response - <<-CSV -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" -"TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" + <<~CSV + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" + "TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" CSV end def unsuccessful_authorization_response - <<-CSV -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode"\r\n"EMPTY","000386891209","100","","","","b1eec256d0182f29375e0cbae685092d","","0","","","-35","","" + <<~CSV + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode"\r\n"EMPTY","000386891209","100","","","","b1eec256d0182f29375e0cbae685092d","","0","","","-35","","" CSV end def unsuccessful_paymentech_authorization_response - <<-CSV -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", -"EMPTY","000000000000","1.00","","","","43985b7953199d1f02c3017f948e9f13","","0","","","-83","","", + <<~CSV + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", + "EMPTY","000000000000","1.00","","","","43985b7953199d1f02c3017f948e9f13","","0","","","-83","","", CSV end def successful_paymentech_authorization_response - <<-CSV -"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", -"093223","000000000000","1.00","","Y","Card authorized, exact address match with 5 digit zip code.","5ac0f04e737baea5a5370037afe827f6","093223","1","M","Match","1","40000024585892.109","", + <<~CSV + "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", + "093223","000000000000","1.00","","Y","Card authorized, exact address match with 5 digit zip code.","5ac0f04e737baea5a5370037afe827f6","093223","1","M","Match","1","40000024585892.109","", CSV end end diff --git a/test/unit/gateways/so_easy_pay_test.rb b/test/unit/gateways/so_easy_pay_test.rb index 94a7f913a80..ae4c9579feb 100644 --- a/test/unit/gateways/so_easy_pay_test.rb +++ b/test/unit/gateways/so_easy_pay_test.rb @@ -3,17 +3,17 @@ class SoEasyPayTest < Test::Unit::TestCase def setup @gateway = SoEasyPayGateway.new( - :login => 'login', - :password => 'password' - ) + login: 'login', + password: 'password' + ) @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end @@ -87,7 +87,7 @@ def test_do_not_depend_on_expiry_date_class def test_use_ducktyping_for_credit_card @gateway.expects(:ssl_post).returns(successful_purchase_response) - credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => 'Hans Tester', :year => 2012, :month => 1) + credit_card = stub(number: '4242424242424242', verification_value: '123', name: 'Hans Tester', year: 2012, month: 1) assert_nothing_raised do assert_success @gateway.purchase(@amount, credit_card, @options) @@ -220,5 +220,4 @@ def failed_credit_response
) end - end diff --git a/test/unit/gateways/spreedly_core_test.rb b/test/unit/gateways/spreedly_core_test.rb index afc5d4dd961..2b2b0b1de49 100644 --- a/test/unit/gateways/spreedly_core_test.rb +++ b/test/unit/gateways/spreedly_core_test.rb @@ -1,12 +1,12 @@ require 'test_helper' class SpreedlyCoreTest < Test::Unit::TestCase - def setup - @gateway = SpreedlyCoreGateway.new(:login => 'api_login', :password => 'api_secret', :gateway_token => 'token') + @gateway = SpreedlyCoreGateway.new(login: 'api_login', password: 'api_secret', gateway_token: 'token') @payment_method_token = 'E3eQGR3E0xiosj7FOJRtIKbF8Ch' @credit_card = credit_card + @check = check @amount = 103 @existing_transaction = 'LKA3RchoqYO0njAfhHVw60ohjrC' @not_found_transaction = 'AdyQXaG0SVpSoMPdmFlvd3aA3uz' @@ -41,7 +41,7 @@ def test_failed_purchase_with_payment_method_token end def test_successful_purchase_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_purchase_response) + @gateway.stubs(:raw_ssl_request).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) assert_success response @@ -57,6 +57,21 @@ def test_successful_purchase_with_credit_card assert_equal 'used', response.params['payment_method_storage_state'] end + def test_successful_purchase_with_check + @gateway.stubs(:raw_ssl_request).returns(successful_check_purchase_response) + response = @gateway.purchase(@amount, @check) + + assert_success response + assert !response.test? + + assert_equal 'ZwnfZs3Qy4gRDPWXHopamNuarCJ', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + assert_equal 'HtCrYfW17wEzWWfrMbwDX4TwPVW', response.params['payment_method_token'] + assert_equal '021*', response.params['payment_method_routing_number'] + assert_equal '*3210', response.params['payment_method_account_number'] + end + def test_failed_purchase_with_invalid_credit_card @gateway.expects(:raw_ssl_request).returns(failed_store_response) response = @gateway.purchase(@amount, @credit_card) @@ -65,7 +80,7 @@ def test_failed_purchase_with_invalid_credit_card end def test_failed_purchase_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_purchase_response) + @gateway.stubs(:raw_ssl_request).returns(failed_purchase_response) response = @gateway.purchase(@amount, @credit_card) assert_failure response @@ -121,7 +136,7 @@ def test_failed_authorize_with_token end def test_successful_authorize_with_credit_card_and_capture - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_authorize_response) + @gateway.stubs(:raw_ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card) assert_success response @@ -144,7 +159,7 @@ def test_successful_authorize_with_credit_card_and_capture end def test_failed_authorize_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_authorize_response) + @gateway.stubs(:raw_ssl_request).returns(failed_authorize_response) response = @gateway.authorize(@amount, @credit_card) assert_failure response assert_equal 'This transaction cannot be processed.', response.message @@ -279,6 +294,21 @@ def test_failed_find assert_match %r(#{@not_found_transaction}), response.message end + def test_gateway_specific_response_fileds_returned_correctly + @gateway.expects(:raw_ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @payment_method_token) + assert_success response + + assert_not_empty response.params['gateway_specific_response_fields'] + assert_includes response.params['gateway_specific_response_fields'].keys, 'migs' + + migs_response_fields = response.params.dig('gateway_specific_response_fields', 'migs') + assert_equal migs_response_fields['batch_no'], '20122018' + assert_equal migs_response_fields['receipt_no'], 'rxI320t' + assert_equal migs_response_fields['authorize_id'], '800385' + end + def test_scrubbing assert @gateway.supports_scrubbing? assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed @@ -317,6 +347,13 @@ def successful_purchase_response 2012-12-06T20:28:14Z 2012-12-06T20:28:14Z + + + 20122018 + 800385 + rxI320t + + 5WxC03VQ0LmmkYvIHl7XsPKIpUb 2012-12-06T20:20:29Z @@ -351,6 +388,97 @@ def successful_purchase_response XML end + def successful_check_purchase_response + MockResponse.succeeded <<-XML + + false + 2019-01-06T18:24:33Z + 2019-01-06T18:24:33Z + true + succeeded + ZwnfZs3Qy4gRDPWXHopamNuarCJ + Purchase + + + + + + + + + + 49 + 0 + 100 + USD + false + true + Succeeded! + 3gLeg4726V5P0HK7cq7QzHsL0a6 + test + + + + + + + + + + + + true + Successful purchase + + + + + false + false + + + false + + 2019-01-06T18:24:33Z + 2019-01-06T18:24:33Z + + + + + HtCrYfW17wEzWWfrMbwDX4TwPVW + 2019-01-06T18:24:33Z + 2019-01-06T18:24:33Z + + + cached + true + + Jim Smith + + checking + personal + 021 + 3210 + Jim + Smith + + + + + + + + + bank_account + + + 021* + *3210 + + + XML + end + def failed_purchase_response MockResponse.failed <<-XML @@ -843,7 +971,7 @@ def successful_unstore_response end def pre_scrubbed - <<-EOS + <<-REQUEST opening connection to core.spreedly.com:443... opened starting SSL for core.spreedly.com:443... @@ -868,11 +996,11 @@ def pre_scrubbed -> "\n NRBpydUCWn658GHV8h2kVlUzB0i\n 2018-03-10T22:04:06Z\n 2018-03-10T22:04:06Z\n true\n AddPaymentMethod\n false\n succeeded\n Succeeded!\n \n Wd25UIrH1uopTkZZ4UDdb5XmSDd\n 2018-03-10T22:04:06Z\n 2018-03-10T22:04:06Z\n \n \n cached\n true\n 4444\n 555555\n master\n Longbob\n Longsen\n 9\n 2019\n \n \n \n \n \n \n \n \n Longbob Longsen\n true\n \n \n \n \n \n \n \n credit_card\n \n \n XXX\n XXXX-XXXX-XXXX-4444\n 125370bb396dff6fed4f581f85a91a9e5317\n \n\n" read 1875 bytes Conn close - EOS + REQUEST end def post_scrubbed - <<-EOS + <<-REQUEST opening connection to core.spreedly.com:443... opened starting SSL for core.spreedly.com:443... @@ -897,7 +1025,7 @@ def post_scrubbed -> "\n NRBpydUCWn658GHV8h2kVlUzB0i\n 2018-03-10T22:04:06Z\n 2018-03-10T22:04:06Z\n true\n AddPaymentMethod\n false\n succeeded\n Succeeded!\n \n Wd25UIrH1uopTkZZ4UDdb5XmSDd\n 2018-03-10T22:04:06Z\n 2018-03-10T22:04:06Z\n \n \n cached\n true\n 4444\n 555555\n master\n Longbob\n Longsen\n 9\n 2019\n \n \n \n \n \n \n \n \n Longbob Longsen\n true\n \n \n \n \n \n \n \n credit_card\n \n \n [FILTERED]\n [FILTERED]\n 125370bb396dff6fed4f581f85a91a9e5317\n \n\n" read 1875 bytes Conn close - EOS + REQUEST end def successful_verify_response diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb new file mode 100644 index 00000000000..175b6d16211 --- /dev/null +++ b/test/unit/gateways/stripe_payment_intents_test.rb @@ -0,0 +1,2080 @@ +require 'test_helper' + +class StripePaymentIntentsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = StripePaymentIntentsGateway.new(login: 'sk_test_login') + + @credit_card = credit_card() + @threeds_2_card = credit_card('4000000000003220') + @visa_token = 'pm_card_visa' + + @three_ds_authentication_required_setup_for_off_session = 'pm_card_authenticationRequiredSetupForOffSession' + @three_ds_off_session_credit_card = credit_card( + '4000002500003155', + verification_value: '737', + month: 10, + year: 2022 + ) + + @amount = 2020 + @update_amount = 2050 + + @options = { + currency: 'GBP', + confirmation_method: 'manual' + } + + @google_pay = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :google_pay, + brand: 'visa', + eci: '05', + month: '09', + year: '2030' + ) + + @apple_pay = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :apple_pay, + brand: 'visa', + eci: '05', + month: '09', + year: '2030', + first_name: 'Longbob', + last_name: 'Longsen' + ) + + @network_token_credit_card = network_tokenization_credit_card( + '4000056655665556', + verification_value: '123', + payment_cryptogram: 'dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==', + source: :network_token, + brand: 'visa', + month: '09', + year: '2030', + first_name: 'Longbob', + last_name: 'Longsen' + ) + end + + def test_successful_create_and_confirm_intent + @gateway.expects(:ssl_request).times(3).returns(successful_create_3ds2_payment_method, successful_create_3ds2_intent_response, successful_confirm_3ds2_intent_response) + + assert create = @gateway.create_intent(@amount, @threeds_2_card, @options.merge(return_url: 'https://www.example.com', capture_method: 'manual')) + assert_instance_of MultiResponse, create + assert_success create + + assert_equal 'pi_1F1wpFAWOtgoysog8nTulYGk', create.authorization + assert_equal 'requires_confirmation', create.params['status'] + assert create.test? + + assert confirm = @gateway.confirm_intent(create.params['id'], nil, @options.merge(return_url: 'https://example.com/return-to-me', payment_method_types: 'card')) + assert_equal 'redirect_to_url', confirm.params.dig('next_action', 'type') + assert_equal 'card', confirm.params.dig('payment_method_types')[0] + end + + def test_successful_create_and_capture_intent + options = @options.merge(capture_method: 'manual', confirm: true) + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_capture_response) + assert create = @gateway.create_intent(@amount, @visa_token, options) + assert_success create + assert_equal 'requires_capture', create.params['status'] + + assert capture = @gateway.capture(@amount, create.params['id'], options) + assert_success capture + assert_equal 'succeeded', capture.params['status'] + assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_successful_create_and_capture_intent_with_network_token + options = @options.merge(capture_method: 'manual', confirm: true) + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_manual_capture_response_with_network_token_fields, successful_manual_capture_of_payment_intent_response_with_network_token_fields) + assert create = @gateway.create_intent(@amount, @network_token_credit_card, options) + assert_success create + assert_equal 'requires_capture', create.params['status'] + + assert capture = @gateway.capture(@amount, create.params['id'], options) + assert_success capture + assert_equal 'succeeded', capture.params['status'] + assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_successful_create_and_update_intent + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_update_intent_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual')) + + assert update = @gateway.update_intent(@update_amount, create.params['id'], nil, @options.merge(capture_method: 'manual')) + assert_equal @update_amount, update.params['amount'] + assert_equal 'requires_confirmation', update.params['status'] + end + + def test_contains_statement_descriptor_suffix + options = @options.merge(capture_method: 'manual', statement_descriptor_suffix: 'suffix') + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/statement_descriptor_suffix=suffix/, data) + end.respond_with(successful_create_intent_response) + end + + def test_successful_create_and_void_intent + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_void_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual', confirm: true)) + + assert cancel = @gateway.void(create.params['id']) + assert_equal @amount, cancel.params.dig('charges', 'data')[0].dig('amount_refunded') + assert_equal 'canceled', cancel.params['status'] + end + + def test_create_intent_with_optional_idempotency_key_header + idempotency_key = 'test123' + options = @options.merge(idempotency_key: idempotency_key) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, _data, headers| + assert_equal idempotency_key, headers['Idempotency-Key'] + end.respond_with(successful_create_intent_response) + end + + def test_request_three_d_secure + request_three_d_secure = 'any' + options = @options.merge(request_three_d_secure: request_three_d_secure) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/\[request_three_d_secure\]=any/, data) + end.respond_with(successful_request_three_d_secure_response) + + request_three_d_secure = 'automatic' + options = @options.merge(request_three_d_secure: request_three_d_secure) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/\[request_three_d_secure\]=automatic/, data) + end.respond_with(successful_request_three_d_secure_response) + + request_three_d_secure = true + options = @options.merge(request_three_d_secure: request_three_d_secure) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + refute_match(/\[request_three_d_secure\]/, data) + end.respond_with(successful_request_three_d_secure_response) + end + + def test_external_three_d_secure_auth_data + options = @options.merge( + three_d_secure: { + eci: '05', + cavv: '4BQwsg4yuKt0S1LI1nDZTcO9vUM=', + xid: 'd+NEBKSpEMauwleRhdrDY06qj4A=' + } + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/payment_method_options\[card\]\[three_d_secure\]/, data) + assert_match(/three_d_secure\]\[version\]=1.0.2/, data) + assert_match(/three_d_secure\]\[electronic_commerce_indicator\]=05/, data) + assert_match(/three_d_secure\]\[cryptogram\]=4BQwsg4yuKt0S1LI1nDZTcO9vUM%3D/, data) + assert_match(/three_d_secure\]\[transaction_id\]=d%2BNEBKSpEMauwleRhdrDY06qj4A%3D/, data) + end.respond_with(successful_request_three_d_secure_response) + + options = @options.merge( + three_d_secure: { + version: '2.1.0', + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + ds_transaction_id: 'f879ea1c-aa2c-4441-806d-e30406466d79' + } + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/payment_method_options\[card\]\[three_d_secure\]/, data) + assert_match(/three_d_secure\]\[version\]=2.1.0/, data) + assert_match(/three_d_secure\]\[electronic_commerce_indicator\]=02/, data) + assert_match(/three_d_secure\]\[cryptogram\]=jJ81HADVRtXfCBATEp01CJUAAAA%3D/, data) + assert_match(/three_d_secure\]\[transaction_id\]=f879ea1c-aa2c-4441-806d-e30406466d79/, data) + end.respond_with(successful_request_three_d_secure_response) + end + + def test_failed_capture_after_creation + @gateway.expects(:ssl_request).returns(failed_capture_response) + + assert create = @gateway.create_intent(@amount, 'pm_card_chargeDeclined', @options.merge(confirm: true)) + assert_equal 'requires_payment_method', create.params.dig('error', 'payment_intent', 'status') + assert_equal false, create.params.dig('error', 'payment_intent', 'charges', 'data')[0].dig('captured') + end + + def test_failed_void_after_capture + @gateway.expects(:ssl_request).twice.returns(successful_capture_response, failed_cancel_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(confirm: true)) + assert_equal 'succeeded', create.params['status'] + intent_id = create.params['id'] + + assert cancel = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal 'You cannot cancel this PaymentIntent because ' \ + 'it has a status of succeeded. Only a PaymentIntent with ' \ + 'one of the following statuses may be canceled: ' \ + 'requires_payment_method, requires_capture, requires_confirmation, requires_action.', cancel.message + end + + def test_connected_account + destination = 'account_27701' + amount = 8000 + on_behalf_of = 'account_27704' + transfer_group = 'TG1000' + application_fee_amount = 100 + + options = @options.merge( + transfer_destination: destination, + transfer_amount: amount, + on_behalf_of: on_behalf_of, + transfer_group: transfer_group, + application_fee: application_fee_amount + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/transfer_data\[destination\]=#{destination}/, data) + assert_match(/transfer_data\[amount\]=#{amount}/, data) + assert_match(/on_behalf_of=#{on_behalf_of}/, data) + assert_match(/transfer_group=#{transfer_group}/, data) + assert_match(/application_fee_amount=#{application_fee_amount}/, data) + end.respond_with(successful_create_intent_response) + end + + def test_on_behalf_of + on_behalf_of = 'account_27704' + + options = @options.merge( + on_behalf_of: on_behalf_of + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.create_intent(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(/transfer_data\[destination\]/, data) + assert_no_match(/transfer_data\[amount\]/, data) + assert_match(/on_behalf_of=#{on_behalf_of}/, data) + assert_no_match(/transfer_group/, data) + assert_no_match(/application_fee_amount/, data) + end.respond_with(successful_create_intent_response) + end + + def test_failed_payment_methods_post + @gateway.expects(:ssl_request).returns(failed_payment_method_response) + + assert create = @gateway.create_intent(@amount, 'pm_failed', @options) + assert_equal 'validation_error', create.params.dig('error', 'code') + assert_equal 'You must verify a phone number on your Stripe account before you can send raw credit card numbers to the Stripe API. You can avoid this requirement by using Stripe.js, the Stripe mobile bindings, or Stripe Checkout. For more information, see https://dashboard.stripe.com/phone-verification.', create.params.dig('error', 'message') + assert_equal 'invalid_request_error', create.params.dig('error', 'type') + end + + def test_invalid_test_login_for_sk_key + gateway = StripePaymentIntentsGateway.new(login: 'sk_live_3422') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Invalid API Key provided', response.message + end + + def test_invalid_test_login_for_rk_key + gateway = StripePaymentIntentsGateway.new(login: 'rk_live_3422') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Invalid API Key provided', response.message + end + + def test_successful_purchase + gateway = StripePaymentIntentsGateway.new(login: '3422e230423s') + + stub_comms(gateway, :ssl_request) do + gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_create_intent_response) + end + + def test_failed_error_on_requires_action + @gateway.expects(:ssl_request).returns(failed_with_set_error_on_requires_action_response) + + assert create = @gateway.create_intent(@amount, 'pm_failed', @options) + assert_equal 'This payment required an authentication action to complete, but `error_on_requires_action` was set. When you\'re ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.', create.params.dig('error', 'message') + assert_equal 'card_error', create.params.dig('error', 'type') + end + + def test_failed_refund_due_to_service_unavailability + @gateway.expects(:ssl_request).returns(failed_service_response) + + assert refund = @gateway.refund(@amount, 'pi_123') + assert_failure refund + assert_match(/Error while communicating with one of our backends/, refund.params.dig('error', 'message')) + end + + def test_failed_refund_due_to_pending_3ds_auth + @gateway.expects(:ssl_request).returns(successful_confirm_3ds2_intent_response) + + assert refund = @gateway.refund(@amount, 'pi_123') + assert_failure refund + assert_equal 'requires_action', refund.params['status'] + assert_match(/payment_intent has a status of requires_action/, refund.message) + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_verify_response) + assert verify = @gateway.verify(@visa_token) + assert_success verify + assert_equal 'succeeded', verify.params['status'] + end + + def test_successful_purchase_with_level3_data + @options[:merchant_reference] = 123 + @options[:customer_reference] = 456 + @options[:shipping_address_zip] = 98765 + @options[:shipping_from_zip] = 54321 + @options[:shipping_amount] = 40 + @options[:line_items] = [ + { + 'product_code' => 1234, + 'product_description' => 'An item', + 'unit_cost' => 60, + 'quantity' => 7, + 'tax_amount' => 0 + }, + { + 'product_code' => 999, + 'tax_amount' => 888 + } + ] + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, @options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('level3[merchant_reference]=123', data) + assert_match('level3[customer_reference]=456', data) + assert_match('level3[shipping_address_zip]=98765', data) + assert_match('level3[shipping_amount]=40', data) + assert_match('level3[shipping_from_zip]=54321', data) + assert_match('level3[line_items][0][product_description]=An+item', data) + assert_match('level3[line_items][1][product_code]=999', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_stored_credentials_without_sending_ntid + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + network_transaction_id = '1098510912210968' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + stored_credential_transaction_type: true, + stored_credential: { + initiator: 'cardholder', + reason_type: 'installment', + initial_transaction: true, + network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/ + ds_transaction_id: 'null' # this is optional and can be null if not available. + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=}, data) + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_succesful_purchase_with_ntid_when_off_session + # don't send NTID if setup_future_usage == off_session + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + network_transaction_id = '1098510912210968' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + setup_future_usage: 'off_session', + stored_credential: { + initiator: 'cardholder', + reason_type: 'installment', + initial_transaction: true, + network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/ + ds_transaction_id: 'null' # this is optional and can be null if not available. + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=}, data) + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_succesful_purchase_with_stored_credentials + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + network_transaction_id = '1098510912210968' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + stored_credential: { + network_transaction_id: network_transaction_id, # TEST env seems happy with any value :/ + ds_transaction_id: 'null' # this is optional and can be null if not available. + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=#{network_transaction_id}}, data) + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_succesful_purchase_with_stored_credentials_without_optional_ds_transaction_id + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + network_transaction_id = '1098510912210968' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + stored_credential: { + network_transaction_id: network_transaction_id # TEST env seems happy with any value :/ + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=#{network_transaction_id}}, data) + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_succesful_purchase_without_stored_credentials_introduces_no_exemption_fields + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD' + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=}, data) + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[ds_transaction_id\]=null}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_sends_network_transaction_id_separate_from_stored_creds + network_transaction_id = '1098510912210968' + options = @options.merge( + network_transaction_id: network_transaction_id + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=#{network_transaction_id}}, data) + end.respond_with(successful_create_intent_response) + end + + def test_sends_expand_balance_transaction + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('expand[0]=charges.data.balance_transaction', data) + end.respond_with(successful_create_intent_response) + end + + def test_purchase_with_google_pay + options = { + currency: 'GBP' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @google_pay, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match('card[tokenization_method]=android_pay', data) if %r{/tokens}.match?(endpoint) + assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint) + end.respond_with(successful_create_intent_response) + end + + def test_purchase_with_google_pay_with_billing_address + options = { + currency: 'GBP', + billing_address: address + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @google_pay, options) + end.check_request do |_method, endpoint, data, _headers| + if %r{/tokens}.match?(endpoint) + assert_match('card[name]=Jim+Smith', data) + assert_match('card[tokenization_method]=android_pay', data) + assert_match('card[address_line1]=456+My+Street', data) + end + assert_match('wallet[type]=google_pay', data) if %r{/wallet}.match?(endpoint) + assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint) + end.respond_with(successful_create_intent_response_with_google_pay_and_billing_address) + end + + def test_purchase_with_network_token_card + options = { + currency: 'USD', + last_4: '4242' + } + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @network_token_credit_card, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match(%r{/payment_intents}, endpoint) + assert_match('confirm=true', data) + assert_match('payment_method_data[type]=card', data) + assert_match('[card][exp_month]=9', data) + assert_match('[card][exp_year]=2030', data) + assert_match('[card][last4]=4242', data) + assert_match('[card][network_token][number]=4000056655665556', data) + assert_match("[card][network_token][cryptogram]=#{URI.encode_www_form_component('dGVzdGNyeXB0b2dyYW1YWFhYWFhYWFhYWFg9PQ==')}", data) + assert_match('[card][network_token][exp_month]=9', data) + assert_match('[card][network_token][exp_year]=2030', data) + end.respond_with(successful_create_intent_response_with_network_token_fields) + end + + def test_purchase_with_shipping_options + options = { + currency: 'GBP', + customer: 'abc123', + shipping_address: { + name: 'John Adam', + phone_number: '+0018313818368', + city: 'San Diego', + country: 'USA', + address1: 'block C', + address2: 'street 48', + zip: '22400', + state: 'California', + email: 'test@email.com' + } + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('shipping[address][city]=San+Diego', data) + assert_match('shipping[address][country]=USA', data) + assert_match('shipping[address][line1]=block+C', data) + assert_match('shipping[address][line2]=street+48', data) + assert_match('shipping[address][postal_code]=22400', data) + assert_match('shipping[address][state]=California', data) + assert_match('shipping[name]=John+Adam', data) + assert_match('shipping[phone]=%2B0018313818368', data) + assert_no_match(/shipping[email]/, data) + end.respond_with(successful_create_intent_response) + end + + def test_purchase_with_shipping_carrier_and_tracking_number + options = { + currency: 'GBP', + shipping_address: { + name: 'John Adam', + address1: 'block C' + }, + shipping_tracking_number: 'TXNABC123', + shipping_carrier: 'FEDEX' + } + options[:customer] = @customer if defined?(@customer) + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('shipping[address][line1]=block+C', data) + assert_match('shipping[name]=John+Adam', data) + assert_match('shipping[carrier]=FEDEX', data) + assert_match('shipping[tracking_number]=TXNABC123', data) + end.respond_with(successful_create_intent_response) + end + + def test_authorize_with_apple_pay + options = { + currency: 'GBP' + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @apple_pay, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match('card[tokenization_method]=apple_pay', data) if %r{/tokens}.match?(endpoint) + assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint) + end.respond_with(successful_create_intent_response) + end + + def test_authorize_with_apple_pay_with_billing_address + options = { + currency: 'GBP', + billing_address: address + } + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @apple_pay, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match('card[tokenization_method]=apple_pay', data) if %r{/tokens}.match?(endpoint) + assert_match('card[address_line1]=456+My+Street', data) if %r{/tokens}.match?(endpoint) + assert_match('payment_method=pi_', data) if %r{/payment_intents}.match?(endpoint) + end.respond_with(successful_create_intent_response_with_apple_pay_and_billing_address) + end + + def test_stored_credentials_does_not_override_ntid_field + network_transaction_id = '1098510912210968' + sc_network_transaction_id = '1078784111114777' + options = @options.merge( + network_transaction_id: network_transaction_id, + stored_credential: { + network_transaction_id: sc_network_transaction_id, + ds_transaction_id: 'null' + } + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[network_transaction_id\]=#{network_transaction_id}}, data) + end.respond_with(successful_create_intent_response) + end + + def test_successful_off_session_intent_creation_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| + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + claim_without_transaction_id: true + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(%r{payment_method_options\[card\]\[mit_exemption\]\[claim_without_transaction_id\]=true}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_successful_off_session_intent_creation_when_claim_without_transaction_id_is_false + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true, + claim_without_transaction_id: false + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[claim_without_transaction_id\]}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_successful_off_session_intent_creation_without_claim_without_transaction_id + [@three_ds_off_session_credit_card, @three_ds_authentication_required_setup_for_off_session].each do |card_to_use| + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, card_to_use, { + currency: 'USD', + execute_threed: true, + confirm: true, + off_session: true + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_no_match(%r{payment_method_options\[card\]\[mit_exemption\]\[claim_without_transaction_id\]}, data) + end.respond_with(successful_create_intent_response) + end + end + + def test_store_does_not_pass_validation_to_attach_by_default + stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card) + end.check_request do |_method, endpoint, data, _headers| + assert_no_match(/validate=/, data) if /attach/.match?(endpoint) + end.respond_with(successful_payment_method_response, successful_create_customer_response, successful_payment_method_attach_response) + end + + def test_store_sets_validation_on_attach_to_false_when_false_in_options + options = @options.merge( + validate: false + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/validate=false/, data) if /attach/.match?(endpoint) + end.respond_with(successful_payment_method_response, successful_create_customer_response, successful_payment_method_attach_response) + end + + def test_store_sets_validationon_attach_to_true_when_true_in_options + options = @options.merge( + validate: true + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, options) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/validate=true/, data) if /attach/.match?(endpoint) + end.respond_with(successful_payment_method_response, successful_create_customer_response, successful_payment_method_attach_response) + end + + def test_succesful_purchase_with_radar_session + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { + radar_session_id: 'test_radar_session_id' + }) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/radar_options\[session\]=test_radar_session_id/, data) if /payment_intents/.match?(endpoint) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_authorize_with_radar_session + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, { + radar_session_id: 'test_radar_session_id' + }) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/radar_options\[session\]=test_radar_session_id/, data) if /payment_intents/.match?(endpoint) + end.respond_with(successful_create_intent_response) + end + + def test_successful_authorize_with_skip_radar_rules + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, { + skip_radar_rules: true + }) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/radar_options\[skip_rules\]\[0\]=all/, data) if /payment_intents/.match?(endpoint) + end.respond_with(successful_create_intent_response) + end + + def test_successful_authorization_with_event_type_metadata + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, { + email: 'wow@example.com', + event_type: 'concert' + }) + end.check_request do |_method, endpoint, data, _headers| + if /payment_intents/.match?(endpoint) + assert_match(/metadata\[email\]=wow%40example.com/, data) + assert_match(/metadata\[event_type\]=concert/, data) + end + end.respond_with(successful_create_intent_response) + end + + def test_successful_setup_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.setup_purchase(@amount, { payment_method_types: %w[afterpay_clearpay card] }) + end.check_request do |_method, endpoint, data, _headers| + assert_match(/payment_method_types\[0\]=afterpay_clearpay&payment_method_types\[1\]=card/, data) if /payment_intents/.match?(endpoint) + end.respond_with(successful_setup_purchase) + end + + def test_supported_countries + countries = %w(AE AT AU BE BG BR CA CH CY CZ DE DK EE ES FI FR GB GR HK HU IE IN IT JP LT LU LV MT MX MY NL NO NZ PL PT RO SE SG SI SK US) + assert_equal countries.sort, StripePaymentIntentsGateway.supported_countries.sort + end + + def test_scrub_filter_token + assert_equal @gateway.scrub(pre_scrubbed), scrubbed + end + + def test_succesful_purchase_with_initial_cit_unscheduled + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: true, + initiator: 'cardholder', + reason_type: 'unscheduled' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_off_session_unscheduled', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_initial_cit_recurring + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: true, + initiator: 'cardholder', + reason_type: 'recurring' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_off_session_recurring', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_initial_cit_installment + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: true, + initiator: 'cardholder', + reason_type: 'installment' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=setup_on_session', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_subsequent_cit + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: false, + initiator: 'cardholder', + reason_type: 'installment' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_on_session', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_mit_recurring + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'recurring' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_off_session_recurring', data) + end.respond_with(successful_create_intent_response) + end + + def test_succesful_purchase_with_mit_unscheduled + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @visa_token, { + currency: 'USD', + confirm: true, + stored_credential_transaction_type: true, + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'unscheduled' + } + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match('payment_method_options[card][stored_credential_transaction_type]=stored_off_session_unscheduled', data) + end.respond_with(successful_create_intent_response) + end + + private + + def successful_setup_purchase + <<-RESPONSE + { + "id": "pi_3Jr0wXAWOtgoysog2Sp0iKjo", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "charges": { + "object": "list", + "data": [ + + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_3Jr0wXAWOtgoysog2Sp0iKjo" + }, + "client_secret": "pi_3Jr0wXAWOtgoysog2Sp0iKjo_secret_1l5cE3MskZ8AMOZaNdpmgZDCn", + "confirmation_method": "automatic", + "created": 1635774777, + "currency": "usd", + "customer": null, + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": null, + "payment_method_options": { + "afterpay_clearpay": { + "reference": null + }, + "card": { + "installments": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "afterpay_clearpay", + "card" + ], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "requires_payment_method", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_create_intent_response + <<-RESPONSE + {"id":"pi_1F1xauAWOtgoysogIfHO8jGi","object":"payment_intent","amount":2020,"amount_capturable":2020,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1xavAWOtgoysogxrtSiCu4","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564501833,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":58,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1xauAWOtgoysogIfHO8jGi","payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1xavAWOtgoysogxrtSiCu4/rcpt_FX1eGdFRi8ssOY8Fqk4X6nEjNeGV5PG","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F1xavAWOtgoysogxrtSiCu4/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1xauAWOtgoysogIfHO8jGi"},"client_secret":"pi_1F1xauAWOtgoysogIfHO8jGi_secret_ZrXvfydFv0BelaMQJgHxjts5b","confirmation_method":"manual","created":1564501832,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_capture","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_create_intent_response_with_network_token_fields + <<~RESPONSE + { + "id": "pi_3NfRruAWOtgoysog1FxgDwtf", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 0, + "amount_details": { + "tip": { + } + }, + "amount_received": 2000, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfRruAWOtgoysog1ptwVNHx", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3NfRruAWOtgoysog1mtFHzZr", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": true, + "created": 1692123686, + "currency": "usd", + "customer": null, + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 34, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfRruAWOtgoysog1FxgDwtf", + "payment_method": "pm_1NfRruAWOtgoysogjdx336vt", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKeE76YGMgbjse9I0TM6LBZ6z9Y1XXMETb-LDQ5oyLVXQhIMltBU0qwDkNKpNvrIGvXOhYmhorDkkE36", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfRruAWOtgoysog1ptwVNHx/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfRruAWOtgoysog1FxgDwtf" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "automatic", + "created": 1692123686, + "currency": "usd", + "customer": null, + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfRruAWOtgoysog1ptwVNHx", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfRruAWOtgoysogjdx336vt", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_create_intent_manual_capture_response_with_network_token_fields + <<~RESPONSE + { + "id": "pi_3NfTpgAWOtgoysog1SqST5dL", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 2000, + "amount_details": { + "tip": { + } + }, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "object": "charge", + "amount": 2000, + "amount_captured": 0, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": null, + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": false, + "created": 1692131237, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 24, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL", + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKW_76YGMgZFk46uT_Y6LBZ51LZOrwdCQ0w176ShWIhNs2CXEh-L6A9pDYW33I_z6C6SenKNrWasw9Ie", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "manual", + "created": 1692131236, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "requires_capture", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_manual_capture_of_payment_intent_response_with_network_token_fields + <<-RESPONSE + { + "id": "pi_3NfTpgAWOtgoysog1SqST5dL", + "object": "payment_intent", + "amount": 2000, + "amount_capturable": 0, + "amount_details": { + "tip": { + } + }, + "amount_received": 2000, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "object": "charge", + "amount": 2000, + "amount_captured": 2000, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3NfTpgAWOtgoysog1ZTZXCvO", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": "Longbob Longsen", + "phone": null + }, + "calculated_statement_descriptor": "SPREEDLY", + "captured": true, + "created": 1692131237, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 24, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3NfTpgAWOtgoysog1SqST5dL", + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 9, + "exp_year": 2030, + "fingerprint": null, + "funding": "debit", + "installments": null, + "last4": "4242", + "mandate": null, + "moto": null, + "network": "visa", + "network_token": { + "exp_month": 9, + "exp_year": 2030, + "fingerprint": "OdTRtGskBulROtqa", + "last4": "5556", + "used": false + }, + "network_transaction_id": "791008482116711", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKa_76YGMgZZ4Fl_Etg6LBYGcD6D2xFTlgp69zLDZz1ZToBrKKjxhRCpYcnLWInSmJZHcjcBdrhyAKGv", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3NfTpgAWOtgoysog1ZcuSdwZ/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3NfTpgAWOtgoysog1SqST5dL" + }, + "client_secret": "pi_3NfRruAWOtgoysog1FxgDwtf_secret_f4ke", + "confirmation_method": "manual", + "created": 1692131236, + "currency": "gbp", + "customer": "cus_OSOcijtQkDdBbF", + "description": null, + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_3NfTpgAWOtgoysog1ZcuSdwZ", + "level3": null, + "livemode": false, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1NfTpgAWOtgoysogHnl1rNCf", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_create_intent_response_with_apple_pay_and_billing_address + <<-RESPONSE + {"id"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0mqdAWOtgoysog1EpiFDCD", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>15, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0mqdAWOtgoysog1IQeiLiz", "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"apple_pay"=>{"type"=>"apple_pay"}, "dynamic_last4"=>"4242", "type"=>"apple_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKPTGn6IGMgZMGrHHLa46LBY0n2_9_Yar0wPTNukle4t28eKG0ZDZnxGYr6GyKn8VsKIEVjU4NkW8NHTL", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0mqdAWOtgoysog1HddFSKg/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0mqdAWOtgoysog1IQeiLiz"}, "client_secret"=>"pi_3N0mqdAWOtgoysog1IQeiLiz_secret_laDLUM6rVleLRqz0nMus9HktB", "confirmation_method"=>"automatic", "created"=>1682432883, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0mqdAWOtgoysog1HddFSKg", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0mqdAWOtgoysogloANIhUF", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil} + RESPONSE + end + + def successful_create_intent_response_with_google_pay_and_billing_address + <<-RESPONSE + {"id"=>"pi_3N0nKLAWOtgoysog3cRTGUqD", "object"=>"payment_intent", "amount"=>2000, "amount_capturable"=>0, "amount_details"=>{"tip"=>{}}, "amount_received"=>2000, "application"=>nil, "application_fee_amount"=>nil, "automatic_payment_methods"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[{"id"=>"ch_3N0nKLAWOtgoysog3npJdWNI", "object"=>"charge", "amount"=>2000, "amount_captured"=>2000, "amount_refunded"=>0, "application"=>nil, "application_fee"=>nil, "application_fee_amount"=>nil, "balance_transaction"=>"txn_3N0nKLAWOtgoysog3ZAmtAMT", "billing_details"=>{"address"=>{"city"=>"Ottawa", "country"=>"CA", "line1"=>"456 My Street", "line2"=>"Apt 1", "postal_code"=>"K1C2N6", "state"=>"ON"}, "email"=>nil, "name"=>nil, "phone"=>nil}, "calculated_statement_descriptor"=>"SPREEDLY", "captured"=>true, "created"=>1682434726, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "destination"=>nil, "dispute"=>nil, "disputed"=>false, "failure_balance_transaction"=>nil, "failure_code"=>nil, "failure_message"=>nil, "fraud_details"=>{}, "invoice"=>nil, "livemode"=>false, "metadata"=>{}, "on_behalf_of"=>nil, "order"=>nil, "outcome"=>{"network_status"=>"approved_by_network", "reason"=>nil, "risk_level"=>"normal", "risk_score"=>61, "seller_message"=>"Payment complete.", "type"=>"authorized"}, "paid"=>true, "payment_intent"=>"pi_3N0nKLAWOtgoysog3cRTGUqD", "payment_method"=>"pm_1N0nKLAWOtgoysoglKSvcZz9", "payment_method_details"=>{"card"=>{"brand"=>"visa", "checks"=>{"address_line1_check"=>"pass", "address_postal_code_check"=>"pass", "cvc_check"=>nil}, "country"=>"US", "ds_transaction_id"=>nil, "exp_month"=>9, "exp_year"=>2030, "fingerprint"=>"hfaVNMiXc0dYSiC5", "funding"=>"credit", "installments"=>nil, "last4"=>"0000", "mandate"=>nil, "moto"=>nil, "network"=>"visa", "network_token"=>{"used"=>false}, "network_transaction_id"=>"104102978678771", "three_d_secure"=>nil, "wallet"=>{"dynamic_last4"=>"4242", "google_pay"=>{}, "type"=>"google_pay"}}, "type"=>"card"}, "receipt_email"=>nil, "receipt_number"=>nil, "receipt_url"=>"https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xNjBEWDZBV090Z295c29nKKbVn6IGMgbEjx6eavI6LBZciyBuj3wwsvIi6Fdr1gNyM807fxUBTGDg2j_1c42EB8vLZl4KcSJA0otk", "refunded"=>false, "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_3N0nKLAWOtgoysog3npJdWNI/refunds"}, "review"=>nil, "shipping"=>nil, "source"=>nil, "source_transfer"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil}], "has_more"=>false, "total_count"=>1, "url"=>"/v1/charges?payment_intent=pi_3N0nKLAWOtgoysog3cRTGUqD"}, "client_secret"=>"pi_3N0nKLAWOtgoysog3cRTGUqD_secret_L4UFErMf6H4itOcZrZRqTwsuA", "confirmation_method"=>"automatic", "created"=>1682434725, "currency"=>"gbp", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "latest_charge"=>"ch_3N0nKLAWOtgoysog3npJdWNI", "level3"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>"pm_1N0nKLAWOtgoysoglKSvcZz9", "payment_method_options"=>{"card"=>{"installments"=>nil, "mandate_options"=>nil, "network"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "processing"=>nil, "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"succeeded", "transfer_data"=>nil, "transfer_group"=>nil} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + {"id":"pi_1F1xauAWOtgoysogIfHO8jGi","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":2020,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1xavAWOtgoysogxrtSiCu4","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_1F1xawAWOtgoysog27xGBjM6","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":true,"created":1564501833,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":58,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1xauAWOtgoysogIfHO8jGi","payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1xavAWOtgoysogxrtSiCu4/rcpt_FX1eGdFRi8ssOY8Fqk4X6nEjNeGV5PG","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F1xavAWOtgoysogxrtSiCu4/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1xauAWOtgoysogIfHO8jGi"},"client_secret":"pi_1F1xauAWOtgoysogIfHO8jGi_secret_ZrXvfydFv0BelaMQJgHxjts5b","confirmation_method":"manual","created":1564501832,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_void_response + <<-RESPONSE + {"id":"pi_1F1yBVAWOtgoysogearamRvl","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":1564504103,"cancellation_reason":"requested_by_customer","capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1yBWAWOtgoysog1MQfDpJH","object":"charge","amount":2020,"amount_refunded":2020,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564504102,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":46,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1yBVAWOtgoysogearamRvl","payment_method":"pm_1F1yBVAWOtgoysogddy4E3hL","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1yBWAWOtgoysog1MQfDpJH/rcpt_FX2Go3YHBqAYQPJuKGMeab3nyCU0Kks","refunded":true,"refunds":{"object":"list","data":[{"id":"re_1F1yBXAWOtgoysog0PU371Yz","object":"refund","amount":2020,"balance_transaction":null,"charge":"ch_1F1yBWAWOtgoysog1MQfDpJH","created":1564504103,"currency":"gbp","metadata":{},"reason":"requested_by_customer","receipt_number":null,"source_transfer_reversal":null,"status":"succeeded","transfer_reversal":null}],"has_more":false,"total_count":1,"url":"/v1/charges/ch_1F1yBWAWOtgoysog1MQfDpJH/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1yBVAWOtgoysogearamRvl"},"client_secret":"pi_1F1yBVAWOtgoysogearamRvl_secret_oCnlR2t0GPclqACgHt2rst4gM","confirmation_method":"manual","created":1564504101,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1yBVAWOtgoysogddy4E3hL","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"canceled","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_update_intent_response + <<-RESPONSE + {"id":"pi_1F1yBbAWOtgoysog52J88BuO","object":"payment_intent","amount":2050,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges?payment_intent=pi_1F1yBbAWOtgoysog52J88BuO"},"client_secret":"pi_1F1yBbAWOtgoysog52J88BuO_secret_olw5rmbtm7cd72S9JfbKjTJJv","confirmation_method":"manual","created":1564504107,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1yBbAWOtgoysoguJQsDdYj","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_confirmation","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_create_3ds2_payment_method + <<-RESPONSE + { + "id": "pm_1F1xK0AWOtgoysogfPuRKN1d", + "object": "payment_method", + "billing_details": { + "address": {"city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null}, + "email": null, + "name": null, + "phone": null}, + "card": { + "brand": "visa", + "checks": {"address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked"}, + "country": null, + "exp_month": 10, + "exp_year": 2020, + "fingerprint": "l3J0NJaGgv0jAGLV", + "funding": "credit", + "generated_from": null, + "last4": "3220", + "three_d_secure_usage": {"supported": true}, + "wallet": null}, + "created": 1564500784, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "card" + } + RESPONSE + end + + def successful_create_3ds2_intent_response + <<-RESPONSE + { + "id": "pi_1F1wpFAWOtgoysog8nTulYGk", + "object": "payment_intent", + "amount": 2020, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_1F1wpFAWOtgoysog8nTulYGk" + }, + "client_secret": "pi_1F1wpFAWOtgoysog8nTulYGk_secret_75qf7rjBDsTTz279LfS1feXUj", + "confirmation_method": "manual", + "created": 1564498877, + "currency": "gbp", + "customer": "cus_7s22nNueP2Hjj6", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1F1wpFAWOtgoysogJ8zQ8K07", + "payment_method_options": { + "card": {"request_three_d_secure": "automatic"} + }, + "payment_method_types": ["card"], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "status": "requires_confirmation", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_confirm_3ds2_intent_response + <<-RESPONSE + { + "id": "pi_1F1wpFAWOtgoysog8nTulYGk", + "object": "payment_intent", + "amount": 2020, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_1F1wpFAWOtgoysog8nTulYGk"}, + "client_secret": "pi_1F1wpFAWOtgoysog8nTulYGk_secret_75qf7rjBDsTTz279LfS1feXUj", + "confirmation_method": "manual", + "created": 1564498877, + "currency": "gbp", + "customer": "cus_7s22nNueP2Hjj6", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": { + "redirect_to_url": { + "return_url": "https://example.com/return-to-me", + "url": "https://hooks.stripe.com/3d_secure_2_eap/begin_test/src_1F1wpGAWOtgoysog4f00umCp/src_client_secret_FX0qk3uQ04woFWgdJbN3pnHD"}, + "type": "redirect_to_url"}, + "on_behalf_of": null, + "payment_method": "pm_1F1wpFAWOtgoysogJ8zQ8K07", + "payment_method_options": { + "card": {"request_three_d_secure": "automatic"} + }, + "payment_method_types": ["card"], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "status": "requires_action", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_request_three_d_secure_response + <<-RESPONSE + {"id"=>"pi_1HZJGPAWOtgoysogrKURP11Q", + "object"=>"payment_intent", + "amount"=>2000, + "amount_capturable"=>0, + "amount_received"=>2000, + "application"=>nil, + "application_fee_amount"=>nil, + "canceled_at"=>nil, + "cancellation_reason"=>nil, + "capture_method"=>"automatic", + "charges"=> + {"object"=>"list", + "data"=> + [{"id"=>"ch_1HZJGQAWOtgoysogEpbZTGIl", + "object"=>"charge", + "amount"=>2000, + "amount_captured"=>2000, + "amount_refunded"=>0, + "application"=>nil, + "application_fee"=>nil, + "application_fee_amount"=>nil, + "balance_transaction"=>"txn_1HZJGQAWOtgoysogEKwV2r5N", + "billing_details"=> + {"address"=>{"city"=>nil, "country"=>nil, "line1"=>nil, "line2"=>nil, "postal_code"=>nil, "state"=>nil}, "email"=>nil, "name"=>nil, "phone"=>nil}, + "calculated_statement_descriptor"=>"SPREEDLY", + "captured"=>true, + "created"=>1602002626, + "currency"=>"gbp", + "customer"=>nil, + "description"=>nil, + "destination"=>nil, + "dispute"=>nil, + "disputed"=>false, + "failure_code"=>nil, + "failure_message"=>nil, + "fraud_details"=>{}, + "invoice"=>nil, + "livemode"=>false, + "metadata"=>{}, + "on_behalf_of"=>nil, + "order"=>nil, + "outcome"=> + {"network_status"=>"approved_by_network", + "reason"=>nil, + "risk_level"=>"normal", + "risk_score"=>16, + "seller_message"=>"Payment complete.", + "type"=>"authorized"}, + "paid"=>true, + "payment_intent"=>"pi_1HZJGPAWOtgoysogrKURP11Q", + "payment_method"=>"pm_1HZJGOAWOtgoysogvnMsnnG1", + "payment_method_details"=> + {"card"=> + {"brand"=>"visa", + "checks"=>{"address_line1_check"=>nil, "address_postal_code_check"=>nil, "cvc_check"=>"pass"}, + "country"=>"US", + "ds_transaction_id"=>nil, + "exp_month"=>10, + "exp_year"=>2020, + "fingerprint"=>"hfaVNMiXc0dYSiC5", + "funding"=>"credit", + "installments"=>nil, + "last4"=>"4242", + "moto"=>nil, + "network"=>"visa", + "network_transaction_id"=>"1041029786787710", + "three_d_secure"=> + {"authenticated"=>false, + "authentication_flow"=>nil, + "electronic_commerce_indicator"=>"06", + "result"=>"attempt_acknowledged", + "result_reason"=>nil, + "succeeded"=>true, + "transaction_id"=>"d1VlRVF6a1BVNXN1cjMzZVl0RU0=", + "version"=>"1.0.2"}, + "wallet"=>nil}, + "type"=>"card"}, + "receipt_email"=>nil, + "receipt_number"=>nil, + "receipt_url"=>"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1HZJGQAWOtgoysogEpbZTGIl/rcpt_I9cVpN9xAeS39FhMqTS33Fj8gHsjjuX", + "refunded"=>false, + "refunds"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges/ch_1HZJGQAWOtgoysogEpbZTGIl/refunds"}, + "review"=>nil, + "shipping"=>nil, + "source"=>nil, + "source_transfer"=>nil, + "statement_descriptor"=>nil, + "statement_descriptor_suffix"=>nil, + "status"=>"succeeded", + "transfer_data"=>nil, + "transfer_group"=>nil}], + "has_more"=>false, + "total_count"=>1, + "url"=>"/v1/charges?payment_intent=pi_1HZJGPAWOtgoysogrKURP11Q"}, + "client_secret"=>"pi_1HZJGPAWOtgoysogrKURP11Q_secret_dJNY00dYXC22Fc9nPscAmhFMt", + "confirmation_method"=>"automatic", + "created"=>1602002625, + "currency"=>"gbp", + "customer"=>nil, + "description"=>nil, + "invoice"=>nil, + "last_payment_error"=>nil, + "livemode"=>false, + "metadata"=>{}, + "next_action"=>nil, + "on_behalf_of"=>nil, + "payment_method"=>"pm_1HZJGOAWOtgoysogvnMsnnG1", + "payment_method_options"=>{"card"=>{"installments"=>nil, "network"=>nil, "request_three_d_secure"=>"any"}}, + "payment_method_types"=>["card"], + "receipt_email"=>nil, + "review"=>nil, + "setup_future_usage"=>nil, + "shipping"=>nil, + "source"=>nil, + "statement_descriptor"=>nil, + "statement_descriptor_suffix"=>nil, + "status"=>"succeeded", + "transfer_data"=>nil, + "transfer_group"=>nil + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + {"error":{"charge":"ch_1F2MB6AWOtgoysogAIvNV32Z","code":"card_declined","decline_code":"generic_decline","doc_url":"https://stripe.com/docs/error-codes/card-declined","message":"Your card was declined.","payment_intent":{"id":"pi_1F2MB5AWOtgoysogCMt8BaxR","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","charges":{"object":"list","data":[{"id":"ch_1F2MB6AWOtgoysogAIvNV32Z","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564596332,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":"card_declined","failure_message":"Your card was declined.","fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"declined_by_network","reason":"generic_decline","risk_level":"normal","risk_score":41,"seller_message":"The bank did not return any further details with this decline.","type":"issuer_declined"},"paid":false,"payment_intent":"pi_1F2MB5AWOtgoysogCMt8BaxR","payment_method":"pm_1F2MB5AWOtgoysogq3yXZ98h","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","last4":"0002","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F2MB6AWOtgoysogAIvNV32Z/rcpt_FXR3PjBGluHmHsnLmp0S2KQiHl3yg6W","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F2MB6AWOtgoysogAIvNV32Z/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"failed","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F2MB5AWOtgoysogCMt8BaxR"},"client_secret":"pi_1F2MB5AWOtgoysogCMt8BaxR_secret_fOHryjtjBE4gACiHTcREraXSQ","confirmation_method":"manual","created":1564596331,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":{"charge":"ch_1F2MB6AWOtgoysogAIvNV32Z","code":"card_declined","decline_code":"generic_decline","doc_url":"https://stripe.com/docs/error-codes/card-declined","message":"Your card was declined.","payment_method":{"id":"pm_1F2MB5AWOtgoysogq3yXZ98h","object":"payment_method","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","generated_from":null,"last4":"0002","three_d_secure_usage":{"supported":true},"wallet":null},"created":1564596331,"customer":null,"livemode":false,"metadata":{},"type":"card"},"type":"card_error"},"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_payment_method","transfer_data":null,"transfer_group":null},"payment_method":{"id":"pm_1F2MB5AWOtgoysogq3yXZ98h","object":"payment_method","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","generated_from":null,"last4":"0002","three_d_secure_usage":{"supported":true},"wallet":null},"created":1564596331,"customer":null,"livemode":false,"metadata":{},"type":"card"},"type":"card_error"}} + RESPONSE + end + + def failed_cancel_response + <<-RESPONSE + {"error":{"code":"payment_intent_unexpected_state","doc_url":"https://stripe.com/docs/error-codes/payment-intent-unexpected-state","message":"You cannot cancel this PaymentIntent because it has a status of succeeded. Only a PaymentIntent with one of the following statuses may be canceled: requires_payment_method, requires_capture, requires_confirmation, requires_action.","payment_intent":{"id":"pi_1F2McmAWOtgoysoglFLDRWab","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":2020,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","charges":{"object":"list","data":[{"id":"ch_1F2McmAWOtgoysogQgUS1YtH","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_1F2McmAWOtgoysog8uxBEJ30","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":true,"created":1564598048,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":53,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F2McmAWOtgoysoglFLDRWab","payment_method":"pm_1F2MclAWOtgoysogq80GBBMO","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F2McmAWOtgoysogQgUS1YtH/rcpt_FXRVzyFnf7aCS1r13N3uym1u8AaboOJ","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F2McmAWOtgoysogQgUS1YtH/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F2McmAWOtgoysoglFLDRWab"},"client_secret":"pi_1F2McmAWOtgoysoglFLDRWab_secret_z4faDF0Cv0JZJ6pxK3bdIodkD","confirmation_method":"manual","created":1564598048,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F2MclAWOtgoysogq80GBBMO","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null},"type":"invalid_request_error"}} + RESPONSE + end + + def failed_payment_method_response + <<-RESPONSE + {"error": {"code": "validation_error", "message": "You must verify a phone number on your Stripe account before you can send raw credit card numbers to the Stripe API. You can avoid this requirement by using Stripe.js, the Stripe mobile bindings, or Stripe Checkout. For more information, see https://dashboard.stripe.com/phone-verification.", "type": "invalid_request_error"}} + RESPONSE + end + + def failed_service_response + <<-RESPONSE + {"error": {"message": "Error while communicating with one of our backends. Sorry about that! We have been notified of the problem. If you have any questions, we can help at https://support.stripe.com/.", "type": "api_error" }} + RESPONSE + end + + def failed_with_set_error_on_requires_action_response + <<-RESPONSE + {"error": {"message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "type": "card_error" }} + RESPONSE + end + + def successful_verify_response + <<-RESPONSE + { + "id": "seti_1Gsw0aAWOtgoysog0XjSBPVX", + "object": "setup_intent", + "application": null, + "cancellation_reason": null, + "client_secret": "seti_1Gsw0aAWOtgoysog0XjSBPVX_secret_HRpfHkvewAdYQJgee27ihJfm4E4zWmW", + "created": 1591903456, + "customer": "cus_GkjsDZC58SgUcY", + "description": null, + "last_setup_error": null, + "livemode": false, + "mandate": null, + "metadata": { + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1Gsw0aAWOtgoysog304wX4J9", + "payment_method_options": { + "card": { + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "single_use_mandate": null, + "status": "succeeded", + "usage": "off_session" + } + RESPONSE + end + + def successful_payment_method_response + <<-RESPONSE + { + "id": "pm_1IQ3OhAWOtgoysogUkVwJ5MT", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked" + }, + "country": "US", + "exp_month": 10, + "exp_year": 2021, + "fingerprint": "hfaVNMiXc0dYSiC5", + "funding": "credit", + "generated_from": null, + "last4": "4242", + "networks": { + "available": [ + "visa" + ], + "preferred": null + }, + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1614573020, + "customer": null, + "livemode": false, + "metadata": { + }, + "type": "card" + } + RESPONSE + end + + def successful_create_customer_response + <<-RESPONSE + { + "id": "cus_J27e2tthifSmpm", + "object": "customer", + "account_balance": 0, + "address": null, + "balance": 0, + "created": 1614573020, + "currency": null, + "default_source": null, + "delinquent": false, + "description": null, + "discount": null, + "email": null, + "invoice_prefix": "B0C3D1B5", + "invoice_settings": { + "custom_fields": null, + "default_payment_method": null, + "footer": null + }, + "livemode": false, + "metadata": { + }, + "name": null, + "next_invoice_sequence": 1, + "phone": null, + "preferred_locales": [], + "shipping": null, + "sources": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_J27e2tthifSmpm/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_J27e2tthifSmpm/subscriptions" + }, + "tax_exempt": "none", + "tax_ids": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_J27e2tthifSmpm/tax_ids" + }, + "tax_info": null, + "tax_info_verification": null + } + RESPONSE + end + + def successful_payment_method_attach_response + <<-RESPONSE + { + "id": "pm_1IQ3AYAWOtgoysogcvbllgNa", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked" + }, + "country": "US", + "exp_month": 10, + "exp_year": 2021, + "fingerprint": "hfaVNMiXc0dYSiC5", + "funding": "credit", + "generated_from": null, + "last4": "4242", + "networks": { + "available": [ + "visa" + ], + "preferred": null + }, + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1614572142, + "customer": "cus_J27PL9krZlnw82", + "livemode": false, + "metadata": { + }, + "type": "card" + } + RESPONSE + end + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF9oQkwwTXF6ZGZ6Rnk3OXU0cFloUmVhQlo6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100¤cy=usd&card[number]=4242424242424242&card[exp_month]=9&card[exp_year]=2015&card[tokenization_method]=android_pay&card[eci]=07&capture_method=automatic&card[name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&card[cryptogram]=sensitive_data&payment_method_types[0]=card&payment_method_data[type]=card&payment_method_data[card][token]=tok_1KHrnVAWOtgoysogWbF1jrM9&metadata[connect_agent]=placeholder&metadata[transaction_token]=Coe7nlopnvhfcNRXhJMH5DTVusU&metadata[email]=john.smith%40example.com&metadata[order_id]=order_id-xxxxxx-x&confirm=true&return_url=http%3A%2F%2Fexaple.com%2Ftransaction%transaction_idxxxx%2Fredirect" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Fri, 14 Jan 2022 15:34:39 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 5204\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "idempotency-key: 87bd1ae5-1cf2-4735-85e0-c8cdafb25fff\r\n" + -> "original-request: req_VkIqZgctQBI9yo\r\n" + -> "request-id: req_VkIqZgctQBI9yo\r\n" + -> "stripe-should-retry: false\r\n" + -> "stripe-version: 2020-08-27\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 5204 bytes... + -> "{\n \"id\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc\",\n \"object\": \"payment_intent\",\n \"amount\": 100,\n \"amount_capturable\": 0,\n \"amount_received\": 100,\n \"application\": null,\n \"application_fee_amount\": null,\n \"automatic_payment_methods\": null,\n \"canceled_at\": null,\n \"cancellation_reason\": null,\n \"capture_method\": \"automatic\",\n \"charges\": {\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"ch_3KHrnWAWOtgoysog1noj1iU9\",\n \"object\": \"charge\",\n \"amount\": 100,\n \"amount_captured\": 100,\n \"amount_refunded\": 0,\n \"application\": null,\n \"application_fee\": null,\n \"application_fee_amount\": null,\n \"balance_transaction\": \"txn_3KHrnWAWOtgoysog1vy6pmxk\",\n \"billing_details\": {\n \"address\": {\n \"city\": null,\n \"country\": null,\n \"line1\": null,\n \"line2\": null,\n \"postal_code\": null,\n \"state\": null\n },\n \"email\": null,\n \"name\": null,\n \"phone\": null\n },\n \"calculated_statement_descriptor\": \"SPREEDLY\",\n \"captured\": true,\n \"created\": 1642174478,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"destination\": null,\n \"dispute\": null,\n \"disputed\": false,\n \"failure_code\": null,\n \"failure_message\": null,\n \"fraud_details\": {\n },\n \"invoice\": null,\n \"livemode\": false,\n \"metadata\": {\n \"connect_agent\": \"placeholder\",\n \"transaction_token\": \"Coe7nlopnvhfcNRXhJMH5DTVusU\",\n \"email\": \"john.smith@example.com\",\n \"order_id\": \"AH2EjtfMGoZkWNEwLU90sq7VzcDlzWH_KugIYT4aVWEtJF9AwmqiXqsBs2l9q6F2Ruq9WKkUBbuLWNmA3P22ShFXFCZosTwkoflaDeTD2xeiMvmYv29VPINEDtLdSAoJ-DDlRKnsxa-n\"\n },\n \"on_behalf_of\": null,\n \"order\": null,\n \"outcome\": {\n \"network_status\": \"approved_by_network\",\n \"reason\": null,\n \"risk_level\": \"normal\",\n \"risk_score\": 36,\n \"seller_message\": \"Payment complete.\",\n \"type\": \"authorized\"\n },\n \"paid\": true,\n \"payment_intent\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc\",\n \"payment_method\": \"pm_1KHrnWAWOtgoysogqXkTXrCb\",\n \"payment_method_details\": {\n \"card\": {\n \"brand\": \"visa\",\n \"checks\": {\n \"address_line1_check\": null,\n \"address_postal_code_check\": null,\n \"cvc_check\": null\n },\n \"country\": \"US\",\n \"ds_transaction_id\": null,\n \"exp_month\": 12,\n \"exp_year\": 2027,\n \"fingerprint\": \"sUdMrygQwzOKqwSm\",\n \"funding\": \"debit\",\n \"installments\": null,\n \"last4\": \"0000\",\n \"mandate\": null,\n \"moto\": null,\n \"network\": \"visa\",\n \"network_transaction_id\": \"1158510077114121\",\n \"three_d_secure\": null,\n \"wallet\": {\n \"dynamic_last4\": \"3478\",\n \"google_pay\": {\n },\n \"type\": \"google_pay\"\n }\n },\n \"type\": \"card\"\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"receipt_url\": \"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_3KHrnWAWOtgoysog1noj1iU9/rcpt_KxnOefAivglRgWZmxp0PLOJUQg0VhS9\",\n \"refunded\": false,\n \"refunds\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/charges/ch_3KHrnWAWOtgoysog1noj1iU9/refunds\"\n },\n \"review\": null,\n \"shipping\": null,\n \"source\": null,\n \"source_transfer\": null,\n \"statement_descriptor\": null,\n \"statement_descriptor_suffix\": null,\n \"status\": \"succeeded\",\n \"transfer_data\": null,\n \"transfer_group\": null\n }\n ],\n \"has_more\": false,\n \"total_count\": 1,\n \"url\": \"/v1/charges?payment_intent=pi_3KHrnWAWOtgoysog1Y5qMLqc\"\n },\n \"client_secret\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc_secret_5ZEt4fzM7YCi1zdMzs4iQXLjC\",\n \"confirmation_method\": \"automatic\",\n \"created\": 1642174478,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"invoice\": null,\n \"last_payment_error\": null,\n \"livemode\": false,\n \"metadata\": {\n \"connect_agent\": \"placeholder\",\n \"transaction_token\": \"Coe7nlopnvhfcNRXhJMH5DTVusU\",\n \"email\": \"john.smith@example.com\",\n \"order_id\": \"AH2EjtfMGoZkWNEwLU90sq7VzcDlzWH_KugIYT4aVWEtJF9AwmqiXqsBs2l9q6F2Ruq9WKkUBbuLWNmA3P22ShFXFCZosTwkoflaDeTD2xeiMvmYv29VPINEDtLdSAoJ-DDlRKnsxa-n\"\n },\n \"next_action\": null,\n \"on_behalf_of\": null,\n \"payment_method\": \"pm_1KHrnWAWOtgoysogqXkTXrCb\",\n \"payment_method_options\": {\n \"card\": {\n \"installments\": null,\n \"mandate_options\": null,\n \"network\": null,\n \"request_three_d_secure\": \"automatic\"\n }\n },\n \"payment_method_types\": [\n \"card\"\n ],\n \"processing\": null,\n \"receipt_email\": null,\n \"review\": null,\n \"setup_future_usage\": null,\n \"shipping\": null,\n \"source\": null,\n \"statement_descriptor\": null,\n \"statement_descriptor_suffix\": null,\n \"status\": \"succeeded\",\n \"transfer_data\": null,\n \"transfer_group\": null\n}\n" + read 5204 bytes + Conn close + PRE_SCRUBBED + end + + def scrubbed + <<-SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100¤cy=usd&card[number]=[FILTERED]&card[exp_month]=9&card[exp_year]=2015&card[tokenization_method]=android_pay&card[eci]=07&capture_method=automatic&card[name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&card[cryptogram]=[FILTERED]&payment_method_types[0]=card&payment_method_data[type]=card&payment_method_data[card][token]=[FILTERED]&metadata[connect_agent]=placeholder&metadata[transaction_token]=Coe7nlopnvhfcNRXhJMH5DTVusU&metadata[email]=john.smith%40example.com&metadata[order_id]=order_id-xxxxxx-x&confirm=true&return_url=http%3A%2F%2Fexaple.com%2Ftransaction%transaction_idxxxx%2Fredirect" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Fri, 14 Jan 2022 15:34:39 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 5204\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "idempotency-key: 87bd1ae5-1cf2-4735-85e0-c8cdafb25fff\r\n" + -> "original-request: req_VkIqZgctQBI9yo\r\n" + -> "request-id: req_VkIqZgctQBI9yo\r\n" + -> "stripe-should-retry: false\r\n" + -> "stripe-version: 2020-08-27\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 5204 bytes... + -> "{\n \"id\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc\",\n \"object\": \"payment_intent\",\n \"amount\": 100,\n \"amount_capturable\": 0,\n \"amount_received\": 100,\n \"application\": null,\n \"application_fee_amount\": null,\n \"automatic_payment_methods\": null,\n \"canceled_at\": null,\n \"cancellation_reason\": null,\n \"capture_method\": \"automatic\",\n \"charges\": {\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"ch_3KHrnWAWOtgoysog1noj1iU9\",\n \"object\": \"charge\",\n \"amount\": 100,\n \"amount_captured\": 100,\n \"amount_refunded\": 0,\n \"application\": null,\n \"application_fee\": null,\n \"application_fee_amount\": null,\n \"balance_transaction\": \"txn_3KHrnWAWOtgoysog1vy6pmxk\",\n \"billing_details\": {\n \"address\": {\n \"city\": null,\n \"country\": null,\n \"line1\": null,\n \"line2\": null,\n \"postal_code\": null,\n \"state\": null\n },\n \"email\": null,\n \"name\": null,\n \"phone\": null\n },\n \"calculated_statement_descriptor\": \"SPREEDLY\",\n \"captured\": true,\n \"created\": 1642174478,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"destination\": null,\n \"dispute\": null,\n \"disputed\": false,\n \"failure_code\": null,\n \"failure_message\": null,\n \"fraud_details\": {\n },\n \"invoice\": null,\n \"livemode\": false,\n \"metadata\": {\n \"connect_agent\": \"placeholder\",\n \"transaction_token\": \"Coe7nlopnvhfcNRXhJMH5DTVusU\",\n \"email\": \"john.smith@example.com\",\n \"order_id\": \"AH2EjtfMGoZkWNEwLU90sq7VzcDlzWH_KugIYT4aVWEtJF9AwmqiXqsBs2l9q6F2Ruq9WKkUBbuLWNmA3P22ShFXFCZosTwkoflaDeTD2xeiMvmYv29VPINEDtLdSAoJ-DDlRKnsxa-n\"\n },\n \"on_behalf_of\": null,\n \"order\": null,\n \"outcome\": {\n \"network_status\": \"approved_by_network\",\n \"reason\": null,\n \"risk_level\": \"normal\",\n \"risk_score\": 36,\n \"seller_message\": \"Payment complete.\",\n \"type\": \"authorized\"\n },\n \"paid\": true,\n \"payment_intent\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc\",\n \"payment_method\": \"pm_1KHrnWAWOtgoysogqXkTXrCb\",\n \"payment_method_details\": {\n \"card\": {\n \"brand\": \"visa\",\n \"checks\": {\n \"address_line1_check\": null,\n \"address_postal_code_check\": null,\n \"cvc_check\": null\n },\n \"country\": \"US\",\n \"ds_transaction_id\": null,\n \"exp_month\": 12,\n \"exp_year\": 2027,\n \"fingerprint\": \"sUdMrygQwzOKqwSm\",\n \"funding\": \"debit\",\n \"installments\": null,\n \"last4\": \"0000\",\n \"mandate\": null,\n \"moto\": null,\n \"network\": \"visa\",\n \"network_transaction_id\": \"1158510077114121\",\n \"three_d_secure\": null,\n \"wallet\": {\n \"dynamic_last4\": \"3478\",\n \"google_pay\": {\n },\n \"type\": \"google_pay\"\n }\n },\n \"type\": \"card\"\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"receipt_url\": \"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_3KHrnWAWOtgoysog1noj1iU9/rcpt_KxnOefAivglRgWZmxp0PLOJUQg0VhS9\",\n \"refunded\": false,\n \"refunds\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/charges/ch_3KHrnWAWOtgoysog1noj1iU9/refunds\"\n },\n \"review\": null,\n \"shipping\": null,\n \"source\": null,\n \"source_transfer\": null,\n \"statement_descriptor\": null,\n \"statement_descriptor_suffix\": null,\n \"status\": \"succeeded\",\n \"transfer_data\": null,\n \"transfer_group\": null\n }\n ],\n \"has_more\": false,\n \"total_count\": 1,\n \"url\": \"/v1/charges?payment_intent=pi_3KHrnWAWOtgoysog1Y5qMLqc\"\n },\n \"client_secret\": \"pi_3KHrnWAWOtgoysog1Y5qMLqc_secret_5ZEt4fzM7YCi1zdMzs4iQXLjC\",\n \"confirmation_method\": \"automatic\",\n \"created\": 1642174478,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"invoice\": null,\n \"last_payment_error\": null,\n \"livemode\": false,\n \"metadata\": {\n \"connect_agent\": \"placeholder\",\n \"transaction_token\": \"Coe7nlopnvhfcNRXhJMH5DTVusU\",\n \"email\": \"john.smith@example.com\",\n \"order_id\": \"AH2EjtfMGoZkWNEwLU90sq7VzcDlzWH_KugIYT4aVWEtJF9AwmqiXqsBs2l9q6F2Ruq9WKkUBbuLWNmA3P22ShFXFCZosTwkoflaDeTD2xeiMvmYv29VPINEDtLdSAoJ-DDlRKnsxa-n\"\n },\n \"next_action\": null,\n \"on_behalf_of\": null,\n \"payment_method\": \"pm_1KHrnWAWOtgoysogqXkTXrCb\",\n \"payment_method_options\": {\n \"card\": {\n \"installments\": null,\n \"mandate_options\": null,\n \"network\": null,\n \"request_three_d_secure\": \"automatic\"\n }\n },\n \"payment_method_types\": [\n \"card\"\n ],\n \"processing\": null,\n \"receipt_email\": null,\n \"review\": null,\n \"setup_future_usage\": null,\n \"shipping\": null,\n \"source\": null,\n \"statement_descriptor\": null,\n \"statement_descriptor_suffix\": null,\n \"status\": \"succeeded\",\n \"transfer_data\": null,\n \"transfer_group\": null\n}\n" + read 5204 bytes + Conn close + SCRUBBED + end +end diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 76e7231c712..1ce0e52c96c 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -4,7 +4,7 @@ class StripeTest < Test::Unit::TestCase include CommStub def setup - @gateway = StripeGateway.new(:login => 'login') + @gateway = StripeGateway.new(login: 'sk_test_login') @credit_card = credit_card() @threeds_card = credit_card('4000000000003063') @@ -13,14 +13,15 @@ def setup @refund_amount = 200 @options = { - :billing_address => address(), - :statement_address => statement_address(), - :description => 'Test Purchase' + billing_address: address(), + statement_address: statement_address(), + shipping_address: shipping_address(), + description: 'Test Purchase' } @threeds_options = { - :execute_threed => true, - :callback_url => 'http://www.example.com/callback' + execute_threed: true, + callback_url: 'http://www.example.com/callback' } @apple_pay_payment_token = apple_pay_payment_token @@ -31,7 +32,7 @@ def setup @check = check({ bank_name: 'STRIPE TEST BANK', account_number: '000123456789', - routing_number: '110000000', + routing_number: '110000000' }) end @@ -82,7 +83,7 @@ def test_successful_new_card @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@credit_card, :customer => 'cus_3sgheFxeBgTQ3M') + assert response = @gateway.store(@credit_card, customer: 'cus_3sgheFxeBgTQ3M') assert_instance_of MultiResponse, response assert_success response @@ -94,7 +95,7 @@ def test_successful_new_card_via_apple_pay_payment_token @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.store(@apple_pay_payment_token, :customer => 'cus_3sgheFxeBgTQ3M') + assert response = @gateway.store(@apple_pay_payment_token, customer: 'cus_3sgheFxeBgTQ3M') assert_instance_of MultiResponse, response assert_success response @@ -106,7 +107,7 @@ def test_successful_new_card_with_emv_credit_card @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M') + assert response = @gateway.store(@emv_credit_card, customer: 'cus_3sgheFxeBgTQ3M') assert_instance_of MultiResponse, response assert_success response @@ -118,7 +119,7 @@ def test_successful_new_card_with_token_string @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@token_string, :customer => 'cus_3sgheFxeBgTQ3M') + assert response = @gateway.store(@token_string, customer: 'cus_3sgheFxeBgTQ3M') assert_instance_of MultiResponse, response assert_success response @@ -130,7 +131,7 @@ def test_successful_new_card_with_payment_token @gateway.expects(:ssl_request).returns(successful_new_card_response) @gateway.expects(:add_payment_token) - assert response = @gateway.store(@payment_token, :customer => 'cus_3sgheFxeBgTQ3M') + assert response = @gateway.store(@payment_token, customer: 'cus_3sgheFxeBgTQ3M') assert_instance_of MultiResponse, response assert_success response @@ -142,7 +143,7 @@ def test_successful_new_card_and_customer_update @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@credit_card, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert response = @gateway.store(@credit_card, customer: 'cus_3sgheFxeBgTQ3M', email: 'test@test.com') assert_instance_of MultiResponse, response assert_success response @@ -157,7 +158,7 @@ def test_successful_new_card_and_customer_update_via_apple_pay_payment_token @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.store(@apple_pay_payment_token, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert response = @gateway.store(@apple_pay_payment_token, customer: 'cus_3sgheFxeBgTQ3M', email: 'test@test.com') assert_instance_of MultiResponse, response assert_success response @@ -171,7 +172,7 @@ def test_successful_new_card_and_customer_update_via_apple_pay_payment_token def test_successful_new_card_and_customer_update_with_emv_credit_card @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert response = @gateway.store(@emv_credit_card, customer: 'cus_3sgheFxeBgTQ3M', email: 'test@test.com') assert_instance_of MultiResponse, response assert_success response @@ -185,7 +186,7 @@ def test_successful_new_card_and_customer_update_with_emv_credit_card def test_successful_new_card_and_customer_update_with_token_string @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.store(@token_string, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert response = @gateway.store(@token_string, customer: 'cus_3sgheFxeBgTQ3M', email: 'test@test.com') assert_instance_of MultiResponse, response assert_success response @@ -199,7 +200,7 @@ def test_successful_new_card_and_customer_update_with_token_string def test_successful_new_card_and_customer_update_with_payment_token @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.store(@payment_token, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert response = @gateway.store(@payment_token, customer: 'cus_3sgheFxeBgTQ3M', email: 'test@test.com') assert_instance_of MultiResponse, response assert_success response @@ -214,7 +215,7 @@ def test_successful_new_default_card @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert response = @gateway.store(@credit_card, @options.merge(customer: 'cus_3sgheFxeBgTQ3M', set_default: true)) assert_instance_of MultiResponse, response assert_success response @@ -229,7 +230,7 @@ def test_successful_new_default_card_via_apple_pay_payment_token @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.store(@apple_pay_payment_token, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert response = @gateway.store(@apple_pay_payment_token, @options.merge(customer: 'cus_3sgheFxeBgTQ3M', set_default: true)) assert_instance_of MultiResponse, response assert_success response @@ -243,7 +244,7 @@ def test_successful_new_default_card_via_apple_pay_payment_token def test_successful_new_default_card_with_emv_credit_card @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.store(@emv_credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert response = @gateway.store(@emv_credit_card, @options.merge(customer: 'cus_3sgheFxeBgTQ3M', set_default: true)) assert_instance_of MultiResponse, response assert_success response @@ -258,7 +259,7 @@ def test_successful_new_default_card_with_token_string @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_creditcard) - assert response = @gateway.store(@token_string, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert response = @gateway.store(@token_string, @options.merge(customer: 'cus_3sgheFxeBgTQ3M', set_default: true)) assert_instance_of MultiResponse, response assert_success response @@ -273,7 +274,7 @@ def test_successful_new_default_card_with_payment_token @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) @gateway.expects(:add_payment_token) - assert response = @gateway.store(@payment_token, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert response = @gateway.store(@payment_token, @options.merge(customer: 'cus_3sgheFxeBgTQ3M', set_default: true)) assert_instance_of MultiResponse, response assert_success response @@ -287,7 +288,7 @@ def test_successful_new_default_card_with_payment_token def test_passing_validate_false_on_store response = stub_comms(@gateway, :ssl_request) do @gateway.store(@credit_card, validate: false) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/validate=false/, data) end.respond_with(successful_new_customer_response) @@ -297,7 +298,7 @@ def test_passing_validate_false_on_store def test_empty_values_not_sent response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, referrer: '') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| refute_match(/referrer/, data) end.respond_with(successful_purchase_response) @@ -363,6 +364,42 @@ def test_successful_authorization_with_emv_credit_card assert response.emv_authorization, 'Response should include emv_authorization containing the EMV ARPC' end + def test_contains_statement_descriptor_suffix + options = @options.merge(statement_descriptor_suffix: 'suffix') + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/statement_descriptor_suffix=suffix/, data) + end.respond_with(successful_purchase_response) + end + + def test_connected_account + destination = 'account_27701' + amount = 8000 + on_behalf_of = 'account_27704' + transfer_group = 'TG1000' + application_fee_amount = 100 + + options = @options.merge( + transfer_destination: destination, + transfer_amount: amount, + on_behalf_of: on_behalf_of, + transfer_group: transfer_group, + application_fee_amount: application_fee_amount + ) + + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/transfer_data\[destination\]=#{destination}/, data) + assert_match(/transfer_data\[amount\]=#{amount}/, data) + assert_match(/on_behalf_of=#{on_behalf_of}/, data) + assert_match(/transfer_group=#{transfer_group}/, data) + assert_match(/application_fee_amount=#{application_fee_amount}/, data) + end.respond_with(successful_purchase_response) + end + def test_declined_authorization_with_emv_credit_card @gateway.expects(:ssl_request).returns(declined_authorization_response_with_emv_auth_data) @@ -383,9 +420,23 @@ def test_successful_capture end def test_successful_capture_with_emv_credit_card_tc - @gateway.expects(:ssl_request).returns(successful_capture_response_with_icc_data) - - assert response = @gateway.capture(@amount, 'ch_test_emv_charge') + stripe_charge_id = 'ch_test_emv_charge' + tc_emv_response = 'mock_icc_data' + @gateway. + expects(:ssl_request). + with( + :post, + "https://api.stripe.com/v1/charges/#{stripe_charge_id}", + "card[emv_approval_data]=#{tc_emv_response}", + anything + ).returns(successful_capture_response_with_icc_data) + + @gateway. + expects(:ssl_request). + with(:post, 'https://api.stripe.com/v1/charges/ch_test_emv_charge/capture', '', anything). + returns(successful_capture_response_without_icc_data) + + assert response = @gateway.capture(@amount, stripe_charge_id, icc_data: tc_emv_response) assert_success response assert response.emv_authorization, 'Response should include emv_authorization containing the EMV TC' end @@ -461,7 +512,7 @@ def test_successful_purchase_with_level3_data response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |_method, endpoint, data, _headers| - if %r{/charges} =~ endpoint + if %r{/charges}.match?(endpoint) assert_match('level3[merchant_reference]=123', data) assert_match('level3[customer_reference]=456', data) assert_match('level3[shipping_address_zip]=98765', data) @@ -495,8 +546,8 @@ def test_adds_application_to_x_stripe_client_user_agent_header } response = stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, 'cus_xxx|card_xxx', @options.merge({application: application})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, 'cus_xxx|card_xxx', @options.merge({ application: application })) + end.check_request do |_method, _endpoint, _data, headers| assert_match(/\"application\"/, headers['X-Stripe-Client-User-Agent']) assert_match(/\"name\":\"app\"/, headers['X-Stripe-Client-User-Agent']) assert_match(/\"version\":\"1.0\"/, headers['X-Stripe-Client-User-Agent']) @@ -509,7 +560,7 @@ def test_adds_application_to_x_stripe_client_user_agent_header def test_successful_purchase_with_token_including_customer response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, 'cus_xxx|card_xxx') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/customer=cus_xxx/, data) assert_match(/card=card_xxx/, data) end.respond_with(successful_purchase_response) @@ -520,7 +571,7 @@ def test_successful_purchase_with_token_including_customer def test_successful_purchase_with_token response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, 'card_xxx') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/card=card_xxx/, data) end.respond_with(successful_purchase_response) @@ -530,7 +581,7 @@ def test_successful_purchase_with_token def test_successful_purchase_with_statement_description stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, statement_description: '5K RACE TICKET') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/statement_descriptor=5K\+RACE\+TICKET/, data) end.respond_with(successful_purchase_response) end @@ -581,7 +632,7 @@ def test_successful_void_with_metadata post.include?('metadata[first_value]=true') end.returns(successful_purchase_response(true)) - assert response = @gateway.void('ch_test_charge', {metadata: {first_value: true}}) + assert response = @gateway.void('ch_test_charge', { metadata: { first_value: true } }) assert_success response end @@ -590,7 +641,16 @@ def test_successful_void_with_reason post.include?('reason=fraudulent') end.returns(successful_purchase_response(true)) - assert response = @gateway.void('ch_test_charge', {reason: 'fraudulent'}) + assert response = @gateway.void('ch_test_charge', { reason: 'fraudulent' }) + assert_success response + end + + def test_successful_void_with_reverse_transfer + @gateway.expects(:ssl_request).with do |_, _, post, _| + post.include?('reverse_transfer=true') + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge', { reverse_transfer: true }) assert_success response end @@ -620,11 +680,11 @@ def test_unsuccessful_refund end def test_successful_refund_with_refund_application_fee - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('refund_application_fee=true') end.returns(successful_partially_refunded_response) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_application_fee => true) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_application_fee: true) assert_success response end @@ -659,18 +719,18 @@ def test_refund_with_expand_charge_only_sends_one_charge_expand end def test_successful_refund_with_metadata - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('metadata[first_value]=true') end.returns(successful_partially_refunded_response) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', {metadata: {first_value: true}}) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', { metadata: { first_value: true } }) assert_success response end def test_successful_refund_with_reverse_transfer stub_comms(@gateway, :ssl_request) do @gateway.refund(@amount, 'auth', reverse_transfer: true) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/reverse_transfer=true/, data) end.respond_with(successful_partially_refunded_response) end @@ -681,39 +741,45 @@ def test_successful_refund_with_refund_fee_amount @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) @gateway.expects(:ssl_request).returns(successful_partially_refunded_application_fee_response).in_sequence(s) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_fee_amount: 100) assert_success response end - # What is the significance of this??? it's to test that the first response is used as primary. so identical to above with an extra assertion - def test_refund_with_fee_response_gives_a_charge_authorization + def test_refund_with_fee_response_responds_with_the_refund_authorization s = sequence('request') @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) @gateway.expects(:ssl_request).returns(successful_partially_refunded_application_fee_response).in_sequence(s) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_fee_amount: 100) assert_success response assert_equal 're_test_refund', response.authorization end - def test_unsuccessful_refund_with_refund_fee_amount_when_application_fee_id_not_found + def test_successful_refund_with_failed_fee_refund_fetch s = sequence('request') @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) @gateway.expects(:ssl_request).returns(unsuccessful_fetch_application_fee_response).in_sequence(s) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) - assert_failure response - assert_match(/^Application fee id could not be retrieved/, response.message) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_fee_amount: 100) + assert_success response end - def test_unsuccessful_refund_with_refund_fee_amount_when_refunding_application_fee + def test_successful_refund_with_failed_fee_refund s = sequence('request') @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) @gateway.expects(:ssl_request).returns(generic_error_response).in_sequence(s) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_fee_amount: 100) + assert_success response + end + + def test_unsuccessful_refund_does_not_refund_fee + s = sequence('request') + @gateway.expects(:ssl_request).returns(generic_error_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', refund_fee_amount: 100) assert_failure response end @@ -810,6 +876,13 @@ def test_invalid_raw_response assert_match(/^Invalid response received from the Stripe API/, response.message) end + def test_invalid_login_test_transaction + gateway = StripeGateway.new(login: 'sk_live_3422') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Invalid API Key provided', response.message + end + def test_add_creditcard_with_credit_card post = {} @gateway.send(:add_creditcard, post, @credit_card, {}) @@ -880,7 +953,7 @@ def test_add_creditcard_with_card_token_and_customer def test_add_creditcard_with_card_token_and_track_data post = {} credit_card_token = 'card_2iD4AezYnNNzkW' - @gateway.send(:add_creditcard, post, credit_card_token, :track_data => 'Tracking data') + @gateway.send(:add_creditcard, post, credit_card_token, track_data: 'Tracking data') assert_equal 'Tracking data', post[:card][:swipe_data] end @@ -893,7 +966,8 @@ def test_add_creditcard_with_emv_credit_card def test_add_creditcard_pads_eci_value post = {} - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: '111111111100cryptogram', verification_value: nil, eci: '7' @@ -906,57 +980,87 @@ def test_add_creditcard_pads_eci_value def test_application_fee_is_submitted_for_purchase stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:application_fee => 144})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ application_fee: 144 })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/application_fee=144/, data) end.respond_with(successful_purchase_response) end def test_application_fee_is_submitted_for_capture stub_comms(@gateway, :ssl_request) do - @gateway.capture(@amount, 'ch_test_charge', @options.merge({:application_fee => 144})) - end.check_request do |method, endpoint, data, headers| + @gateway.capture(@amount, 'ch_test_charge', @options.merge({ application_fee: 144 })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/application_fee=144/, data) end.respond_with(successful_capture_response) end def test_exchange_rate_is_submitted_for_purchase stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:exchange_rate => 0.96251})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ exchange_rate: 0.96251 })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/exchange_rate=0.96251/, data) end.respond_with(successful_purchase_response) end def test_exchange_rate_is_submitted_for_capture stub_comms(@gateway, :ssl_request) do - @gateway.capture(@amount, 'ch_test_charge', @options.merge({:exchange_rate => 0.96251})) - end.check_request do |method, endpoint, data, headers| + @gateway.capture(@amount, 'ch_test_charge', @options.merge({ exchange_rate: 0.96251 })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/exchange_rate=0.96251/, data) end.respond_with(successful_capture_response) end def test_destination_is_submitted_for_purchase stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:destination => 'subaccountid'})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ destination: 'subaccountid' })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/destination\[account\]=subaccountid/, data) end.respond_with(successful_purchase_response) end def test_destination_amount_is_submitted_for_purchase stub_comms(@gateway, :ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:destination => 'subaccountid', :destination_amount => @amount - 20})) - end.check_request do |method, endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge({ destination: 'subaccountid', destination_amount: @amount - 20 })) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/destination\[amount\]=#{@amount - 20}/, data) end.respond_with(successful_purchase_response) end + def test_radar_session_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { + radar_session_id: 'test_radar_session_id' + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/radar_options\[session\]=test_radar_session_id/, data) + end.respond_with(successful_purchase_response) + end + + def test_radar_session_is_submitted_for_authorize + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, { + radar_session_id: 'test_radar_session_id' + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/radar_options\[session\]=test_radar_session_id/, data) + end.respond_with(successful_authorization_response) + end + + def test_skip_rules_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, { + skip_radar_rules: true + }) + end.check_request do |_method, _endpoint, data, _headers| + assert_match(/radar_options\[skip_rules\]\[0\]=all/, data) + end.respond_with(successful_authorization_response) + end + def test_client_data_submitted_with_purchase stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:description => 'a test customer', :ip => '127.127.127.127', :user_agent => 'some browser', :order_id => '42', :email => 'foo@wonderfullyfakedomain.com', :receipt_email => 'receipt-receiver@wonderfullyfakedomain.com', :referrer =>'http://www.shopify.com'}) + updated_options = @options.merge({ description: 'a test customer', ip: '127.127.127.127', user_agent: 'some browser', order_id: '42', email: 'foo@wonderfullyfakedomain.com', receipt_email: 'receipt-receiver@wonderfullyfakedomain.com', referrer: 'http://www.shopify.com' }) @gateway.purchase(@amount, @credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/description=a\+test\+customer/, data) assert_match(/ip=127\.127\.127\.127/, data) assert_match(/user_agent=some\+browser/, data) @@ -971,9 +1075,9 @@ def test_client_data_submitted_with_purchase def test_client_data_submitted_with_purchase_without_email_or_order stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:description => 'a test customer', :ip => '127.127.127.127', :user_agent => 'some browser', :referrer =>'http://www.shopify.com'}) + updated_options = @options.merge({ description: 'a test customer', ip: '127.127.127.127', user_agent: 'some browser', referrer: 'http://www.shopify.com' }) @gateway.purchase(@amount, @credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/description=a\+test\+customer/, data) assert_match(/ip=127\.127\.127\.127/, data) assert_match(/user_agent=some\+browser/, data) @@ -985,9 +1089,9 @@ def test_client_data_submitted_with_purchase_without_email_or_order def test_client_data_submitted_with_metadata_in_options stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + updated_options = @options.merge({ metadata: { this_is_a_random_key_name: 'with a random value', i_made_up_this_key_too: 'canyoutell' }, order_id: '42', email: 'foo@wonderfullyfakedomain.com' }) @gateway.purchase(@amount, @credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) @@ -997,9 +1101,9 @@ def test_client_data_submitted_with_metadata_in_options def test_client_data_submitted_with_metadata_in_options_with_emv_credit_card_purchase stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + updated_options = @options.merge({ metadata: { this_is_a_random_key_name: 'with a random value', i_made_up_this_key_too: 'canyoutell' }, order_id: '42', email: 'foo@wonderfullyfakedomain.com' }) @gateway.purchase(@amount, @emv_credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) @@ -1010,9 +1114,9 @@ def test_client_data_submitted_with_metadata_in_options_with_emv_credit_card_pur def test_client_data_submitted_with_metadata_in_options_with_emv_credit_card_authorize stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + updated_options = @options.merge({ metadata: { this_is_a_random_key_name: 'with a random value', i_made_up_this_key_too: 'canyoutell' }, order_id: '42', email: 'foo@wonderfullyfakedomain.com' }) @gateway.authorize(@amount, @emv_credit_card, updated_options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) @@ -1025,7 +1129,7 @@ def test_quickchip_is_set_on_purchase stub_comms(@gateway, :ssl_request) do @emv_credit_card.read_method = 'contact_quickchip' @gateway.purchase(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/card\[processing_method\]=quick_chip/, data) end.respond_with(successful_purchase_response) end @@ -1034,13 +1138,13 @@ def test_quickchip_is_not_set_on_authorize stub_comms(@gateway, :ssl_request) do @emv_credit_card.read_method = 'contact_quickchip' @gateway.authorize(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| refute_match(/card\[processing_method\]=quick_chip/, data) end.respond_with(successful_purchase_response) end def test_add_address - post = {:card => {}} + post = { card: {} } @gateway.send(:add_address, post, @options) assert_equal @options[:billing_address][:zip], post[:card][:address_zip] assert_equal @options[:billing_address][:state], post[:card][:address_state] @@ -1063,9 +1167,34 @@ def test_add_statement_address assert_equal @options[:statement_address][:city], post[:statement_address][:city] end + def test_add_shipping_address + post = {} + + @gateway.send(:add_shipping_address, post, @credit_card, @options) + + assert_equal @options[:shipping_address][:zip], post[:shipping][:address][:postal_code] + assert_equal @options[:shipping_address][:state], post[:shipping][:address][:state] + assert_equal @options[:shipping_address][:address1], post[:shipping][:address][:line1] + assert_equal @options[:shipping_address][:address2], post[:shipping][:address][:line2] + assert_equal @options[:shipping_address][:country], post[:shipping][:address][:country] + assert_equal @options[:shipping_address][:city], post[:shipping][:address][:city] + assert_equal @options[:shipping_address][:name], post[:shipping][:name] + assert_equal @options[:shipping_address][:phone_number], post[:shipping][:phone] + end + + def test_shipping_address_not_added_if_no_name_present + post = {} + + options = @options.dup + options[:shipping_address] = options[:shipping_address].except(:name) + @gateway.send(:add_shipping_address, post, @credit_card, options) + + assert_empty post + end + def test_add_statement_address_returns_nil_if_required_fields_missing post = {} - [:address1, :city, :zip, :state].each do |required_key| + %i[address1 city zip state].each do |required_key| missing_required = @options.tap do |options| options[:statement_address].delete_if { |k| k == required_key } end @@ -1087,55 +1216,55 @@ def test_gateway_without_credentials end def test_metadata_header - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| - headers && headers['X-Stripe-Client-User-Metadata'] == {:ip => '1.1.1.1'}.to_json + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| + headers && headers['X-Stripe-Client-User-Metadata'] == { ip: '1.1.1.1' }.to_json }.returns(successful_purchase_response) - @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '1.1.1.1')) + @gateway.purchase(@amount, @credit_card, @options.merge(ip: '1.1.1.1')) end def test_optional_version_header - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| headers && headers['Stripe-Version'] == '2013-10-29' }.returns(successful_purchase_response) - @gateway.purchase(@amount, @credit_card, @options.merge(:version => '2013-10-29')) + @gateway.purchase(@amount, @credit_card, @options.merge(version: '2013-10-29')) end def test_optional_idempotency_key_header - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| headers && headers['Idempotency-Key'] == 'test123' }.returns(successful_purchase_response) - response = @gateway.purchase(@amount, @credit_card, @options.merge(:idempotency_key => 'test123')) + response = @gateway.purchase(@amount, @credit_card, @options.merge(idempotency_key: 'test123')) assert_success response end def test_optional_idempotency_on_void - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| headers && headers['Idempotency-Key'] == 'test123' }.returns(successful_purchase_response(true)) - response = @gateway.void('ch_test_charge', @options.merge(:idempotency_key => 'test123')) + response = @gateway.void('ch_test_charge', @options.merge(idempotency_key: 'test123')) assert_success response end def test_optional_idempotency_on_verify - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, _post, headers| headers && headers['Idempotency-Key'] == nil end.returns(successful_void_response) - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, _post, headers| headers && headers['Idempotency-Key'] == 'test123' end.returns(successful_authorization_response) - response = @gateway.verify(@credit_card, @options.merge(:idempotency_key => 'test123')) + response = @gateway.verify(@credit_card, @options.merge(idempotency_key: 'test123')) assert_success response end def test_initialize_gateway_with_version - @gateway = StripeGateway.new(:login => 'login', :version => '2013-12-03') - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + @gateway = StripeGateway.new(login: 'sk_test_login', version: '2013-12-03') + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| headers && headers['Stripe-Version'] == '2013-12-03' }.returns(successful_purchase_response) @@ -1145,7 +1274,7 @@ def test_initialize_gateway_with_version def test_track_data_and_traditional_should_be_mutually_exclusive stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[name\]/ assert data !~ /card\[swipe_data\]/ end.respond_with(successful_purchase_response) @@ -1153,7 +1282,7 @@ def test_track_data_and_traditional_should_be_mutually_exclusive stub_comms(@gateway, :ssl_request) do @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data !~ /card\[name\]/ assert data =~ /card\[swipe_data\]/ end.respond_with(successful_purchase_response) @@ -1162,7 +1291,7 @@ def test_track_data_and_traditional_should_be_mutually_exclusive def test_address_is_included_with_card_data stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[address_line1\]/ end.respond_with(successful_purchase_response) end @@ -1171,7 +1300,7 @@ def test_contactless_flag_is_included_with_emv_card_data stub_comms(@gateway, :ssl_request) do @emv_credit_card.read_method = 'contactless' @gateway.purchase(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[read_method\]=contactless/ end.respond_with(successful_purchase_response) end @@ -1180,7 +1309,7 @@ def test_contactless_magstripe_flag_is_included_with_emv_card_data stub_comms(@gateway, :ssl_request) do @emv_credit_card.read_method = 'contactless_magstripe' @gateway.purchase(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[read_method\]=contactless_magstripe_mode/ end.respond_with(successful_purchase_response) end @@ -1188,7 +1317,7 @@ def test_contactless_magstripe_flag_is_included_with_emv_card_data def test_contactless_flag_is_not_included_with_emv_card_data_by_default stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data !~ /card\[read_method\]=contactless/ && data !~ /card\[read_method\]=contactless_magstripe_mode/ end.respond_with(successful_purchase_response) end @@ -1198,18 +1327,18 @@ def test_encrypted_pin_is_included_with_emv_card_data @emv_credit_card.encrypted_pin_cryptogram = '8b68af72199529b8' @emv_credit_card.encrypted_pin_ksn = 'ffff0102628d12000001' @gateway.purchase(@amount, @emv_credit_card, @options) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /card\[encrypted_pin\]=8b68af72199529b8/ assert data =~ /card\[encrypted_pin_key_id\]=ffff0102628d12000001/ end.respond_with(successful_purchase_response) end def generate_options_should_allow_key - assert_equal({:key => '12345'}, generate_options({:key => '12345'})) + assert_equal({ key: '12345' }, generate_options({ key: '12345' })) end def test_passing_expand_parameters - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('expand[0]=balance_transaction') end.returns(successful_authorization_response) @@ -1219,17 +1348,17 @@ def test_passing_expand_parameters end def test_passing_expand_parameters_as_array - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('expand[0]=balance_transaction&expand[1]=customer') end.returns(successful_authorization_response) - @options[:expand] = [:balance_transaction, :customer] + @options[:expand] = %i[balance_transaction customer] @gateway.authorize(@amount, @credit_card, @options) end def test_recurring_flag_not_set_by_default - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| !post.include?('recurring') end.returns(successful_authorization_response) @@ -1237,7 +1366,7 @@ def test_recurring_flag_not_set_by_default end def test_passing_recurring_eci_sets_recurring_flag - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('recurring=true') end.returns(successful_authorization_response) @@ -1247,7 +1376,7 @@ def test_passing_recurring_eci_sets_recurring_flag end def test_passing_unknown_eci_does_not_set_recurring_flag - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| !post.include?('recurring') end.returns(successful_authorization_response) @@ -1257,7 +1386,7 @@ def test_passing_unknown_eci_does_not_set_recurring_flag end def test_passing_recurring_true_option_sets_recurring_flag - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| post.include?('recurring=true') end.returns(successful_authorization_response) @@ -1267,7 +1396,7 @@ def test_passing_recurring_true_option_sets_recurring_flag end def test_passing_recurring_false_option_does_not_set_recurring_flag - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, post, _headers| !post.include?('recurring') end.returns(successful_authorization_response) @@ -1278,8 +1407,8 @@ def test_passing_recurring_false_option_does_not_set_recurring_flag def test_new_attributes_are_included_in_update stub_comms(@gateway, :ssl_request) do - @gateway.send(:update, 'cus_3sgheFxeBgTQ3M', 'card_483etw4er9fg4vF3sQdrt3FG', { :name => 'John Smith', :exp_year => 2021, :exp_month => 6 }) - end.check_request do |method, endpoint, data, headers| + @gateway.send(:update, 'cus_3sgheFxeBgTQ3M', 'card_483etw4er9fg4vF3sQdrt3FG', { name: 'John Smith', exp_year: 2021, exp_month: 6 }) + end.check_request do |_method, endpoint, data, _headers| assert data == 'name=John+Smith&exp_year=2021&exp_month=6' assert endpoint.include? '/customers/cus_3sgheFxeBgTQ3M/cards/card_483etw4er9fg4vF3sQdrt3FG' end.respond_with(successful_update_credit_card_response) @@ -1287,6 +1416,7 @@ def test_new_attributes_are_included_in_update def test_scrub assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_nested_payment_method_data), post_scrubbed_nested_payment_method_data end def test_scrubs_track_data @@ -1297,18 +1427,23 @@ def test_scrubs_emv_data assert_equal @gateway.scrub(pre_scrubbed_with_emv_data), post_scrubbed_with_emv_data end + def test_scrubs_account_number + assert_equal @gateway.scrub(pre_scrubbed_with_account_number), post_scrubbed_with_account_number + end + def test_supports_scrubbing? assert @gateway.supports_scrubbing? end def test_successful_auth_with_network_tokenization_apple_pay - @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + @gateway.expects(:ssl_request).with do |method, _endpoint, data, _headers| assert_equal :post, method assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=apple_pay', data true end.returns(successful_authorization_response) - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: '111111111100cryptogram', verification_value: nil, eci: '05' @@ -1323,13 +1458,14 @@ def test_successful_auth_with_network_tokenization_apple_pay end def test_successful_auth_with_network_tokenization_android_pay - @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + @gateway.expects(:ssl_request).with do |method, _endpoint, data, _headers| assert_equal :post, method assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=android_pay', data true end.returns(successful_authorization_response) - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: '111111111100cryptogram', verification_value: nil, eci: '05', @@ -1345,13 +1481,14 @@ def test_successful_auth_with_network_tokenization_android_pay end def test_successful_purchase_with_network_tokenization_apple_pay - @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + @gateway.expects(:ssl_request).with do |method, _endpoint, data, _headers| assert_equal :post, method assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=apple_pay', data true end.returns(successful_authorization_response) - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: '111111111100cryptogram', verification_value: nil, eci: '05' @@ -1366,13 +1503,14 @@ def test_successful_purchase_with_network_tokenization_apple_pay end def test_successful_purchase_with_network_tokenization_android_pay - @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + @gateway.expects(:ssl_request).with do |method, _endpoint, data, _headers| assert_equal :post, method assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=android_pay', data true end.returns(successful_authorization_response) - credit_card = network_tokenization_credit_card('4242424242424242', + credit_card = network_tokenization_credit_card( + '4242424242424242', payment_cryptogram: '111111111100cryptogram', verification_value: nil, eci: '05', @@ -1394,7 +1532,7 @@ def test_supports_network_tokenization def test_emv_capture_application_fee_ignored response = stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, 'ch_test_charge', application_fee: 100, icc_data: @emv_credit_card.icc_data) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data !~ /application_fee/, 'request should not include application_fee' end.respond_with(successful_capture_response_with_icc_data) @@ -1404,15 +1542,25 @@ def test_emv_capture_application_fee_ignored def test_authorization_with_emv_payment_application_fee_included response = stub_comms(@gateway, :ssl_request) do @gateway.authorize(@amount, 'ch_test_charge', application_fee: 100, icc_data: @emv_credit_card.icc_data) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert data =~ /application_fee/, 'request should include application_fee' end.respond_with(successful_capture_response_with_icc_data) assert_success response end + def test_authorization_with_emv_payment_sets_capture_to_false + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, 'ch_test_charge', application_fee: 100, icc_data: @emv_credit_card.icc_data) + end.check_request do |_method, _endpoint, data, _headers| + assert data =~ /capture\=false/, 'request should set capture to false' + end.respond_with(successful_capture_response_with_icc_data) + + assert_success response + end + def test_passing_stripe_account_header - @gateway.expects(:ssl_request).with do |method, url, post, headers| + @gateway.expects(:ssl_request).with do |_method, _url, _post, headers| headers.include?('Stripe-Account') end.returns(successful_authorization_response) @@ -1451,7 +1599,7 @@ def test_webhook_creation def test_webhook_deletion @gateway.expects(:ssl_request).twice.returns(webhook_event_creation_response, webhook_event_deletion_response) webhook = @gateway.send(:create_webhook_endpoint, @options.merge(@threeds_options), ['source.chargeable']) - response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(webhook_id: webhook.params['id'])) assert_equal response.params['id'], webhook.params['id'] assert_equal true, response.params['deleted'] end @@ -1513,6 +1661,35 @@ def pre_scrubbed PRE_SCRUBBED end + def pre_scrubbed_nested_payment_method_data + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF9oQkwwTXF6ZGZ6Rnk3OXU0cFloUmVhQlo6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100¤cy=usd&payment_method_data[card][number]=4242424242424242&payment_method_data[card][exp_month]=9&payment_method_data[card][exp_year]=2015&payment_method_data[card][cvc]=123&payment_method_data[card][name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&payment_method_data[card][cryptogram]=sensitive_data&three_d_secure[cryptogram]=123456789abcdefghijklmnop&three_d_secure[apple_pay]=true" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 02 Dec 2014 19:44:17 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1303\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: 89de951c-f880-4c39-93b0-832b3cc6dd32\r\n" + -> "Stripe-Version: 2013-12-03\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1303 bytes... + -> "{\n \"id\": \"ch_155MZJ2gKyKnHxtY1dGqFhSb\",\n \"object\": \"charge\",\n \"created\": 1417549457,\n \"livemode\": false,\n \"paid\": true,\n \"amount\": 100,\n \"currency\": \"usd\",\n \"refunded\": false,\n \"captured\": true,\n \"refunds\": [],\n \"card\": {\n \"id\": \"card_155MZJ2gKyKnHxtYihrJ8z94\",\n \"object\": \"card\",\n \"last4\": \"4242\",\n \"brand\": \"Visa\",\n \"funding\": \"credit\",\n \"exp_month\": 9,\n \"exp_year\": 2015,\n \"fingerprint\": \"944LvWcY01HVTbVc\",\n \"country\": \"US\",\n \"name\": \"Longbob Longsen\",\n \"address_line1\": null,\n \"address_line2\": null,\n \"address_city\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_country\": null,\n \"cvc_check\": \"pass\",\n \"address_line1_check\": null,\n \"address_zip_check\": null,\n \"dynamic_last4\": null,\n \"customer\": null,\n \"type\": \"Visa\"\n },\n \"balance_transaction\": \"txn_155MZJ2gKyKnHxtYxpYDI5OW\",\n \"failure_message\": null,\n \"failure_code\": null,\n \"amount_refunded\": 0,\n \"customer\": null,\n \"invoice\": null,\n \"description\": \"ActiveMerchant Test Purchase\",\n \"dispute\": null,\n \"metadata\": {\n \"email\": \"wow@example.com\"\n },\n \"statement_description\": null,\n \"fraud_details\": {\n \"stripe_report\": \"unavailable\",\n \"user_report\": null\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"shipping\": null\n}\n" + read 1303 bytes + Conn close + PRE_SCRUBBED + end + def pre_scrubbed_with_track_data <<-PRE_SCRUBBED opening connection to api.stripe.com:443... @@ -1572,6 +1749,63 @@ def pre_scrubbed_with_emv_data PRE_SCRUBBED end + def pre_scrubbed_with_account_number + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/tokens?bank_account[account_number]=000123456789&bank_account[country]=US&bank_account[currency]=usd&bank_account[routing_number]=110000000&bank_account[name]=Jim+Smith&bank_account[account_holder_type]=individual HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF8zT0Q0VGRLU0lPaERPTDIxNDZKSmNDNzk6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.123.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.123.0\",\"lang\":\"ruby\",\"lang_version\":\"2.6.6 p146 (2020-03-31)\",\"platform\":\"x86_64-darwin20\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.stripe.com\r\nContent-Length: 0\r\n\r\n" + <- "" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 06 Oct 2021 18:51:30 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 610\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "request-id: req_cueTxQR09SjIOA\r\n" + -> "stripe-version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 610 bytes... + -> "{\n \"id\": \"btok_1JhfDCAWOtgoysogF0IbYRWH\",\n \"object\": \"token\",\n \"bank_account\": {\n \"id\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"object\": \"bank_account\",\n \"account_holder_name\": \"Jim Smith\",\n \"account_holder_type\": \"individual\",\n \"account_type\": null,\n \"bank_name\": \"STRIPE TEST BANK\",\n \"country\": \"US\",\n \"currency\": \"usd\",\n \"fingerprint\": \"uCkXlMFxqys7GosR\",\n \"last4\": \"6789\",\n \"name\": \"Jim Smith\",\n \"routing_number\": \"110000000\",\n \"status\": \"new\"\n },\n \"client_ip\": \"172.74.90.160\",\n \"created\": 1633546290,\n \"livemode\": false,\n \"type\": \"bank_account\",\n \"used\": false\n}\n" + read 610 bytes + Conn close + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/customers HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF8zT0Q0VGRLU0lPaERPTDIxNDZKSmNDNzk6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.123.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.123.0\",\"lang\":\"ruby\",\"lang_version\":\"2.6.6 p146 (2020-03-31)\",\"platform\":\"x86_64-darwin20\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.stripe.com\r\nContent-Length: 36\r\n\r\n" + <- "source=btok_1JhfDCAWOtgoysogF0IbYRWH" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 06 Oct 2021 18:51:31 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1713\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "request-id: req_CfMAs2B351RDr0\r\n" + -> "stripe-version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 1713 bytes... + -> "{\n \"id\": \"cus_KMNzQZK4SN7Agn\",\n \"object\": \"customer\",\n \"account_balance\": 0,\n \"address\": null,\n \"balance\": 0,\n \"created\": 1633546290,\n \"currency\": null,\n \"default_source\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"delinquent\": false,\n \"description\": null,\n \"discount\": null,\n \"email\": null,\n \"invoice_prefix\": \"02D58981\",\n \"invoice_settings\": {\n \"custom_fields\": null,\n \"default_payment_method\": null,\n \"footer\": null\n },\n \"livemode\": false,\n \"metadata\": {\n },\n \"name\": null,\n \"next_invoice_sequence\": 1,\n \"phone\": null,\n \"preferred_locales\": [\n\n ],\n \"shipping\": null,\n \"sources\": {\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"object\": \"bank_account\",\n \"account_holder_name\": \"Jim Smith\",\n \"account_holder_type\": \"individual\",\n \"account_type\": null,\n \"bank_name\": \"STRIPE TEST BANK\",\n \"country\": \"US\",\n \"currency\": \"usd\",\n \"customer\": \"cus_KMNzQZK4SN7Agn\",\n \"fingerprint\": \"uCkXlMFxqys7GosR\",\n \"last4\": \"6789\",\n \"metadata\": {\n },\n \"name\": \"Jim Smith\",\n \"routing_number\": \"110000000\",\n \"status\": \"new\"\n }\n ],\n \"has_more\": false,\n \"total_count\": 1,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/sources\"\n },\n \"subscriptions\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/subscriptions\"\n },\n \"tax_exempt\": \"none\",\n \"tax_ids\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/tax_ids\"\n },\n \"tax_info\": null,\n \"tax_info_verification\": null\n}\n" + read 1713 bytes + Conn close + PRE_SCRUBBED + end + def post_scrubbed_with_emv_data <<-POST_SCRUBBED opening connection to api.stripe.com:443... @@ -1660,6 +1894,92 @@ def post_scrubbed POST_SCRUBBED end + def post_scrubbed_nested_payment_method_data + <<-POST_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100¤cy=usd&payment_method_data[card][number]=[FILTERED]&payment_method_data[card][exp_month]=9&payment_method_data[card][exp_year]=2015&payment_method_data[card][cvc]=[FILTERED]&payment_method_data[card][name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&payment_method_data[card][cryptogram]=[FILTERED]&three_d_secure[cryptogram]=[FILTERED]&three_d_secure[apple_pay]=true" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 02 Dec 2014 19:44:17 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1303\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: 89de951c-f880-4c39-93b0-832b3cc6dd32\r\n" + -> "Stripe-Version: 2013-12-03\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1303 bytes... + -> "{\n \"id\": \"ch_155MZJ2gKyKnHxtY1dGqFhSb\",\n \"object\": \"charge\",\n \"created\": 1417549457,\n \"livemode\": false,\n \"paid\": true,\n \"amount\": 100,\n \"currency\": \"usd\",\n \"refunded\": false,\n \"captured\": true,\n \"refunds\": [],\n \"card\": {\n \"id\": \"card_155MZJ2gKyKnHxtYihrJ8z94\",\n \"object\": \"card\",\n \"last4\": \"4242\",\n \"brand\": \"Visa\",\n \"funding\": \"credit\",\n \"exp_month\": 9,\n \"exp_year\": 2015,\n \"fingerprint\": \"944LvWcY01HVTbVc\",\n \"country\": \"US\",\n \"name\": \"Longbob Longsen\",\n \"address_line1\": null,\n \"address_line2\": null,\n \"address_city\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_country\": null,\n \"cvc_check\": \"pass\",\n \"address_line1_check\": null,\n \"address_zip_check\": null,\n \"dynamic_last4\": null,\n \"customer\": null,\n \"type\": \"Visa\"\n },\n \"balance_transaction\": \"txn_155MZJ2gKyKnHxtYxpYDI5OW\",\n \"failure_message\": null,\n \"failure_code\": null,\n \"amount_refunded\": 0,\n \"customer\": null,\n \"invoice\": null,\n \"description\": \"ActiveMerchant Test Purchase\",\n \"dispute\": null,\n \"metadata\": {\n \"email\": \"wow@example.com\"\n },\n \"statement_description\": null,\n \"fraud_details\": {\n \"stripe_report\": \"unavailable\",\n \"user_report\": null\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"shipping\": null\n}\n" + read 1303 bytes + Conn close + POST_SCRUBBED + end + + def post_scrubbed_with_account_number + <<-POST_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/tokens?bank_account[account_number]=[FILTERED]&bank_account[country]=US&bank_account[currency]=usd&bank_account[routing_number]=110000000&bank_account[name]=Jim+Smith&bank_account[account_holder_type]=individual HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.123.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.123.0\",\"lang\":\"ruby\",\"lang_version\":\"2.6.6 p146 (2020-03-31)\",\"platform\":\"x86_64-darwin20\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.stripe.com\r\nContent-Length: 0\r\n\r\n" + <- "" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 06 Oct 2021 18:51:30 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 610\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "request-id: req_cueTxQR09SjIOA\r\n" + -> "stripe-version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 610 bytes... + -> "{\n \"id\": \"btok_1JhfDCAWOtgoysogF0IbYRWH\",\n \"object\": \"token\",\n \"bank_account\": {\n \"id\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"object\": \"bank_account\",\n \"account_holder_name\": \"Jim Smith\",\n \"account_holder_type\": \"individual\",\n \"account_type\": null,\n \"bank_name\": \"STRIPE TEST BANK\",\n \"country\": \"US\",\n \"currency\": \"usd\",\n \"fingerprint\": \"uCkXlMFxqys7GosR\",\n \"last4\": \"6789\",\n \"name\": \"Jim Smith\",\n \"routing_number\": \"110000000\",\n \"status\": \"new\"\n },\n \"client_ip\": \"172.74.90.160\",\n \"created\": 1633546290,\n \"livemode\": false,\n \"type\": \"bank_account\",\n \"used\": false\n}\n" + read 610 bytes + Conn close + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256 + <- "POST /v1/customers HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.123.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.123.0\",\"lang\":\"ruby\",\"lang_version\":\"2.6.6 p146 (2020-03-31)\",\"platform\":\"x86_64-darwin20\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nHost: api.stripe.com\r\nContent-Length: 36\r\n\r\n" + <- "source=btok_1JhfDCAWOtgoysogF0IbYRWH" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 06 Oct 2021 18:51:31 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1713\r\n" + -> "Connection: close\r\n" + -> "access-control-allow-credentials: true\r\n" + -> "access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "access-control-allow-origin: *\r\n" + -> "access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required\r\n" + -> "access-control-max-age: 300\r\n" + -> "cache-control: no-cache, no-store\r\n" + -> "request-id: req_CfMAs2B351RDr0\r\n" + -> "stripe-version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains; preload\r\n" + -> "\r\n" + reading 1713 bytes... + -> "{\n \"id\": \"cus_KMNzQZK4SN7Agn\",\n \"object\": \"customer\",\n \"account_balance\": 0,\n \"address\": null,\n \"balance\": 0,\n \"created\": 1633546290,\n \"currency\": null,\n \"default_source\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"delinquent\": false,\n \"description\": null,\n \"discount\": null,\n \"email\": null,\n \"invoice_prefix\": \"02D58981\",\n \"invoice_settings\": {\n \"custom_fields\": null,\n \"default_payment_method\": null,\n \"footer\": null\n },\n \"livemode\": false,\n \"metadata\": {\n },\n \"name\": null,\n \"next_invoice_sequence\": 1,\n \"phone\": null,\n \"preferred_locales\": [\n\n ],\n \"shipping\": null,\n \"sources\": {\n \"object\": \"list\",\n \"data\": [\n {\n \"id\": \"ba_1JhfDCAWOtgoysogLB5vljcp\",\n \"object\": \"bank_account\",\n \"account_holder_name\": \"Jim Smith\",\n \"account_holder_type\": \"individual\",\n \"account_type\": null,\n \"bank_name\": \"STRIPE TEST BANK\",\n \"country\": \"US\",\n \"currency\": \"usd\",\n \"customer\": \"cus_KMNzQZK4SN7Agn\",\n \"fingerprint\": \"uCkXlMFxqys7GosR\",\n \"last4\": \"6789\",\n \"metadata\": {\n },\n \"name\": \"Jim Smith\",\n \"routing_number\": \"110000000\",\n \"status\": \"new\"\n }\n ],\n \"has_more\": false,\n \"total_count\": 1,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/sources\"\n },\n \"subscriptions\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/subscriptions\"\n },\n \"tax_exempt\": \"none\",\n \"tax_ids\": {\n \"object\": \"list\",\n \"data\": [\n\n ],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/customers/cus_KMNzQZK4SN7Agn/tax_ids\"\n },\n \"tax_info\": null,\n \"tax_info_verification\": null\n}\n" + read 1713 bytes + Conn close + POST_SCRUBBED + end + def successful_new_customer_response <<-RESPONSE { @@ -1938,6 +2258,173 @@ def successful_capture_response RESPONSE end + def successful_capture_response_without_icc_data + <<-RESPONSE + { + "id": "ch_test_emv_charge", + "object": "charge", + "amount": 1000, + "amount_refunded": 0, + "application": "ca_37BT8jOfv0Cu42Vfd", + "application_fee": "fee_test_fee", + "application_fee_amount": 55, + "authorization_code": "123456", + "balance_transaction": { + "id": "txn_1FSQUtFwNjfzuchLeWPB9X8K", + "object": "balance_transaction", + "amount": 1000, + "available_on": 1571184000, + "created": 1570809463, + "currency": "usd", + "description": null, + "exchange_rate": null, + "fee": 55, + "fee_details": [ + { + "amount": 55, + "application": "ca_37BT8jOfv0Cu42Vfd", + "currency": "usd", + "description": "application fee", + "type": "application_fee" + } + ], + "net": 945, + "source": "ch_test_emv_charge", + "sourced_transfers": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/transfers?source_transaction=ch_test_emv_charge" + }, + "status": "pending", + "type": "charge" + }, + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "captured": true, + "created": 1570809458, + "currency": "usd", + "customer": null, + "description": null, + "destination": null, + "dispute": null, + "failure_code": null, + "failure_message": null, + "fraud_details": { + }, + "invoice": null, + "livemode": false, + "metadata": { + "card_read_method": "contact", + "shop_id": "1", + "shop_name": "Shop 1", + "transaction_fee_total_amount": "55", + "transaction_fee_tax_amount": "0", + "payments_charge_id": "86", + "order_transaction_id": "149", + "order_id": "c62.1" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 2, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": null, + "payment_method": "card_1FSQUo", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": null + }, + "country": "US", + "ds_transaction_id": null, + "exp_month": 12, + "exp_year": 2022, + "fingerprint": "PgHpMSUia1FIuXM6", + "funding": "unknown", + "installments": null, + "last4": "0119", + "moto": null, + "network": "visa", + "network_transaction_id": "ihQUSGFPfpVpg6J3", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/acct_1FPxOBFwNjfzuchL/ch_test_emv_charge/rcpt_FyNFASxNs66DXe7zTe9jW9hpYZkQda9", + "refunded": false, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_test_emv_charge/refunds" + }, + "review": null, + "shipping": null, + "source": { + "id": "card_1FSQUo", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": null, + "cvc_check": null, + "dynamic_last4": null, + "emv_auth_data": "8A023030", + "exp_month": 12, + "exp_year": 2022, + "fingerprint": "ihQUSGFPfpVpg6J3", + "funding": "unknown", + "last4": "0119", + "metadata": { + }, + "name": null, + "tokenization_method": null + }, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + def successful_capture_response_with_icc_data <<-RESPONSE { @@ -2016,7 +2503,7 @@ def successful_capture_response_with_icc_data RESPONSE end - def successful_purchase_response(refunded=false) + def successful_purchase_response(refunded = false) <<-RESPONSE { "amount": 400, diff --git a/test/unit/gateways/sum_up_test.rb b/test/unit/gateways/sum_up_test.rb new file mode 100644 index 00000000000..5fc1995f19c --- /dev/null +++ b/test/unit/gateways/sum_up_test.rb @@ -0,0 +1,494 @@ +require 'test_helper' + +class SumUpTest < Test::Unit::TestCase + def setup + @gateway = SumUpGateway.new( + access_token: 'sup_sk_ABC123', + pay_to_email: 'example@example.com' + ) + @credit_card = credit_card + @amount = 100 + + @options = { + payment_type: 'card', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_create_checkout_response) + @gateway.expects(:ssl_request).returns(successful_complete_checkout_response) + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + + assert_equal 'PENDING', response.message + refute_empty response.params + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_complete_checkout_array_response) + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + + assert_equal SumUpGateway::STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd') + assert_success response + assert_equal 'EXPIRED', response.message + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd22') + assert_failure response + assert_equal 'Resource not found', response.message + assert_equal 'NOT_FOUND', response.error_code + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + response = @gateway.refund(nil, 'c0887be5-9fd2-4018-a531-e573e0298fdd22') + assert_failure response + assert_equal 'The transaction is not refundable in its current state', response.message + assert_equal 'CONFLICT', response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_success_from + response = @gateway.send(:parse, successful_complete_checkout_response) + success_from = @gateway.send(:success_from, response.symbolize_keys) + assert_equal true, success_from + end + + def test_message_from + response = @gateway.send(:parse, successful_complete_checkout_response) + message_from = @gateway.send(:message_from, response.symbolize_keys) + assert_equal 'PENDING', message_from + end + + def test_authorization_from + response = @gateway.send(:parse, successful_complete_checkout_response) + authorization_from = @gateway.send(:authorization_from, response.symbolize_keys) + assert_equal '8d8336a1-32e2-4f96-820a-5c9ee47e76fc', authorization_from + end + + def test_format_multiple_errors + responses = @gateway.send(:parse, failed_complete_checkout_array_response) + error_code = @gateway.send(:format_multiple_errors, responses) + assert_equal format_multiple_errors_response, error_code + end + + def test_error_code_from + response = @gateway.send(:parse, failed_complete_checkout_response) + error_code_from = @gateway.send(:error_code_from, response.symbolize_keys) + assert_equal 'CHECKOUT_SESSION_IS_EXPIRED', error_code_from + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.sumup.com:443... + opened + starting SSL for api.sumup.com:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- \"POST /v0.1/checkouts HTTP/1.1\\r\ + Content-Type: application/json\\r\ + Authorization: Bearer sup_sk_ABC123\\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.sumup.com\\r\ + Content-Length: 422\\r\ + \\r\ + \" + <- \"{\\\"pay_to_email\\\":\\\"example@example.com\\\",\\\"redirect_url\\\":null,\\\"return_url\\\":null,\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"USD\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"personal_details\\\":{\\\"address\\\":{\\\"city\\\":\\\"Ottawa\\\",\\\"state\\\":\\\"ON\\\",\\\"country\\\":\\\"CA\\\",\\\"line_1\\\":\\\"456 My Street\\\",\\\"postal_code\\\":\\\"K1C2N6\\\"},\\\"email\\\":null,\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\",\\\"tax_id\\\":null},\\\"customer_id\\\":null}\" + -> \"HTTP/1.1 201 Created\\r\ + \" + -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\ + \" + -> \"Content-Type: application/json;charset=UTF-8\\r\ + \" + -> \"Content-Length: 360\\r\ + \" + -> \"Connection: close\\r\ + \" + -> \"x-powered-by: Express\\r\ + \" + -> \"access-control-allow-origin: *\\r\ + \" + -> \"x-fong-id: 723b20084f2c, 723b20084f2c, 723b20084f2c 5df223126f1c\\r\ + \" + -> \"cf-cache-status: DYNAMIC\\r\ + \" + -> \"vary: Accept-Encoding\\r\ + \" + -> \"apigw-requestid: LOyHiheuDoEEJSA=\\r\ + \" + -> \"set-cookie: __cf_bm=1unGPonmyW_H8VRqo.O6h20hrSJ_0GtU3VqD9i3uYkI-1694668540-0-AaYQ1MVLyKxcwSNy8oNS5t/uVdk5ZU6aFPI/yvVcohm0Fm+Kltk55ngpG/Bms3cvRtxVX9DidO4ziiP2IsQcM41uJZq6TrcgLUD7KbJfJwV8; path=/; expires=Thu, 14-Sep-23 05:45:40 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"x-op-gateway: true\\r\ + \" + -> \"Set-Cookie: __cf_bm=OYzsPf_HGhiUfF0EETH_zOM74zPZpYhmqI.FJxehmpY-1694668541-0-AWVAexX304k53VB3HIhdyg+uP4ElzrS23jwIAdPGccfN5DM/81TE0ioW7jb7kA3jCZDuGENGofaZz0pBwSr66lRiWu9fdAzdUIbwNDOBivWY; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"Server: cloudflare\\r\ + \" + -> \"CF-RAY: 80662747af463995-BOG\\r\ + \" + -> \"\\r\ + \" + reading 360 bytes... + -> \"{\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":1.0,\\\"currency\\\":\\\"USD\\\",\\\"pay_to_email\\\":\\\"example@example.com\\\",\\\"merchant_code\\\":\\\"MTVU2XGK\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"id\\\":\\\"70f71869-ed81-40b0-b2d8-c98f80f4c39d\\\",\\\"status\\\":\\\"PENDING\\\",\\\"date\\\":\\\"2023-09-14T05:15:40.000+00:00\\\",\\\"merchant_name\\\":\\\"Spreedly\\\",\\\"purpose\\\":\\\"CHECKOUT\\\",\\\"transactions\\\":[]}\" + read 360 bytes + Conn close + opening connection to api.sumup.com:443... + opened + starting SSL for api.sumup.com:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- \"PUT /v0.1/checkouts/70f71869-ed81-40b0-b2d8-c98f80f4c39d HTTP/1.1\\r\ + Content-Type: application/json\\r\ + Authorization: Bearer sup_sk_ABC123\\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.sumup.com\\r\ + Content-Length: 136\\r\ + \\r\ + \" + <- \"{\\\"payment_type\\\":\\\"card\\\",\\\"card\\\":{\\\"name\\\":\\\"Longbob Longsen\\\",\\\"number\\\":\\\"4000100011112224\\\",\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"24\\\",\\\"cvv\\\":\\\"123\\\"}}\" + -> \"HTTP/1.1 200 OK\\r\ + \" + -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\ + \" + -> \"Content-Type: application/json\\r\ + \" + -> \"Transfer-Encoding: chunked\\r\ + \" + -> \"Connection: close\\r\ + \" + -> \"x-powered-by: Express\\r\ + \" + -> \"access-control-allow-origin: *\\r\ + \" + -> \"x-fong-id: 8a116d29420e, 8a116d29420e, 8a116d29420e a534b6871710\\r\ + \" + -> \"cf-cache-status: DYNAMIC\\r\ + \" + -> \"vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers\\r\ + \" + -> \"apigw-requestid: LOyHoggJjoEEMxA=\\r\ + \" + -> \"set-cookie: __cf_bm=AoWMlPJNg1_THatbGnZchhj7K0QaqwlU0SqYrlDJ.78-1694668541-0-AdHrPpd/94p0oyLJWzsEUYatqVZMiJ0i1BJICEiprAo8AMDiya+V3OjljwbCpaNQNAPFVJpX1S4KxIFEUEeeNfAJv1HOjjaToNYhJuhLQ1NT; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"x-op-gateway: true\\r\ + \" + -> \"Set-Cookie: __cf_bm=UcJRX.Pe233lWIyCGlqNICBOhruxwESN41sDCDfzQBQ-1694668541-0-ASJ/Wl84HRovjKIq/p+Re8GrxkxHM1XvbDE/mXT/4r7PYA1cpTzG2uhp7WEkqVpEj7FCb2ahP5ExApEWWx0JDut8Uhx1SeQJHYFR/26E8BTv; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"Server: cloudflare\\r\ + \" + -> \"CF-RAY: 8066274e3a95399b-BOG\\r\ + \" + -> \"Content-Encoding: gzip\\r\ + \" + -> \"\\r\ + \" + -> \"1bc\\r\ + \" + reading 444 bytes... + -> \"\\x1F\\x8B\\b\\x00\\x00\\x00\\x00\\x00\\x00\\x03|\\x92[\\x8B\\xDB0\\x10\\x85\\xFFJ\\x99\\xD7ZA\\x92\\x15G\\xD6S!1\\xDB\\xB2\\xCD\\x85\\x8D]RJ1\\xB2$wMm\\xD9Hr\\xC1,\\xFB\\xDF\\x8B\\xF6R\\x1A\\xBA\\xF4\\xF50G\\xF3\\xCD9z\\x00uo\\xD4\\xCFq\\x0E\\xB53\\xADq\\xC6*\\x03\\x02\\bS\\x9C\\xD0V!\\x96\\xF1\\x1C\\xB1\\x86K$\\x99\\xDE \\xA3)i\\xDAT\\xA5y\\xDBB\\x02r\\x18g\\e@\\x90\\x15N@\\xCD.\\xDA\\x17\\x10P\\x9Dw\\x90\\xC0$\\x97:\\x8C\\xB5\\x19d\\xD7\\x83\\x80\\xCE\\x06\\xF3\\xC3\\xC9\\xD0\\x8D\\xD6\\x7F\\xF0\\x933F\\xF7\\xCBJ\\x8D\\x03$0\\x18\\xA7\\xEE\\xA5\\r\\xB5\\x1Au\\xDC\\xBF/\\xBFT\\xF4rs\\v\\th\\xE3\\x95\\xEB\\xA6h\\x03\\x01\\xE70:\\xF3\\xEE4\\xC7qo \\x81N\\x83\\x80\\rn7\\x84g92\\x9A\\x13\\xC4p\\x83QC5G*\\xE7-\\xC7-Si\\xAE!\\x01\\x1Fd\\x98=\\b8\\x15\\x87\\xDD\\xA7\\xC3M|]\\x86\\xB8\\x8Fb\\x9A\\\"\\x9C#\\xC2J\\xBC\\x16d-\\x18^a\\x8C\\xDFc,0\\xFE\\x9B\\xCF\\xCA!\\xCE\\x9F_\\xF0\\xE3\\x95\\xB3\\x9BF\\x1F\\xC5\\xED\\xC7b{{\\xACJH 8i\\xBDTO\\xB7\\x82\\xF8\\xF6\\xF0\\x8C\\x893\\xCD\\x15[S\\xD4\\xB2\\xD4 \\x96R\\x8E8\\xC7\" + -> \")\\xE2\\xBAU\\x9A\\xF0\\x94\\xD0&\\xBD6\\xBF\\xE6Q\\xEE(\\xADN\\x97\\xCF\\x97\\xF2\\xFFa]\\x15\\xF2K\\x86\\xFAU\\xC0Q\\b\\xDDt-\\xFCSY\\xE8\\x06\\xE3\\x83\\x1C\\xA673!+\\xC6\\xF3?\\x99\\xBC\\x91\\xE6$\\x97\\xC1\\xD8P\\x87e\\x8A`\\xC5\\xF6\\xB8\\x87\\x04\\x8C\\rn\\xA9\\x87g\\xD8mu.\\x8F\\xFB\\xE2\\xAE.\\x0E\\xE5\\xDD\\xD7X\\xA0\\xF5A\\xF6}\\xF4\\xF9Z\\xBD\\xE0'O\\xBF\\xC5Y\\xD9\\xD71\\xB95\\xC9\\xE8\\x06\\xA7,\\xA3\\x8F\\xDF\\x1F\\x7F\\x03\\x00\\x00\\xFF\\xFF\\x03\\x00\\xB5\\x12\\xCA\\x11\\xB3\\x02\\x00\\x00\" + read 444 bytes + reading 2 bytes... + -> \"\\r\ + \" + read 2 bytes + -> \"0\\r\ + \" + -> \"\\r\ + \" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.sumup.com:443... + opened + starting SSL for api.sumup.com:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- \"POST /v0.1/checkouts 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.sumup.com\\r\ + Content-Length: 422\\r\ + \\r\ + \" + <- \"{\\\"pay_to_email\\\":\\\"[FILTERED]\",\\\"redirect_url\\\":null,\\\"return_url\\\":null,\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":\\\"1.00\\\",\\\"currency\\\":\\\"USD\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"personal_details\\\":{\\\"address\\\":{\\\"city\\\":\\\"Ottawa\\\",\\\"state\\\":\\\"ON\\\",\\\"country\\\":\\\"CA\\\",\\\"line_1\\\":\\\"456 My Street\\\",\\\"postal_code\\\":\\\"K1C2N6\\\"},\\\"email\\\":null,\\\"first_name\\\":\\\"Longbob\\\",\\\"last_name\\\":\\\"Longsen\\\",\\\"tax_id\\\":null},\\\"customer_id\\\":null}\" + -> \"HTTP/1.1 201 Created\\r\ + \" + -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\ + \" + -> \"Content-Type: application/json;charset=UTF-8\\r\ + \" + -> \"Content-Length: 360\\r\ + \" + -> \"Connection: close\\r\ + \" + -> \"x-powered-by: Express\\r\ + \" + -> \"access-control-allow-origin: *\\r\ + \" + -> \"x-fong-id: 723b20084f2c, 723b20084f2c, 723b20084f2c 5df223126f1c\\r\ + \" + -> \"cf-cache-status: DYNAMIC\\r\ + \" + -> \"vary: Accept-Encoding\\r\ + \" + -> \"apigw-requestid: LOyHiheuDoEEJSA=\\r\ + \" + -> \"set-cookie: __cf_bm=1unGPonmyW_H8VRqo.O6h20hrSJ_0GtU3VqD9i3uYkI-1694668540-0-AaYQ1MVLyKxcwSNy8oNS5t/uVdk5ZU6aFPI/yvVcohm0Fm+Kltk55ngpG/Bms3cvRtxVX9DidO4ziiP2IsQcM41uJZq6TrcgLUD7KbJfJwV8; path=/; expires=Thu, 14-Sep-23 05:45:40 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"x-op-gateway: true\\r\ + \" + -> \"Set-Cookie: __cf_bm=OYzsPf_HGhiUfF0EETH_zOM74zPZpYhmqI.FJxehmpY-1694668541-0-AWVAexX304k53VB3HIhdyg+uP4ElzrS23jwIAdPGccfN5DM/81TE0ioW7jb7kA3jCZDuGENGofaZz0pBwSr66lRiWu9fdAzdUIbwNDOBivWY; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"Server: cloudflare\\r\ + \" + -> \"CF-RAY: 80662747af463995-BOG\\r\ + \" + -> \"\\r\ + \" + reading 360 bytes... + -> \"{\\\"checkout_reference\\\":\\\"14c812fc-4689-4b8a-a4d7-ed21bf3c39ff\\\",\\\"amount\\\":1.0,\\\"currency\\\":\\\"USD\\\",\\\"pay_to_email\\\":\\\"[FILTERED]\",\\\"merchant_code\\\":\\\"MTVU2XGK\\\",\\\"description\\\":\\\"Store Purchase\\\",\\\"id\\\":\\\"70f71869-ed81-40b0-b2d8-c98f80f4c39d\\\",\\\"status\\\":\\\"PENDING\\\",\\\"date\\\":\\\"2023-09-14T05:15:40.000+00:00\\\",\\\"merchant_name\\\":\\\"Spreedly\\\",\\\"purpose\\\":\\\"CHECKOUT\\\",\\\"transactions\\\":[]}\" + read 360 bytes + Conn close + opening connection to api.sumup.com:443... + opened + starting SSL for api.sumup.com:443... + SSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384 + <- \"PUT /v0.1/checkouts/70f71869-ed81-40b0-b2d8-c98f80f4c39d 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.sumup.com\\r\ + Content-Length: 136\\r\ + \\r\ + \" + <- \"{\\\"payment_type\\\":\\\"card\\\",\\\"card\\\":{\\\"name\\\":\\\"Longbob Longsen\\\",\\\"number\\\":\\\"[FILTERED]\",\\\"expiry_month\\\":\\\"09\\\",\\\"expiry_year\\\":\\\"24\\\",\\\"cvv\\\":\\\"[FILTERED]\"}}\" + -> \"HTTP/1.1 200 OK\\r\ + \" + -> \"Date: Thu, 14 Sep 2023 05:15:41 GMT\\r\ + \" + -> \"Content-Type: application/json\\r\ + \" + -> \"Transfer-Encoding: chunked\\r\ + \" + -> \"Connection: close\\r\ + \" + -> \"x-powered-by: Express\\r\ + \" + -> \"access-control-allow-origin: *\\r\ + \" + -> \"x-fong-id: 8a116d29420e, 8a116d29420e, 8a116d29420e a534b6871710\\r\ + \" + -> \"cf-cache-status: DYNAMIC\\r\ + \" + -> \"vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers\\r\ + \" + -> \"apigw-requestid: LOyHoggJjoEEMxA=\\r\ + \" + -> \"set-cookie: __cf_bm=AoWMlPJNg1_THatbGnZchhj7K0QaqwlU0SqYrlDJ.78-1694668541-0-AdHrPpd/94p0oyLJWzsEUYatqVZMiJ0i1BJICEiprAo8AMDiya+V3OjljwbCpaNQNAPFVJpX1S4KxIFEUEeeNfAJv1HOjjaToNYhJuhLQ1NT; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"x-op-gateway: true\\r\ + \" + -> \"Set-Cookie: __cf_bm=UcJRX.Pe233lWIyCGlqNICBOhruxwESN41sDCDfzQBQ-1694668541-0-ASJ/Wl84HRovjKIq/p+Re8GrxkxHM1XvbDE/mXT/4r7PYA1cpTzG2uhp7WEkqVpEj7FCb2ahP5ExApEWWx0JDut8Uhx1SeQJHYFR/26E8BTv; path=/; expires=Thu, 14-Sep-23 05:45:41 GMT; domain=.sumup.com; HttpOnly; Secure; SameSite=None\\r\ + \" + -> \"Server: cloudflare\\r\ + \" + -> \"CF-RAY: 8066274e3a95399b-BOG\\r\ + \" + -> \"Content-Encoding: gzip\\r\ + \" + -> \"\\r\ + \" + -> \"1bc\\r\ + \" + reading 444 bytes... + -> \"\\x1F\\x8B\\b\\x00\\x00\\x00\\x00\\x00\\x00\\x03|\\x92[\\x8B\\xDB0\\x10\\x85\\xFFJ\\x99\\xD7ZA\\x92\\x15G\\xD6S!1\\xDB\\xB2\\xCD\\x85\\x8D]RJ1\\xB2$wMm\\xD9Hr\\xC1,\\xFB\\xDF\\x8B\\xF6R\\x1A\\xBA\\xF4\\xF50G\\xF3\\xCD9z\\x00uo\\xD4\\xCFq\\x0E\\xB53\\xADq\\xC6*\\x03\\x02\\bS\\x9C\\xD0V!\\x96\\xF1\\x1C\\xB1\\x86K$\\x99\\xDE \\xA3)i\\xDAT\\xA5y\\xDBB\\x02r\\x18g\\e@\\x90\\x15N@\\xCD.\\xDA\\x17\\x10P\\x9Dw\\x90\\xC0$\\x97:\\x8C\\xB5\\x19d\\xD7\\x83\\x80\\xCE\\x06\\xF3\\xC3\\xC9\\xD0\\x8D\\xD6\\x7F\\xF0\\x933F\\xF7\\xCBJ\\x8D\\x03$0\\x18\\xA7\\xEE\\xA5\\r\\xB5\\x1Au\\xDC\\xBF/\\xBFT\\xF4rs\\v\\th\\xE3\\x95\\xEB\\xA6h\\x03\\x01\\xE70:\\xF3\\xEE4\\xC7qo \\x81N\\x83\\x80\\rn7\\x84g92\\x9A\\x13\\xC4p\\x83QC5G*\\xE7-\\xC7-Si\\xAE!\\x01\\x1Fd\\x98=\\b8\\x15\\x87\\xDD\\xA7\\xC3M|]\\x86\\xB8\\x8Fb\\x9A\\\"\\x9C#\\xC2J\\xBC\\x16d-\\x18^a\\x8C\\xDFc,0\\xFE\\x9B\\xCF\\xCA!\\xCE\\x9F_\\xF0\\xE3\\x95\\xB3\\x9BF\\x1F\\xC5\\xED\\xC7b{{\\xACJH 8i\\xBDTO\\xB7\\x82\\xF8\\xF6\\xF0\\x8C\\x893\\xCD\\x15[S\\xD4\\xB2\\xD4 \\x96R\\x8E8\\xC7\" + -> \")\\xE2\\xBAU\\x9A\\xF0\\x94\\xD0&\\xBD6\\xBF\\xE6Q\\xEE(\\xADN\\x97\\xCF\\x97\\xF2\\xFFa]\\x15\\xF2K\\x86\\xFAU\\xC0Q\\b\\xDDt-\\xFCSY\\xE8\\x06\\xE3\\x83\\x1C\\xA673!+\\xC6\\xF3?\\x99\\xBC\\x91\\xE6$\\x97\\xC1\\xD8P\\x87e\\x8A`\\xC5\\xF6\\xB8\\x87\\x04\\x8C\\rn\\xA9\\x87g\\xD8mu.\\x8F\\xFB\\xE2\\xAE.\\x0E\\xE5\\xDD\\xD7X\\xA0\\xF5A\\xF6}\\xF4\\xF9Z\\xBD\\xE0'O\\xBF\\xC5Y\\xD9\\xD71\\xB95\\xC9\\xE8\\x06\\xA7,\\xA3\\x8F\\xDF\\x1F\\x7F\\x03\\x00\\x00\\xFF\\xFF\\x03\\x00\\xB5\\x12\\xCA\\x11\\xB3\\x02\\x00\\x00\" + read 444 bytes + reading 2 bytes... + -> \"\\r\ + \" + read 2 bytes + -> \"0\\r\ + \" + -> \"\\r\ + \" + Conn close + POST_SCRUBBED + end + + def successful_create_checkout_response + <<-RESPONSE + { + "checkout_reference": "e86ba553-b3d0-49f6-b4b5-18bd67502db2", + "amount": 1.0, + "currency": "USD", + "pay_to_email": "example@example.com", + "merchant_code": "ABC123", + "description": "Store Purchase", + "id": "8d8336a1-32e2-4f96-820a-5c9ee47e76fc", + "status": "PENDING", + "date": "2023-09-14T00:26:37.000+00:00", + "merchant_name": "Spreedly", + "purpose": "CHECKOUT", + "transactions": [] + } + RESPONSE + end + + def successful_complete_checkout_response + <<-RESPONSE + { + "checkout_reference": "e86ba553-b3d0-49f6-b4b5-18bd67502db2", + "amount": 1.0, + "currency": "USD", + "pay_to_email": "example@example.com", + "merchant_code": "ABC123", + "description": "Store Purchase", + "id": "8d8336a1-32e2-4f96-820a-5c9ee47e76fc", + "status": "PENDING", + "date": "2023-09-14T00: 26: 37.000+00: 00", + "merchant_name": "Spreedly", + "purpose": "CHECKOUT", + "transactions": [{ + "id": "1bce6072-1865-4a90-887f-cb7fda97b300", + "transaction_code": "TDMNUPS33H", + "merchant_code": "MTVU2XGK", + "amount": 1.0, + "vat_amount": 0.0, + "tip_amount": 0.0, + "currency": "USD", + "timestamp": "2023-09-14T00:26:38.420+00:00", + "status": "PENDING", + "payment_type": "ECOM", + "entry_mode": "CUSTOMER_ENTRY", + "installments_count": 1, + "internal_id": 5162527027 + }] + } + RESPONSE + end + + def failed_complete_checkout_response + <<-RESPONSE + { + "type": "https://developer.sumup.com/docs/problem/session-expired/", + "title": "Conflict", + "status": 409, + "detail": "The checkout session 79c866c2-0b2d-470d-925a-37ddc8855ec2 is expired", + "instance": "79a4ed94d177, 79a4ed94d177 c24ac3136c71", + "error_code": "CHECKOUT_SESSION_IS_EXPIRED", + "message": "Checkout is expired" + } + RESPONSE + end + + def failed_complete_checkout_array_response + <<-RESPONSE + [ + { + "message": "Validation error", + "param": "card", + "error_code": "The card is expired" + } + ] + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "checkout_reference": "b5a47552-50e0-4c6e-af23-2495124b5091", + "id": "c0887be5-9fd2-4018-a531-e573e0298fdd", + "amount": 100.00, + "currency": "USD", + "pay_to_email": "integrations@spreedly.com", + "merchant_code": "MTVU2XGK", + "description": "Sample one-time payment", + "purpose": "CHECKOUT", + "status": "EXPIRED", + "date": "2023-09-14T16:32:39.200+00:00", + "valid_until": "2023-09-14T18:08:49.977+00:00", + "merchant_name": "Spreedly", + "transactions": [{ + "id": "fc805fc9-4864-4c6d-8e29-630c171fce54", + "transaction_code": "TDYEQ2RQ23", + "merchant_code": "MTVU2XGK", + "amount": 100.0, + "vat_amount": 0.0, + "tip_amount": 0.0, + "currency": "USD", + "timestamp": "2023-09-14T16:32:50.111+00:00", + "status": "CANCELLED", + "payment_type": "ECOM", + "entry_mode": "CUSTOMER_ENTRY", + "installments_count": 1, + "internal_id": 5165839144 + }] + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "type": "https://developer.sumup.com/docs/problem/checkout-not-found/", + "title": "Not Found", + "status": 404, + "detail": "A checkout session with the id c0887be5-9fd2-4018-a531-e573e0298fdd22 does not exist", + "instance": "5e07254b2f25, 5e07254b2f25 a30463b627e3", + "error_code": "NOT_FOUND", + "message": "Resource not found" + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "message": "The transaction is not refundable in its current state", + "error_code": "CONFLICT" + } + RESPONSE + end + + def format_multiple_errors_response + { + error_code: 'MULTIPLE_INVALID_PARAMETERS', + message: 'Validation error', + errors: [{ error_code: 'The card is expired', param: 'card' }] + } + end +end diff --git a/test/unit/gateways/swipe_checkout_test.rb b/test/unit/gateways/swipe_checkout_test.rb index e6c4e0da63a..0536f400685 100644 --- a/test/unit/gateways/swipe_checkout_test.rb +++ b/test/unit/gateways/swipe_checkout_test.rb @@ -19,7 +19,7 @@ def setup end def test_supported_countries - assert @gateway.supported_countries == ['NZ', 'CA'] + assert @gateway.supported_countries == %w[NZ CA] end def test_successful_purchase diff --git a/test/unit/gateways/telr_test.rb b/test/unit/gateways/telr_test.rb index 8e3ac5766bd..e34399b6faf 100644 --- a/test/unit/gateways/telr_test.rb +++ b/test/unit/gateways/telr_test.rb @@ -43,7 +43,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/029894296182/, data) end.respond_with(successful_capture_response) @@ -77,7 +77,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/029894296182/, data) end.respond_with(successful_void_response) @@ -87,7 +87,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) end.respond_with(failed_void_response) @@ -104,7 +104,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/029724176180/, data) end.respond_with(successful_refund_response) @@ -145,7 +145,7 @@ def test_successful_reference_purchase ref_purchase = stub_comms do @gateway.purchase(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/029724176180/, data) end.respond_with(successful_reference_purchase_response) diff --git a/test/unit/gateways/tns_test.rb b/test/unit/gateways/tns_test.rb index f97a0e6e242..d7939f10630 100644 --- a/test/unit/gateways/tns_test.rb +++ b/test/unit/gateways/tns_test.rb @@ -48,7 +48,7 @@ def test_authorize_and_capture capture = stub_comms(@gateway, :ssl_request) do @gateway.capture(@amount, response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/f3d100a7-18d9-4609-aabc-8a710ad0e210/, data) end.respond_with(successful_capture_response) @@ -65,7 +65,7 @@ def test_refund refund = stub_comms(@gateway, :ssl_request) do @gateway.refund(@amount, response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) end.respond_with(successful_refund_response) @@ -82,7 +82,7 @@ def test_void void = stub_comms(@gateway, :ssl_request) do @gateway.void(response.authorization) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) end.respond_with(successful_void_response) @@ -91,16 +91,16 @@ def test_void def test_passing_alpha3_country_code stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'US'}) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { country: 'US' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/USA/, data) end.respond_with(successful_authorize_response) end def test_non_existent_country stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'Blah'}) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: { country: 'Blah' }) + end.check_request do |_method, _endpoint, data, _headers| assert_match(/"country":null/, data) end.respond_with(successful_authorize_response) end @@ -108,15 +108,15 @@ def test_non_existent_country def test_passing_cvv stub_comms(@gateway, :ssl_request) do @gateway.authorize(@amount, @credit_card) - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_authorize_response) end def test_passing_billing_address stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :billing_address => address) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, billing_address: address) + end.check_request do |_method, _endpoint, data, _headers| parsed = JSON.parse(data) assert_equal('456 My Street', parsed['billing']['address']['street']) assert_equal('K1C2N6', parsed['billing']['address']['postcodeZip']) @@ -125,8 +125,8 @@ def test_passing_billing_address def test_passing_shipping_name stub_comms(@gateway, :ssl_request) do - @gateway.authorize(@amount, @credit_card, :shipping_address => address) - end.check_request do |method, endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, shipping_address: address) + end.check_request do |_method, _endpoint, data, _headers| parsed = JSON.parse(data) assert_equal('Jim', parsed['shipping']['firstName']) assert_equal('Smith', parsed['shipping']['lastName']) @@ -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', @@ -166,24 +166,8 @@ 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) + end.check_request do |_method, endpoint, _data, _headers| + assert_match(/secure.uat.tnspayments.com/, endpoint) end.respond_with(successful_capture_response) assert_success response diff --git a/test/unit/gateways/trans_first_test.rb b/test/unit/gateways/trans_first_test.rb index 34f04f9a75c..d8a2ca93ed2 100644 --- a/test/unit/gateways/trans_first_test.rb +++ b/test/unit/gateways/trans_first_test.rb @@ -1,17 +1,16 @@ require 'test_helper' class TransFirstTest < Test::Unit::TestCase - def setup @gateway = TransFirstGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + login: 'LOGIN', + password: 'PASSWORD' ) @credit_card = credit_card('4242424242424242') @check = check @options = { - :billing_address => address + billing_address: address } @amount = 100 end @@ -68,7 +67,7 @@ def test_successful_refund response = @gateway.refund(@amount, 'TransID') assert_success response assert_equal '207686608|creditcard', response.authorization - assert_equal @amount, response.params['amount'].to_i*100 + assert_equal @amount, response.params['amount'].to_i * 100 end def test_failed_refund @@ -351,7 +350,7 @@ def successful_void_response N - XML + XML end def failed_void_response diff --git a/test/unit/gateways/trans_first_transaction_express_test.rb b/test/unit/gateways/trans_first_transaction_express_test.rb index 1bbfdf81f88..38521f93391 100644 --- a/test/unit/gateways/trans_first_transaction_express_test.rb +++ b/test/unit/gateways/trans_first_transaction_express_test.rb @@ -26,6 +26,25 @@ def test_successful_purchase assert response.test? end + def test_strip_hyphens_from_zip + options = { + billing_address: { + name: 'John & Mary Smith', + address1: '1 Main St.', + city: 'Burlington', + state: 'MA', + zip: '01803-3747', + country: 'US' + } + } + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match(/018033747/, data) + end.respond_with(successful_purchase_response) + end + def test_failed_purchase response = stub_comms do @gateway.purchase(@declined_amount, @credit_card) @@ -63,7 +82,7 @@ def test_successful_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/000015377801/, data) end.respond_with(successful_capture_response) @@ -99,7 +118,7 @@ def test_successful_void void = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/000015212561/, data) end.respond_with(successful_void_response) @@ -109,7 +128,7 @@ def test_successful_void def test_failed_void response = stub_comms do @gateway.void('purchase|5d53a33d960c46d00f5dc061947d998c') - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) end.respond_with(failed_void_response) @@ -127,7 +146,7 @@ def test_successful_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/000015212561/, data) end.respond_with(successful_refund_response) @@ -153,7 +172,7 @@ def test_successful_refund_with_echeck refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/000028705491/, data) end.respond_with(successful_refund_echeck_response) @@ -238,10 +257,23 @@ def test_empty_response_fails assert_equal nil, response.message end + def test_transaction_code_xml_tag_added_with_correct_prefix + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |_endpoint, data, _headers| + doc = Nokogiri::XML(data) + assert_not_empty doc.xpath('//v1:tranCode', 'v1' => 'http://postilion/realtime/merchantframework/xsd/v1/') + end.respond_with(successful_purchase_response) + end + def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end + def test_account_number_scrubbing + assert_equal post_scrubbed_account_number, @gateway.scrub(pre_scrubbed_account_number) + end + private def successful_purchase_response @@ -321,62 +353,118 @@ def empty_purchase_response end def transcript - <<-PRE_SCRUBBED -opening connection to ws.cert.transactionexpress.com:443... -opened -starting SSL for ws.cert.transactionexpress.com:443... -SSL established -<- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" -<- "7777778764M84PKPDMD5BY86HN1144858962610177081709Longbob LongsenAcmeQA Manager43334445555450 MainSuite 100BroomfieldCO85284USexample@example.comLongbob Longsen450 MainSuite 100BroomfieldCO8528433344455551007a0f975b6e86aff44364360cbc6d0f00" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: text/xml;charset=utf-8\r\n" --> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" --> "Server: WebServer\r\n" --> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" --> "Cache-Control: private\r\n" --> "Content-Encoding: gzip\r\n" --> "Transfer-Encoding: chunked\r\n" --> "\r\n" --> "1AA \r\n" -reading 426 bytes... --> "" -read 426 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close + <<~PRE_SCRUBBED + opening connection to ws.cert.transactionexpress.com:443... + opened + starting SSL for ws.cert.transactionexpress.com:443... + SSL established + <- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" + <- "7777778764M84PKPDMD5BY86HN1144858962610177081709Longbob LongsenAcmeQA Manager43334445555450 MainSuite 100BroomfieldCO85284USexample@example.comLongbob Longsen450 MainSuite 100BroomfieldCO8528433344455551007a0f975b6e86aff44364360cbc6d0f00" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" + -> "Server: WebServer\r\n" + -> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "1AA \r\n" + reading 426 bytes... + -> "" + read 426 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close PRE_SCRUBBED end def scrubbed_transcript - <<-POST_SCRUBBED -opening connection to ws.cert.transactionexpress.com:443... -opened -starting SSL for ws.cert.transactionexpress.com:443... -SSL established -<- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" -<- "[FILTERED][FILTERED]11[FILTERED]1709Longbob LongsenAcmeQA Manager43334445555450 MainSuite 100BroomfieldCO85284USexample@example.comLongbob Longsen450 MainSuite 100BroomfieldCO8528433344455551007a0f975b6e86aff44364360cbc6d0f00" --> "HTTP/1.1 200 OK\r\n" --> "Content-Type: text/xml;charset=utf-8\r\n" --> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" --> "Server: WebServer\r\n" --> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" --> "Cache-Control: private\r\n" --> "Content-Encoding: gzip\r\n" --> "Transfer-Encoding: chunked\r\n" --> "\r\n" --> "1AA \r\n" -reading 426 bytes... --> "" -read 426 bytes -reading 2 bytes... --> "\r\n" -read 2 bytes --> "0\r\n" --> "\r\n" -Conn close + <<~POST_SCRUBBED + opening connection to ws.cert.transactionexpress.com:443... + opened + starting SSL for ws.cert.transactionexpress.com:443... + SSL established + <- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" + <- "[FILTERED][FILTERED]11[FILTERED]1709Longbob LongsenAcmeQA Manager43334445555450 MainSuite 100BroomfieldCO85284USexample@example.comLongbob Longsen450 MainSuite 100BroomfieldCO8528433344455551007a0f975b6e86aff44364360cbc6d0f00" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" + -> "Server: WebServer\r\n" + -> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "1AA \r\n" + reading 426 bytes... + -> "" + read 426 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def pre_scrubbed_account_number + <<~PRE_SCRUBBED + opening connection to ws.cert.transactionexpress.com:443... + opened + starting SSL for ws.cert.transactionexpress.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384 + <- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1553\r\n\r\n" + <- "\n \n \n \n 7777778764\n M84PKPDMD5BY86HN\n 1\n 11\n \n 244183602\n 15378535\n \n \n Jim Smith\n Acme\n QA Manager\n \n 4\n 3334445555\n \n 450 Main\n Suite 100\n Broomfield\n CO\n 85284\n US\n example@example.com\n \n Jim Smith\n 450 Main\n Suite 100\n Broomfield\n CO\n 85284\n 3334445555\n \n \n 100\n \n 20811a5033205f7dcdc5c7e0c89a0189\n \n \n \n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/10.0\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Date: Mon, 11 Oct 2021 13:53:27 GMT\r\n" + -> "Cteonnt-Length: 782\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Set-Cookie: NSC_JOyfb3nwcgpzfpmezjperccrokp05cn=ffffffff09180b7045525d5f4f58455e445a4a42378b;expires=Mon, 11-Oct-2021 14:03:27 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 437\r\n" + -> "\r\n" + reading 437 bytes... + read 437 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed_account_number + <<~POST_SCRUBBED + opening connection to ws.cert.transactionexpress.com:443... + opened + starting SSL for ws.cert.transactionexpress.com:443... + SSL established, protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384 + <- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1553\r\n\r\n" + <- "\n \n \n \n [FILTERED]\n [FILTERED]\n 1\n 11\n \n 244183602\n [FILTERED]\n \n \n Jim Smith\n Acme\n QA Manager\n \n 4\n 3334445555\n \n 450 Main\n Suite 100\n Broomfield\n CO\n 85284\n US\n example@example.com\n \n Jim Smith\n 450 Main\n Suite 100\n Broomfield\n CO\n 85284\n 3334445555\n \n \n 100\n \n 20811a5033205f7dcdc5c7e0c89a0189\n \n \n \n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/10.0\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Date: Mon, 11 Oct 2021 13:53:27 GMT\r\n" + -> "Cteonnt-Length: 782\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Set-Cookie: NSC_JOyfb3nwcgpzfpmezjperccrokp05cn=ffffffff09180b7045525d5f4f58455e445a4a42378b;expires=Mon, 11-Oct-2021 14:03:27 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 437\r\n" + -> "\r\n" + reading 437 bytes... + read 437 bytes + Conn close POST_SCRUBBED end end diff --git a/test/unit/gateways/transact_pro_test.rb b/test/unit/gateways/transact_pro_test.rb index 6323dc78536..642fc6f4ed6 100644 --- a/test/unit/gateways/transact_pro_test.rb +++ b/test/unit/gateways/transact_pro_test.rb @@ -75,7 +75,7 @@ def test_partial_capture @gateway.expects(:ssl_post).never assert_raise(ArgumentError) do - @gateway.capture(@amount-1, '3d25ab044075924479d3836f549b015481d15d74|100') + @gateway.capture(@amount - 1, '3d25ab044075924479d3836f549b015481d15d74|100') end end @@ -100,7 +100,7 @@ def test_successful_refund def test_partial_refund @gateway.expects(:ssl_post).returns(successful_refund_response) - assert refund = @gateway.refund(@amount-1, '3d25ab044075924479d3836f549b015481d15d74|100') + assert refund = @gateway.refund(@amount - 1, '3d25ab044075924479d3836f549b015481d15d74|100') assert_success refund assert_equal 'Refund Success', refund.message assert_equal '3d25ab044075924479d3836f549b015481d15d74', refund.authorization @@ -109,7 +109,7 @@ def test_partial_refund def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) - assert refund = @gateway.refund(@amount+1, '3d25ab044075924479d3836f549b015481d15d74|100') + assert refund = @gateway.refund(@amount + 1, '3d25ab044075924479d3836f549b015481d15d74|100') assert_failure refund end diff --git a/test/unit/gateways/trexle_test.rb b/test/unit/gateways/trexle_test.rb index a1ee4295ccc..b1bb3625ec2 100644 --- a/test/unit/gateways/trexle_test.rb +++ b/test/unit/gateways/trexle_test.rb @@ -46,7 +46,7 @@ def test_supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master, :american_express], TrexleGateway.supported_cardtypes + assert_equal %i[visa master american_express], TrexleGateway.supported_cardtypes end def test_display_name @@ -123,7 +123,7 @@ def test_successful_update def test_successful_refund token = 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367' - @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", {amount: '100'}.to_json, instance_of(Hash)).returns(successful_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", { amount: '100' }.to_json, instance_of(Hash)).returns(successful_refund_response) assert response = @gateway.refund(100, token) assert_equal 'refund_7f696a86f9cb136520c51ea90c17f687b8df40b0', response.authorization @@ -133,7 +133,7 @@ def test_successful_refund def test_unsuccessful_refund token = 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367' - @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", {amount: '100'}.to_json, instance_of(Hash)).returns(failed_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", { amount: '100' }.to_json, instance_of(Hash)).returns(failed_refund_response) assert response = @gateway.refund(100, token) assert_failure response @@ -440,5 +440,4 @@ def scrubbed_transcript } }' end - end diff --git a/test/unit/gateways/trust_commerce_test.rb b/test/unit/gateways/trust_commerce_test.rb index 9221ae96d46..9d1782e4e51 100644 --- a/test/unit/gateways/trust_commerce_test.rb +++ b/test/unit/gateways/trust_commerce_test.rb @@ -4,9 +4,9 @@ class TrustCommerceTest < Test::Unit::TestCase include CommStub def setup @gateway = TrustCommerceGateway.new( - :login => 'TestMerchant', - :password => 'password', - :aggregator_id => 'abc123' + login: 'TestMerchant', + password: 'password', + aggregator_id: 'abc123' ) # Force SSL post @gateway.stubs(:tclink?).returns(false) @@ -14,6 +14,12 @@ def setup @amount = 100 @check = check @credit_card = credit_card('4111111111111111') + + @options_with_custom_fields = { + custom_fields: { + 'customfield1' => 'test1' + } + } end def test_successful_purchase @@ -21,7 +27,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_instance_of Response, response assert_success response - assert_equal '025-0007423614', response.authorization + assert_equal '025-0007423614|sale', response.authorization end def test_unsuccessful_purchase @@ -35,12 +41,84 @@ def test_succesful_purchase_with_check ActiveMerchant::Billing::TrustCommerceGateway.application_id = 'abc123' stub_comms do @gateway.purchase(@amount, @check) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(%r{aggregator1}, data) assert_match(%r{name=Jim\+Smith}, data) end.respond_with(successful_purchase_response) end + def test_succesful_purchase_with_custom_fields + stub_comms do + @gateway.purchase(@amount, @credit_card, @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_purchase_response) + end + + def test_succesful_authorize_with_custom_fields + stub_comms do + @gateway.authorize(@amount, @check, @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_authorize_response) + end + + def test_successful_void_from_purchase + stub_comms do + @gateway.void('1235|sale') + end.check_request do |_endpoint, data, _headers| + assert_match(%r{action=void}, data) + end.respond_with(successful_void_response) + end + + def test_successful_void_from_authorize + stub_comms do + @gateway.void('1235|preauth') + end.check_request do |_endpoint, data, _headers| + assert_match(%r{action=reversal}, data) + end.respond_with(successful_void_response) + end + + def test_succesful_capture_with_custom_fields + stub_comms do + @gateway.capture(@amount, 'auth', @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_capture_response) + end + + def test_succesful_refund_with_custom_fields + stub_comms do + @gateway.refund(@amount, 'auth|100', @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_refund_response) + end + + def test_succesful_void_with_custom_fields + stub_comms do + @gateway.void('1235|sale', @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_void_response) + end + + def test_succesful_store_with_custom_fields + stub_comms do + @gateway.store(@credit_card, @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_store_response) + end + + def test_succesful_unstore_with_custom_fields + stub_comms do + @gateway.unstore('test', @options_with_custom_fields) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_unstore_response) + end + def test_amount_style assert_equal '1034', @gateway.send(:amount, 1034) @@ -68,7 +146,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :discover, :american_express, :diners_club, :jcb], TrustCommerceGateway.supported_cardtypes + assert_equal %i[visa master discover american_express diners_club jcb], TrustCommerceGateway.supported_cardtypes end def test_test_flag_should_be_set_when_using_test_login_in_production @@ -85,35 +163,132 @@ def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end + def test_transcript_scrubbing_echeck + assert_equal scrubbed_echeck_transcript, @gateway.scrub(echeck_transcript) + end + + def test_successful_verify + stub_comms do + @gateway.verify(@credit_card) + end.check_request do |_endpoint, data, _headers| + assert_match(%r{action=verify}, data) + end.respond_with(successful_verify_response) + end + + def test_unsuccessful_verify + bad_credit_card = credit_card('42909090990') + @gateway.expects(:ssl_post).returns(unsuccessful_verify_response) + assert response = @gateway.verify(bad_credit_card) + assert_instance_of Response, response + assert_failure response + end + private + def successful_authorize_response + <<~RESPONSE + authcode=123456 + transid=026-0193338367, + status=approved + avs=Y + cvv=M + RESPONSE + end + def successful_purchase_response - <<-RESPONSE -transid=025-0007423614 -status=approved -avs=Y -cvv=P + <<~RESPONSE + transid=025-0007423614 + status=approved + avs=Y + cvv=P + RESPONSE + end + + def successful_capture_response + <<~RESPONSE + transid=026-0193338993 + status=accepted RESPONSE end def unsuccessful_purchase_response - <<-RESPONSE -transid=025-0007423827 -declinetype=cvv -status=decline -cvv=N + <<~RESPONSE + transid=025-0007423827 + declinetype=cvv + status=decline + cvv=N + RESPONSE + end + + def successful_void_response + <<~RESPONSE + transid=025-0007423828 + status=accpeted + RESPONSE + end + + def successful_refund_response + <<~RESPONSE + transid=026-0193345407 + status=accepted + RESPONSE + end + + def successful_store_response + <<~RESPONSE + transid=026-0193346109 + status=approved, + cvv=M, + avs=0 + billingid=Q5T7PT + RESPONSE + end + + def successful_unstore_response + <<~RESPONSE + transid=026-0193346231 + status=rejected + RESPONSE + end + + def successful_verify_response + <<~RESPONSE + transid=039-0170402443 + status=approved + avs=0 + cvv=M + RESPONSE + end + + def unsuccessful_verify_response + <<~RESPONSE + offenders=cc + error=badformat + status=baddata RESPONSE end def transcript - <<-TRANSCRIPT -action=sale&demo=y&password=password&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=1234&exp=0916&cc=4111111111111111&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 + <<~TRANSCRIPT + action=sale&demo=y&password=password&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=1234&exp=0916&cc=4111111111111111&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 TRANSCRIPT end def scrubbed_transcript - <<-TRANSCRIPT -action=sale&demo=y&password=[FILTERED]&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=[FILTERED]&exp=0916&cc=[FILTERED]&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 + <<~TRANSCRIPT + action=sale&demo=y&password=[FILTERED]&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=[FILTERED]&exp=0916&cc=[FILTERED]&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 + TRANSCRIPT + end + + def echeck_transcript + <<~TRANSCRIPT + action=sale&demo=y&password=A3pN3F3Am8du&custid=1249400&customfield1=test1&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&name=Jim+Smith&account=55544433221&routing=789456124&media=ach&ip=10.10.10.10&email=cody%40example.com&aggregator1=2FCTLKF&aggregators=1&ticket=%231000.1&amount=100 + TRANSCRIPT + end + + def scrubbed_echeck_transcript + <<~TRANSCRIPT + action=sale&demo=y&password=[FILTERED]&custid=1249400&customfield1=test1&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&name=Jim+Smith&account=[FILTERED]&routing=789456124&media=ach&ip=10.10.10.10&email=cody%40example.com&aggregator1=2FCTLKF&aggregators=1&ticket=%231000.1&amount=100 TRANSCRIPT end end diff --git a/test/unit/gateways/usa_epay_advanced_test.rb b/test/unit/gateways/usa_epay_advanced_test.rb index 7c22b87d841..7edd03a6728 100644 --- a/test/unit/gateways/usa_epay_advanced_test.rb +++ b/test/unit/gateways/usa_epay_advanced_test.rb @@ -16,90 +16,90 @@ def setup # UsaEpayAdvancedGateway.wiredump_device.sync = true @gateway = UsaEpayAdvancedGateway.new( - :login => 'X', - :password => 'Y', - :software_id => 'Z' - ) + login: 'X', + password: 'Y', + software_id: 'Z' + ) @credit_card = ActiveMerchant::Billing::CreditCard.new( - :number => '4000100011112224', - :month => 12, - :year => 12, - :brand => 'visa', - :verification_value => '123', - :first_name => 'Fred', - :last_name => 'Flintstone' + number: '4000100011112224', + month: 12, + year: 12, + brand: 'visa', + verification_value: '123', + first_name: 'Fred', + last_name: 'Flintstone' ) @check = ActiveMerchant::Billing::Check.new( - :account_number => '123456789012', - :routing_number => '123456789', - :account_type => 'checking', - :first_name => 'Fred', - :last_name => 'Flintstone' + account_number: '123456789012', + routing_number: '123456789', + account_type: 'checking', + first_name: 'Fred', + last_name: 'Flintstone' ) payment_methods = [ { - :name => 'My Visa', # optional - :sort => 2, # optional - :method => @credit_card + name: 'My Visa', # optional + sort: 2, # optional + method: @credit_card }, { - :name => 'My Checking', - :method => @check + name: 'My Checking', + method: @check } ] payment_method = { - :name => 'My new Visa', # optional - :method => @credit_card + name: 'My new Visa', # optional + method: @credit_card } @customer_options = { - :id => 1, # optional: merchant assigned id, usually db id - :notes => 'Note about customer', # optional - :data => 'Some Data', # optional - :url => 'awesomesite.com', # optional - :payment_methods => payment_methods # optional + id: 1, # optional: merchant assigned id, usually db id + notes: 'Note about customer', # optional + data: 'Some Data', # optional + url: 'awesomesite.com', # optional + payment_methods: payment_methods # optional } @payment_options = { - :payment_method => payment_method + payment_method: payment_method } @transaction_options = { - :payment_method => @credit_card, - :recurring => { - :schedule => 'monthly', - :amount => 4000 + payment_method: @credit_card, + recurring: { + schedule: 'monthly', + amount: 4000 } } @standard_transaction_options = { - :method_id => 0, - :command => 'Sale', - :amount => 2000 # 20.00 + method_id: 0, + command: 'Sale', + amount: 2000 # 20.00 } @get_payment_options = { - :method_id => 0 + method_id: 0 } @delete_customer_options = { - :customer_number => 299461 + customer_number: 299461 } @custom_options = { - :fields => ['Response.StatusCode', 'Response.Status'] + fields: ['Response.StatusCode', 'Response.Status'] } @options = { - :client_ip => '127.0.0.1', - :billing_address => address, + client_ip: '127.0.0.1', + billing_address: address, - :customer_number => 298741, - :reference_number => 9999 + customer_number: 298741, + reference_number: 9999 } end @@ -203,7 +203,7 @@ def test_successful_update_customer def test_successful_quick_update_customer @gateway.expects(:ssl_post).returns(successful_customer_response('quickUpdateCustomer')) - assert response = @gateway.quick_update_customer({customer_number: @options[:customer_number], update_data: @customer_options}) + assert response = @gateway.quick_update_customer({ customer_number: @options[:customer_number], update_data: @customer_options }) assert_instance_of Response, response assert response.test? assert_success response @@ -279,7 +279,7 @@ def test_successful_get_customer_payment_method def test_successful_get_customer_payment_methods @gateway.expects(:ssl_post).returns(successful_get_customer_payment_methods_response) - assert response = @gateway.get_customer_payment_methods(@options.merge!(:customer_number => 298741)) + assert response = @gateway.get_customer_payment_methods(@options.merge!(customer_number: 298741)) assert_instance_of Response, response assert_success response assert response.test? @@ -305,7 +305,7 @@ def test_successful_update_customer_payment_method def test_successful_delete_customer_payment_method @gateway.expects(:ssl_post).returns(successful_delete_customer_payment_method_response) - assert response = @gateway.delete_customer_payment_method(@options.merge!(:customer_number => 298741, :method_id => 15)) + assert response = @gateway.delete_customer_payment_method(@options.merge!(customer_number: 298741, method_id: 15)) assert_instance_of Response, response assert_success response assert_equal 'true', response.message @@ -396,8 +396,8 @@ def test_successful_run_check_sale @options.merge!(@transaction_options) response = stub_comms do - @gateway.run_check_sale(@options.merge(:payment_method => @check)) - end.check_request do |endpoint, data, headers| + @gateway.run_check_sale(@options.merge(payment_method: @check)) + end.check_request do |_endpoint, data, _headers| assert_match(/123456789012/, data) end.respond_with(successful_transaction_response('runCheckSale')) @@ -580,125 +580,125 @@ def test_mismatch_response # Standard Gateway ================================================== def successful_purchase_response - <<-XML - -0017523Address: Match & 5 Digit Zip: MatchYYY114004MatchMVisa TraditionalA008400Approved0false47602591ApprovedAPendingP + <<~XML + + 0017523Address: Match & 5 Digit Zip: MatchYYY114004MatchMVisa TraditionalA008400Approved0false47602591ApprovedAPendingP XML end def successful_authorize_response - <<-XML - -0017524Address: Match & 5 Digit Zip: MatchYYY114004MatchMVisa TraditionalA008400Approved0false47602592ApprovedAPendingP + <<~XML + + 0017524Address: Match & 5 Digit Zip: MatchYYY114004MatchMVisa TraditionalA008400Approved0false47602592ApprovedAPendingP XML end def successful_capture_response - <<-XML - -0017525No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 008400Approved0false47602593ApprovedAPendingP + <<~XML + + 0017525No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 008400Approved0false47602593ApprovedAPendingP XML end def successful_void_response - <<-XML - -true + <<~XML + + true XML end def successful_credit_response - <<-XML - -047612622Unmapped AVS response ( )No CVV2/CVC data available for transaction.Unknown Code 000false47602599ApprovedAPendingP + <<~XML + + 047612622Unmapped AVS response ( )No CVV2/CVC data available for transaction.Unknown Code 000false47602599ApprovedAPendingP XML end # Customer ========================================================== def successful_add_customer_response - <<-XML - -274141 + <<~XML + + 274141 XML end def invalid_checking_add_customer_response - <<-XML -SOAP-ENV:Server39: Invalid Checking Account Number. + <<~XML + SOAP-ENV:Server39: Invalid Checking Account Number. XML end def successful_customer_response(method) - <<-XML -<#{method}Return xsi:type="xsd:boolean">true + <<~XML + <#{method}Return xsi:type="xsd:boolean">true XML end def successful_run_customer_transaction_response - <<-XML -038460Address: Match & 5 Digit Zip: MatchYYY114004Not ProcessedP000Approved0false47555081ApprovedAPendingP + <<~XML + 038460Address: Match & 5 Digit Zip: MatchYYY114004Not ProcessedP000Approved0false47555081ApprovedAPendingP XML end def successful_add_customer_payment_method_response - <<-XML -77 + <<~XML + 77 XML end def failed_add_customer_payment_method_response - <<-XML -SOAP-ENV:Server40459: Payment method not added because verification returned a Declined:10127:Card Declined (00) + <<~XML + SOAP-ENV:Server40459: Payment method not added because verification returned a Declined:10127:Card Declined (00) XML end def successful_get_customer_payment_method_response - <<-XML -cc103My CC52011-06-09T13:48:57+08:002011-06-09T13:48:57+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224V + <<~XML + cc103My CC52011-06-09T13:48:57+08:002011-06-09T13:48:57+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224V XML end def failed_get_customer_payment_method_response - <<-XML -SOAP-ENV:Server40453: Unable to locate requested payment method. + <<~XML + SOAP-ENV:Server40453: Unable to locate requested payment method. XML end def successful_get_customer_payment_methods_response - <<-XML -cc93My CC52011-06-09T08:10:44+08:002011-06-09T08:10:44+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224Vcc94Other CC122011-06-09T08:10:44+08:002011-06-09T08:10:44+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224V + <<~XML + cc93My CC52011-06-09T08:10:44+08:002011-06-09T08:10:44+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224Vcc94Other CC122011-06-09T08:10:44+08:002011-06-09T08:10:44+08:00456 My StreetK1C2N62012-12XXXXXXXXXXXX2224V XML end def successful_single_get_customer_payment_methods_response - <<-XML -cc15My Visa22011-06-05T19:44:09+08:002011-06-05T19:44:09+08:00456 My StreetK1C2N62012-09XXXXXXXXXXXX4242V + <<~XML + cc15My Visa22011-06-05T19:44:09+08:002011-06-05T19:44:09+08:00456 My StreetK1C2N62012-09XXXXXXXXXXXX4242V XML end def successful_update_customer_payment_method_response - <<-XML - -true + <<~XML + + true XML end def successful_delete_customer_payment_method_response - <<-XML -true + <<~XML + true XML end def failed_delete_customer_payment_method_response - <<-XML -SOAP-ENV:Server40453: Unable to locate requested payment method. + <<~XML + SOAP-ENV:Server40453: Unable to locate requested payment method. XML end def failed_delete_customer_response - <<-XML -SOAP-ENV:Server40030: Customer Not Found + <<~XML + SOAP-ENV:Server40030: Customer Not Found XML end @@ -710,20 +710,20 @@ def successful_avs_cvv_transaction_response(method) end def successful_transaction_response(method) - <<-XML -<#{method}Return xsi:type="ns1:TransactionResponse">047578712Unmapped AVS response ( )00No CVV2/CVC data available for transaction.Unknown Code 0084000false47568689ApprovedAPendingP + <<~XML + <#{method}Return xsi:type="ns1:TransactionResponse">047578712Unmapped AVS response ( )00No CVV2/CVC data available for transaction.Unknown Code 0084000false47568689ApprovedAPendingP XML end def failed_run_check_sale_response - <<-XML -0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Invalid Routing Number.38false0ErrorEPendingP + <<~XML + 0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Invalid Routing Number.38false0ErrorEPendingP XML end def failed_run_check_credit_response - <<-XML -0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Invalid Routing Number.38false0ErrorEPendingP + <<~XML + 0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Invalid Routing Number.38false0ErrorEPendingP XML end @@ -733,15 +733,15 @@ def successful_post_auth_response end def failed_post_auth_response - <<-XML -0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Valid AuthCode required for PostAuth108false0ErrorEPendingP + <<~XML + 0000000No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 000Valid AuthCode required for PostAuth108false0ErrorEPendingP XML end def successful_capture_transaction_response - <<-XML - -0004043No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 008400Approved0false47587252ApprovedAPendingP + <<~XML + + 0004043No AVS response (Typically no AVS data sent or swiped transaction)00No CVV2/CVC data available for transaction.Unknown Code 008400Approved0false47587252ApprovedAPendingP XML end @@ -751,61 +751,61 @@ def successful_override_transaction_response end def failed_override_transaction_response - <<-XML -SOAP-ENV:Server105: Override not available for requested transaction. + <<~XML + SOAP-ENV:Server105: Override not available for requested transaction. XML end def successful_void_transaction_response - <<-XML - -true + <<~XML + + true XML end def successful_refund_transaction_response - <<-XML - -047597281Unmapped AVS response ( )No CVV2/CVC data available for transaction.Unknown Code 000false47587258ApprovedAPendingP + <<~XML + + 047597281Unmapped AVS response ( )No CVV2/CVC data available for transaction.Unknown Code 000false47587258ApprovedAPendingP XML end # Transaction Response ============================================== def successful_get_transaction_status_response - <<-XML - -50050162Address: Match & 5 Digit Zip: MatchYYY114004MatchM000Approved0false47569011ApprovedAPendingP + <<~XML + + 50050162Address: Match & 5 Digit Zip: MatchYYY114004MatchM000Approved0false47569011ApprovedAPendingP XML end def successful_get_transaction_custom_response - <<-XML - -Response.StatusCodePResponse.StatusPending + <<~XML + + Response.StatusCodePResponse.StatusPending XML end def successful_get_check_trace_response - <<-XML - -PendingP2011-06-2111061908516155 + <<~XML + + PendingP2011-06-2111061908516155 XML end def successful_get_transaction_response - <<-XML - -OttawaWidgets IncCAJimSmith(555)555-5555ON456 My StreetApt 1K1C2N6127.0.0.1456 My StreetK1C2N6XXXXXXXXXXXXXXXXXXX2224falseVfalse2011-06-11 19:23:37
500false00
00
50050129Address: Match & 5 Digit Zip: MatchYYY114004MatchM000Approved0false47568950ApprovedAPendingP67.168.21.42OttawaWidgets IncCAJimSmith(555)555-5555ON456 My StreetApt 1K1C2N6testAuthorized (Pending Settlement)Saleauto
+ <<~XML + + OttawaWidgets IncCAJimSmith(555)555-5555ON456 My StreetApt 1K1C2N6127.0.0.1456 My StreetK1C2N6XXXXXXXXXXXXXXXXXXX2224falseVfalse2011-06-11 19:23:37
500false00
00
50050129Address: Match & 5 Digit Zip: MatchYYY114004MatchM000Approved0false47568950ApprovedAPendingP67.168.21.42OttawaWidgets IncCAJimSmith(555)555-5555ON456 My StreetApt 1K1C2N6testAuthorized (Pending Settlement)Saleauto
XML end # Account =========================================================== def successful_get_account_details - <<-XML -Disabled -TestBedTest BedfalseDisabledeCommerce + <<~XML + Disabled + TestBedTest BedfalseDisabledeCommerce XML end diff --git a/test/unit/gateways/usa_epay_test.rb b/test/unit/gateways/usa_epay_test.rb index 4aa91905676..e7647eb15c8 100644 --- a/test/unit/gateways/usa_epay_test.rb +++ b/test/unit/gateways/usa_epay_test.rb @@ -2,29 +2,28 @@ require 'logger' class UsaEpayTest < Test::Unit::TestCase - def test_transaction_gateway_created gateway = UsaEpayGateway.new( - :login => 'X' + login: 'X' ) assert_kind_of UsaEpayTransactionGateway, gateway end def test_advanced_gateway_created_with_software_id gateway = UsaEpayGateway.new( - :login => 'X', - :password => 'Y', - :software_id => 'Z' + login: 'X', + password: 'Y', + software_id: 'Z' ) assert_kind_of UsaEpayAdvancedGateway, gateway end def test_advanced_gateway_created_with_urls gateway = UsaEpayGateway.new( - :login => 'X', - :password => 'Y', - :test_url => 'Z', - :live_url => 'Z' + login: 'X', + password: 'Y', + test_url: 'Z', + live_url: 'Z' ) assert_kind_of UsaEpayAdvancedGateway, gateway end diff --git a/test/unit/gateways/usa_epay_transaction_test.rb b/test/unit/gateways/usa_epay_transaction_test.rb index b75959ee308..297e1a2ef65 100644 --- a/test/unit/gateways/usa_epay_transaction_test.rb +++ b/test/unit/gateways/usa_epay_transaction_test.rb @@ -4,13 +4,13 @@ class UsaEpayTransactionTest < Test::Unit::TestCase include CommStub def setup - @gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN') + @gateway = UsaEpayTransactionGateway.new(login: 'LOGIN') @credit_card = credit_card('4242424242424242') @check = check @options = { - :billing_address => address, - :shipping_address => address + billing_address: address, + shipping_address: address } @amount = 100 end @@ -21,7 +21,7 @@ def test_urls end def test_request_url_live - gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN', :test => false) + gateway = UsaEpayTransactionGateway.new(login: 'LOGIN', test: false) gateway.expects(:ssl_post). with('https://www.usaepay.com/gate', regexp_matches(Regexp.new('^' + Regexp.escape(purchase_request)))). returns(successful_purchase_response) @@ -56,7 +56,7 @@ def test_successful_request_with_echeck def test_successful_purchase_with_echeck_and_extra_options response = stub_comms do @gateway.purchase(@amount, check(account_type: 'savings'), @options.merge(check_format: 'ARC')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/UMcheckformat=ARC/, data) assert_match(/UMaccounttype=Savings/, data) end.respond_with(successful_purchase_response_echeck) @@ -78,8 +78,8 @@ def test_unsuccessful_request def test_successful_purchase_passing_extra_info response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:invoice => '1337', :description => 'socool')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge(invoice: '1337', description: 'socool')) + end.check_request do |_endpoint, data, _headers| assert_match(/UMinvoice=1337/, data) assert_match(/UMdescription=socool/, data) assert_match(/UMtestmode=0/, data) @@ -89,8 +89,8 @@ def test_successful_purchase_passing_extra_info def test_successful_purchase_passing_extra_test_mode response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:test_mode => true)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge(test_mode: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMtestmode=1/, data) end.respond_with(successful_purchase_response) assert_success response @@ -98,8 +98,8 @@ def test_successful_purchase_passing_extra_test_mode def test_successful_purchase_email_receipt response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'bobby@hill.com', :cust_receipt => 'Yes', :cust_receipt_name => 'socool')) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, @options.merge(email: 'bobby@hill.com', cust_receipt: 'Yes', cust_receipt_name: 'socool')) + end.check_request do |_endpoint, data, _headers| assert_match(/UMcustreceipt=Yes/, data) assert_match(/UMcustreceiptname=socool/, data) assert_match(/UMtestmode=0/, data) @@ -108,14 +108,15 @@ def test_successful_purchase_email_receipt end def test_successful_purchase_split_payment + options = @options.merge( + split_payments: [ + { key: 'abc123', amount: 199, description: 'Second payee' }, + { key: 'def456', amount: 911, description: 'Third payee' }, + ] + ) response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge( - :split_payments => [ - { :key => 'abc123', :amount => 199, :description => 'Second payee' }, - { :key => 'def456', :amount => 911, :description => 'Third payee' }, - ] - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match %r{UM02key=abc123}, data assert_match %r{UM02amount=1.99}, data assert_match %r{UM02description=Second\+payee}, data @@ -130,33 +131,35 @@ def test_successful_purchase_split_payment end def test_successful_purchase_split_payment_with_custom_on_error + options = @options.merge( + split_payments: [ + { key: 'abc123', amount: 199, description: 'Second payee' } + ], + on_error: 'Continue' + ) response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge( - :split_payments => [ - { :key => 'abc123', :amount => 199, :description => 'Second payee' } - ], - :on_error => 'Continue' - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match %r{UMonError=Continue}, data end.respond_with(successful_purchase_response) assert_success response end def test_successful_purchase_recurring_fields + options = @options.merge( + recurring_fields: { + add_customer: true, + schedule: 'quarterly', + bill_source_key: 'bill source key', + bill_amount: 123, + num_left: 5, + start: '20501212', + recurring_receipt: true + } + ) response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge( - :recurring_fields => { - add_customer: true, - schedule: 'quarterly', - bill_source_key: 'bill source key', - bill_amount: 123, - num_left: 5, - start: '20501212', - recurring_receipt: true - } - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match %r{UMaddcustomer=yes}, data assert_match %r{UMschedule=quarterly}, data assert_match %r{UMbillsourcekey=bill\+source\+key}, data @@ -169,15 +172,16 @@ def test_successful_purchase_recurring_fields end def test_successful_purchase_custom_fields + options = @options.merge( + custom_fields: { + 1 => 'diablo', + 2 => 'mephisto', + 3 => 'baal' + } + ) response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge( - :custom_fields => { - 1 => 'diablo', - 2 => 'mephisto', - 3 => 'baal' - } - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match %r{UMcustom1=diablo}, data assert_match %r{UMcustom2=mephisto}, data assert_match %r{UMcustom3=baal}, data @@ -186,39 +190,42 @@ def test_successful_purchase_custom_fields end def test_first_index_guard_on_custom_fields + num_options = @options.merge( + custom_fields: { + 0 => 'butcher', + 1 => 'diablo', + 2 => 'mephisto', + 3 => 'baal' + } + ) assert_raise(ArgumentError) do - @gateway.purchase(@amount, @credit_card, @options.merge( - :custom_fields => { - 0 => 'butcher', - 1 => 'diablo', - 2 => 'mephisto', - 3 => 'baal' - } - )) + @gateway.purchase(@amount, @credit_card, num_options) end + str_options = @options.merge( + custom_fields: { + '0' => 'butcher', + '1' => 'diablo', + '2' => 'mephisto', + '3' => 'baal' + } + ) assert_raise(ArgumentError) do - @gateway.purchase(@amount, @credit_card, @options.merge( - :custom_fields => { - '0' => 'butcher', - '1' => 'diablo', - '2' => 'mephisto', - '3' => 'baal' - } - )) + @gateway.purchase(@amount, @credit_card, str_options) end end def test_successful_purchase_line_items + options = @options.merge( + line_items: [ + { sku: 'abc123', cost: 119, quantity: 1 }, + { sku: 'def456', cost: 200, quantity: 2, name: 'an item' }, + { cost: 300, qty: 4 } + ] + ) response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge( - :line_items => [ - { :sku=> 'abc123', :cost => 119, :quantity => 1 }, - { :sku => 'def456', :cost => 200, :quantity => 2, :name => 'an item' }, - { :cost => 300, :qty => 4 } - ] - )) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| assert_match %r{UMline0sku=abc123}, data assert_match %r{UMline0cost=1.19}, data assert_match %r{UMline0qty=1}, data @@ -245,8 +252,8 @@ def test_successful_authorize_request def test_successful_authorize_passing_extra_info response = stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(:invoice => '1337', order_id: 'w00t', :description => 'socool')) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(invoice: '1337', order_id: 'w00t', description: 'socool')) + end.check_request do |_endpoint, data, _headers| assert_match(/UMinvoice=1337/, data) assert_match(/UMorderid=w00t/, data) assert_match(/UMdescription=socool/, data) @@ -257,8 +264,8 @@ def test_successful_authorize_passing_extra_info def test_successful_authorize_passing_extra_test_mode response = stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(:test_mode => true)) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(test_mode: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMtestmode=1/, data) end.respond_with(successful_authorize_response) assert_success response @@ -273,10 +280,19 @@ def test_successful_capture_request assert response.test? end + def test_successful_store_request + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + expected_auth = response.params['card_ref'] + assert_equal expected_auth, response.authorization + end + def test_successful_capture_passing_extra_info response = stub_comms do @gateway.capture(@amount, '65074409', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/UMamount=1.00/, data) assert_match(/UMtestmode=0/, data) end.respond_with(successful_capture_response) @@ -285,8 +301,8 @@ def test_successful_capture_passing_extra_info def test_successful_capture_passing_extra_test_mode response = stub_comms do - @gateway.capture(@amount, '65074409', @options.merge(:test_mode => true)) - end.check_request do |endpoint, data, headers| + @gateway.capture(@amount, '65074409', @options.merge(test_mode: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMtestmode=1/, data) end.respond_with(successful_capture_response) assert_success response @@ -313,7 +329,7 @@ def test_successful_refund_request_with_echeck def test_successful_refund_passing_extra_info response = stub_comms do @gateway.refund(@amount, '65074409', @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/UMamount=1.00/, data) assert_match(/UMtestmode=0/, data) end.respond_with(successful_refund_response) @@ -322,8 +338,8 @@ def test_successful_refund_passing_extra_info def test_successful_refund_passing_extra_test_mode response = stub_comms do - @gateway.refund(@amount, '65074409', @options.merge(:test_mode => true)) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, '65074409', @options.merge(test_mode: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMtestmode=1/, data) end.respond_with(successful_refund_response) assert_success response @@ -349,8 +365,8 @@ def test_successful_void_request_with_echeck def test_successful_void_passing_extra_info response = stub_comms do - @gateway.void('65074409', @options.merge(:no_release => true)) - end.check_request do |endpoint, data, headers| + @gateway.void('65074409', @options.merge(no_release: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMcommand=cc%3Avoid/, data) assert_match(/UMtestmode=0/, data) end.respond_with(successful_void_response) @@ -359,8 +375,8 @@ def test_successful_void_passing_extra_info def test_successful_void_passing_extra_test_mode response = stub_comms do - @gateway.refund(@amount, '65074409', @options.merge(:test_mode => true)) - end.check_request do |endpoint, data, headers| + @gateway.refund(@amount, '65074409', @options.merge(test_mode: true)) + end.check_request do |_endpoint, data, _headers| assert_match(/UMtestmode=1/, data) end.respond_with(successful_void_response) assert_success response @@ -412,8 +428,8 @@ def test_add_address_with_empty_billing_and_shipping_names def test_add_address_with_single_billing_and_shipping_names post = {} options = { - :billing_address => address(:name => 'Smith'), - :shipping_address => address(:name => 'Longsen') + billing_address: address(name: 'Smith'), + shipping_address: address(name: 'Longsen') } @gateway.send(:add_address, post, @credit_card, options) @@ -430,13 +446,13 @@ def test_add_test_mode_without_test_mode_option def test_add_test_mode_with_true_test_mode_option post = {} - @gateway.send(:add_test_mode, post, :test_mode => true) + @gateway.send(:add_test_mode, post, test_mode: true) assert_equal 1, post[:testmode] end def test_add_test_mode_with_false_test_mode_option post = {} - @gateway.send(:add_test_mode, post, :test_mode => false) + @gateway.send(:add_test_mode, post, test_mode: false) assert_equal 0, post[:testmode] end @@ -453,7 +469,7 @@ def test_supported_countries end def test_supported_card_types - assert_equal [:visa, :master, :american_express], UsaEpayTransactionGateway.supported_cardtypes + assert_equal %i[visa master american_express], UsaEpayTransactionGateway.supported_cardtypes end def test_avs_result @@ -501,7 +517,7 @@ def test_manual_entry_is_properly_indicated_on_purchase @credit_card.manual_entry = true response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r{UMcard=4242424242424242}, data assert_match %r{UMcardpresent=true}, data end.respond_with(successful_purchase_response) @@ -599,147 +615,151 @@ def successful_void_response_echeck 'UMversion=2.9&UMstatus=Approved&UMauthCode=TM80A5&UMrefNum=133134971&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' end + def successful_store_response + 'UMversion=2.9&UMstatus=Approved&UMauthCode=&UMrefNum=&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMcardRef=whx6-fvz2-24l1-39a8&UMcardType=Visa&UMmaskedCardNum=XXXXXXXXXXXX2224&UMfiller=filled' + end + def pre_scrubbed - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=4000100011112224&UMcvv2=123&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 485\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 485 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" -read 485 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=4000100011112224&UMcvv2=123&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 485\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 485 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" + read 485 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=[FILTERED]&UMcvv2=[FILTERED]&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 485\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 485 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" -read 485 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=[FILTERED]&UMcvv2=[FILTERED]&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 485\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 485 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" + read 485 bytes + Conn close + REQUEST end def pre_scrubbed_track_data - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=%25B4000100011112224%5ELONGSEN%2FL.+%5E19091200000000000000%2A%2A123%2A%2A%2A%2A%2A%2A%3F&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 485\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 485 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" -read 485 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=%25B4000100011112224%5ELONGSEN%2FL.+%5E19091200000000000000%2A%2A123%2A%2A%2A%2A%2A%2A%3F&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 485\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 485 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" + read 485 bytes + Conn close + REQUEST end def post_scrubbed_track_data - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=[FILTERED]&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 485\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 485 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" -read 485 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=[FILTERED]&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 485\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 485 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" + read 485 bytes + Conn close + REQUEST end def pre_scrubbed_echeck - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=15378535&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 572\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 572 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" -read 572 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=15378535&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 572\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 572 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" + read 572 bytes + Conn close + REQUEST end def post_scrubbed_echeck - <<-EOS -opening connection to sandbox.usaepay.com:443... -opened -starting SSL for sandbox.usaepay.com:443... -SSL established -<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" -<- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=[FILTERED]&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" --> "HTTP/1.1 200 OK\r\n" --> "Server: http\r\n" --> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" --> "Content-Type: text/html\r\n" --> "Content-Length: 572\r\n" --> "Connection: close\r\n" --> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" --> "Strict-Transport-Security: max-age=15768000\r\n" --> "\r\n" -reading 572 bytes... --> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" -read 572 bytes -Conn close - EOS + <<~REQUEST + opening connection to sandbox.usaepay.com:443... + opened + starting SSL for sandbox.usaepay.com:443... + SSL established + <- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" + <- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=[FILTERED]&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: http\r\n" + -> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" + -> "Content-Type: text/html\r\n" + -> "Content-Length: 572\r\n" + -> "Connection: close\r\n" + -> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" + -> "Strict-Transport-Security: max-age=15768000\r\n" + -> "\r\n" + reading 572 bytes... + -> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" + read 572 bytes + Conn close + REQUEST end end diff --git a/test/unit/gateways/vanco_test.rb b/test/unit/gateways/vanco_test.rb index 449cd696448..7fe1eafa753 100644 --- a/test/unit/gateways/vanco_test.rb +++ b/test/unit/gateways/vanco_test.rb @@ -3,6 +3,8 @@ class VancoTest < Test::Unit::TestCase include CommStub + SECONDS_PER_DAY = 3600 * 24 + def setup @gateway = VancoGateway.new(user_id: 'login', password: 'password', client_id: 'client_id') @credit_card = credit_card @@ -29,10 +31,8 @@ def test_successful_purchase def test_successful_purchase_with_fund_id response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(fund_id: 'MyEggcellentFund')) - end.check_request do |endpoint, data, headers| - if data =~ /EFTAdd/ - assert_match(%r(MyEggcellentFund<\/FundID>), data) - end + end.check_request do |_endpoint, data, _headers| + assert_match(%r(MyEggcellentFund<\/FundID>), data) if data =~ /EFTAdd/ end.respond_with(successful_login_response, successful_purchase_with_fund_id_response) assert_success response @@ -43,12 +43,58 @@ def test_successful_purchase_with_fund_id def test_successful_purchase_with_ip_address response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(ip: '192.168.0.1')) - end.check_request do |endpoint, data, headers| - if data =~ /EFTAdd/ - assert_match(%r(192), data) - end + end.check_request do |_endpoint, data, _headers| + assert_match(%r(192), data) if data =~ /EFTAdd/ + end.respond_with(successful_login_response, successful_purchase_response) + assert_success response + end + + def test_successful_purchase_with_existing_session_id + response = stub_comms do + previous_login_response = @gateway.send(:login) + @gateway.purchase( + @amount, + @credit_card, + @options.merge( + session_id: + { + id: '5d4177ec3c356ec5f7abe0e17e814250', + created_at: Time.parse(previous_login_response.params['auth_requesttime']) + } + ) + ) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(5d4177ec3c356ec5f7abe0e17e814250<\/SessionID>), data) if data =~ /EFTAddCompleteTransaction/ + end.respond_with(login_request_response, successful_purchase_with_existing_session_id_response) + + assert_success response + assert_equal '14949117|15756594|16136938', response.authorization + assert_equal 'Success', response.message + assert response.test? + end + + def test_successful_purchase_with_expired_session_id + two_days_from_now = Time.now - (2 * SECONDS_PER_DAY) + response = stub_comms do + @gateway.purchase( + @amount, + @credit_card, + @options.merge( + session_id: + { + id: 'f34177ec3c356ec5f7abe0e17e814250', + created_at: two_days_from_now + } + ) + ) + end.check_request do |_endpoint, data, _headers| + assert_match(%r(5d8b104c9d8265db46bdf35ae9685472f4789dc8<\/SessionID>), data) if data =~ /EFTAddCompleteTransaction/ end.respond_with(successful_login_response, successful_purchase_response) + assert_success response + assert_equal '14949117|15756594|16136938', response.authorization + assert_equal 'Success', response.message + assert response.test? end def test_failed_purchase @@ -143,6 +189,12 @@ def successful_purchase_response ) end + def successful_purchase_with_existing_session_id_response + %( + ad4cbab9740e909423a02e622689d62015-05-01 16:08:07 -0400EFTAddCompleteTransaction5d4177ec3c356ec5f7abe0e17e81425022015-05-011494911715756594161369383.20 + ) + end + def successful_purchase_with_fund_id_response %( 8cf42301416298c9d6d71a39b27a0d2015-05-05 15:45:57 -0400EFTAddCompleteTransactionb2ec96e366f38a5c1ecd3f5343475526beaba4f922015-05-051494911715756594161373313.20\n\n @@ -185,4 +237,9 @@ def failed_refund_response ) end + def login_request_response + %( + 38de95050240d49f8897578825c717 #{Time.now} Login 2 9edc7951048106b821f5e304e9ccdcc0700f533a + ) + end end diff --git a/test/unit/gateways/verifi_test.rb b/test/unit/gateways/verifi_test.rb index 404556b2d6c..f32e8ee6a74 100644 --- a/test/unit/gateways/verifi_test.rb +++ b/test/unit/gateways/verifi_test.rb @@ -5,16 +5,16 @@ class VerifiTest < Test::Unit::TestCase def setup @gateway = VerifiGateway.new( - :login => 'l', - :password => 'p' + login: 'l', + password: 'p' ) @credit_card = credit_card('4111111111111111') @options = { - :order_id => '37', - :email => 'paul@example.com', - :billing_address => address + order_id: '37', + email: 'paul@example.com', + billing_address: address } @amount = 100 @@ -67,7 +67,7 @@ def test_amount_style def test_add_description result = {} - @gateway.send(:add_invoice_data, result, :description => 'My Purchase is great') + @gateway.send(:add_invoice_data, result, description: 'My Purchase is great') assert_equal 'My Purchase is great', result[:orderdescription] end diff --git a/test/unit/gateways/viaklix_test.rb b/test/unit/gateways/viaklix_test.rb index ef288b6b652..1c92a986145 100644 --- a/test/unit/gateways/viaklix_test.rb +++ b/test/unit/gateways/viaklix_test.rb @@ -1,19 +1,18 @@ require 'test_helper' class ViaklixTest < Test::Unit::TestCase - def setup @gateway = ViaklixGateway.new( - :login => 'LOGIN', - :password => 'PIN' + login: 'LOGIN', + password: 'PIN' ) @credit_card = credit_card @options = { - :order_id => '37', - :email => 'paul@domain.com', - :description => 'Test Transaction', - :billing_address => address + order_id: '37', + email: 'paul@domain.com', + description: 'Test Transaction', + billing_address: address } @amount = 100 end @@ -70,9 +69,9 @@ def unsuccessful_purchase_response end def invalid_login_response - <<-RESPONSE -ssl_result=7000\r -ssl_result_message=The viaKLIX ID and/or User ID supplied in the authorization request is invalid.\r + <<~RESPONSE + ssl_result=7000\r + ssl_result_message=The viaKLIX ID and/or User ID supplied in the authorization request is invalid.\r RESPONSE end end diff --git a/test/unit/gateways/visanet_peru_test.rb b/test/unit/gateways/visanet_peru_test.rb index 49b7a78951b..896f2bbb5a9 100644 --- a/test/unit/gateways/visanet_peru_test.rb +++ b/test/unit/gateways/visanet_peru_test.rb @@ -1,6 +1,9 @@ require 'test_helper' +require 'timecop' class VisanetPeruTest < Test::Unit::TestCase + include CommStub + def setup @gateway = VisanetPeruGateway.new(fixtures(:visanet_peru)) @@ -37,6 +40,41 @@ def test_failed_purchase assert_equal 'Operacion Denegada.', response.message end + def test_nonconsecutive_purchase_numbers + purchase_times = [] + + Timecop.freeze do + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + purchase_times << JSON.parse(data)['purchaseNumber'].to_i + end.respond_with(successful_authorize_response) + + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + purchase_times << JSON.parse(data)['purchaseNumber'].to_i + end.respond_with(successful_authorize_response) + + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + purchase_times << JSON.parse(data)['purchaseNumber'].to_i + end.respond_with(successful_authorize_response) + + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_method, _endpoint, data, _headers| + purchase_times << JSON.parse(data)['purchaseNumber'].to_i + end.respond_with(successful_authorize_response) + end + + purchase_times.each do |t| + assert_equal(t.to_s.length, 12) + end + assert_equal(purchase_times.uniq.size, purchase_times.size) + end + def test_successful_authorize @gateway.expects(:ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) @@ -60,14 +98,14 @@ def test_failed_authorize response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 400, response.error_code - assert_equal 'REJECT', response.message + assert_equal 'REJECT | Operacion denegada', response.message end def test_successful_capture @gateway.expects(:ssl_request).with(:post, any_parameters).returns(successful_authorize_response) @gateway.expects(:ssl_request).with(:put, any_parameters).returns(successful_capture_response) response = @gateway.authorize(@amount, @credit_card, @options) - capture = @gateway.capture(response.authorization, @options) + capture = @gateway.capture(@amount, response.authorization, @options) assert_success capture assert_equal 'OK', capture.message assert_match %r(^[0-9]{9}|$), capture.authorization @@ -78,7 +116,7 @@ def test_successful_capture def test_failed_capture @gateway.expects(:ssl_request).returns(failed_capture_response) invalid_purchase_number = '900000044' - response = @gateway.capture(invalid_purchase_number) + response = @gateway.capture(@amount, invalid_purchase_number) assert_failure response assert_equal '[ "NUMORDEN 900000044 no se encuentra registrado", "No se realizo el deposito" ]', response.message assert_equal 400, response.error_code @@ -104,6 +142,27 @@ def test_failed_refund assert_equal 400, response.error_code end + def test_failed_full_refund_when_unsettled + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(failed_refund_response) + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(failed_refund_with_action_code_response) + response = @gateway.refund(@amount, '122333444|444333221', force_full_refund_if_unsettled: true) + assert_failure response + assert_equal("Operacion Denegada. | [ 'NUMORDEN 122333444 no se encuentra registrado', 'No se realizo la anulacion del deposito' ]", response.message) + assert_equal 400, response.error_code + end + + def test_failed_full_refund_when_unsettled_additional_message_concatenation + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(failed_refund_with_message_and_action_code_response) + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(failed_refund_with_message_and_action_code_response_2) + first_msg = 'No se realizo la anulacion del deposito' + first_dsc = 'Operacion Denegada.' + second_msg = 'Mal funcionamiento de la inteligencia artificial' + second_dsc = 'Lo siento Dave, me temo que no puedo hacer eso.' + + response = @gateway.refund(@amount, '122333444|444333221', force_full_refund_if_unsettled: true) + assert_equal("#{second_msg} | #{second_dsc} | #{first_msg} | #{first_dsc}", response.message) + end + def test_successful_void @gateway.expects(:ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) @@ -446,4 +505,55 @@ def failed_refund_response } RESPONSE end + + def failed_refund_with_action_code_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ ]", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Operacion Denegada." + }, + "transactionLog": { + + } + } + RESPONSE + end + + def failed_refund_with_message_and_action_code_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "No se realizo la anulacion del deposito", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Operacion Denegada." + }, + "transactionLog": { + + } + } + RESPONSE + end + + def failed_refund_with_message_and_action_code_response_2 + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "Mal funcionamiento de la inteligencia artificial", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Lo siento Dave, me temo que no puedo hacer eso." + }, + "transactionLog": { + + } + } + RESPONSE + end end diff --git a/test/unit/gateways/vpos_test.rb b/test/unit/gateways/vpos_test.rb new file mode 100644 index 00000000000..ebbe5bf96ad --- /dev/null +++ b/test/unit/gateways/vpos_test.rb @@ -0,0 +1,227 @@ +require 'test_helper' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class VposGateway + def one_time_public_key + OpenSSL::PKey::RSA.new(2048) + end + end + end +end + +class VposTest < Test::Unit::TestCase + def setup + @gateway = VposGateway.new(public_key: 'some_key', private_key: 'some_other_key', encryption_key: OpenSSL::PKey::RSA.new(512)) + @credit_card = credit_card + @amount = 10000 + + @options = { + commerce: '123', + commerce_branch: '45' + } + 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 '701175#233024225526089', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '57', response.error_code + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + + assert_equal '707860#948868617271843', response.authorization + assert response.test? + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_failure response + assert_equal 'RefundsServiceError:TIPO DE TRANSACCION NO PERMITIDA PARA TARJETAS EXTRANJERAS', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('123#456') + assert_success response + assert_equal 'RollbackSuccessful:Transacción Aprobada', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void(nil) + assert_failure response + assert_equal 'AlreadyRollbackedError:The payment has already been rollbacked.', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<~TRANSCRIPT + opening connection to vpos.infonet.com.py:8888... + opened + starting SSL for vpos.infonet.com.py:8888... + SSL established + <- "POST /vpos/api/0.3/application/encryption-key HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: vpos.infonet.com.py:8888\r\nContent-Length: 106\r\n\r\n" + <- "{\"public_key\":\"joszzJzNMkn6SKn0a5P9GcMw1HqZjC1u\",\"operation\":{\"token\":\"683137179e606c700805e7773751b705\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.18.0\r\n" + -> "Date: Tue, 06 Apr 2021 01:56:12 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "1a6\r\n" + reading 422 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03e\x91K\x93\xA20\x00\x84\xFF\xCA\x14\xD7\xDD-QWe\xF6\x16H\x80\x00Fq\xE4!E\xD5\x96\x13\x94wT\x12\x84\xB8\xB5\xFF}u\xAE\xDB\xA7\xAE\xFE.\xDD\xD5\x7F\x14.\x8E\xA2\xE7\xCA/\x85\xF7\x94\x9E8W\xBE+'F;y\x15\xE5\x85\xFD\xAEO\xF2\x89~\xBC\xA4#\v\x93\xB7m\xA0{\xD8xs\xD1\xE1+L\xD9\x1Ac\x1DW\x80\xE8y}+\xEA\xD2z\x1FT\x1D\xF8\xC8\x04`c\x00_\x03/n\xE4\xEE\xD3#p\x93\xC0@\x0E0>\xAF\xD1\x81Q\xF3>\xEF!N\x99\xEF\xCC\xBD\x82\xC7\x17\x1Fy\xB3f\x9C\xAD\xC3\xE8\f\x9Dlb\xD9\xB1D\xC70\xB43>\x8Do\xCB\x0E\x0FZ\xC1\xEF\xE3\xC2\xCC\xB22\xDA\x98\xCD6\xA2\xF7,&E\x9B2\x14s\xAF<4\x1F\x89\xD7\r\x8E1U\x17\xC2\xDA\x17A\xEE\xC8\xED#\xB0\xF1\xA2\xCF\xF5\x1C\xCC\x06]\xEC\xF2\xE1!+\xCA\xC7\x99]H\xEA\xD5$\x88o\xFD\x1E\xE8)\xCB\xC7\xB2\xDA8\xB2\x8E\xC6D}\x9C\xE2\x05\fa?\xC9\xD5\xB3OiI|UD\xA1\xF9y\xF5\x7F\x0E\xE2\x83\x11\xDET\x8E;q\xC2\xB6Y\xD5\xF1\x9Dm\xC3GC\x9E\x1D\x8E\x9Ef\x10\x86\xA5\xBA\x81\xAA\xD7;\xA5m\xA06\x0F`\xAD\xAE\x9A\x8B\x95\x8834\xE5\xE84\xFB\xD5\xD0\xEF\xE2\x15JB\xAER\xB3n\xC66\xC4\xA2\x1A\x10O\xD9\xB7\xED5\x98$r\x9C/\xF4\xDD\xB5\x8B\x96\xD2>\xC4\x19;,\xB9\x06\x89h]w\xDA\xE5;\xB8\nGK\x9C\x93~\xDDV\x13\x88\x9DNK\x102\x1F\xCDL\xA3n\xCA\xC8\x80!\xF0_{\xBE\xCEA\x04\xFE\xFF\x97\xF2\xF7\x1Fe\xDC\xA8\x0E\xF4\x01\x00\x00" + read 422 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to vpos.infonet.com.py:8888... + opened + starting SSL for vpos.infonet.com.py:8888... + SSL established + <- "POST /vpos/api/0.3/pci/encrypted HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: vpos.infonet.com.py:8888\r\nContent-Length: 850\r\n\r\n" + <- "{\"public_key\":\"joszzJzNMkn6SKn0a5P9GcMw1HqZjC1u\",\"operation\":{\"token\":\"e0ca140e32edb506b20f47260b97b1ad\",\"commerce\":232,\"commerce_branch\":77,\"shop_process_id\":552384714818596,\"number_of_payments\":1,\"recursive\":false,\"amount\":\"1000.00\",\"currency\":\"PYG\",\"card_encrypted_data\":\"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.bWLbgRHAl7GmGveBFEoi64bX472TQao5lCausaMSB2LsES8StjWxPbAZpfrFZDcWksnD2WfDbajSX11WsJJApohjp5fawPP30QcDjmSG-I9WXVnW_Qm-mcrejc82Km8A76-pr9aZd_od81QfQCYwOzpA6V_fz1zY_s8oWBBoudBThDQ__fhazJS5UXM8qMWtooUEmsiiGNDlv-0QTvWAQ-ShhZSDeMRQW6E6p8Jo-1rAlaPEpY2a9yUwT1Emq8eqWz6Fb3w6LA2fUCA1-aXwzfm1vs-LQ2ISgEugMU19gYqhl6qKLNXOJs0KkJCCuKutlHC9zbDPoKU8oO0cDSOfNg.6xi5G9fBauLK2c6p.1pF9qw6fMJyfbNU8y0Hi_x4WNH8GZASuZS6tNpfhnJjhUmdHHcEBV-WGF5FoKw.r4cVO2MlpKe229paSt2D1Q\",\"card_month_expiration\":\"08\",\"card_year_expiration\":\"21\",\"additional_data\":null}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.18.0\r\n" + -> "Date: Tue, 06 Apr 2021 01:56:21 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "147\r\n" + reading 327 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03U\x91\xCBn\xC3 \x10E\x7F%b\x1DY\x80\x9F\xF1\xCE\xABn\xBA\xA8\x9Av\xD1\x15\xC20VPb\xB0xTI\xA3\xFC{\aWU\xDD\x1D\xC3p\xE7\x9E\xB9\xDCI\x882\xA6@z\x12\x92R\x10\x02\xD9\x13\xE5\xECd\xFC,\xA3q\x96\xF4w\x12\xDD\x19\xF0@\xDAV\xB7S5\xF2\t\xA4\xEE\xBAj*\x99\xAC&\xD6\x8CeS\xD2j\x1C\x19J\xC3\xC9-b\xF1.O\x12F\x93\xBE\xAEy\xD9U-\xAB:\xD6\xD5\x87fO<\x84\xC5\xD9\x008\xEF\x88\x82\xDFRh\x88\xD2\\2\xC8\xCB*\x97\xDA\xED\x8E\x88\x10&\xA9\xA2\xF3F\xCE`#\xA0B\xA6x\xC2\xFAk\xC5\x136\xCD#\xF8\fG9\xAD\e\xECG\xA3\xCE\x10\xFF\x1A\x9C\xB1\xF6\xD0\xF0\x86\xF1\xAD\x9Dr:#P\xFA\x9F!(o\x96\x9F\xBD\xC9\x9B\x976H\xA5\xD0f'q\xA7Qj\x89\xAF\xE1\x1A\xC1j\xD0b\x83\xBE\x91\xD9t\xB9`\x0E\xA0\x927\xF1&\x8C\x9D\xDC&J%\xBD\x16\xC1%\xAF\xB2\xFB3\x8ES)D7\x83\x17f\xC1\eF\xBB\x82\xD7e\xC1yS\xF02'\xBA*\x94K6\xFA[\x0Egx\x1D\x9E\xDE\x87\x0F\xEC|\x82\x0F\xEB\x0F\x11Z\x94y\r\x13\xCE\xE8\xA7\xE1Jz\xFAx<\xBE\x01eg ^\xDC\x01\x00\x00" + read 327 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT + end + + def post_scrubbed + transcript = <<~TRANSCRIPT + opening connection to vpos.infonet.com.py:8888... + opened + starting SSL for vpos.infonet.com.py:8888... + SSL established + <- "POST /vpos/api/0.3/application/encryption-key HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: vpos.infonet.com.py:8888\r\nContent-Length: 106\r\n\r\n" + <- "{\"public_key\":\"joszzJzNMkn6SKn0a5P9GcMw1HqZjC1u\",\"operation\":{\"token\":\"683137179e606c700805e7773751b705\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.18.0\r\n" + -> "Date: Tue, 06 Apr 2021 01:56:12 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "1a6\r\n" + reading 422 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03e\x91K\x93\xA20\x00\x84\xFF\xCA\x14\xD7\xDD-QWe\xF6\x16H\x80\x00Fq\xE4!E\xD5\x96\x13\x94wT\x12\x84\xB8\xB5\xFF}u\xAE\xDB\xA7\xAE\xFE.\xDD\xD5\x7F\x14.\x8E\xA2\xE7\xCA/\x85\xF7\x94\x9E8W\xBE+'F;y\x15\xE5\x85\xFD\xAEO\xF2\x89~\xBC\xA4#\v\x93\xB7m\xA0{\xD8xs\xD1\xE1+L\xD9\x1Ac\x1DW\x80\xE8y}+\xEA\xD2z\x1FT\x1D\xF8\xC8\x04`c\x00_\x03/n\xE4\xEE\xD3#p\x93\xC0@\x0E0>\xAF\xD1\x81Q\xF3>\xEF!N\x99\xEF\xCC\xBD\x82\xC7\x17\x1Fy\xB3f\x9C\xAD\xC3\xE8\f\x9Dlb\xD9\xB1D\xC70\xB43>\x8Do\xCB\x0E\x0FZ\xC1\xEF\xE3\xC2\xCC\xB22\xDA\x98\xCD6\xA2\xF7,&E\x9B2\x14s\xAF<4\x1F\x89\xD7\r\x8E1U\x17\xC2\xDA\x17A\xEE\xC8\xED#\xB0\xF1\xA2\xCF\xF5\x1C\xCC\x06]\xEC\xF2\xE1!+\xCA\xC7\x99]H\xEA\xD5$\x88o\xFD\x1E\xE8)\xCB\xC7\xB2\xDA8\xB2\x8E\xC6D}\x9C\xE2\x05\fa?\xC9\xD5\xB3OiI|UD\xA1\xF9y\xF5\x7F\x0E\xE2\x83\x11\xDET\x8E;q\xC2\xB6Y\xD5\xF1\x9Dm\xC3GC\x9E\x1D\x8E\x9Ef\x10\x86\xA5\xBA\x81\xAA\xD7;\xA5m\xA06\x0F`\xAD\xAE\x9A\x8B\x95\x8834\xE5\xE84\xFB\xD5\xD0\xEF\xE2\x15JB\xAER\xB3n\xC66\xC4\xA2\x1A\x10O\xD9\xB7\xED5\x98$r\x9C/\xF4\xDD\xB5\x8B\x96\xD2>\xC4\x19;,\xB9\x06\x89h]w\xDA\xE5;\xB8\nGK\x9C\x93~\xDDV\x13\x88\x9DNK\x102\x1F\xCDL\xA3n\xCA\xC8\x80!\xF0_{\xBE\xCEA\x04\xFE\xFF\x97\xF2\xF7\x1Fe\xDC\xA8\x0E\xF4\x01\x00\x00" + read 422 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to vpos.infonet.com.py:8888... + opened + starting SSL for vpos.infonet.com.py:8888... + SSL established + <- "POST /vpos/api/0.3/pci/encrypted HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: vpos.infonet.com.py:8888\r\nContent-Length: 850\r\n\r\n" + <- "{\"public_key\":\"joszzJzNMkn6SKn0a5P9GcMw1HqZjC1u\",\"operation\":{\"token\":\"e0ca140e32edb506b20f47260b97b1ad\",\"commerce\":232,\"commerce_branch\":77,\"shop_process_id\":552384714818596,\"number_of_payments\":1,\"recursive\":false,\"amount\":\"1000.00\",\"currency\":\"PYG\",\"card_encrypted_data\":\"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.bWLbgRHAl7GmGveBFEoi64bX472TQao5lCausaMSB2LsES8StjWxPbAZpfrFZDcWksnD2WfDbajSX11WsJJApohjp5fawPP30QcDjmSG-I9WXVnW_Qm-mcrejc82Km8A76-pr9aZd_od81QfQCYwOzpA6V_fz1zY_s8oWBBoudBThDQ__fhazJS5UXM8qMWtooUEmsiiGNDlv-0QTvWAQ-ShhZSDeMRQW6E6p8Jo-1rAlaPEpY2a9yUwT1Emq8eqWz6Fb3w6LA2fUCA1-aXwzfm1vs-LQ2ISgEugMU19gYqhl6qKLNXOJs0KkJCCuKutlHC9zbDPoKU8oO0cDSOfNg.6xi5G9fBauLK2c6p.1pF9qw6fMJyfbNU8y0Hi_x4WNH8GZASuZS6tNpfhnJjhUmdHHcEBV-WGF5FoKw.r4cVO2MlpKe229paSt2D1Q\",\"card_month_expiration\":\"08\",\"card_year_expiration\":\"21\",\"additional_data\":null}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.18.0\r\n" + -> "Date: Tue, 06 Apr 2021 01:56:21 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "147\r\n" + reading 327 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03U\x91\xCBn\xC3 \x10E\x7F%b\x1DY\x80\x9F\xF1\xCE\xABn\xBA\xA8\x9Av\xD1\x15\xC20VPb\xB0xTI\xA3\xFC{\aWU\xDD\x1D\xC3p\xE7\x9E\xB9\xDCI\x882\xA6@z\x12\x92R\x10\x02\xD9\x13\xE5\xECd\xFC,\xA3q\x96\xF4w\x12\xDD\x19\xF0@\xDAV\xB7S5\xF2\t\xA4\xEE\xBAj*\x99\xAC&\xD6\x8CeS\xD2j\x1C\x19J\xC3\xC9-b\xF1.O\x12F\x93\xBE\xAEy\xD9U-\xAB:\xD6\xD5\x87fO<\x84\xC5\xD9\x008\xEF\x88\x82\xDFRh\x88\xD2\\2\xC8\xCB*\x97\xDA\xED\x8E\x88\x10&\xA9\xA2\xF3F\xCE`#\xA0B\xA6x\xC2\xFAk\xC5\x136\xCD#\xF8\fG9\xAD\e\xECG\xA3\xCE\x10\xFF\x1A\x9C\xB1\xF6\xD0\xF0\x86\xF1\xAD\x9Dr:#P\xFA\x9F!(o\x96\x9F\xBD\xC9\x9B\x976H\xA5\xD0f'q\xA7Qj\x89\xAF\xE1\x1A\xC1j\xD0b\x83\xBE\x91\xD9t\xB9`\x0E\xA0\x927\xF1&\x8C\x9D\xDC&J%\xBD\x16\xC1%\xAF\xB2\xFB3\x8ES)D7\x83\x17f\xC1\eF\xBB\x82\xD7e\xC1yS\xF02'\xBA*\x94K6\xFA[\x0Egx\x1D\x9E\xDE\x87\x0F\xEC|\x82\x0F\xEB\x0F\x11Z\x94y\r\x13\xCE\xE8\xA7\xE1Jz\xFAx<\xBE\x01eg ^\xDC\x01\x00\x00" + read 327 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + TRANSCRIPT + @gateway.remove_invalid_utf_8_byte_sequences(transcript) + end + + def successful_purchase_response + %({"status":"success","confirmation":{"token":"2e2f60bd985018defc145a2f5dc0060e","shop_process_id":233024225526089,"response":"S","response_details":"Procesado Satisfactoriamente","authorization_number":"701175","ticket_number":"2117959993","response_code":"00","response_description":"Transaccion aprobada","extended_response_description":null,"security_information":{"card_source":"L","customer_ip":"108.253.226.231","card_country":"PARAGUAY","version":"0.3","risk_index":0}}} + ) + end + + def failed_purchase_response + %({"status":"success","confirmation":{"token":"d08dd5bd604f4c4ba1049195b9e015e2","shop_process_id":845868143743681,"response":"N","response_details":"Procesado Satisfactoriamente","authorization_number":null,"ticket_number":"2117962608","response_code":"57","response_description":"Transaccion denegada","extended_response_description":"IMPORTE DE LA TRN INFERIOR AL M\u00bfNIMO PERMITIDO","security_information":{"card_source":"I","customer_ip":"108.253.226.231","card_country":"UNITED STATES","version":"0.3","risk_index":0}}}) + end + + def successful_credit_response + %({"status":"success","refund":{"status":4,"request_token":"74845bf692d3ff78ce5d5c7d0d8ecdfa","shop_process_id":948868617271843,"origin_shop_process_id":null,"amount":"1000.0","currency":"PYG","commerce":232,"commerce_branch":77,"ticket_number":2117984322,"authorization_code":"707860","response_code":"00","response_description":"Transaccion aprobada","extended_response":null}}) + end + + def failed_credit_response + %({"status":"error","messages":[{"level":"error","key":"RefundsServiceError","dsc":"TIPO DE TRANSACCION NO PERMITIDA PARA TARJETAS EXTRANJERAS"}]}) + end + + def successful_void_response + %({"status":"success","messages":[{"dsc":"Transacción Aprobada","key":"RollbackSuccessful","level":"info"}]}) + end + + def failed_void_response + %({"status":"error","messages":[{"level":"error","key":"AlreadyRollbackedError","dsc":"The payment has already been rollbacked."}]}) + end +end diff --git a/test/unit/gateways/webpay_test.rb b/test/unit/gateways/webpay_test.rb index 25123a5623b..66176804c87 100644 --- a/test/unit/gateways/webpay_test.rb +++ b/test/unit/gateways/webpay_test.rb @@ -4,15 +4,15 @@ class WebpayTest < Test::Unit::TestCase include CommStub def setup - @gateway = WebpayGateway.new(:login => 'login') + @gateway = WebpayGateway.new(login: 'sk_test_login') @credit_card = credit_card() @amount = 40000 @refund_amount = 20000 @options = { - :billing_address => address(), - :description => 'Test Purchase' + billing_address: address(), + description: 'Test Purchase' } end @@ -60,7 +60,7 @@ def test_appropriate_purchase_amount def test_successful_purchase_with_token response = stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, 'cus_xxx|card_xxx') - end.check_request do |method, endpoint, data, headers| + end.check_request do |_method, _endpoint, data, _headers| assert_match(/customer=cus_xxx/, data) assert_match(/card=card_xxx/, data) end.respond_with(successful_purchase_response) @@ -94,7 +94,7 @@ def test_successful_refund end def test_successful_request_always_uses_live_mode_to_determine_test_request - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response(:livemode => true)) + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response(livemode: true)) assert response = @gateway.refund(@refund_amount, 'ch_test_charge') assert_success response @@ -121,24 +121,24 @@ def test_invalid_raw_response def test_add_customer post = {} - @gateway.send(:add_customer, post, 'card_token', {:customer => 'test_customer'}) + @gateway.send(:add_customer, post, 'card_token', { customer: 'test_customer' }) assert_equal 'test_customer', post[:customer] end def test_doesnt_add_customer_if_card post = {} - @gateway.send(:add_customer, post, @credit_card, {:customer => 'test_customer'}) + @gateway.send(:add_customer, post, @credit_card, { customer: 'test_customer' }) assert !post[:customer] end def test_add_customer_data post = {} - @gateway.send(:add_customer_data, post, {:description => 'a test customer'}) + @gateway.send(:add_customer_data, post, { description: 'a test customer' }) assert_equal 'a test customer', post[:description] end def test_add_address - post = {:card => {}} + post = { card: {} } @gateway.send(:add_address, post, @options) assert_equal @options[:billing_address][:zip], post[:card][:address_zip] assert_equal @options[:billing_address][:state], post[:card][:address_state] @@ -158,270 +158,270 @@ def test_gateway_without_credentials end def test_metadata_header - @gateway.expects(:ssl_request).once.with { |method, url, post, headers| - headers && headers['X-Webpay-Client-User-Metadata'] == {:ip => '1.1.1.1'}.to_json + @gateway.expects(:ssl_request).once.with { |_method, _url, _post, headers| + headers && headers['X-Webpay-Client-User-Metadata'] == { ip: '1.1.1.1' }.to_json }.returns(successful_purchase_response) - @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '1.1.1.1')) + @gateway.purchase(@amount, @credit_card, @options.merge(ip: '1.1.1.1')) end private def successful_authorization_response - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "livemode": false, - "currency": "jpy", - "description": "ActiveMerchant Test Purchase", - "amount": 40000, - "amount_refunded": 0, - "customer": null, - "recursion": null, - "created": 1309131571, - "paid": false, - "refunded": false, - "failure_message": null, - "card": { - "object": "card", - "exp_year": #{Time.now.year + 1}, - "exp_month": 11, - "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", - "name": "LONGBOB LONGSEN", - "country": "JP", - "type": "Visa", - "cvc_check": "pass", - "last4": "4242" - }, - "captured": false, - "expire_time": 1309736371, - "fees": [ - - ] -} + <<~RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 40000, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1309131571, + "paid": false, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": false, + "expire_time": 1309736371, + "fees": [ + + ] + } RESPONSE end def successful_capture_response - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "livemode": false, - "currency": "jpy", - "description": "ActiveMerchant Test Purchase", - "amount": 40000, - "amount_refunded": 0, - "customer": null, - "recursion": null, - "created": 1309131571, - "paid": true, - "refunded": false, - "failure_message": null, - "card": { - "object": "card", - "exp_year": #{Time.now.year + 1}, - "exp_month": 11, - "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", - "name": "LONGBOB LONGSEN", - "country": "JP", - "type": "Visa", - "cvc_check": "pass", - "last4": "4242" - }, - "captured": true, - "expire_time": 1309736371, - "fees": [ - { - "object": "fee", - "transaction_type": "payment", - "transaction_fee": 0, - "rate": 3.25, - "amount": 1300, - "created": 1408585142 - } - ] -} + <<~RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 40000, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1309131571, + "paid": true, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": 1309736371, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585142 + } + ] + } RESPONSE end # Place raw successful response from gateway here - def successful_purchase_response(refunded=false) - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "livemode": false, - "currency": "jpy", - "description": "ActiveMerchant Test Purchase", - "amount": 400, - "amount_refunded": 0, - "customer": null, - "recursion": null, - "created": 1408585273, - "paid": true, - "refunded": false, - "failure_message": null, - "card": { - "object": "card", - "exp_year": #{Time.now.year + 1}, - "exp_month": 11, - "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", - "name": "LONGBOB LONGSEN", - "country": "JP", - "type": "Visa", - "cvc_check": "pass", - "last4": "4242" - }, - "captured": true, - "expire_time": null, - "fees": [ - { - "object": "fee", - "transaction_type": "payment", - "transaction_fee": 0, - "rate": 3.25, - "amount": 1300, - "created": 1408585273 - } - ] -} + def successful_purchase_response(refunded = false) + <<~RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1408585273, + "paid": true, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": null, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585273 + } + ] + } RESPONSE end def successful_refunded_response - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "livemode": false, - "currency": "jpy", - "description": "ActiveMerchant Test Purchase", - "amount": 400, - "amount_refunded": 400, - "customer": null, - "recursion": null, - "created": 1408585273, - "paid": true, - "refunded": true, - "failure_message": null, - "card": { - "object": "card", - "exp_year": #{Time.now.year + 1}, - "exp_month": 11, - "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", - "name": "KEI KUBO", - "country": "JP", - "type": "Visa", - "cvc_check": "pass", - "last4": "4242" - }, - "captured": true, - "expire_time": null, - "fees": [ - { - "object": "fee", - "transaction_type": "payment", - "transaction_fee": 0, - "rate": 3.25, - "amount": 1300, - "created": 1408585273 - }, - { - "object": "fee", - "transaction_type": "refund", - "transaction_fee": 0, - "rate": 3.25, - "amount": -1300, - "created": 1408585461 - } - ] -} + <<~RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 400, + "customer": null, + "recursion": null, + "created": 1408585273, + "paid": true, + "refunded": true, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "KEI KUBO", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": null, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585273 + }, + { + "object": "fee", + "transaction_type": "refund", + "transaction_fee": 0, + "rate": 3.25, + "amount": -1300, + "created": 1408585461 + } + ] + } RESPONSE end def successful_partially_refunded_response(options = {}) - options = {:livemode=>false}.merge!(options) - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "livemode": #{options[:livemode]}, - "currency": "jpy", - "description": "ActiveMerchant Test Purchase", - "amount": 400, - "amount_refunded": 200, - "customer": null, - "recursion": null, - "created": 1408584994, - "paid": true, - "refunded": false, - "failure_message": null, - "card": { - "object": "card", - "exp_year": #{Time.now.year + 1}, - "exp_month": 11, - "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", - "name": "KEI KUBO", - "country": "JP", - "type": "Visa", - "cvc_check": "pass", - "last4": "4242" - }, - "captured": true, - "expire_time": 1409189794, - "fees": [ - { - "object": "fee", - "transaction_type": "payment", - "transaction_fee": 0, - "rate": 3.25, - "amount": 1300, - "created": 1408585142 - }, - { - "object": "fee", - "transaction_type": "refund", - "transaction_fee": 0, - "rate": 3.25, - "amount": -1300, - "created": 1408585699 - }, - { - "object": "fee", - "transaction_type": "payment", - "transaction_fee": 0, - "rate": 3.25, - "amount": 650, - "created": 1408585699 - } - ] -} + options = { livemode: false }.merge!(options) + <<~RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "livemode": #{options[:livemode]}, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 200, + "customer": null, + "recursion": null, + "created": 1408584994, + "paid": true, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "KEI KUBO", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": 1409189794, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585142 + }, + { + "object": "fee", + "transaction_type": "refund", + "transaction_fee": 0, + "rate": 3.25, + "amount": -1300, + "created": 1408585699 + }, + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 650, + "created": 1408585699 + } + ] + } RESPONSE end # Place raw failed response from gateway here def failed_purchase_response - <<-RESPONSE -{ - "error": { - "message": "The card number is invalid. Make sure the number entered matches your credit card.", - "caused_by": "buyer", - "param": "number", - "type": "card_error", - "code": "incorrect_number" - } -} + <<~RESPONSE + { + "error": { + "message": "The card number is invalid. Make sure the number entered matches your credit card.", + "caused_by": "buyer", + "param": "number", + "type": "card_error", + "code": "incorrect_number" + } + } RESPONSE end # Place raw invalid JSON from gateway here def invalid_json_response - <<-RESPONSE - { - foo : bar - } + <<~RESPONSE + { + foo : bar + } RESPONSE end end diff --git a/test/unit/gateways/wepay_test.rb b/test/unit/gateways/wepay_test.rb index 9f749e27916..30c012b590a 100644 --- a/test/unit/gateways/wepay_test.rb +++ b/test/unit/gateways/wepay_test.rb @@ -160,7 +160,7 @@ def test_invalid_json_response def test_no_version_by_default stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, _data, headers| assert_no_match(/Api-Version/, headers.to_s) end.respond_with(successful_authorize_response) end @@ -168,7 +168,7 @@ def test_no_version_by_default def test_version_override stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(version: '2017-05-31')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, _data, headers| assert_match(/"Api-Version\"=>\"2017-05-31\"/, headers.to_s) end.respond_with(successful_authorize_response) end @@ -425,5 +425,4 @@ def failed_capture_response def invalid_json_response %({"checkout_id"=1852898602,"state":"captured") end - end diff --git a/test/unit/gateways/wirecard_test.rb b/test/unit/gateways/wirecard_test.rb index f8f3e2d2b90..fcbd76c83d6 100644 --- a/test/unit/gateways/wirecard_test.rb +++ b/test/unit/gateways/wirecard_test.rb @@ -32,7 +32,7 @@ def setup city: 'Ottawa', zip: 'K12 P2A', country: 'CA', - state: nil, + state: nil } @address_avs = { @@ -40,7 +40,7 @@ def setup city: 'London', zip: 'W8 2TE', country: 'GB', - state: 'London', + state: 'London' } end @@ -192,7 +192,7 @@ def test_description_trucated_to_32_chars_in_authorize stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/32chars-------------------------<\/FunctionID>/, data) end.respond_with(successful_authorization_response) end @@ -202,7 +202,7 @@ def test_description_trucated_to_32_chars_in_purchase stub_comms do @gateway.purchase(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/32chars-------------------------<\/FunctionID>/, data) end.respond_with(successful_purchase_response) end @@ -212,7 +212,7 @@ def test_description_is_ascii_encoded_since_wirecard_does_not_like_utf_8 stub_comms do @gateway.purchase(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/\?D\?nde est\? la estaci\?n\?<\/FunctionID>/, data) end.respond_with(successful_purchase_response) end @@ -236,7 +236,7 @@ def test_commerce_type_option stub_comms do @gateway.purchase(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/MOTO<\/CommerceType>/, data) end.respond_with(successful_purchase_response) end @@ -244,7 +244,7 @@ def test_commerce_type_option def test_store_sets_recurring_transaction_type_to_initial stub_comms do @gateway.store(@credit_card) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') end.respond_with(successful_authorization_response) end @@ -252,15 +252,15 @@ def test_store_sets_recurring_transaction_type_to_initial def test_store_sets_amount_to_100_by_default stub_comms do @gateway.store(@credit_card) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//CC_TRANSACTION/Amount', '100') end.respond_with(successful_authorization_response) end def test_store_sets_amount_to_amount_from_options stub_comms do - @gateway.store(@credit_card, :amount => 120) - end.check_request do |endpoint, body, headers| + @gateway.store(@credit_card, amount: 120) + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//CC_TRANSACTION/Amount', '120') end.respond_with(successful_authorization_response) end @@ -268,7 +268,7 @@ def test_store_sets_amount_to_amount_from_options def test_authorization_using_reference_sets_proper_elements stub_comms do @gateway.authorize(@amount, '45678', @options) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//GuWID', '45678') assert_no_match(//, body) end.respond_with(successful_authorization_response) @@ -277,7 +277,7 @@ def test_authorization_using_reference_sets_proper_elements def test_purchase_using_reference_sets_proper_elements stub_comms do @gateway.purchase(@amount, '87654', @options) - end.check_request do |endpoint, body, headers| + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//GuWID', '87654') assert_no_match(//, body) end.respond_with(successful_authorization_response) @@ -285,16 +285,16 @@ def test_purchase_using_reference_sets_proper_elements def test_authorization_with_recurring_transaction_type_initial stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(:recurring => 'Initial')) - end.check_request do |endpoint, body, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(recurring: 'Initial')) + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') end.respond_with(successful_authorization_response) end def test_purchase_using_with_recurring_transaction_type_initial stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:recurring => 'Initial')) - end.check_request do |endpoint, body, headers| + @gateway.purchase(@amount, @credit_card, @options.merge(recurring: 'Initial')) + end.check_request do |_endpoint, body, _headers| assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') end.respond_with(successful_authorization_response) end @@ -329,43 +329,43 @@ def assert_xml_element_text(xml, xpath, expected_text) # Authorization success def successful_authorization_response - <<-XML - - - - - test dummy data - - Wirecard remote test purchase - - 1 - - C822580121385121429927 - 709678 - THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. - INFO - ACK - 2008-06-19 06:53:33 - - - - - - + <<~XML + + + + + test dummy data + + Wirecard remote test purchase + + 1 + + C822580121385121429927 + 709678 + THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. + INFO + ACK + 2008-06-19 06:53:33 + + + + + + XML end # Authorization failure # TODO: replace with real xml string here (current way seems to complicated) def wrong_creditcard_authorization_response - error = <<-XML - - DATA_ERROR - 24997 - Credit card number not allowed in demo mode. - Only demo card number '4200000000000000' is allowed for VISA in demo mode. - - XML + error = <<~XML + + DATA_ERROR + 24997 + Credit card number not allowed in demo mode. + Only demo card number '4200000000000000' is allowed for VISA in demo mode. + + XML result_node = '' auth = 'AuthorizationCode' successful_authorization_response. @@ -377,94 +377,94 @@ def wrong_creditcard_authorization_response # Capture success def successful_capture_response - <<-XML - - - - - test dummy data - - Wirecard remote test purchase - - 1 - - C833707121385268439116 - 915025 - THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. - INFO - ACK - 2008-06-19 07:18:04 - - - - - - + <<~XML + + + + + test dummy data + + Wirecard remote test purchase + + 1 + + C833707121385268439116 + 915025 + THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. + INFO + ACK + 2008-06-19 07:18:04 + + + + + + XML end # Capture failure def unauthorized_capture_response - <<-XML - - - - - test dummy data - - Test dummy FunctionID - - a2783d471ccc98825b8c498f1a62ce8f - - C833707121385268439116 - - INFO - NOK - - DATA_ERROR - 20080 - Could not find referenced transaction for GuWID 1234567890123456789012. - - 2008-06-19 08:09:20 - - - - - - + <<~XML + + + + + test dummy data + + Test dummy FunctionID + + a2783d471ccc98825b8c498f1a62ce8f + + C833707121385268439116 + + INFO + NOK + + DATA_ERROR + 20080 + Could not find referenced transaction for GuWID 1234567890123456789012. + + 2008-06-19 08:09:20 + + + + + + XML end # Purchase success def successful_purchase_response - <<-XML - - - - - test dummy data - - Wirecard remote test purchase - - 1 - - C865402121385575982910 - 531750 - THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. - INFO - ACK - 2008-06-19 08:09:19 - - - - - - + <<~XML + + + + + test dummy data + + Wirecard remote test purchase + + 1 + + C865402121385575982910 + 531750 + THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. + INFO + ACK + 2008-06-19 08:09:19 + + + + + + XML end def successful_refund_response - <<-XML + <<~XML @@ -491,7 +491,7 @@ def successful_refund_response end def failed_refund_response - <<-XML + <<~XML @@ -522,7 +522,7 @@ def failed_refund_response end def successful_void_response - <<-XML + <<~XML @@ -549,7 +549,7 @@ def successful_void_response end def failed_void_response - <<-XML + <<~XML @@ -581,202 +581,202 @@ def failed_void_response # Purchase failure def wrong_creditcard_purchase_response - <<-XML - - - - - test dummy data - - Wirecard remote test purchase - - 1 - - C824697121385153203112 - - INFO - NOK - - DATA_ERROR 24997 - Credit card number not allowed in demo mode. - Only demo card number '4200000000000000' is allowed for VISA in demo mode. - - 2008-06-19 06:58:51 - - - - - - + <<~XML + + + + + test dummy data + + Wirecard remote test purchase + + 1 + + C824697121385153203112 + + INFO + NOK + + DATA_ERROR 24997 + Credit card number not allowed in demo mode. + Only demo card number '4200000000000000' is allowed for VISA in demo mode. + + 2008-06-19 06:58:51 + + + + + + XML end # AVS failure def failed_avs_response - <<-XML - - - - - - - - - E0BCBF30B82D0131000000000000E4CF - - C997753139988691610455 - 732129 - THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. - INFO - PENDING - - U - AVS Unavailable. - 5 - Response provided by issuer processor. - A - Address information is unavailable, or the Issuer does not support AVS. Acquirer has representment rights. - - 2014-05-12 11:28:36 - - - - - - + <<~XML + + + + + + + + + E0BCBF30B82D0131000000000000E4CF + + C997753139988691610455 + 732129 + THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. + INFO + PENDING + + U + AVS Unavailable. + 5 + Response provided by issuer processor. + A + Address information is unavailable, or the Issuer does not support AVS. Acquirer has representment rights. + + 2014-05-12 11:28:36 + + + + + + XML end def system_error_response - <<-XML - - - - - - - - - 3A368E50D50B01310000000000009153 - - C967464140265180577024 - - THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. - INFO - NOK - - SYSTEM_ERROR - 20205 - - - 2014-06-13 11:30:05 - - - - - - + <<~XML + + + + + + + + + 3A368E50D50B01310000000000009153 + + C967464140265180577024 + + THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED. + INFO + NOK + + SYSTEM_ERROR + 20205 + + + 2014-06-13 11:30:05 + + + + + + XML end def system_error_response_without_job - <<-XML - - - - - SYSTEM_ERROR - 10003 - Job Refused - - - + <<~XML + + + + + SYSTEM_ERROR + 10003 + Job Refused + + + XML end def transcript - <<-XML - - - - - 00000031629CAFD5 - - Wirecard remote test purchase - - 1 - 100 - EUR - CA - - Single - - - 4200000000000000 - 123 - 2016 - 09 - Longbob Longsen - - -
- 456 My Street - Apt 1 - Ottawa - K1C2N6 - ON - CA - soleone@example.com -
-
-
-
-
-
-
+ <<~XML + + + + + 00000031629CAFD5 + + Wirecard remote test purchase + + 1 + 100 + EUR + CA + + Single + + + 4200000000000000 + 123 + 2016 + 09 + Longbob Longsen + + +
+ 456 My Street + Apt 1 + Ottawa + K1C2N6 + ON + CA + soleone@example.com +
+
+
+
+
+
+
XML end def scrubbed_transcript - <<-XML - - - - - 00000031629CAFD5 - - Wirecard remote test purchase - - 1 - 100 - EUR - CA - - Single - - - [FILTERED] - [FILTERED] - 2016 - 09 - Longbob Longsen - - -
- 456 My Street - Apt 1 - Ottawa - K1C2N6 - ON - CA - soleone@example.com -
-
-
-
-
-
-
+ <<~XML + + + + + 00000031629CAFD5 + + Wirecard remote test purchase + + 1 + 100 + EUR + CA + + Single + + + [FILTERED] + [FILTERED] + 2016 + 09 + Longbob Longsen + + +
+ 456 My Street + Apt 1 + Ottawa + K1C2N6 + ON + CA + soleone@example.com +
+
+
+
+
+
+
XML end end diff --git a/test/unit/gateways/wompi_test.rb b/test/unit/gateways/wompi_test.rb new file mode 100644 index 00000000000..81a7d4683d5 --- /dev/null +++ b/test/unit/gateways/wompi_test.rb @@ -0,0 +1,223 @@ +require 'test_helper' + +class WompiTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = WompiGateway.new(test_public_key: 'pub_test_1234', test_private_key: 'priv_test_5678') + @prod_gateway = WompiGateway.new(prod_public_key: 'pub_prod_1234', prod_private_key: 'priv_prod_5678') + @ambidextrous_gateway = WompiGateway.new(prod_public_key: 'pub_prod_1234', prod_private_key: 'priv_prod_5678', test_public_key: 'pub_test_1234', test_private_key: 'priv_test_5678') + @credit_card = credit_card + @amount = 150000 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_ambidextrous_gateway_behaves_accordingly + response = stub_comms(@ambidextrous_gateway) do + @ambidextrous_gateway.purchase(@amount, @credit_card) + end.check_request do |_endpoint, _data, headers| + assert_match(/Bearer priv_test_5678/, headers['Authorization']) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '113879-1635300853-71494', response.authorization + assert response.test? + end + + def test_gateway_without_creds_raises_useful_error + assert_raise ArgumentError, 'Gateway requires both test_private_key and test_public_key, or both prod_private_key and prod_public_key' do + WompiGateway.new() + end + 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 '113879-1635300853-71494', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'La transacción fue rechazada (Sandbox)', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 19930, response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '113879-1638483506-80282', @options) + assert_success response + + assert_equal '113879-1638483506-80282', response.authorization + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '') + assert_failure response + + assert_equal 'La transacción fue rechazada (Sandbox)', response.message + end + + # def test_successful_refund + # @gateway.expects(:ssl_post).returns(successful_refund_response) + + # response = @gateway.refund(@amount, @credit_card, @options) + # assert_success response + + # assert_equal '113879-1635301011-28454', response.authorization + # assert response.test? + # end + + # def test_failed_refund + # @gateway.expects(:ssl_post).returns(failed_refund_response) + + # response = @gateway.refund(@amount, @credit_card, @options) + # assert_failure response + # message = JSON.parse(response.message) + # assert_equal 'transaction_id Debe ser completado', message['transaction_id'].first + # end + + def test_successful_refund_to_void + response = stub_comms(@gateway) do + @gateway.refund(@amount, @credit_card, @options) + end.check_request do |endpoint, data, _headers| + assert_match 'void_sync', endpoint + assert_match @amount.to_s, data + end.respond_with(successful_void_response) + assert_success response + + assert_equal '113879-1635301067-17128', response.authorization + assert response.test? + end + + def test_successful_partial_refund_to_void + response = stub_comms(@gateway) do + @gateway.refund(@amount - 50000, @credit_card, @options) + end.check_request do |endpoint, data, _headers| + assert_match 'void_sync', endpoint + assert_match (@amount - 50000).to_s, data + end.respond_with(successful_void_response) + assert_success response + + assert_equal '113879-1635301067-17128', response.authorization + assert response.test? + end + + def test_successful_void + response = stub_comms(@gateway) do + @gateway.void(@amount, @options) + end.check_request do |endpoint, data, _headers| + assert_match 'void_sync', endpoint + assert_match '{}', data + end.respond_with(successful_void_response) + assert_success response + + assert_equal '113879-1635301067-17128', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void(@amount, @options) + assert_failure response + assert_equal 'La entidad solicitada no existe', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<~TRANSCRIPT + "opening connection to sandbox.wompi.co:443...\nopened\nstarting SSL for sandbox.wompi.co:443...\nSSL established\n<- \"POST /v1/transactions_sync HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nAuthorization: Bearer prv_test_apOk1L1TV4qPqrZkfsPsJz5PBABQSI7F\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: sandbox.wompi.co\\r\\nContent-Length: 282\\r\\n\\r\\n\"\n<- \"{\\\"reference\\\":\\\"rk6PBsDIxaBH\\\",\\\"public_key\\\":\\\"pub_test_RGehkiIXU3opWWryDE6jByz4W9kq6Hdk\\\",\\\"amount_in_cents\\\":150000,\\\"currency\\\":\\\"COP\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"CARD\\\",\\\"number\\\":\\\"4242424242424242\\\",\\\"exp_month\\\":\\\"09\\\",\\\"exp_year\\\":\\\"22\\\",\\\"installments\\\":2,\\\"cvc\\\":\\\"123\\\",\\\"card_holder\\\":\\\"Longbob Longsen\\\"}}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Content-Length: 621\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Date: Wed, 27 Oct 2021 03:48:17 GMT\\r\\n\"\n-> \"x-amzn-RequestId: 9a64471b-b1ab-4835-9ef0-70b3d6a838fc\\r\\n\"\n-> \"x-amz-apigw-id: H2TOPERIoAMF0kA=\\r\\n\"\n-> \"X-Amzn-Trace-Id: Root=1-6178cbf4-2ba684d62e9bd4bd04017a4b;Sampled=0\\r\\n\"\n-> \"X-Cache: Miss from cloudfront\\r\\n\"\n-> \"Via: 1.1 ee9de9e6182ae0c8e8f119177e905245.cloudfront.net (CloudFront)\\r\\n\"\n-> \"X-Amz-Cf-Pop: DEN50-C2\\r\\n\"\n-> \"X-Amz-Cf-Id: QJH1Iy_rtMcjnWs4FI44anx5cX6RNZbk6JnHd6wvxqlDZnKl5j4W5g==\\r\\n\"\n-> \"\\r\\n\"\nreading 621 bytes...\n-> \"{\\\"data\\\":{\\\"id\\\":\\\"113879-1635306496-65846\\\",\\\"created_at\\\":\\\"2021-10-27T03:48:17.706Z\\\",\\\"amount_in_cents\\\":150000,\\\"reference\\\":\\\"rk6PBsDIxaBH\\\",\\\"currency\\\":\\\"COP\\\",\\\"payment_method_type\\\":\\\"CARD\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"CARD\\\",\\\"extra\\\":{\\\"name\\\":\\\"VISA-4242\\\",\\\"brand\\\":\\\"VISA\\\",\\\"last_four\\\":\\\"4242\\\",\\\"external_identifier\\\":\\\"JdGjsAGDPQ\\\"},\\\"installments\\\":2},\\\"redirect_url\\\":null,\\\"status\\\":\\\"APPROVED\\\",\\\"status_message\\\":null,\\\"merchant\\\":{\\\"name\\\":\\\"Spreedly MV\\\",\\\"legal_name\\\":\\\"Longbob Longsen\\\",\\\"contact_name\\\":\\\"Longbob Longsen\\\",\\\"phone_number\\\":\\\"+573017654567\\\",\\\"logo_url\\\":null,\\\"legal_id_type\\\":\\\"CC\\\",\\\"email\\\":\\\"longbob@example.com\\\",\\\"legal_id\\\":\\\"14671275\\\"},\\\"taxes\\\":[]}}\"\nread 621 bytes\nConn close\n" + TRANSCRIPT + end + + def post_scrubbed + <<~SCRUBBED + "opening connection to sandbox.wompi.co:443...\nopened\nstarting SSL for sandbox.wompi.co:443...\nSSL established\n<- \"POST /v1/transactions_sync HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nAuthorization: Bearer [REDACTED]\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: sandbox.wompi.co\\r\\nContent-Length: 282\\r\\n\\r\\n\"\n<- \"{\\\"reference\\\":\\\"rk6PBsDIxaBH\\\",\\\"public_key\\\":\\\"pub_test_RGehkiIXU3opWWryDE6jByz4W9kq6Hdk\\\",\\\"amount_in_cents\\\":150000,\\\"currency\\\":\\\"COP\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"CARD\\\",\\\"number\\\":\\\"[REDACTED]\\\",\\\"exp_month\\\":\\\"09\\\",\\\"exp_year\\\":\\\"22\\\",\\\"installments\\\":2,\\\"cvc\\\":\\\"[REDACTED]\\\",\\\"card_holder\\\":\\\"Longbob Longsen\\\"}}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"Content-Length: 621\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Date: Wed, 27 Oct 2021 03:48:17 GMT\\r\\n\"\n-> \"x-amzn-RequestId: 9a64471b-b1ab-4835-9ef0-70b3d6a838fc\\r\\n\"\n-> \"x-amz-apigw-id: H2TOPERIoAMF0kA=\\r\\n\"\n-> \"X-Amzn-Trace-Id: Root=1-6178cbf4-2ba684d62e9bd4bd04017a4b;Sampled=0\\r\\n\"\n-> \"X-Cache: Miss from cloudfront\\r\\n\"\n-> \"Via: 1.1 ee9de9e6182ae0c8e8f119177e905245.cloudfront.net (CloudFront)\\r\\n\"\n-> \"X-Amz-Cf-Pop: DEN50-C2\\r\\n\"\n-> \"X-Amz-Cf-Id: QJH1Iy_rtMcjnWs4FI44anx5cX6RNZbk6JnHd6wvxqlDZnKl5j4W5g==\\r\\n\"\n-> \"\\r\\n\"\nreading 621 bytes...\n-> \"{\\\"data\\\":{\\\"id\\\":\\\"113879-1635306496-65846\\\",\\\"created_at\\\":\\\"2021-10-27T03:48:17.706Z\\\",\\\"amount_in_cents\\\":150000,\\\"reference\\\":\\\"rk6PBsDIxaBH\\\",\\\"currency\\\":\\\"COP\\\",\\\"payment_method_type\\\":\\\"CARD\\\",\\\"payment_method\\\":{\\\"type\\\":\\\"CARD\\\",\\\"extra\\\":{\\\"name\\\":\\\"VISA-4242\\\",\\\"brand\\\":\\\"VISA\\\",\\\"last_four\\\":\\\"4242\\\",\\\"external_identifier\\\":\\\"JdGjsAGDPQ\\\"},\\\"installments\\\":2},\\\"redirect_url\\\":null,\\\"status\\\":\\\"APPROVED\\\",\\\"status_message\\\":null,\\\"merchant\\\":{\\\"name\\\":\\\"Spreedly MV\\\",\\\"legal_name\\\":\\\"Longbob Longsen\\\",\\\"contact_name\\\":\\\"Longbob Longsen\\\",\\\"phone_number\\\":\\\"[REDACTED]\\\",\\\"logo_url\\\":null,\\\"legal_id_type\\\":\\\"CC\\\",\\\"email\\\":\\\"[REDACTED]\\\",\\\"legal_id\\\":\\\"[REDACTED]\\\"},\\\"taxes\\\":[]}}\"\nread 621 bytes\nConn close\n" + SCRUBBED + end + + def successful_purchase_response + %( + {"data":{"id":"113879-1635300853-71494","created_at":"2021-10-27T02:14:16.181Z","amount_in_cents":150000,"reference":"b4DxpcrtsvRs","currency":"COP","payment_method_type":"CARD","payment_method":{"type":"CARD","extra":{"name":"VISA-4242","brand":"VISA","last_four":"4242","external_identifier":"fOYBYDRGuP"},"installments":2},"redirect_url":null,"status":"APPROVED","status_message":null,"merchant":{"name":"Spreedly MV","legal_name":"Longbob Longsen","contact_name":"Longbob Longsen","phone_number":"+573017654567","logo_url":null,"legal_id_type":"CC","email":"longbob@example.com","legal_id":"14671275"},"taxes":[]}} + ) + end + + def failed_purchase_response + %( + {"data":{"id":"113879-1635300920-47863","created_at":"2021-10-27T02:15:21.455Z","amount_in_cents":150000,"reference":"sljAsra9maeh","currency":"COP","payment_method_type":"CARD","payment_method":{"type":"CARD","extra":{"name":"VISA-1111","brand":"VISA","last_four":"1111","external_identifier":"liEZAwoiCD"},"installments":2},"redirect_url":null,"status":"DECLINED","status_message":"La transacción fue rechazada (Sandbox)","merchant":{"name":"Spreedly MV","legal_name":"Longbob Longsen","contact_name":"Longbob Longsen","phone_number":"+573017654567","logo_url":null,"legal_id_type":"CC","email":"longbob@example.com","legal_id":"14671275"},"taxes":[]}} + ) + end + + def successful_authorize_response + %( + {"data":{"id":19930,"public_data":{"type":"CARD","financial_operation":"PREAUTHORIZATION","amount_in_cents":1500,"number_of_installments":1,"currency":"COP"},"token":"tok_test_13879_29dbd1E75A7dc06e42bE08dbad959771","type":"CARD","status":"AVAILABLE","customer_email":null}} + ) + end + + def successful_capture_response + %( + {"data":{"id":"113879-1638483506-80282","created_at":"2021-12-02T22:18:27.877Z","amount_in_cents":160000,"reference":"larenciadediana3","currency":"COP","payment_method_type":"CARD","payment_method":{"type":"CARD","extra":{"name":"VISA-4242","brand":"VISA","last_four":"4242","external_identifier":"N4Dup17YZn"}},"redirect_url":null,"status":"APPROVED","status_message":null,"merchant":{"name":"Spreedly MV","legal_name":"Miguel Valencia","contact_name":"Miguel Valencia","phone_number":"+573117654567","logo_url":null,"legal_id_type":"CC","email":"mvalencia@spreedly.com","legal_id":"14671275"},"taxes":[]}} + ) + end + + def failed_capture_response + %( + {"data":{"id":"113879-1638802203-50693","created_at":"2021-12-06T14:50:04.497Z","amount_in_cents":160000,"reference":"larencia987diana37","currency":"COP","payment_method_type":"CARD","payment_method":{"type":"CARD","extra":{"name":"VISA-1111","brand":"VISA","last_four":"1111","external_identifier":"1cAREQ60RX"}},"redirect_url":null,"status":"DECLINED","status_message":"La transacción fue rechazada (Sandbox)","merchant":{"name":"Spreedly MV","legal_name":"Miguel Valencia","contact_name":"Miguel Valencia","phone_number":"+573117654567","logo_url":null,"legal_id_type":"CC","email":"mvalencia@spreedly.com","legal_id":"14671275"},"taxes":[]}} + ) + end + + def successful_refund_response + %( + {"data":{"id":61,"created_at":"2021-10-27T02:16:55.333Z","transaction_id":"113879-1635301011-28454","status":"APPROVED","amount_in_cents":150000}} + ) + end + + def failed_refund_response + %( + {"error":{"type":"INPUT_VALIDATION_ERROR","messages":{"transaction_id":["transaction_id Debe ser completado"]}}} + ) + end + + def successful_void_response + %( + {"data":{"status":"APPROVED","status_message":null,"transaction":{"id":"113879-1635301067-17128","created_at":"2021-10-27T02:17:48.368Z","finalized_at":"2021-10-27T02:17:48.000Z","amount_in_cents":150000,"reference":"89BFPG90NHAY","customer_email":null,"currency":"COP","payment_method_type":"CARD","payment_method":{"type":"CARD","extra":{"bin":"424242","name":"VISA-4242","brand":"VISA","exp_year":"22","exp_month":"09","last_four":"4242","card_holder":"Longbob Longsen","external_identifier":"hRd7HK6Euo"},"installments":2},"status":"APPROVED","status_message":null,"billing_data":null,"shipping_address":null,"redirect_url":null,"payment_source_id":null,"payment_link_id":null,"customer_data":null,"bill_id":null,"taxes":[]}},"meta":{"trace_id":"03951732b922897303397336c99e2523"}} + ) + end + + def failed_void_response + %( + {"error":{"type":"NOT_FOUND_ERROR","reason":"La entidad solicitada no existe"},"meta":{"trace_id":"f9a18f00e69e61c14bf0abe507d8d110"}} + ) + end +end diff --git a/test/unit/gateways/worldpay_online_payments_test.rb b/test/unit/gateways/worldpay_online_payments_test.rb index 697ac11aba0..124f8b3e179 100644 --- a/test/unit/gateways/worldpay_online_payments_test.rb +++ b/test/unit/gateways/worldpay_online_payments_test.rb @@ -43,7 +43,7 @@ def test_successful_authorize_and_capture assert_success authorize @gateway.expects(:ssl_request).returns(successful_capture_response) - assert capture = @gateway.capture(@amount-1, authorize.authorization) + assert capture = @gateway.capture(@amount - 1, authorize.authorization) assert_success capture end @@ -71,7 +71,7 @@ def test_partial_capture assert_success authorize @gateway.expects(:ssl_request).returns(successful_capture_response) - assert capture = @gateway.capture(@amount-1, authorize.authorization) + assert capture = @gateway.capture(@amount - 1, authorize.authorization) assert_success capture end diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb index 1810f8d22ae..fddad02e088 100644 --- a/test/unit/gateways/worldpay_test.rb +++ b/test/unit/gateways/worldpay_test.rb @@ -5,28 +5,215 @@ class WorldpayTest < Test::Unit::TestCase def setup @gateway = WorldpayGateway.new( - :login => 'testlogin', - :password => 'testpassword' - ) + login: 'testlogin', + password: 'testpassword' + ) @amount = 100 @credit_card = credit_card('4242424242424242') - @elo_credit_card = credit_card('4514 1600 0000 0008', - :month => 10, - :year => 2020, - :first_name => 'John', - :last_name => 'Smith', - :verification_value => '737', - :brand => 'elo' + @token = '|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + @elo_credit_card = credit_card( + '4514 1600 0000 0008', + month: 10, + year: 2020, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'elo' + ) + @nt_credit_card = network_tokenization_credit_card( + '4895370015293175', + brand: 'visa', + eci: 5, + source: :network_token, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + @nt_credit_card_without_eci = network_tokenization_credit_card( + '4895370015293175', + source: :network_token, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + @credit_card_with_two_digits_year = credit_card( + '4514 1600 0000 0008', + month: 10, + year: 22, + first_name: 'John', + last_name: 'Smith', + verification_value: '737' + ) + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') + @options = { order_id: 1 } + @store_options = { + customer: '59424549c291397379f30c5c082dbed8', + email: 'wow@example.com' + } + @sub_merchant_options = { + sub_merchant_data: { + pf_id: '12345678901', + sub_name: 'Example Shop', + sub_id: '1234567' + } + } + + @apple_play_network_token = network_tokenization_credit_card( + '4895370015293175', + month: 10, + year: 24, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + 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' ) - @options = {:order_id => 1} + + @level_two_data = { + level_2_data: { + invoice_reference_number: 'INV12233565', + customer_reference: 'CUST00000101', + card_acceptor_tax_id: 'VAT1999292', + sales_tax: { + amount: '20', + exponent: '2', + currency: 'USD' + }, + ship_from_postal_code: '43245', + destination_postal_code: '54545', + destination_country_code: 'CO', + order_date: { + day_of_month: Date.today.day, + month: Date.today.month, + year: Date.today.year + }, + tax_exempt: 'false' + } + } + + @level_three_data = { + 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: { + description: 'Laptop 14', + product_code: 'LP00125', + commodity_code: 'COM00125', + quantity: '2', + unit_cost: { + amount: '1500', + exponent: '2', + currency: 'USD' + }, + 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' + } + } + } + } + end + + def test_payment_type_for_network_card + payment = @gateway.send(:payment_details, @nt_credit_card)[:payment_type] + assert_equal payment, :network_token + end + + def test_payment_type_returns_network_token_if_the_payment_method_responds_to_source_payment_cryptogram_and_eci + payment_method = mock + payment_method.stubs(source: nil, payment_cryptogram: nil, eci: nil) + result = @gateway.send(:payment_details, payment_method) + assert_equal({ payment_type: :network_token }, result) + end + + def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_source + payment_method = mock + payment_method.stubs(payment_cryptogram: nil, eci: nil) + result = @gateway.send(:payment_details, payment_method) + assert_equal({ payment_type: :credit }, result) + end + + def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_payment_cryptogram + payment_method = mock + payment_method.stubs(source: nil, eci: nil) + result = @gateway.send(:payment_details, payment_method) + assert_equal({ payment_type: :credit }, result) + end + + def test_payment_type_returns_credit_if_the_payment_method_does_not_responds_to_eci + payment_method = mock + payment_method.stubs(source: nil, payment_cryptogram: nil) + result = @gateway.send(:payment_details, payment_method) + assert_equal({ payment_type: :credit }, result) + end + + def test_payment_type_for_credit_card + payment = @gateway.send(:payment_details, @credit_card)[:payment_type] + assert_equal payment, :credit end def test_successful_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| + assert_match(/4242424242424242/, data) + assert_match(/cardHolderName/, data) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal 'R50704213207145707', response.authorization + end + + def test_successful_authorize_without_name + credit_card = credit_card('4242424242424242', first_name: nil, last_name: nil) + response = stub_comms do + @gateway.authorize(@amount, credit_card, @options) + end.check_request do |_endpoint, data, _headers| assert_match(/4242424242424242/, data) + assert_no_match(/cardHolderName/, data) + assert_match(/CARD-SSL/, data) end.respond_with(successful_authorize_response) assert_success response assert_equal 'R50704213207145707', response.authorization @@ -35,17 +222,84 @@ def test_successful_authorize def test_successful_authorize_by_reference response = stub_comms do @gateway.authorize(@amount, @options[:order_id].to_s, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/payAsOrder/, data) end.respond_with(successful_authorize_response) assert_success response assert_equal 'R50704213207145707', response.authorization end + def test_exemption_in_request + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ exemption_type: 'LV', exemption_placement: 'AUTHENTICATION' })) + end.check_request do |_endpoint, data, _headers| + assert_match(/exemption/, data) + assert_match(/AUTHENTICATION/, data) + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_risk_data_in_request + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(risk_data: risk_data)) + end.check_request do |_endpoint, data, _headers| + doc = Nokogiri::XML(data) + + authentication_risk_data = doc.at_xpath('//riskData//authenticationRiskData') + assert_equal(risk_data[:authentication_risk_data][:authentication_method], authentication_risk_data.attribute('authenticationMethod').value) + + timestamp = doc.at_xpath('//riskData//authenticationRiskData//authenticationTimestamp//date') + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:day_of_month], timestamp.attribute('dayOfMonth').value) + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:month], timestamp.attribute('month').value) + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:year], timestamp.attribute('year').value) + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:hour], timestamp.attribute('hour').value) + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:minute], timestamp.attribute('minute').value) + assert_equal(risk_data[:authentication_risk_data][:authentication_date][:second], timestamp.attribute('second').value) + + shopper_account_risk_data_xml = doc.at_xpath('//riskData//shopperAccountRiskData') + shopper_account_risk_data = risk_data[:shopper_account_risk_data] + assert_equal(shopper_account_risk_data[:transactions_attempted_last_day], shopper_account_risk_data_xml.attribute('transactionsAttemptedLastDay').value) + assert_equal(shopper_account_risk_data[:transactions_attempted_last_year], shopper_account_risk_data_xml.attribute('transactionsAttemptedLastYear').value) + assert_equal(shopper_account_risk_data[:purchases_completed_last_six_months], shopper_account_risk_data_xml.attribute('purchasesCompletedLastSixMonths').value) + assert_equal(shopper_account_risk_data[:add_card_attempts_last_day], shopper_account_risk_data_xml.attribute('addCardAttemptsLastDay').value) + assert_equal(shopper_account_risk_data[:previous_suspicious_activity], shopper_account_risk_data_xml.attribute('previousSuspiciousActivity').value) + assert_equal(shopper_account_risk_data[:shipping_name_matches_account_name], shopper_account_risk_data_xml.attribute('shippingNameMatchesAccountName').value) + assert_equal(shopper_account_risk_data[:shopper_account_age_indicator], shopper_account_risk_data_xml.attribute('shopperAccountAgeIndicator').value) + assert_equal(shopper_account_risk_data[:shopper_account_change_indicator], shopper_account_risk_data_xml.attribute('shopperAccountChangeIndicator').value) + assert_equal(shopper_account_risk_data[:shopper_account_password_change_indicator], shopper_account_risk_data_xml.attribute('shopperAccountPasswordChangeIndicator').value) + assert_equal(shopper_account_risk_data[:shopper_account_shipping_address_usage_indicator], shopper_account_risk_data_xml.attribute('shopperAccountShippingAddressUsageIndicator').value) + assert_equal(shopper_account_risk_data[:shopper_account_payment_account_indicator], shopper_account_risk_data_xml.attribute('shopperAccountPaymentAccountIndicator').value) + assert_date_element(shopper_account_risk_data[:shopper_account_creation_date], shopper_account_risk_data_xml.at_xpath('//shopperAccountCreationDate//date')) + assert_date_element(shopper_account_risk_data[:shopper_account_modification_date], shopper_account_risk_data_xml.at_xpath('//shopperAccountModificationDate//date')) + assert_date_element(shopper_account_risk_data[:shopper_account_password_change_date], shopper_account_risk_data_xml.at_xpath('//shopperAccountPasswordChangeDate//date')) + assert_date_element(shopper_account_risk_data[:shopper_account_shipping_address_first_use_date], shopper_account_risk_data_xml.at_xpath('//shopperAccountShippingAddressFirstUseDate//date')) + assert_date_element(shopper_account_risk_data[:shopper_account_payment_account_first_use_date], shopper_account_risk_data_xml.at_xpath('//shopperAccountPaymentAccountFirstUseDate//date')) + + transaction_risk_data_xml = doc.at_xpath('//riskData//transactionRiskData') + transaction_risk_data = risk_data[:transaction_risk_data] + assert_equal(transaction_risk_data[:shipping_method], transaction_risk_data_xml.attribute('shippingMethod').value) + assert_equal(transaction_risk_data[:delivery_timeframe], transaction_risk_data_xml.attribute('deliveryTimeframe').value) + assert_equal(transaction_risk_data[:delivery_email_address], transaction_risk_data_xml.attribute('deliveryEmailAddress').value) + assert_equal(transaction_risk_data[:reordering_previous_purchases], transaction_risk_data_xml.attribute('reorderingPreviousPurchases').value) + assert_equal(transaction_risk_data[:pre_order_purchase], transaction_risk_data_xml.attribute('preOrderPurchase').value) + assert_equal(transaction_risk_data[:gift_card_count], transaction_risk_data_xml.attribute('giftCardCount').value) + + amount_xml = doc.at_xpath('//riskData//transactionRiskData//transactionRiskDataGiftCardAmount//amount') + amount_data = transaction_risk_data[:transaction_risk_data_gift_card_amount] + assert_equal(amount_data[:value], amount_xml.attribute('value').value) + assert_equal(amount_data[:currency], amount_xml.attribute('currencyCode').value) + assert_equal(amount_data[:exponent], amount_xml.attribute('exponent').value) + assert_equal(amount_data[:debit_credit_indicator], amount_xml.attribute('debitCreditIndicator').value) + + assert_date_element(transaction_risk_data[:transaction_risk_data_pre_order_date], transaction_risk_data_xml.at_xpath('//transactionRiskDataPreOrderDate//date')) + end.respond_with(successful_authorize_response) + assert_success response + end + def test_successful_reference_transaction_authorize_with_merchant_code response = stub_comms do - @gateway.authorize(@amount, @options[:order_id].to_s, @options.merge({ merchant_code: 'testlogin2'})) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @options[:order_id].to_s, @options.merge({ merchant_code: 'testlogin2' })) + end.check_request do |_endpoint, data, _headers| assert_match(/testlogin2/, data) end.respond_with(successful_authorize_response) assert_success response @@ -55,7 +309,7 @@ def test_successful_reference_transaction_authorize_with_merchant_code def test_authorize_passes_ip_and_session_id response = stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(ip: '127.0.0.1', session_id: '0215ui8ib1')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) end.respond_with(successful_authorize_response) assert_success response @@ -69,18 +323,31 @@ def test_authorize_passes_stored_credential_options ) response = stub_comms do @gateway.authorize(@amount, @credit_card, options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) assert_match(/000000000000020005060720116005060\<\/schemeTransactionIdentifier\>/, data) end.respond_with(successful_authorize_response) assert_success response end + def test_authorize_passes_sub_merchant_data + options = @options.merge(@sub_merchant_options) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(12345678901), data + assert_match %r(Example Shop), data + assert_match %r(1234567), data + end.respond_with(successful_authorize_response) + assert_success response + end + def test_failed_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) end.respond_with(failed_authorize_response) assert_equal '7', response.error_code + assert_match 'Invalid payment details', response.message assert_failure response end @@ -91,6 +358,82 @@ def test_successful_purchase assert_success response end + def test_transaction_with_level_two_data + options = @options.merge(@level_two_data) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(INV12233565), data + assert_match %r(CUST00000101), data + assert_match %r(VAT1999292), data + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(43245), data + assert_match %r(54545), data + assert_match %r(CO), data + assert_match %r(false), data + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(), data.gsub(/\s+/, '') + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_transaction_with_level_three_data + options = @options.merge(@level_three_data) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(CUST00000102), data + assert_match %r(VAT1999285), data + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(), data.gsub(/\s+/, '') + assert_match %r(Laptop14LP00125COM001252), data.gsub(/\s+/, '') + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_purchase_with_sub_merchant_data + options = @options.merge(@sub_merchant_options) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_successful_purchase_skipping_capture + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(skip_capture: true)) + end.respond_with(successful_authorize_response, successful_capture_response) + assert response.responses.length == 1 + assert_success response + end + + def test_successful_purchase_with_network_token + response = stub_comms do + @gateway.purchase(@amount, @nt_credit_card, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_successful_authorize_with_network_token_with_eci + response = stub_comms do + @gateway.authorize(@amount, @nt_credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(05), data + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_authorize_with_network_token_without_eci + response = stub_comms do + @gateway.authorize(@amount, @nt_credit_card_without_eci, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(07), data + end.respond_with(successful_authorize_response) + assert_success response + end + def test_successful_purchase_with_elo response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'BRL')) @@ -101,17 +444,26 @@ def test_successful_purchase_with_elo def test_purchase_passes_correct_currency response = stub_comms do @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/CAD/, data) end.respond_with(successful_authorize_response, successful_capture_response) assert_success response end + def test_successful_purchase_with_two_digits_year + response = stub_comms do + @gateway.purchase(@amount, @credit_card_with_two_digits_year, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + def test_purchase_authorize_fails response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.respond_with(failed_authorize_response) assert_failure response + assert_equal '7', response.error_code + assert_match 'Invalid payment details', response.message assert_equal 1, response.responses.size end @@ -130,6 +482,26 @@ def test_purchase_does_not_run_inquiry assert_equal(%w(authorize capture), response.responses.collect { |e| e.params['action'] }) end + def test_failed_purchase_with_issuer_response_code + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_purchase_response_with_issuer_response_code) + + assert_failure response + assert_equal('51', response.params['issuer_response_code']) + assert_equal('Insufficient funds/over credit limit', response.params['issuer_response_description']) + end + + def test_failed_purchase_without_active_merchant_generated_response_message + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_purchase_response_without_useful_error_from_gateway) + + assert_failure response + assert_equal('61', response.params['issuer_response_code']) + assert_equal('Exceeds withdrawal amount limit', response.message) + end + def test_successful_void response = stub_comms do @gateway.void(@options[:order_id], @options) @@ -156,6 +528,23 @@ def test_void_fails_unless_status_is_authorized assert_equal "A transaction status of 'AUTHORISED' is required.", response.message end + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + def test_void_using_order_id_embedded_with_token + response = stub_comms do + authorization = "#{@options[:order_id]}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.void(authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('orderInquiry', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + assert_tag_with_attributes('orderModification', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_void_inquiry_response, successful_void_response) + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '924e810350efc21a989e0ac7727ce43b', response.params['cancel_received_order_code'] + end + def test_successful_refund_for_captured_payment response = stub_comms do @gateway.refund(@amount, @options[:order_id], @options) @@ -184,7 +573,7 @@ def test_refund_fails_unless_status_is_captured @gateway.refund(@amount, @options[:order_id], @options) end.respond_with(failed_refund_inquiry_response, successful_refund_response) assert_failure response - assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' or 'SETTLED_BY_MERCHANT' is required.", response.message + assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' or 'SETTLED_BY_MERCHANT' or 'SENT_FOR_REFUND' is required.", response.message end def test_full_refund_for_unsettled_payment_forces_void @@ -195,6 +584,25 @@ def test_full_refund_for_unsettled_payment_forces_void assert 'cancel', response.responses.last.params['action'] end + def test_refund_failure_with_force_full_refund_if_unsettled_does_not_force_void + response = stub_comms do + @gateway.refund(@amount, @options[:order_id], @options.merge(force_full_refund_if_unsettled: true)) + end.respond_with('total garbage') + + assert_failure response + end + + def test_refund_using_order_id_embedded_with_token + response = stub_comms do + authorization = "#{@options[:order_id]}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.refund(@amount, authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('orderInquiry', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + assert_tag_with_attributes('orderModification', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_refund_inquiry_response('CAPTURED'), successful_refund_response) + assert_success response + end + def test_capture response = stub_comms do response = @gateway.authorize(@amount, @credit_card, @options) @@ -203,10 +611,21 @@ def test_capture assert_success response end + def test_capture_using_order_id_embedded_with_token + response = stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options) + authorization = "#{response.authorization}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.capture(@amount, authorization, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('orderModification', { 'orderCode' => response.authorization }, data) if %r().match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + def test_successful_visa_credit response = stub_comms do @gateway.credit(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) end.respond_with(successful_visa_credit_response) assert_success response @@ -216,7 +635,7 @@ def test_successful_visa_credit def test_successful_mastercard_credit response = stub_comms do @gateway.credit(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(//, data) end.respond_with(successful_mastercard_credit_response) assert_success response @@ -226,13 +645,13 @@ def test_successful_mastercard_credit def test_description stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(Purchase), data end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(description: 'Something cool.')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(Something cool.), data end.respond_with(successful_authorize_response) end @@ -240,13 +659,13 @@ def test_description def test_order_content stub_comms do @gateway.authorize(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r(orderContent), data end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(@amount, @credit_card, @options.merge(order_content: "Lots 'o' crazy stuff.")) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(\s* stuff\.\]\]>\s*), data end.respond_with(successful_authorize_response) end @@ -254,12 +673,10 @@ def test_order_content def test_capture_time stub_comms do @gateway.capture(@amount, 'bogus', @options) - end.check_request do |endpoint, data, headers| - if data =~ /capture/ + end.check_request do |_endpoint, data, _headers| + if /capture/.match?(data) t = Time.now - assert_tag_with_attributes 'date', - {'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s}, - data + assert_tag_with_attributes 'date', { 'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s }, data end end.respond_with(successful_inquiry_response, successful_capture_response) end @@ -267,35 +684,29 @@ def test_capture_time def test_amount_handling stub_comms do @gateway.authorize(100, @credit_card, @options) - end.check_request do |endpoint, data, headers| - assert_tag_with_attributes 'amount', - {'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP'}, - data + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes 'amount', { 'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP' }, data end.respond_with(successful_authorize_response) end def test_currency_exponent_handling stub_comms do @gateway.authorize(10000, @credit_card, @options.merge(currency: :JPY)) - end.check_request do |endpoint, data, headers| - assert_tag_with_attributes 'amount', - {'value' => '100', 'exponent' => '0', 'currencyCode' => 'JPY'}, - data + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes 'amount', { 'value' => '100', 'exponent' => '0', 'currencyCode' => 'JPY' }, data end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(10000, @credit_card, @options.merge(currency: :OMR)) - end.check_request do |endpoint, data, headers| - assert_tag_with_attributes 'amount', - {'value' => '10000', 'exponent' => '3', 'currencyCode' => 'OMR'}, - data + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes 'amount', { 'value' => '10000', 'exponent' => '3', 'currencyCode' => 'OMR' }, data end.respond_with(successful_authorize_response) end def test_address_handling stub_comms do @gateway.authorize(100, @credit_card, @options.merge(billing_address: address)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(Jim), data assert_match %r(Smith), data assert_match %r(456 My Street), data @@ -309,7 +720,7 @@ def test_address_handling stub_comms do @gateway.authorize(100, @credit_card, @options.merge(billing_address: address.with_indifferent_access)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(Jim), data assert_match %r(Smith), data assert_match %r(456 My Street), data @@ -323,7 +734,7 @@ def test_address_handling stub_comms do @gateway.authorize(100, @credit_card, @options.merge(address: address)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(Jim), data assert_match %r(Smith), data assert_match %r(456 My Street), data @@ -337,14 +748,14 @@ def test_address_handling stub_comms do @gateway.authorize(100, @credit_card, @options.merge(billing_address: { phone: '555-3323' })) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r(firstName), data assert_no_match %r(lastName), data assert_no_match %r(address2), data assert_match %r(N/A), data assert_match %r(N/A), data assert_match %r(0000), data - assert_match %r(N/A), data + assert_match %r(), data assert_match %r(US), data assert_match %r(555-3323), data end.respond_with(successful_authorize_response) @@ -353,7 +764,7 @@ def test_address_handling def test_no_address_specified stub_comms do @gateway.authorize(100, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r(cardAddress), data assert_no_match %r(address), data assert_no_match %r(firstName), data @@ -374,38 +785,80 @@ def test_address_with_parts_unspecified stub_comms do @gateway.authorize(100, @credit_card, @options.merge(billing_address: address_with_nils)) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r(firstName), data assert_no_match %r(lastName), data assert_no_match %r(address2), data assert_match %r(N/A), data assert_match %r(N/A), data assert_match %r(0000), data - assert_match %r(N/A), data + assert_match %r(), data assert_match %r(US), data assert_match %r(555-3323), data end.respond_with(successful_authorize_response) end + def test_state_sent_for_3ds_transactions_in_us_country + us_billing_address = address.merge(country: 'US') + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(billing_address: us_billing_address, execute_threed: true)) + end.check_request do |_endpoint, data, _headers| + assert_match %r(firstName), data + assert_match %r(lastName), data + assert_match %r(456 My Street), data + assert_match %r(Apt 1), data + assert_match %r(Ottawa), data + assert_match %r(K1C2N6), data + assert_match %r(ON), data + assert_match %r(US), data + assert_match %r(\(555\)555-5555), data + end.respond_with(successful_authorize_response) + end + + def test_state_not_sent_for_3ds_transactions_in_non_us_country + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(billing_address: address, execute_threed: true)) + end.check_request do |_endpoint, data, _headers| + assert_match %r(firstName), data + assert_match %r(lastName), data + assert_match %r(456 My Street), data + assert_match %r(Apt 1), data + assert_match %r(Ottawa), data + assert_match %r(K1C2N6), data + assert_no_match %r(ON), data + assert_match %r(CA), data + assert_match %r(\(555\)555-5555), data + end.respond_with(successful_authorize_response) + end + def test_email stub_comms do @gateway.authorize(100, @credit_card, @options.merge(email: 'eggcellent@example.com')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(eggcellent@example.com), data end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(100, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r(shopperEmailAddress), data end.respond_with(successful_authorize_response) end + def test_statement_narrative_and_truncation + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(statement_narrative: 'Merchant Statement Narrative The Story Of Your Purchase')) + end.check_request do |_endpoint, data, _headers| + assert_match %r(Merchant Statement Narrative The Story Of Your Pur), data + assert_no_match %r(Merchant Statement Narrative The Story Of Your Purchase), data + end.respond_with(successful_authorize_response) + end + def test_instalments stub_comms do @gateway.purchase(100, @credit_card, @options.merge(instalments: 3)) - end.check_request do |endpoint, data, headers| - unless // =~ data + end.check_request do |_endpoint, data, _headers| + unless //.match?(data) assert_match %r(3), data assert_no_match %r(cpf), data end @@ -413,8 +866,8 @@ def test_instalments stub_comms do @gateway.purchase(100, @credit_card, @options.merge(instalments: 3, cpf: 12341234)) - end.check_request do |endpoint, data, headers| - unless // =~ data + end.check_request do |_endpoint, data, _headers| + unless //.match?(data) assert_match %r(3), data assert_match %r(12341234), data end @@ -424,50 +877,50 @@ def test_instalments def test_ip stub_comms do @gateway.authorize(100, @credit_card, @options.merge(ip: '192.137.11.44')) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match %r(), data end.respond_with(successful_authorize_response) stub_comms do @gateway.authorize(100, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_no_match %r('authorize', - 'amount_currency_code'=>'HKD', - 'amount_debit_credit_indicator'=>'credit', - 'amount_exponent'=>'2', - 'amount_value'=>'15000', - 'avs_result_code_description'=>'UNKNOWN', - 'balance'=>true, - 'balance_account_type'=>'IN_PROCESS_AUTHORISED', - 'card_number'=>'4111********1111', - 'cvc_result_code_description'=>'UNKNOWN', - 'last_event'=>'AUTHORISED', - 'order_status'=>true, - 'order_status_order_code'=>'R50704213207145707', - 'payment'=>true, - 'payment_method'=>'VISA-SSL', - 'payment_service'=>true, - 'payment_service_merchant_code'=>'XXXXXXXXXXXXXXX', - 'payment_service_version'=>'1.4', - 'reply'=>true, - 'risk_score_value'=>'1', - }, response.params) + 'action' => 'authorize', + 'amount_currency_code' => 'HKD', + 'amount_debit_credit_indicator' => 'credit', + 'amount_exponent' => '2', + 'amount_value' => '15000', + 'avs_result_code_description' => 'UNKNOWN', + 'balance' => true, + 'balance_account_type' => 'IN_PROCESS_AUTHORISED', + 'card_number' => '4111********1111', + 'cvc_result_code_description' => 'UNKNOWN', + 'last_event' => 'AUTHORISED', + 'order_status' => true, + 'order_status_order_code' => 'R50704213207145707', + 'payment' => true, + 'payment_method' => 'VISA-SSL', + 'payment_service' => true, + 'payment_service_merchant_code' => 'XXXXXXXXXXXXXXX', + 'payment_service_version' => '1.4', + 'reply' => true, + 'risk_score_value' => '1' + }, response.params) end def test_auth stub_comms do @gateway.authorize(100, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, _data, headers| assert_equal 'Basic dGVzdGxvZ2luOnRlc3RwYXNzd29yZA==', headers['Authorization'] end.respond_with(successful_authorize_response) end @@ -476,14 +929,14 @@ def test_request_respects_test_mode_on_gateway_instance ActiveMerchant::Billing::Base.mode = :production @gateway = WorldpayGateway.new( - :login => 'testlogin', - :password => 'testpassword', - :test => true + login: 'testlogin', + password: 'testpassword', + test: true ) stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_equal WorldpayGateway.test_url, endpoint end.respond_with(successful_authorize_response, successful_capture_response) ensure @@ -493,20 +946,49 @@ def test_request_respects_test_mode_on_gateway_instance def test_refund_amount_contains_debit_credit_indicator response = stub_comms do @gateway.refund(@amount, @options[:order_id], @options) - end.check_request do |endpoint, data, headers| - if data =~ // + end.check_request do |_endpoint, data, _headers| + if //.match?(data) request_hash = Hash.from_xml(data) assert_equal 'credit', request_hash['paymentService']['modify']['orderModification']['refund']['amount']['debitCreditIndicator'] end - end.respond_with(successful_refund_inquiry_response, successful_refund_response) + end.respond_with(successful_refund_inquiry_response('CAPTURED'), successful_refund_response) assert_success response end - def assert_tag_with_attributes(tag, attributes, string) - assert(m = %r(<#{tag}([^>]+)/>).match(string)) - attributes.each do |attribute, value| - assert_match %r(#{attribute}="#{value}"), m[1] - end + def test_cancel_or_refund + stub_comms do + @gateway.refund(@amount, @options[:order_id], @options) + end.check_request do |_endpoint, data, _headers| + next if data =~ // + + refute_match(//, data) + end.respond_with(successful_refund_inquiry_response, successful_refund_response) + + stub_comms do + @gateway.refund(@amount, @options[:order_id], @options.merge(cancel_or_refund: true)) + end.check_request do |_endpoint, data, _headers| + next if data =~ // + + assert_match(//, data) + end.respond_with(successful_refund_inquiry_response('SENT_FOR_REFUND'), successful_cancel_or_refund_response) + end + + def test_cancel_or_refund_with_void + stub_comms do + @gateway.void(@options[:order_id], @options) + end.check_request do |_endpoint, data, _headers| + next if data =~ // + + refute_match(//, data) + end.respond_with(successful_refund_inquiry_response, successful_refund_response) + + stub_comms do + @gateway.void(@options[:order_id], @options.merge(cancel_or_refund: true)) + end.check_request do |_endpoint, data, _headers| + next if data =~ // + + assert_match(//, data) + end.respond_with(successful_refund_inquiry_response('SENT_FOR_REFUND'), successful_cancel_or_refund_response) end def test_successful_verify @@ -516,6 +998,22 @@ def test_successful_verify assert_success response end + def test_successful_verify_with_0_auth + stub_comms do + @gateway.verify(@credit_card, @options.merge(zero_dollar_auth: true)) + end.check_request do |_endpoint, data, _headers| + assert_match(/amount value="0"/, data) if //.match?(data) + end.respond_with(successful_authorize_response, successful_void_response) + end + + def test_successful_verify_with_0_auth_and_ineligible_card + stub_comms do + @gateway.verify(@elo_credit_card, @options.merge(zero_dollar_auth: true)) + end.check_request do |_endpoint, data, _headers| + refute_match(/amount value="0"/, data) + end.respond_with(successful_authorize_response, successful_void_response) + end + def test_successful_verify_with_elo @gateway.expects(:ssl_post).times(2).returns(successful_authorize_with_elo_response, successful_void_with_elo_response) @@ -537,26 +1035,97 @@ def test_failed_verify assert_failure response end - def test_3ds_name_coersion + def test_empty_inst_id_is_stripped + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ inst_id: '' })) + end.check_request do |_, data, _| + assert_not_match(/installationId/, data) + end.respond_with(successful_authorize_response) + end + + def test_3ds_name_coersion_for_testing @options[:execute_threed] = true response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |endpoint, data, headers| - if // =~ data - assert_match %r{3D}, data - end + end.check_request do |_endpoint, data, _headers| + assert_match %r{3D}, data if //.match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_3ds_name_coersion_based_on_version_for_testing + @options[:execute_threed] = true + @options[:three_ds_version] = '2.0' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{Longbob Longsen}, data if //.match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + + @options[:three_ds_version] = '2' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{Longbob Longsen}, data if //.match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + + @options[:three_ds_version] = '1.0.2' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{3D}, data if //.match?(data) end.respond_with(successful_authorize_response, successful_capture_response) assert_success response end + def test_3ds_name_not_coerced_in_production + ActiveMerchant::Billing::Base.mode = :production + + @options[:execute_threed] = true + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_not_match %r{3D}, data + end.respond_with(successful_authorize_response, successful_capture_response) + ensure + ActiveMerchant::Billing::Base.mode = :test + end + + def test_3ds_additional_information + browser_size = '390x400' + session_id = '0215ui8ib1' + df_reference_id = '1326vj9jc2' + + options = @options.merge( + session_id: session_id, + df_reference_id: df_reference_id, + browser_size: browser_size, + execute_threed: true, + three_ds_version: '2.0.1' + ) + + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes 'additional3DSData', { 'dfReferenceId' => df_reference_id, 'challengeWindowSize' => browser_size }, data + end.respond_with(successful_authorize_response) + end + def test_transcript_scrubbing assert_equal scrubbed_transcript, @gateway.scrub(transcript) end + def test_transcript_scrubbing_on_network_token + assert_equal network_token_transcript_scrubbed, @gateway.scrub(network_token_transcript) + end + def test_3ds_version_1_request stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option('1.0.2'))) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option(version: '1.0.2', xid: 'xid'))) + end.check_request do |_endpoint, data, _headers| assert_match %r{}, data assert_match %r{eci}, data assert_match %r{cavv}, data @@ -567,43 +1136,446 @@ def test_3ds_version_1_request def test_3ds_version_2_request stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option('2.1.0'))) - end.check_request do |endpoint, data, headers| + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option(version: '2.1.0', ds_transaction_id: 'ds_transaction_id'))) + end.check_request do |_endpoint, data, _headers| assert_match %r{}, data assert_match %r{eci}, data assert_match %r{cavv}, data - assert_match %r{xid}, data + assert_match %r{ds_transaction_id}, data assert_match %r{2.1.0}, data end.respond_with(successful_authorize_response) end - private + def test_failed_authorize_with_unknown_card + response = stub_comms do + @gateway.authorize(@amount, @sodexo_voucher, @options) + end.respond_with(failed_with_unknown_card_response) + assert_failure response + assert_equal '5', response.error_code + end - def three_d_secure_option(version) - { - three_d_secure: { - eci: 'eci', - cavv: 'cavv', - xid: 'xid', - version: version - } - } + def test_failed_purchase_with_unknown_card + response = stub_comms do + @gateway.purchase(@amount, @sodexo_voucher, @options) + end.respond_with(failed_with_unknown_card_response) + assert_failure response + assert_equal '5', response.error_code end - def successful_authorize_response - <<-RESPONSE - - - - - - - VISA-SSL - - AUTHORISED - - + def test_failed_verify_with_unknown_card + @gateway.expects(:ssl_post).returns(failed_with_unknown_card_response) + + response = @gateway.verify(@sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @store_options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(), data + assert_match %r(), data + assert_match %r(59424549c291397379f30c5c082dbed8), data + assert_match %r(4242424242424242), data + assert_no_match %r(), data + assert_no_match %r(), data + assert_no_match %r(), data + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal @token, response.authorization + end + + def test_successful_authorize_using_token + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) + assert_match %r(59424549c291397379f30c5c082dbed8), data + assert_tag_with_attributes 'TOKEN-SSL', { 'tokenScope' => 'shopper' }, data + assert_match %r(99411111780163871111), data + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_authorize_with_token_includes_shopper_using_minimal_options + stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(59424549c291397379f30c5c082dbed8), data + end.respond_with(successful_authorize_response) + end + + def test_successful_purchase_using_token + response = stub_comms do + @gateway.purchase(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_verify_using_token + response = stub_comms do + @gateway.verify(@token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_credit_using_token + response = stub_comms do + @gateway.credit(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) + assert_match(//, data) + assert_match %r(59424549c291397379f30c5c082dbed8), data + assert_tag_with_attributes 'TOKEN-SSL', { 'tokenScope' => 'shopper' }, data + assert_match '99411111780163871111', data + end.respond_with(successful_visa_credit_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + + def test_optional_idempotency_key_header + response = stub_comms do + @gateway.authorize(@amount, @token, @options.merge({ idempotency_key: 'test123' })) + end.check_request do |_endpoint, _data, headers| + headers && headers['Idempotency-Key'] == 'test123' + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, @store_options.merge(customer: '_invalidId')) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal '2', response.error_code + assert_equal 'authenticatedShopperID cannot start with an underscore', response.message + end + + def test_store_should_raise_when_customer_not_present + assert_raises(ArgumentError) do + @gateway.store(@credit_card) + end + end + + def test_failed_authorize_using_token + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.respond_with(failed_authorize_response_2) + + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_failed_verify_using_token + response = stub_comms do + @gateway.verify(@token, @options) + end.respond_with(failed_authorize_response_2) + + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_authorize_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) + assert_match %r(59424549c291397379f30c5c082dbed8), data + assert_tag_with_attributes 'TOKEN-SSL', { 'tokenScope' => 'shopper' }, data + assert_match %r(99411111780163871111), data + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_purchase_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.purchase(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_verify_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.verify(@token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) if %r().match?(data) + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_credit_order_id_not_overridden_by_order_if_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.credit(@amount, @token, @options) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('order', { 'orderCode' => @options[:order_id].to_s }, data) + assert_match(//, data) + assert_match %r(59424549c291397379f30c5c082dbed8), data + assert_tag_with_attributes 'TOKEN-SSL', { 'tokenScope' => 'shopper' }, data + assert_match '99411111780163871111', data + end.respond_with(successful_visa_credit_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + + def test_handles_plain_text_response + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with('Temporary Failure, please Retry') + assert_failure response + assert_match "Unparsable response received from Worldpay. Please contact Worldpay if you continue to receive this message. \(The raw response returned by the API was: \"Temporary Failure, please Retry\"\)", response.message + end + + def test_successful_authorize_synchronous_response + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/4242424242424242/, data) + end.respond_with(successful_authorize_synchronous_response) + assert_success response + assert_equal 'fbe493442977787ea2fadabfb23c2574', response.authorization + end + + def test_successful_capture_synchronous_response + response = stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options) + @gateway.capture(@amount, response.authorization, @options) + end.respond_with(successful_authorize_synchronous_response, successful_capture_synchronous_response) + assert_success response + end + + def test_failed_capture_synchronous_response + response = stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options) + @gateway.capture(@amount, response.authorization, @options) + end.respond_with(successful_authorize_synchronous_response, failed_capture_synchronous_response) + assert_failure response + end + + def test_successful_refund_synchronous_response + response = stub_comms do + @gateway.refund(@amount, @options[:order_id], @options) + end.respond_with(successful_refund_synchronous_response) + assert_success response + end + + def test_failed_refund_synchronous_response + response = stub_comms do + @gateway.refund(@amount, @options[:order_id], @options) + end.respond_with(failed_refund_synchronous_response) + assert_failure response + end + + def test_network_token_type_assignation_when_apple_token + stub_comms do + @gateway.authorize(@amount, @apple_play_network_token, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %r(), data + end + end + + def test_network_token_type_assignation_when_network_token + stub_comms do + @gateway.authorize(@amount, @nt_credit_card, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %r(), data + end + end + + def test_network_token_type_assignation_when_google_pay + stub_comms do + @gateway.authorize(@amount, @google_pay_network_token, @options) + end.check_request(skip_response: true) do |_endpoint, data, _headers| + assert_match %r(), data + end + end + + def test_order_id_crop_and_clean + @options[:order_id] = "abc1234 abc1234 'abc1234' \"abc1234\" | abc1234 abc1234 abc1234 abc1234 abc1234" + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r(), data + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_inquire_with_order_id + response = stub_comms do + @gateway.inquire(nil, { order_id: @options[:order_id].to_s }) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('orderInquiry', { 'orderCode' => @options[:order_id].to_s }, data) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal 'R50704213207145707', response.authorization + end + + def test_successful_inquire_with_authorization + response = stub_comms do + @gateway.inquire(@options[:order_id].to_s, {}) + end.check_request do |_endpoint, data, _headers| + assert_tag_with_attributes('orderInquiry', { 'orderCode' => @options[:order_id].to_s }, data) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal 'R50704213207145707', response.authorization + end + + private + + def assert_date_element(expected_date_hash, date_element) + assert_equal(expected_date_hash[:day_of_month], date_element.attribute('dayOfMonth').value) + assert_equal(expected_date_hash[:month], date_element.attribute('month').value) + assert_equal(expected_date_hash[:year], date_element.attribute('year').value) + end + + def assert_tag_with_attributes(tag, attributes, string) + assert(m = %r(<#{tag}([^>]+)/?>).match(string)) + attributes.each do |attribute, value| + assert_match %r(#{attribute}="#{value}"), m[1] + end + end + + def three_d_secure_option(version:, xid: nil, ds_transaction_id: nil) + { + three_d_secure: { + eci: 'eci', + cavv: 'cavv', + xid: xid, + ds_transaction_id: ds_transaction_id, + version: version + } + } + end + + def risk_data + return @risk_data if defined?(@risk_data) + + authentication_time = Time.now + shopper_account_creation_date = Date.today + shopper_account_modification_date = Date.today - 1.day + shopper_account_password_change_date = Date.today - 2.days + shopper_account_shipping_address_first_use_date = Date.today - 3.day + shopper_account_payment_account_first_use_date = Date.today - 4.day + transaction_risk_data_pre_order_date = Date.today + 1.day + + @risk_data = { + authentication_risk_data: { + authentication_method: 'localAccount', + authentication_date: { + day_of_month: authentication_time.strftime('%d'), + month: authentication_time.strftime('%m'), + year: authentication_time.strftime('%Y'), + hour: authentication_time.strftime('%H'), + minute: authentication_time.strftime('%M'), + second: authentication_time.strftime('%S') + } + }, + shopper_account_risk_data: { + transactions_attempted_last_day: '1', + transactions_attempted_last_year: '2', + purchases_completed_last_six_months: '3', + add_card_attempts_last_day: '4', + previous_suspicious_activity: 'false', # Boolean (true or false) + shipping_name_matches_account_name: 'true', # Boolean (true or false) + shopper_account_age_indicator: 'lessThanThirtyDays', # Possible Values: noAccount, createdDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_change_indicator: 'thirtyToSixtyDays', # Possible values: changedDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_password_change_indicator: 'noChange', # Possible Values: noChange, changedDuringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_shipping_address_usage_indicator: 'moreThanSixtyDays', # Possible Values: thisTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_payment_account_indicator: 'thirtyToSixtyDays', # Possible Values: noAccount, duringTransaction, lessThanThirtyDays, thirtyToSixtyDays, moreThanSixtyDays + shopper_account_creation_date: { + day_of_month: shopper_account_creation_date.strftime('%d'), + month: shopper_account_creation_date.strftime('%m'), + year: shopper_account_creation_date.strftime('%Y') + }, + shopper_account_modification_date: { + day_of_month: shopper_account_modification_date.strftime('%d'), + month: shopper_account_modification_date.strftime('%m'), + year: shopper_account_modification_date.strftime('%Y') + }, + shopper_account_password_change_date: { + day_of_month: shopper_account_password_change_date.strftime('%d'), + month: shopper_account_password_change_date.strftime('%m'), + year: shopper_account_password_change_date.strftime('%Y') + }, + shopper_account_shipping_address_first_use_date: { + day_of_month: shopper_account_shipping_address_first_use_date.strftime('%d'), + month: shopper_account_shipping_address_first_use_date.strftime('%m'), + year: shopper_account_shipping_address_first_use_date.strftime('%Y') + }, + shopper_account_payment_account_first_use_date: { + day_of_month: shopper_account_payment_account_first_use_date.strftime('%d'), + month: shopper_account_payment_account_first_use_date.strftime('%m'), + year: shopper_account_payment_account_first_use_date.strftime('%Y') + } + }, + transaction_risk_data: { + shipping_method: 'digital', + delivery_timeframe: 'electronicDelivery', + delivery_email_address: 'abe@lincoln.gov', + reordering_previous_purchases: 'false', + pre_order_purchase: 'false', + gift_card_count: '0', + transaction_risk_data_gift_card_amount: { + value: '123', + currency: 'EUR', + exponent: '2', + debit_credit_indicator: 'credit' + }, + transaction_risk_data_pre_order_date: { + day_of_month: transaction_risk_data_pre_order_date.strftime('%d'), + month: transaction_risk_data_pre_order_date.strftime('%m'), + year: transaction_risk_data_pre_order_date.strftime('%Y') + } + } + } + end + + def successful_authorize_response + <<~RESPONSE + + + + + + + VISA-SSL + + AUTHORISED + + @@ -616,8 +1588,34 @@ def successful_authorize_response RESPONSE end + def successful_authorize_synchronous_response + <<~RESPONSE + + + + + + + VISA_CREDIT-SSL + + AUTHORISED + + + + + + 4111********1111 + + + + + + RESPONSE + end + def failed_authorize_response - <<-RESPONSE + <<~RESPONSE @@ -633,8 +1631,54 @@ def failed_authorize_response RESPONSE end + # main variation is that CDATA is nested inside w/o newlines; also a + # more recent captured response from remote tests where the reply is + # contained the error directly (no ) + def failed_authorize_response_2 + <<~RESPONSE + + + + + + + + RESPONSE + end + + def failed_purchase_response_with_issuer_response_code + <<~RESPONSE + + + + + + + VISA-SSL + + REFUSED + + + + + + + + + + US + TEST BANK + + + + + + RESPONSE + end + def successful_capture_response - <<-RESPONSE + <<~RESPONSE @@ -650,8 +1694,48 @@ def successful_capture_response RESPONSE end + def successful_capture_synchronous_response + <<~RESPONSE + + + + + + + VISA_CREDIT-SSL + + CAPTURED + + + + + + + + + + + RESPONSE + end + + def failed_capture_synchronous_response + <<~RESPONSE + + + + + + + + + + RESPONSE + end + def successful_authorize_with_elo_response - <<-RESPONSE + <<~RESPONSE @@ -676,7 +1760,7 @@ def successful_authorize_with_elo_response end def successful_capture_with_elo_response - <<-RESPONSE + <<~RESPONSE @@ -692,46 +1776,46 @@ def successful_capture_with_elo_response end def successful_void_inquiry_with_elo_response - <<-RESPONSE - - - - - - - ELO-SSL - - AUTHORISED - - - + <<~RESPONSE + + + + + + + ELO-SSL - - 4514********0008 - - - - - + AUTHORISED + + + + + + 4514********0008 + + + + + RESPONSE end def successful_void_with_elo_response - <<-RESPONSE - - - - - - - - - + <<~RESPONSE + + + + + + + + + RESPONSE end def successful_inquiry_response - <<-RESPONSE + <<~RESPONSE @@ -758,7 +1842,7 @@ def successful_inquiry_response end def successful_void_inquiry_response - <<-RESPONSE + <<~RESPONSE @@ -785,7 +1869,7 @@ def successful_void_inquiry_response end def successful_void_response - <<-RESPONSE + <<~RESPONSE @@ -800,7 +1884,7 @@ def successful_void_response end def failed_void_inquiry_response - <<-RESPONSE + <<~RESPONSE @@ -827,35 +1911,64 @@ def failed_void_inquiry_response RESPONSE end - def successful_refund_inquiry_response(last_event='CAPTURED') - <<-RESPONSE - - - - - - - VISA-SSL - - #{last_event} - - - - - - 4111********1111 - - - - - - + def failed_purchase_response_without_useful_error_from_gateway + <<~RESPONSE + + + + + + ECMC_DEBIT-SSL + + REFUSED + + + + + + + + + Snuffy Smith + US + PRETEND BANK + + + + + + RESPONSE + end + + def successful_refund_inquiry_response(last_event = 'CAPTURED') + <<~RESPONSE + + + + + + + VISA-SSL + + #{last_event} + + + + + + 4111********1111 + + + + + + RESPONSE end def successful_refund_response - <<-RESPONSE + <<~RESPONSE @@ -871,8 +1984,70 @@ def successful_refund_response RESPONSE end + def successful_cancel_or_refund_response + <<~RESPONSE + + + + + + + + + + RESPONSE + end + + def successful_refund_synchronous_response + <<~RESPONSE + + + + + ECMC_CREDIT-SSL + + SENT_FOR_REFUND + + + + + + AR + ISSUER-NAME + WA + + 999999999 + + + + + + + + + + + RESPONSE + end + + def failed_refund_synchronous_response + <<~RESPONSE + + + + + + + + + + RESPONSE + end + def failed_refund_inquiry_response - <<-RESPONSE + <<~RESPONSE @@ -901,7 +2076,7 @@ def failed_refund_inquiry_response end def failed_void_response - <<-REQUEST + <<~REQUEST @@ -917,7 +2092,7 @@ def failed_void_response end def successful_visa_credit_response - <<-RESPONSE + <<~RESPONSE @@ -935,29 +2110,29 @@ def successful_visa_credit_response def successful_mastercard_credit_response <<~RESPONSE - - - - - - - ECMC_DEBIT-SSL - - SENT_FOR_REFUND - - - - - - - - + + + + + + + ECMC_DEBIT-SSL + + SENT_FOR_REFUND + + + + + + + + RESPONSE end def sample_authorization_request - <<-REQUEST + <<~REQUEST @@ -967,7 +2142,7 @@ def sample_authorization_request Products Products Products - + 4242424242424242 @@ -987,7 +2162,7 @@ def sample_authorization_request (555)555-5555
- + @@ -1003,72 +2178,200 @@ def sample_authorization_request end def transcript - <<-TRANSCRIPT - - - - Purchase - - - - 4111111111111111 - - - - Longbob Longsen - 123 - -
- N/A - 0000 - N/A - N/A - US -
-
-
-
- - wow@example.com - -
-
-
+ <<~TRANSCRIPT + + + + Purchase + + + + 4111111111111111 + + + + Longbob Longsen + 123 + +
+ N/A + 0000 + N/A + N/A + US +
+
+
+
+ + wow@example.com + +
+
+
TRANSCRIPT end def scrubbed_transcript - <<-TRANSCRIPT - - - - Purchase - - - - [FILTERED] - - - - Longbob Longsen - [FILTERED] - -
- N/A - 0000 - N/A - N/A - US -
-
-
-
- - wow@example.com - -
-
-
+ <<~TRANSCRIPT + + + + Purchase + + + + [FILTERED] + + + + Longbob Longsen + [FILTERED] + +
+ N/A + 0000 + N/A + N/A + US +
+
+
+
+ + wow@example.com + +
+
+
TRANSCRIPT end + + def network_token_transcript + <<~RESPONSE + + + + + + Purchase + + + + 4895370015293175 + + + + PedroPerez + axxxxxxxxx + 07 + + + + wow@ example.com + + + + + + + + + RESPONSE + end + + def network_token_transcript_scrubbed + <<~RESPONSE + + + + + + Purchase + + + + [FILTERED] + + + + PedroPerez + [FILTERED] + 07 + + + + wow@ example.com + + + + + + + + + RESPONSE + end + + def failed_with_unknown_card_response + <<~RESPONSE + + + + + + + + + + RESPONSE + end + + def successful_store_response + <<~RESPONSE + + + + + + 59424549c291397379f30c5c082dbed8 + + 99411111780163871111 + + + + Created token without payment on 2019-05-23 + + + + + + + + + VISA + VISA_CREDIT + N/A + TARGOBANK AG & CO. KGAA + 4111********1111 + + + + + + + RESPONSE + end + + def failed_store_response + <<~RESPONSE + + + + + + + + RESPONSE + end end diff --git a/test/unit/gateways/worldpay_us_test.rb b/test/unit/gateways/worldpay_us_test.rb index ee576daa591..65040db2915 100644 --- a/test/unit/gateways/worldpay_us_test.rb +++ b/test/unit/gateways/worldpay_us_test.rb @@ -67,7 +67,7 @@ def test_authorize_and_capture capture = stub_comms do @gateway.capture(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/postonly=354275517/, data) end.respond_with(successful_capture_response) @@ -84,7 +84,7 @@ def test_refund refund = stub_comms do @gateway.refund(@amount, response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/historykeyid=353583515/, data) assert_match(/orderkeyid=252889136/, data) end.respond_with(successful_refund_response) @@ -102,7 +102,7 @@ def test_void refund = stub_comms do @gateway.void(response.authorization) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/historykeyid=353583515/, data) assert_match(/orderkeyid=252889136/, data) end.respond_with(successful_refund_response) @@ -136,15 +136,15 @@ def test_unsuccessful_verify def test_passing_cvv stub_comms do @gateway.purchase(@amount, @credit_card) - end.check_request do |endpoint, data, headers| + end.check_request do |_endpoint, data, _headers| assert_match(/#{@credit_card.verification_value}/, data) end.respond_with(successful_purchase_response) end def test_passing_billing_address stub_comms do - @gateway.purchase(@amount, @credit_card, :billing_address => address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/ci_billaddr1=456\+My\+Street/, data) assert_match(/ci_billzip=K1C2N6/, data) end.respond_with(successful_purchase_response) @@ -152,16 +152,16 @@ def test_passing_billing_address def test_passing_phone_number stub_comms do - @gateway.purchase(@amount, @credit_card, :billing_address => address) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |_endpoint, data, _headers| assert_match(/ci_phone=%28555%29555-5555/, data) end.respond_with(successful_purchase_response) end def test_passing_billing_address_without_phone stub_comms do - @gateway.purchase(@amount, @credit_card, :billing_address => address(:phone => nil)) - end.check_request do |endpoint, data, headers| + @gateway.purchase(@amount, @credit_card, billing_address: address(phone: nil)) + end.check_request do |_endpoint, data, _headers| assert_no_match(/udf3/, data) end.respond_with(successful_purchase_response) end @@ -178,7 +178,7 @@ def test_empty_response_fails def test_backup_url response = stub_comms(@gateway) do @gateway.purchase(@amount, @credit_card, use_backup_url: true) - end.check_request do |endpoint, data, headers| + end.check_request do |endpoint, _data, _headers| assert_equal WorldpayUsGateway.backup_url, endpoint end.respond_with(successful_purchase_response) assert_success response @@ -436,94 +436,94 @@ def failed_void_response end def pre_scrubbed - <<-EOS -opening connection to trans.worldpay.us:443... -opened -starting SSL for trans.worldpay.us:443... -SSL established -<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" -<- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=4446661234567892&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555¤cycode=USD&cvv2=987&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=1234567890&subid=SPREE" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" --> "Server: Apache\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Content-Type: text/html;charset=ISO-8859-1\r\n" --> "Content-Length: 962\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 962 bytes... --> "\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" -read 962 bytes -Conn close - EOS + <<~REQUEST + opening connection to trans.worldpay.us:443... + opened + starting SSL for trans.worldpay.us:443... + SSL established + <- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" + <- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=4446661234567892&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&currencycode=USD&cvv2=987&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=1234567890&subid=SPREE" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" + -> "Server: Apache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Type: text/html;charset=ISO-8859-1\r\n" + -> "Content-Length: 962\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 962 bytes... + -> "<html><body><plaintext>\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" + read 962 bytes + Conn close + REQUEST end def post_scrubbed - <<-EOS -opening connection to trans.worldpay.us:443... -opened -starting SSL for trans.worldpay.us:443... -SSL established -<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" -<- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=[FILTERED]&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&currencycode=USD&cvv2=[FILTERED]&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=[FILTERED]&subid=SPREE" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" --> "Server: Apache\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Content-Type: text/html;charset=ISO-8859-1\r\n" --> "Content-Length: 962\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 962 bytes... --> "<html><body><plaintext>\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" -read 962 bytes -Conn close - EOS + <<~REQUEST + opening connection to trans.worldpay.us:443... + opened + starting SSL for trans.worldpay.us:443... + SSL established + <- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" + <- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=[FILTERED]&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&currencycode=USD&cvv2=[FILTERED]&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=[FILTERED]&subid=SPREE" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" + -> "Server: Apache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Type: text/html;charset=ISO-8859-1\r\n" + -> "Content-Length: 962\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 962 bytes... + -> "<html><body><plaintext>\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" + read 962 bytes + Conn close + REQUEST end def pre_scrubbed_check - <<-EOS -opening connection to trans.worldpay.us:443... -opened -starting SSL for trans.worldpay.us:443... -SSL established -<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" -<- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=15378535&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=1234567890&subid=SPREE" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" --> "Server: Apache\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Content-Type: text/html;charset=ISO-8859-1\r\n" --> "Content-Length: 414\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 414 bytes... --> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" -read 414 bytes -Conn close - EOS + <<~REQUEST + opening connection to trans.worldpay.us:443... + opened + starting SSL for trans.worldpay.us:443... + SSL established + <- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" + <- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=15378535&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=1234567890&subid=SPREE" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" + -> "Server: Apache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Type: text/html;charset=ISO-8859-1\r\n" + -> "Content-Length: 414\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 414 bytes... + -> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" + read 414 bytes + Conn close + REQUEST end def post_scrubbed_check - <<-EOS -opening connection to trans.worldpay.us:443... -opened -starting SSL for trans.worldpay.us:443... -SSL established -<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" -<- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=[FILTERED]&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=[FILTERED]&subid=SPREE" --> "HTTP/1.1 200 OK\r\n" --> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" --> "Server: Apache\r\n" --> "X-Frame-Options: SAMEORIGIN\r\n" --> "Content-Type: text/html;charset=ISO-8859-1\r\n" --> "Content-Length: 414\r\n" --> "Connection: close\r\n" --> "\r\n" -reading 414 bytes... --> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" -read 414 bytes -Conn close - EOS + <<~REQUEST + opening connection to trans.worldpay.us:443... + opened + starting SSL for trans.worldpay.us:443... + SSL established + <- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" + <- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=[FILTERED]&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=[FILTERED]&subid=SPREE" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" + -> "Server: Apache\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Content-Type: text/html;charset=ISO-8859-1\r\n" + -> "Content-Length: 414\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 414 bytes... + -> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" + read 414 bytes + Conn close + REQUEST end end diff --git a/test/unit/gateways/xpay_test.rb b/test/unit/gateways/xpay_test.rb new file mode 100644 index 00000000000..318e88d3de7 --- /dev/null +++ b/test/unit/gateways/xpay_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class XpayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = XpayGateway.new( + api_key: 'some api key' + ) + @credit_card = credit_card + @amount = 100 + @base_url = @gateway.test_url + @options = {} + end + + def test_supported_countries + assert_equal %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), XpayGateway.supported_countries + end + + def test_supported_cardtypes + assert_equal %i[visa master maestro american_express jcb], @gateway.supported_cardtypes + end + + def test_build_request_url_for_purchase + action = :purchase + assert_equal @gateway.send(:build_request_url, action), "#{@base_url}orders/2steps/payment" + end + + def test_build_request_url_with_id_param + action = :refund + id = 123 + assert_equal @gateway.send(:build_request_url, action, id), "#{@base_url}operations/{123}/refunds" + end + + def test_invalid_instance + assert_raise ArgumentError do + XpayGateway.new() + end + end + + def test_check_request_headers + stub_comms do + @gateway.send(:commit, 'purchase', {}, {}) + end.check_request(skip_response: true) do |_endpoint, _data, headers| + assert_equal headers['Content-Type'], 'application/json' + assert_equal headers['X-Api-Key'], 'some api key' + end + end + + def test_check_authorize_endpoint + stub_comms do + @gateway.send(:authorize, @amount, @credit_card, @options) + end.check_request(skip_response: true) do |endpoint, _data, _headers| + assert_match(/orders\/2steps\/init/, endpoint) + end + end +end diff --git a/test/unit/multi_response_test.rb b/test/unit/multi_response_test.rb index a60062d0003..ed7c1935fd2 100644 --- a/test/unit/multi_response_test.rb +++ b/test/unit/multi_response_test.rb @@ -34,16 +34,16 @@ def test_proxies_last_request r1 = Response.new( true, '1', - {'one' => 1}, - :test => true, - :authorization => 'auth1', - :avs_result => {:code => 'AVS1'}, - :cvv_result => 'CVV1', - :error_code => :card_declined, - :fraud_review => true + { 'one' => 1 }, + test: true, + authorization: 'auth1', + avs_result: { code: 'AVS1' }, + cvv_result: 'CVV1', + error_code: :card_declined, + fraud_review: true ) m.process { r1 } - assert_equal({'one' => 1}, m.params) + assert_equal({ 'one' => 1 }, m.params) assert_equal '1', m.message assert m.test assert_equal 'auth1', m.authorization @@ -56,15 +56,15 @@ def test_proxies_last_request r2 = Response.new( true, '2', - {'two' => 2}, - :test => false, - :authorization => 'auth2', - :avs_result => {:code => 'AVS2'}, - :cvv_result => 'CVV2', - :fraud_review => false + { 'two' => 2 }, + test: false, + authorization: 'auth2', + avs_result: { code: 'AVS2' }, + cvv_result: 'CVV2', + fraud_review: false ) m.process { r2 } - assert_equal({'two' => 2}, m.params) + assert_equal({ 'two' => 2 }, m.params) assert_equal '2', m.message assert !m.test assert_equal 'auth2', m.authorization @@ -80,15 +80,15 @@ def test_proxies_first_request_if_marked r1 = Response.new( true, '1', - {'one' => 1}, - :test => true, - :authorization => 'auth1', - :avs_result => {:code => 'AVS1'}, - :cvv_result => 'CVV1', - :fraud_review => true + { 'one' => 1 }, + test: true, + authorization: 'auth1', + avs_result: { code: 'AVS1' }, + cvv_result: 'CVV1', + fraud_review: true ) m.process { r1 } - assert_equal({'one' => 1}, m.params) + assert_equal({ 'one' => 1 }, m.params) assert_equal '1', m.message assert m.test assert_equal 'auth1', m.authorization @@ -100,15 +100,15 @@ def test_proxies_first_request_if_marked r2 = Response.new( true, '2', - {'two' => 2}, - :test => false, - :authorization => 'auth2', - :avs_result => {:code => 'AVS2'}, - :cvv_result => 'CVV2', - :fraud_review => false + { 'two' => 2 }, + test: false, + authorization: 'auth2', + avs_result: { code: 'AVS2' }, + cvv_result: 'CVV2', + fraud_review: false ) m.process { r2 } - assert_equal({'one' => 1}, m.params) + assert_equal({ 'one' => 1 }, m.params) assert_equal '1', m.message assert m.test assert_equal 'auth1', m.authorization @@ -172,4 +172,94 @@ def test_handles_ignores_optional_request_result assert m.success? end + + def test_handles_responses_with_only_one_with_avs_and_cvv_result + r1 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'Y'), cvv_result: 'M' }) + r2 = Response.new(true, '2', {}) + m = MultiResponse.run do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => 'Y', 'message' => 'Street address and 5-digit postal code match.', 'street_match' => 'Y', 'postal_match' => 'Y' } + assert_equal m.cvv_result, { 'code' => 'M', 'message' => 'CVV matches' } + end + + def test_handles_responses_using_last_response_cvv_and_avs_result + r1 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'Y'), cvv_result: 'M' }) + r2 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'B'), cvv_result: 'N' }) + m = MultiResponse.run do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => 'B', 'message' => 'Street address matches, but postal code not verified.', 'street_match' => 'Y', 'postal_match' => nil } + assert_equal m.cvv_result, { 'code' => 'N', 'message' => 'CVV does not match' } + end + + def test_handles_responses_using_first_response_cvv_and_avs_result + r1 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'Y'), cvv_result: 'M' }) + r2 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'B'), cvv_result: 'N' }) + m = MultiResponse.run(:use_first_response) do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => 'Y', 'message' => 'Street address and 5-digit postal code match.', 'street_match' => 'Y', 'postal_match' => 'Y' } + assert_equal m.cvv_result, { 'code' => 'M', 'message' => 'CVV matches' } + end + + def test_handles_responses_using_first_response_cvv_that_no_has_cvv_and_avs_result + r1 = Response.new(true, '1', {}) + r2 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'B'), cvv_result: 'N' }) + m = MultiResponse.run(:use_first_response) do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => nil, 'message' => nil, 'street_match' => nil, 'postal_match' => nil } + assert_equal m.cvv_result, { 'code' => nil, 'message' => nil } + end + + def test_handles_response_with_avs_and_without_cvv_result + r1 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: 'X'), cvv_result: CVVResult.new(nil) }) + r2 = Response.new(true, '2', {}) + m = MultiResponse.run do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => 'X', 'message' => 'Street address and 9-digit postal code match.', 'street_match' => 'Y', 'postal_match' => 'Y' } + assert_equal m.cvv_result, { 'code' => nil, 'message' => nil } + end + + def test_handles_response_avs_and_cvv_result_with_wrong_values_avs_and_cvv_code + r1 = Response.new(true, '1', {}, { avs_result: AVSResult.new(code: '1234567'), cvv_result: CVVResult.new('987654') }) + r2 = Response.new(true, '2', {}) + m = MultiResponse.run do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => '1234567', 'message' => nil, 'street_match' => nil, 'postal_match' => nil } + assert_equal m.cvv_result, { 'code' => '987654', 'message' => nil } + end + + def test_handles_response_without_avs_and_cvv_result + r1 = Response.new(true, '1', {}) + r2 = Response.new(true, '2', {}) + m = MultiResponse.run do |r| + r.process { r1 } + r.process { r2 } + end + assert_equal [r1, r2], m.responses + assert_equal m.avs_result, { 'code' => nil, 'message' => nil, 'street_match' => nil, 'postal_match' => nil } + assert_equal m.cvv_result, { 'code' => nil, 'message' => nil } + end + + def test_handles_responses_avs_and_cvv_result_with_no_responses_provideds + m = MultiResponse.new + assert_equal m.avs_result, nil + assert_equal m.cvv_result, nil + end end diff --git a/test/unit/network_connection_retries_test.rb b/test/unit/network_connection_retries_test.rb index 49ce4b3d8e6..97cb306e233 100644 --- a/test/unit/network_connection_retries_test.rb +++ b/test/unit/network_connection_retries_test.rb @@ -9,7 +9,7 @@ class MyNewError < StandardError def setup @logger = stubs(:logger) @requester = stubs(:requester) - @ok = stub(:code => 200, :message => 'OK', :body => 'success') + @ok = stub(code: 200, message: 'OK', body: 'success') end def test_eoferror_raises_correctly @@ -78,7 +78,7 @@ def test_invalid_response_error def test_unrecoverable_exception_logged_if_logger_provided @logger.expects(:info).once assert_raises(ActiveMerchant::ConnectionError) do - retry_exceptions :logger => @logger do + retry_exceptions logger: @logger do raise EOFError end end @@ -107,7 +107,7 @@ def test_failure_limit_reached_logs_final_error @requester.expects(:post).times(ActiveMerchant::NetworkConnectionRetries::DEFAULT_RETRIES).raises(Errno::ECONNREFUSED) assert_raises(ActiveMerchant::ConnectionError) do - retry_exceptions(:logger => @logger) do + retry_exceptions(logger: @logger) do @requester.post end end @@ -116,7 +116,7 @@ def test_failure_limit_reached_logs_final_error def test_failure_then_success_with_retry_safe_enabled @requester.expects(:post).times(2).raises(EOFError).then.returns(@ok) - retry_exceptions :retry_safe => true do + retry_exceptions retry_safe: true do @requester.post end end @@ -126,7 +126,7 @@ def test_failure_then_success_logs_success @logger.expects(:info).with(regexp_matches(/success/)) @requester.expects(:post).times(2).raises(EOFError).then.returns(@ok) - retry_exceptions(:logger => @logger, :retry_safe => true) do + retry_exceptions(logger: @logger, retry_safe: true) do @requester.post end end @@ -140,7 +140,7 @@ def test_mixture_of_failures_with_retry_safe_enabled raises(EOFError) assert_raises(ActiveMerchant::ConnectionError) do - retry_exceptions :retry_safe => true do + retry_exceptions retry_safe: true do @requester.post end end @@ -161,7 +161,7 @@ def test_failure_with_ssl_certificate_logs_error_if_logger_specified @requester.expects(:post).raises(OpenSSL::X509::CertificateError) assert_raises(ActiveMerchant::ClientCertificateError) do - retry_exceptions :logger => @logger do + retry_exceptions logger: @logger do @requester.post end end @@ -171,7 +171,7 @@ def test_failure_with_additional_exceptions_specified @requester.expects(:post).raises(MyNewError) assert_raises(ActiveMerchant::ConnectionError) do - retry_exceptions :connection_exceptions => {MyNewError => 'my message'} do + retry_exceptions connection_exceptions: { MyNewError => 'my message' } do @requester.post end end diff --git a/test/unit/network_tokenization_credit_card_test.rb b/test/unit/network_tokenization_credit_card_test.rb index a5ac80e822d..a10356c0c6b 100644 --- a/test/unit/network_tokenization_credit_card_test.rb +++ b/test/unit/network_tokenization_credit_card_test.rb @@ -1,25 +1,28 @@ require 'test_helper' class NetworkTokenizationCreditCardTest < Test::Unit::TestCase - def setup - @tokenized_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ - number: '4242424242424242', :brand => 'visa', + @tokenized_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + number: '4242424242424242', brand: 'visa', month: default_expiration_date.month, year: default_expiration_date.year, - payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', eci: '05' - }) - @tokenized_apple_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', eci: '05', + metadata: { device_manufacturer_id: '1324' } + ) + @tokenized_apple_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( source: :apple_pay - }) - @tokenized_android_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + ) + @tokenized_android_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( source: :android_pay - }) - @tokenized_google_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + ) + @tokenized_google_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( source: :google_pay - }) - @tokenized_bogus_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + ) + @existing_network_token = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + source: :network_token + ) + @tokenized_bogus_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( source: :bogus_pay - }) + ) end def test_type @@ -44,5 +47,6 @@ def test_source assert_equal @tokenized_android_pay_card.source, :android_pay assert_equal @tokenized_google_pay_card.source, :google_pay assert_equal @tokenized_bogus_pay_card.source, :apple_pay + assert_equal @existing_network_token.source, :network_token end end diff --git a/test/unit/post_data_test.rb b/test/unit/post_data_test.rb index dd22426f862..5d6b265b28b 100644 --- a/test/unit/post_data_test.rb +++ b/test/unit/post_data_test.rb @@ -1,7 +1,7 @@ require 'test_helper' class MyPost < ActiveMerchant::PostData - self.required_fields = [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ] + self.required_fields = %i[ccnumber ccexp firstname lastname username password order_id key time] end class PostDataTest < Test::Unit::TestCase @@ -29,7 +29,7 @@ def test_ignore_blank_fields end def test_dont_ignore_required_blank_fields - ActiveMerchant::PostData.required_fields = [ :name ] + ActiveMerchant::PostData.required_fields = [:name] post = ActiveMerchant::PostData.new assert_equal 0, post.keys.size @@ -45,6 +45,6 @@ def test_dont_ignore_required_blank_fields def test_subclass post = MyPost.new - assert_equal [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ], post.required_fields + assert_equal %i[ccnumber ccexp firstname lastname username password order_id key time], post.required_fields end end diff --git a/test/unit/posts_data_test.rb b/test/unit/posts_data_test.rb index 960992cd0d4..58de35f5d6c 100644 --- a/test/unit/posts_data_test.rb +++ b/test/unit/posts_data_test.rb @@ -1,12 +1,11 @@ require 'test_helper' class PostsDataTests < Test::Unit::TestCase - def setup @url = 'http://example.com' @gateway = SimpleTestGateway.new - @ok = stub(:body => '', :code => '200', :message => 'OK') - @error = stub(:code => 500, :message => 'Internal Server Error', :body => 'failure') + @ok = stub(body: '', code: '200', message: 'OK') + @error = stub(code: 500, message: 'Internal Server Error', body: 'failure') end def teardown diff --git a/test/unit/rails_compatibility_test.rb b/test/unit/rails_compatibility_test.rb index addcb3ec701..ebcbaaf4a72 100644 --- a/test/unit/rails_compatibility_test.rb +++ b/test/unit/rails_compatibility_test.rb @@ -2,7 +2,7 @@ class RailsCompatibilityTest < Test::Unit::TestCase def test_should_be_able_to_access_errors_indifferently - cc = credit_card('4779139500118580', :first_name => '') + cc = credit_card('4779139500118580', first_name: '') silence_deprecation_warnings do assert !cc.valid? diff --git a/test/unit/response_test.rb b/test/unit/response_test.rb index 716d14d7619..a5ed17e5d47 100644 --- a/test/unit/response_test.rb +++ b/test/unit/response_test.rb @@ -2,35 +2,35 @@ class ResponseTest < Test::Unit::TestCase def test_response_success - assert Response.new(true, 'message', :param => 'value').success? - assert !Response.new(false, 'message', :param => 'value').success? + assert Response.new(true, 'message', param: 'value').success? + assert !Response.new(false, 'message', param: 'value').success? end def test_response_without_avs - response = Response.new(true, 'message', :param => 'value') + response = Response.new(true, 'message', param: 'value') assert response.avs_result.has_key?('code') end def test_response_without_cvv - response = Response.new(true, 'message', :param => 'value') + response = Response.new(true, 'message', param: 'value') assert response.cvv_result.has_key?('code') end def test_get_params - response = Response.new(true, 'message', :param => 'value') + response = Response.new(true, 'message', param: 'value') assert_equal ['param'], response.params.keys end def test_avs_result - response = Response.new(true, 'message', {}, :avs_result => { :code => 'A', :street_match => 'Y', :zip_match => 'N' }) + response = Response.new(true, 'message', {}, avs_result: { code: 'A', street_match: 'Y', zip_match: 'N' }) avs_result = response.avs_result assert_equal 'A', avs_result['code'] assert_equal AVSResult.messages['A'], avs_result['message'] end def test_cvv_result - response = Response.new(true, 'message', {}, :cvv_result => 'M') + response = Response.new(true, 'message', {}, cvv_result: 'M') cvv_result = response.cvv_result assert_equal 'M', cvv_result['code'] assert_equal CVVResult.messages['M'], cvv_result['message'] diff --git a/test/unit/three_d_secure_eci_mapper_test.rb b/test/unit/three_d_secure_eci_mapper_test.rb new file mode 100644 index 00000000000..e7890dd3e4d --- /dev/null +++ b/test/unit/three_d_secure_eci_mapper_test.rb @@ -0,0 +1,30 @@ +require 'test_helper' + +class ThreeDSecureEciMapperTest < Test::Unit::TestCase + ThreeDSecureEciMapper = ActiveMerchant::Billing::ThreeDSecureEciMapper + + [ + { eci: '00', brands: %i[master maestro], expected_eci_mapping: ThreeDSecureEciMapper::NON_THREE_D_SECURE_TRANSACTION }, + { eci: '01', brands: %i[master maestro], expected_eci_mapping: ThreeDSecureEciMapper::ATTEMPTED_AUTHENTICATION_TRANSACTION }, + { eci: '02', brands: %i[master maestro], expected_eci_mapping: ThreeDSecureEciMapper::FULLY_AUTHENTICATED_TRANSACTION }, + { eci: '05', brands: %i[visa american_express discover diners_club jcb dankort elo], expected_eci_mapping: ThreeDSecureEciMapper::FULLY_AUTHENTICATED_TRANSACTION }, + { eci: '06', brands: %i[visa american_express discover diners_club jcb dankort elo], expected_eci_mapping: ThreeDSecureEciMapper::ATTEMPTED_AUTHENTICATION_TRANSACTION }, + { eci: '07', brands: %i[visa american_express discover diners_club jcb dankort elo], expected_eci_mapping: ThreeDSecureEciMapper::NON_THREE_D_SECURE_TRANSACTION } + ].each do |test_spec| + test_spec[:brands].each do |brand| + eci = test_spec[:eci] + expected_eci_mapping = test_spec[:expected_eci_mapping] + test "#map for #{brand} and '#{eci}' returns :#{expected_eci_mapping}" do + assert_equal expected_eci_mapping, ThreeDSecureEciMapper.map(brand, eci) + end + end + end + + test "#map for :unknown_brand and '05' returns nil" do + assert_nil ThreeDSecureEciMapper.map(:unknown_brand, '05') + end + + test "#map for :visa and 'unknown_eci' returns nil" do + assert_nil ThreeDSecureEciMapper.map(:visa, 'unknown_eci') + end +end diff --git a/test/unit/transcripts/alelo_purchase b/test/unit/transcripts/alelo_purchase new file mode 100644 index 00000000000..6cd426f43a4 --- /dev/null +++ b/test/unit/transcripts/alelo_purchase @@ -0,0 +1,69 @@ +<- "POST /alelo/sandbox/captura-oauth-provider/oauth/token HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox-api.alelo.com.br\r\nContent-Length: 158\r\n\r\n" +<- "grant_type=client_credentials&client_id=ed702c5c-117c-4bc6-989a-055ede547b6d&client_secret=uI3cG0nO5cI0lQ4mY2aF1eN4kV0jA4vC1bJ1rJ0gV2pW7aU4uC&scope=%2Fcapture" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: FAIL FAIL\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Cache-Control: private, no-store, no-cache, must-revalidate\r\n" +-> "Pragma: no-cache\r\n" +-> "Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: POST\r\n" +-> "Content-Encoding: gzip\r\n" +-> "\r\n" +-> xxxxxx some binary string xxxxx +-> Decoded Response Body: +-> { "token_type":"Bearer", "access_token":"AAIkODFiYjJlYWYtM2Q1OS00OTM5LTk4ZTctOTRiZjllZTQ2MzljYzd8XKy3u3vEEWIl6xiQGwMiCSkebtYZzHEZX8N8h4pXS1RtqPrxc9Vz6KszCIITgA0tiGkfjj1yl4n3Z9F4-zic5Va0EvvbHLCBzYiQJCmE5ezh9d_1I5I4ncDnKZa5", "expires_in":86400, "consented_on":1666785813, "scope":"/capture" }opening connection to api.alelo.com.br:443... +starting SSL for sandbox-api.alelo.com.br:443... +SSL established, protocol: TLSv1.2, cipher: DHE-RSA-AES256-GCM-SHA384 +<- "GET /alelo/sandbox/capture/key?format=json HTTP/1.1\r\nAccept: application/json\r\nX-Ibm-Client-Id: ed702c5c-117c-4bc6-989a-055ede547b6d\r\nX-Ibm-Client-Secret: uI3cG0nO5cI0lQ4mY2aF1eN4kV0jA4vC1bJ1rJ0gV2pW7aU4uC\r\nAuthorization: Bearer AAIkZWQ3MDJjNWMtMTE3Yy00YmM2LTk4OWEtMDU1ZWRlNTQ3YjZkT5y20AlJqCMxbP0m6e7NnLCghHw7E50NsrAopbwLbtZ7zbDeSVOfVdxkIMbT6XnQrOAN65uFJ_ZH9FLh4dIUV9KOzJyBYnf4YND493nATHFhQpepNvo41qu7rykjBkEw\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox-api.alelo.com.br\r\n\r\n" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: OK OK\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Date: Wed, 24 Aug 2022 22:20:34 GMT\r\n" +-> "X-Global-Transaction-ID: 379298106306a42155f1a5f2\r\n" +-> "x-content-type-options: nosniff\r\n" +-> "x-xss-protection: 1; mode=block\r\n" +-> "cache-control: no-cache, no-store, max-age=0, must-revalidate\r\n" +-> "pragma: no-cache\r\n" +-> "expires: 0\r\n" +-> "x-frame-options: DENY\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: GET\r\n" +-> "Set-Cookie: 20af66d9029740e40c713b05f443f86d=2bdc6d7099faf31789e088a0d77ea5cc; path=/; HttpOnly; Secure; SameSite=None\r\n" +-> "X-RateLimit-Limit: name=rate-limit,400;\r\n" +-> "X-RateLimit-Remaining: name=rate-limit,399;\r\n" +-> "Content-Encoding: gzip\r\n" +-> xxxxxx some binary string xxxxx +-> Decoded Response Body: +-> {"uuid":"81346b5b-7ce0-4e89-b049-5488b7c8a2b6","format":"BASE64","publicKey":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnWPNWBkwWKDzJmRxxue2fCTrTSMJAoPNn32fIhbw9rNO5GErDOKg5rWzGcIthcH9F2BKIOVZ9YAc0kRVZlsSef2anOxwhFsA6gvj4H4R6lkb8lM9ee9YKw+MrWebKvj78g7o/z7roQkzS6iGcV4/rg4jL819TAz9kHY3Vf2tJi5wW3lPoXawUpvBXGLU1ZPe1RGekFtzBHyFNWniiY7pXF2qRFdeGJ4vcVxfIcYEAV/Pz9vUyLCsscRVBxA24sgYKt8giIp2Ymr2tpsjNwI+bj3jhsej+vdI/CG5klooLW84MSn6LEcCpaG71OB9KN5dDsZ3yKCCLuhmljEX/Wza8QIDAQAB"}opening connection to api.alelo.com.br:443... +<- "POST /alelo/sandbox/capture/transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nX-Ibm-Client-Id: ed702c5c-117c-4bc6-989a-055ede547b6d\r\nX-Ibm-Client-Secret: uI3cG0nO5cI0lQ4mY2aF1eN4kV0jA4vC1bJ1rJ0gV2pW7aU4uC\r\nAuthorization: Bearer AAIkZWQ3MDJjNWMtMTE3Yy00YmM2LTk4OWEtMDU1ZWRlNTQ3YjZkkyzyfNOK8fm61YM_NgGTTp09udTkhoeCdjHA7nMfXwCbTaiU3BJ6NwNkLqJ7ogfDG2cOhwnhVOmdCQ4wwLRwChUZJ__RHzxbt-MlhtQbGqUkF0i1ZwlNUrO7ElCXsyW0\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox-api.alelo.com.br\r\nContent-Length: 913\r\n\r\n" +<- "{\"token\":\"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.Sy8e0y387LVWcv0P7nwKtu7DHKWww4fCnCx68OnjhmM9sbhmZ9FTQwznGfnSljWFYxXmQPy2PnvtsdvMsEPp5jUANMKTpp4aAGfS_1x31UBuMeRQT5NMlFG8lhCwbPmNtrEOyB2fZUQBsmePpTtdzzAn1tj_hY7XC2bqGkPJ2zS6K2jXqdtkGWeLSMjEDRdyjDuQ_ybFx6uHfYcN_ioXcetUU_MJ1Ai3snfBU-150fCKTmY0SJ09tWMLCyYmvL416L_ha2UmY3tZm1zvpkyRQ612t88ZCEeUK-ZXBYp7FJT-KAAogG49G-Nadj3PSe8jQdxoIZTP46knT3vYp6OmxQ._5Y3c8IjGVJEpkvk4rXwNA.H7LHUpASrZB7zf4Wj5og2l7OBIvgLb2zEdpEC2NVcQPW612oS5jMnUucd58NGDoNoQssxfpjJXse5K-2V7KYEkRlYoVN39gqrIYNesnqPqTmU2VvpQsarjPVO62DgOes3R-qAKkytR3uB3VqNdYmgzdhWf-5tKc8XwLRa41kIsWo6cL7KvKzNUNS_a083X5IUje7Gh4yuH21RzAYVH3diuWDX9QcrdFMZ0BQxE1SpddG8QBcyGgQGvMsfju3Q2kEDrXuJZUSURRiOHdGOpHcYaTjHiGv-Q-NNVNtlwcMhGguMW93_YG8m5gI8VyW8Nq_2epq9YqZwK2YC7XO9CAMxQCaak1ol3ZqR5eo_RO6_ZIUe5NY6NjSWCLqijrAnARbpBUnaXY8CJN4_xRpg3zgqg.gFksDAHH--hQMyWZtXyOfQ\",\"uuid\":\"53141521-afc8-4a08-af0c-f0382aef43c1\"}" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: OK OK\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Date: Wed, 24 Aug 2022 22:20:37 GMT\r\n" +-> "X-Global-Transaction-ID: 379298106306a42455f1a6b2\r\n" +-> "x-content-type-options: nosniff\r\n" +-> "x-xss-protection: 1; mode=block\r\n" +-> "cache-control: no-cache, no-store, max-age=0, must-revalidate\r\n" +-> "pragma: no-cache\r\n" +-> "expires: 0\r\n" +-> "x-frame-options: DENY\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: POST\r\n" +-> "Set-Cookie: 20af66d9029740e40c713b05f443f86d=2bdc6d7099faf31789e088a0d77ea5cc; path=/; HttpOnly; Secure; SameSite=None\r\n" +-> "X-RateLimit-Limit: name=rate-limit,400;\r\n" +-> "X-RateLimit-Remaining: name=rate-limit,399;\r\n" +-> "Content-Encoding: gzip\r\n" +-> "\r\n" +-> xxxxxx some binary string xxxxx +Conn close diff --git a/test/unit/transcripts/alelo_purchase_scrubbed b/test/unit/transcripts/alelo_purchase_scrubbed new file mode 100644 index 00000000000..52290d93893 --- /dev/null +++ b/test/unit/transcripts/alelo_purchase_scrubbed @@ -0,0 +1,69 @@ +<- "POST /alelo/sandbox/captura-oauth-provider/oauth/token HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nHost: sandbox-api.alelo.com.br\r\nContent-Length: 158\r\n\r\n" +<- "grant_type=client_credentials&client_id=[FILTERED]&client_secret=[FILTERED]&scope=%2Fcapture" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: FAIL FAIL\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Cache-Control: private, no-store, no-cache, must-revalidate\r\n" +-> "Pragma: no-cache\r\n" +-> "Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: POST\r\n" +-> "Content-Encoding: gzip\r\n" +-> "\r\n" +-> xxxxxx some binary string xxxxx +-> Decoded Response Body: +-> { "token_type":"Bearer", "access_token":"[FILTERED]", "expires_in":86400, "consented_on":1666785813, "scope":"/capture" }opening connection to api.alelo.com.br:443... +starting SSL for sandbox-api.alelo.com.br:443... +SSL established, protocol: TLSv1.2, cipher: DHE-RSA-AES256-GCM-SHA384 +<- "GET /alelo/sandbox/capture/key?format=json HTTP/1.1\r\nAccept: application/json\r\nX-Ibm-Client-Id:[FILTERED]\r\nX-Ibm-Client-Secret:[FILTERED]\r\nAuthorization: Bearer [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: sandbox-api.alelo.com.br\r\n\r\n" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: OK OK\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Date: Wed, 24 Aug 2022 22:20:34 GMT\r\n" +-> "X-Global-Transaction-ID: 379298106306a42155f1a5f2\r\n" +-> "x-content-type-options: nosniff\r\n" +-> "x-xss-protection: 1; mode=block\r\n" +-> "cache-control: no-cache, no-store, max-age=0, must-revalidate\r\n" +-> "pragma: no-cache\r\n" +-> "expires: 0\r\n" +-> "x-frame-options: DENY\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: GET\r\n" +-> "Set-Cookie: 20af66d9029740e40c713b05f443f86d=2bdc6d7099faf31789e088a0d77ea5cc; path=/; HttpOnly; Secure; SameSite=None\r\n" +-> "X-RateLimit-Limit: name=rate-limit,400;\r\n" +-> "X-RateLimit-Remaining: name=rate-limit,399;\r\n" +-> "Content-Encoding: gzip\r\n" +-> xxxxxx some binary string xxxxx +-> Decoded Response Body: +-> {"uuid":"81346b5b-7ce0-4e89-b049-5488b7c8a2b6","format":"BASE64","publicKey":"[FILTERED]"}opening connection to api.alelo.com.br:443... +<- "POST /alelo/sandbox/capture/transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nX-Ibm-Client-Id:[FILTERED]\r\nX-Ibm-Client-Secret:[FILTERED]\r\nAuthorization: Bearer [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: sandbox-api.alelo.com.br\r\nContent-Length: 913\r\n\r\n" +<- "{\"token\":\"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.Sy8e0y387LVWcv0P7nwKtu7DHKWww4fCnCx68OnjhmM9sbhmZ9FTQwznGfnSljWFYxXmQPy2PnvtsdvMsEPp5jUANMKTpp4aAGfS_1x31UBuMeRQT5NMlFG8lhCwbPmNtrEOyB2fZUQBsmePpTtdzzAn1tj_hY7XC2bqGkPJ2zS6K2jXqdtkGWeLSMjEDRdyjDuQ_ybFx6uHfYcN_ioXcetUU_MJ1Ai3snfBU-150fCKTmY0SJ09tWMLCyYmvL416L_ha2UmY3tZm1zvpkyRQ612t88ZCEeUK-ZXBYp7FJT-KAAogG49G-Nadj3PSe8jQdxoIZTP46knT3vYp6OmxQ._5Y3c8IjGVJEpkvk4rXwNA.H7LHUpASrZB7zf4Wj5og2l7OBIvgLb2zEdpEC2NVcQPW612oS5jMnUucd58NGDoNoQssxfpjJXse5K-2V7KYEkRlYoVN39gqrIYNesnqPqTmU2VvpQsarjPVO62DgOes3R-qAKkytR3uB3VqNdYmgzdhWf-5tKc8XwLRa41kIsWo6cL7KvKzNUNS_a083X5IUje7Gh4yuH21RzAYVH3diuWDX9QcrdFMZ0BQxE1SpddG8QBcyGgQGvMsfju3Q2kEDrXuJZUSURRiOHdGOpHcYaTjHiGv-Q-NNVNtlwcMhGguMW93_YG8m5gI8VyW8Nq_2epq9YqZwK2YC7XO9CAMxQCaak1ol3ZqR5eo_RO6_ZIUe5NY6NjSWCLqijrAnARbpBUnaXY8CJN4_xRpg3zgqg.gFksDAHH--hQMyWZtXyOfQ\",\"uuid\":\"53141521-afc8-4a08-af0c-f0382aef43c1\"}" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Backside-Transport: OK OK\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: application/json\r\n" +-> "Date: Wed, 24 Aug 2022 22:20:37 GMT\r\n" +-> "X-Global-Transaction-ID: 379298106306a42455f1a6b2\r\n" +-> "x-content-type-options: nosniff\r\n" +-> "x-xss-protection: 1; mode=block\r\n" +-> "cache-control: no-cache, no-store, max-age=0, must-revalidate\r\n" +-> "pragma: no-cache\r\n" +-> "expires: 0\r\n" +-> "x-frame-options: DENY\r\n" +-> "Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Methods: POST\r\n" +-> "Set-Cookie: 20af66d9029740e40c713b05f443f86d=2bdc6d7099faf31789e088a0d77ea5cc; path=/; HttpOnly; Secure; SameSite=None\r\n" +-> "X-RateLimit-Limit: name=rate-limit,400;\r\n" +-> "X-RateLimit-Remaining: name=rate-limit,399;\r\n" +-> "Content-Encoding: gzip\r\n" +-> "\r\n" +-> xxxxxx some binary string xxxxx +Conn close