github.com/Ingenico-ePayments/connect-sdk-go@v0.0.0-20240318153750-1f8cd329b9c9/Client_Payments_test.go (about) 1 package connectsdk 2 3 import ( 4 "net/url" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/Ingenico-ePayments/connect-sdk-go/communicator" 10 "github.com/Ingenico-ePayments/connect-sdk-go/communicator/communication" 11 "github.com/Ingenico-ePayments/connect-sdk-go/defaultimpl" 12 "github.com/Ingenico-ePayments/connect-sdk-go/domain/payment" 13 "github.com/Ingenico-ePayments/connect-sdk-go/errors" 14 "github.com/Ingenico-ePayments/connect-sdk-go/logging" 15 ) 16 17 func CheckSuccess(cv *TestConnection, resp payment.CreateResponse, err error) string { 18 if err != nil { 19 return "Error during request: " + err.Error() 20 } 21 if payment := resp.Payment; payment == nil || payment.ID == nil || *payment.ID != "000002000020142549460000100001" { 22 return "The ID of the payment is not equal to 000002000020142549460000100001" 23 } 24 if payment := resp.Payment; payment == nil || payment.Status == nil || *payment.Status != "PENDING_APPROVAL" { 25 return "The status of the payment is unequal to PENDING_APPROVAL" 26 } 27 return "" 28 } 29 30 func CheckRejected(cv *TestConnection, res payment.CreateResponse, err error) string { 31 if err == nil { 32 return "Error expected" 33 } 34 if dpe, ok := err.(*errors.DeclinedPaymentError); ok { 35 res := dpe.PaymentResult() 36 if res == nil { 37 return "PaymentResult is nil" 38 } 39 if res.Payment.ID == nil { 40 return "PaymentResult.ID is nil" 41 } 42 if *res.Payment.ID != "000002000020142544360000100001" { 43 return "PaymentResult.ID is not 000002000020142544360000100001" 44 } 45 if res.Payment.Status == nil { 46 return "PaymentResult.Status is nil" 47 } 48 if *res.Payment.Status != "REJECTED" { 49 return "PaymentResult.Status is not equal to " + "REJECTED" 50 } 51 } else { 52 return "Expected DeclinedPaymentError, but got different error" 53 } 54 estr := err.Error() 55 if !strings.Contains(estr, "payment '000002000020142544360000100001'") { 56 return "Error message does not contain" + "payment '000002000020142544360000100001'" 57 } 58 if !strings.Contains(estr, "status 'REJECTED'") { 59 return "Error message: " + estr + " does not contain " + "status 'REJECTED'" 60 } 61 if !strings.Contains(estr, rejectedJSON) { 62 return "Error message: " + estr + " does not contain response body" 63 } 64 return "" 65 } 66 67 func CheckInvalidRequest(cv *TestConnection, res payment.CreateResponse, err error) string { 68 if err == nil { 69 return "Error expected" 70 } 71 if _, ok := err.(*errors.ValidateError); !ok { 72 return "Expected ValidateError, but got different error" 73 } 74 estr := err.Error() 75 if !strings.Contains(estr, invalidRequestJSON) { 76 return "Error message: " + estr + " does not contain response body" 77 } 78 return "" 79 } 80 81 func CheckInvalidAuthorization(cv *TestConnection, res payment.CreateResponse, err error) string { 82 if err == nil { 83 return "Error expected" 84 } 85 if _, ok := err.(errors.APIError); !ok { 86 return "Expected APIError, but got different error" 87 } 88 estr := err.Error() 89 if !strings.Contains(estr, invalidAuthorizationJSON) { 90 return "Error message: " + estr + " does not contain response body" 91 } 92 return "" 93 } 94 95 func CheckReferenceError(cv *TestConnection, res payment.CreateResponse, err error) string { 96 if err == nil { 97 return "Error expected" 98 } 99 if _, ok := err.(*errors.ReferenceError); !ok { 100 return "Expected ReferenceError, but got different error" 101 } 102 estr := err.Error() 103 if !strings.Contains(estr, duplicateRequestJSON) { 104 return "Error message: " + estr + " does not contain response body" 105 } 106 return "" 107 } 108 109 func CheckIdempotenceError(cv *TestConnection, res payment.CreateResponse, err error) string { 110 if err == nil { 111 return "Error expected" 112 } 113 if ie, ok := err.(*errors.IdempotenceError); ok { 114 if ie.IdempotenceKey() != cv.idempotenceKey { 115 return "Returned wrong idempotenceKey" 116 } 117 } else { 118 return "Expected IdempotenceError, but got different error" 119 } 120 estr := err.Error() 121 if !strings.Contains(estr, duplicateRequestJSON) { 122 return "Error message: " + estr + " does not contain response body" 123 } 124 return "" 125 } 126 127 func CheckNotFound(cv *TestConnection, res payment.CreateResponse, err error) string { 128 if err == nil { 129 return "Error expected" 130 } 131 if nfe, ok := err.(*errors.NotFoundError); ok { 132 if nfe.InternalError() == nil { 133 return "Returned NotFoundError without internal error" 134 } 135 if _, ok := nfe.InternalError().(*errors.ResponseError); !ok { 136 return "NotFoundError has a different internal error than ResponseError" 137 } 138 if !strings.Contains(nfe.InternalError().Error(), notFoundHTML) { 139 return "Error message: " + nfe.InternalError().Error() + " does not contain response body" 140 } 141 } else { 142 return "Expected NotFoundError, but got different error" 143 } 144 145 return "" 146 } 147 148 func CheckMethodNotAllowed(cv *TestConnection, res payment.CreateResponse, err error) string { 149 if err == nil { 150 return "Error expected" 151 } 152 if nfe, ok := err.(*errors.CommunicationError); ok { 153 if nfe.InternalError() == nil { 154 return "Returned CommunicationError without internal error" 155 } 156 if _, ok := nfe.InternalError().(*errors.ResponseError); !ok { 157 return "CommunicationError has a different internal error than ResponseError" 158 } 159 if !strings.Contains(nfe.InternalError().Error(), methodNotAllowedHTML) { 160 return "Error message: " + nfe.InternalError().Error() + " does not contain response body" 161 } 162 } else { 163 return "Expected CommunicationError, but got different error" 164 } 165 166 return "" 167 } 168 169 var table = []TestConnection{ 170 // Tests that a non-failure response will not throw an exception. 171 {pendingApprovalJSON, 201, nil, "", CheckSuccess}, 172 173 //Tests that a failure response with a payment result will return a DeclinedPaymentError. 174 {rejectedJSON, 400, nil, "", CheckRejected}, 175 176 // Tests that a 400 failure response without a payment result will return a ValidateError. 177 {invalidRequestJSON, 400, nil, "", CheckInvalidRequest}, 178 179 // Tests that a 401 failure response without a payment result will return a APIError. 180 {invalidAuthorizationJSON, 401, nil, "", CheckInvalidAuthorization}, 181 182 // Tests that a 409 failure response with a duplicate request code but without an idempotence key will return a ReferenceError 183 {duplicateRequestJSON, 409, nil, "", CheckReferenceError}, 184 185 // Tests that a 409 failure response with a duplicate request code and an idempotence key will return a IdempotenceError. 186 {duplicateRequestJSON, 409, nil, "key", CheckIdempotenceError}, 187 188 // Tests that a 404 response with a non-JSON response will throw a NotFoundException. 189 {notFoundHTML, 404, []communication.Header{newHeader("content-type", "text/html")}, "", CheckNotFound}, 190 191 // Tests that a 405 response with a non-JSON response will throw a CommunicationException. 192 {methodNotAllowedHTML, 405, []communication.Header{newHeader("content-type", "text/html")}, "", CheckMethodNotAllowed}, 193 } 194 195 func newHeader(name, value string) communication.Header { 196 h, _ := communication.NewHeader(name, value) 197 return *h 198 } 199 200 type TestConnection struct { 201 body string 202 statusCode int 203 headers []communication.Header 204 idempotenceKey string 205 checkf CheckResult 206 } 207 208 const pendingApprovalJSON string = `{ 209 "creationOutput": { 210 "additionalReference": "00000200002014254946", 211 "externalReference": "000002000020142549460000100001" 212 }, 213 "payment": { 214 "id": "000002000020142549460000100001", 215 "paymentOutput": { 216 "amountOfMoney": { 217 "amount": 2345, 218 "currencyCode": "CAD" 219 }, 220 "references": { 221 "paymentReference": "0" 222 }, 223 "paymentMethod": "card", 224 "cardPaymentMethodSpecificOutput": { 225 "paymentProductId": 1, 226 "authorisationCode": "OK1131", 227 "card": { 228 "cardNumber": "************9176", 229 "expiryDate": "1220" 230 }, 231 "fraudResults": { 232 "fraudServiceResult": "error", 233 "avsResult": "X", 234 "cvvResult": "M" 235 } 236 } 237 }, 238 "status": "PENDING_APPROVAL", 239 "statusOutput": { 240 "isCancellable": true, 241 "statusCode": 600, 242 "statusCodeChangeDateTime": "20150331120036", 243 "isAuthorized": true 244 } 245 } 246 }` 247 248 const rejectedJSON = `{ 249 "errorId": "2c164323-20d3-4e9e-8578-dc562cd7506c-0000003c", 250 "errors": [ 251 { 252 "code": "21000020", 253 "requestId": "2001798", 254 "message": "VALUE **************** OF FIELD CREDITCARDNUMBER DID NOT PASS THE LUHNCHECK" 255 } 256 ], 257 "paymentResult": { 258 "creationOutput": { 259 "additionalReference": "00000200002014254436", 260 "externalReference": "000002000020142544360000100001" 261 }, 262 "payment": { 263 "id": "000002000020142544360000100001", 264 "paymentOutput": { 265 "amountOfMoney": { 266 "amount": 2345, 267 "currencyCode": "CAD" 268 }, 269 "references": { 270 "paymentReference": "0" 271 }, 272 "paymentMethod": "card", 273 "cardPaymentMethodSpecificOutput": { 274 "paymentProductId": 1 275 } 276 }, 277 "status": "REJECTED", 278 "statusOutput": { 279 "errors": [ 280 { 281 "code": "21000020", 282 "requestId": "2001798", 283 "message": "VALUE **************** OF FIELD CREDITCARDNUMBER DID NOT PASS THE LUHNCHECK" 284 } 285 ], 286 "isCancellable": false, 287 "statusCode": 100, 288 "statusCodeChangeDateTime": "20150330173151", 289 "isAuthorized": false 290 } 291 } 292 } 293 }` 294 295 const invalidRequestJSON string = `{ 296 "errorId": "2c164323-20d3-4e9e-8578-dc562cd7506c-00000058", 297 "errors": [ 298 { 299 "code": "21000120", 300 "requestId": "2001803", 301 "propertyName": "cardPaymentMethodSpecificInput.card.expiryDate", 302 "message": "paymentMethodSpecificInput.card.expiryDate (1210) IS IN THE PAST OR NOT IN CORRECT MMYY FORMAT" 303 } 304 ] 305 }` 306 307 const invalidAuthorizationJSON string = `{ 308 "errorId": "fbd8d914-c889-45d3-a396-9e0d9ff9db88-0000006f", 309 "errors": [ 310 { 311 "code": "9002", 312 "message": "MISSING_OR_INVALID_AUTHORIZATION" 313 } 314 ] 315 }` 316 317 const duplicateRequestJSON string = `{ 318 "errorId" : "75b0f13a-04a5-41b3-83b8-b295ddb23439-000013c6", 319 "errors" : [ { 320 "code" : "1409", 321 "message" : "DUPLICATE REQUEST IN PROGRESS", 322 "httpStatusCode" : 409 323 } ] 324 }` 325 326 const notFoundHTML string = "Not Found" 327 328 const methodNotAllowedHTML string = "Not Allowed" 329 330 type CheckResult func(cv *TestConnection, resp payment.CreateResponse, err error) string 331 332 func (t *TestConnection) CloseExpiredConnections() { 333 334 } 335 func (t *TestConnection) CloseIdleConnections(num time.Duration) { 336 337 } 338 func (t *TestConnection) Close() error { 339 return nil 340 } 341 func (t *TestConnection) Get(resourceURI url.URL, requestHeaders []communication.Header, respHandler communication.ResponseHandler) (interface{}, error) { 342 return nil, nil 343 } 344 func (t *TestConnection) Delete(resourceURI url.URL, requestHeaders []communication.Header, respHandler communication.ResponseHandler) (interface{}, error) { 345 return nil, nil 346 } 347 func (t *TestConnection) Put(resourceURI url.URL, requestHeaders []communication.Header, body string, respHandler communication.ResponseHandler) (interface{}, error) { 348 return nil, nil 349 } 350 func (t *TestConnection) PutMultipart(resourceURI url.URL, requestHeaders []communication.Header, body *communication.MultipartFormDataObject, respHandler communication.ResponseHandler) (interface{}, error) { 351 return nil, nil 352 } 353 func (t *TestConnection) Post(resourceURI url.URL, requestHeaders []communication.Header, body string, respHandler communication.ResponseHandler) (interface{}, error) { 354 return respHandler.Handle(t.statusCode, t.headers, strings.NewReader(t.body)) 355 } 356 func (t *TestConnection) PostMultipart(resourceURI url.URL, requestHeaders []communication.Header, body *communication.MultipartFormDataObject, respHandler communication.ResponseHandler) (interface{}, error) { 357 return nil, nil 358 } 359 func (t *TestConnection) DisableLogging() { 360 361 } 362 func (t *TestConnection) EnableLogging(l logging.CommunicatorLogger) { 363 364 } 365 366 func createRequestT() payment.CreateRequest { 367 p := payment.NewCreateRequest() 368 if p != nil { 369 return *p 370 } 371 panic("Cannot Create Request") 372 } 373 374 func TestPayment(t *testing.T) { 375 for _, tv := range table { 376 connectionMock := tv 377 apiEndpoint, _ := url.Parse("http://localhost") 378 // Error not possible 379 380 auth, _ := defaultimpl.NewDefaultAuthenticator(defaultimpl.V1HMAC, "test", "test") 381 // Error not possible 382 383 mp, err := communicator.NewMetaDataProviderBuilder("ingenico").Build() 384 if err != nil { 385 t.Fatalf("Cannot create MetaDataProvider: %s", err) 386 } 387 session, err := communicator.NewSession(apiEndpoint, &connectionMock, auth, mp) 388 if err != nil { 389 t.Fatal("Cannot create Session") 390 } 391 client, err := CreateClientFromSession(session) 392 if err != nil { 393 t.Fatal("Cannot create Client: ", err) 394 } 395 body := createRequestT() 396 cc := &CallContext{} 397 var resp payment.CreateResponse 398 if tv.idempotenceKey != "" { 399 cc.IdempotenceKey = tv.idempotenceKey 400 resp, err = client.Merchant("merchantId").Payments().Create(body, cc) 401 } else { 402 resp, err = client.Merchant("merchantId").Payments().Create(body, nil) 403 } 404 if str := tv.checkf(&tv, resp, err); str != "" { 405 t.Fatal(str) 406 } 407 } 408 }