Repository: activemerchant/active_merchant (Ruby)

Issue:
Global Collect: Unsuccessful refunds show as successful
According to the Global Collect docs it's possible for a refund to be rejected, but it's not necessarily an error. In that case, Global Collect doesn't send an error response with an `errorId` field like [we check for](https://github.com/activemerchant/active_merchant/blob/master/lib/active_merchant/billing/gateways/global_collect.rb#L265) in the `success_from(response)`  method. A refund can fail/be rejected for other reasons and Global Collect will send a response such as-

```json
{
  "id": "00000022184000047564000-100001",
  "refundOutput" : {
    "amountOfMoney" : {
      "amount" : 627000,
      "currencyCode": "COP"
    },
    "references" : {
      "merchantReference": "17091GTgZmcC",
      "paymentReference" : "0"
    },
    "paymentMethod": "card",
    "cardRefundMethodSpecificOutput": {}
  },
  "status": "REJECTED",
  "statusOutput": {
    "isCancellable": false,
    "statusCategory": "UNSUCCESSFUL",
    "statusCode": 1850,
    "statusCodeChangeDateTime": "20170313230631"
  }
}
```

The larger issue is that we get false positives on refunds when we get the following response since the `success_from(response)` method is checking for the absence of the `errorId` field. So, in the `success_from` we should also check if the `status` is **not** `REJECTED`. See the [Global Collect refund docs](https://developer.globalcollect.com/documentation/api/server/#refunds) for more details.


Discussion:

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index a5ead18c729..1c78aabe623 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@
 * Qvalent: Add soft descriptor fields. Add authorize, capture, and void [davidsantoso]
 * Adyen: Add Adyen v18 gateway [adyenpayments] #2272
 * Pin: Add metadata optional field [shasum] #2363
+* GlobalCollect: Set REJECTED refunds as unsuccessful transactions [davidsantoso] #2365
 
 == Version 1.64.0 (March 6, 2017)
 * Authorize.net: Allow settings to be passed for CIM purchases [fwilkins] #2300
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
index 9a0b9142f62..1bef55122c5 100644
--- a/lib/active_merchant/billing/gateways/global_collect.rb
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -262,14 +262,18 @@ def content_type
       end
 
       def success_from(response)
-        !response["errorId"]
+        !response["errorId"] && response["status"] != "REJECTED"
       end
 
       def message_from(succeeded, response)
         if succeeded
           "Succeeded"
         else
-          response["errors"][0]["message"] || "Unable to read error message"
+          if errors = response["errors"]
+            errors.first.try(:[], "message")
+          else
+            "Unable to read error message"
+          end
         end
       end
 
@@ -283,7 +287,11 @@ def authorization_from(succeeded, response)
 
       def error_code_from(succeeded, response)
         unless succeeded
-          response["errors"][0]["code"] || "Unable to read error code"
+          if errors = response["errors"]
+            errors.first.try(:[], "code")
+          else
+            "Unable to read error code"
+          end
         end
       end
 


Test patch:
diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb
index 04043fdf4bf..1e4f7d35267 100644
--- a/test/unit/gateways/global_collect_test.rb
+++ b/test/unit/gateways/global_collect_test.rb
@@ -156,6 +156,14 @@ def test_failed_refund
     assert_failure response
   end
 
+  def test_rejected_refund
+    response = stub_comms do
+      @gateway.refund(@accepted_amount, '000000142800000000920000100001')
+    end.respond_with(rejected_refund_response)
+
+    assert_failure response
+  end
+
   def test_scrub
     assert @gateway.supports_scrubbing?
     assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
@@ -299,6 +307,10 @@ def failed_refund_response
     %({\n   \"errorId\" : \"1bd31e6a-39dd-4214-941a-088a320e0286\",\n   \"errors\" : [ {\n      \"code\" : \"1002\",\n      \"propertyName\" : \"paymentId\",\n      \"message\" : \"INVALID_PAYMENT_ID\"\n   } ]\n})
   end
 
+  def rejected_refund_response
+    %({\n   \"id\" : \"00000022184000047564000-100001\",\n   \"refundOutput\" : {\n      \"amountOfMoney\" : {\n         \"amount\" : 627000,\n         \"currencyCode\" : \"COP\"\n      },\n      \"references\" : {\n         \"merchantReference\" : \"17091GTgZmcC\",\n         \"paymentReference\" : \"0\"\n      },\n      \"paymentMethod\" : \"card\",\n      \"cardRefundMethodSpecificOutput\" : {\n      }\n   },\n   \"status\" : \"REJECTED\",\n   \"statusOutput\" : {\n      \"isCancellable\" : false,\n      \"statusCategory\" : \"UNSUCCESSFUL\",\n      \"statusCode\" : 1850,\n      \"statusCodeChangeDateTime\" : \"20170313230631\"\n   }\n})
+  end
+
   def successful_void_response
     %({\n   \"payment\" : {\n      \"id\" : \"000000142800000000920000100001\",\n      \"paymentOutput\" : {\n         \"amountOfMoney\" : {\n            \"amount\" : 100,\n            \"currencyCode\" : \"USD\"\n         },\n         \"references\" : {\n            \"paymentReference\" : \"0\"\n         },\n         \"paymentMethod\" : \"card\",\n         \"cardPaymentMethodSpecificOutput\" : {\n            \"paymentProductId\" : 1,\n            \"authorisationCode\" : \"OK1131\",\n            \"card\" : {\n               \"cardNumber\" : \"************7977\",\n               \"expiryDate\" : \"0917\"\n            },\n            \"fraudResults\" : {\n               \"fraudServiceResult\" : \"no-advice\",\n               \"avsResult\" : \"0\",\n               \"cvvResult\" : \"0\"\n            }\n         }\n      },\n      \"status\" : \"CANCELLED\",\n      \"statusOutput\" : {\n         \"isCancellable\" : false,\n         \"statusCode\" : 99999,\n         \"statusCodeChangeDateTime\" : \"20160317191526\"\n      }\n   }\n})
   end