Web Drop-in integration "advanced use case" redirect result - adyen

I'm trying to understand if it's possible to retrieve the redirectResult when the user is redirected to our redirectUrl to invoke the /payment/details API endpoint to speed up delivering virtual goods instead of waiting for the web hook (I'm aware it will not work for "async" payment methods).
Looking at https://docs.adyen.com/online-payments/web-drop-in/advanced-use-cases/redirect-result it should be possible, however, the returnUrl in CreateCheckoutSessionResponse returned from com.adyen.service.Checkout#sessions (adyen-java-api-library 17.2.0 - checkout api version v68) does not contain the aforementioned redirectResult param so the configuration we pass into the drop-in template is missing this data and does not seem to be available in the onPaymentCompleted callback either (only resultCode and sessionData).
#Override
public RedirectResponse handleRedirectToPartner(PaymentContext paymentContext) throws PartnerIntegrationException {
final Payment payment = paymentContext.getPayment();
final Amount amount = new Amount();
amount.setCurrency(payment.getCurrency().toUpperCase());
amount.setValue((long) payment.getPriceInCents());
final CreateCheckoutSessionRequest checkoutSessionRequest = new CreateCheckoutSessionRequest();
...
checkoutSessionRequest.setChannel(CreateCheckoutSessionRequest.ChannelEnum.WEB);
checkoutSessionRequest.setReturnUrl(getReturnUrl());
try {
CreateCheckoutSessionResponse checkoutSessionResponse = checkout().sessions(checkoutSessionRequest);
JSONObject params = new JSONObject();
params.put("environment", testMode ? "test" : "live");
params.put("clientKey", adyenClientKey);
JSONObject session = new JSONObject();
session.put("id", checkoutSessionResponse.getId());
session.put("sessionData", checkoutSessionResponse.getSessionData());
params.put("session", session);
params.put("urlKo", getFailureUrl());
params.put("urlOk", checkoutSessionResponse.getReturnUrl());
params.put("urlPending", getUrlPending(checkoutSessionResponse.getReturnUrl()));
return new RedirectResponse(RedirectResponse.Type.REDIRECT_CUSTOM_HTML_ADYEN, null, params);
} catch (ApiException | IOException e) {
throw new PartnerIntegrationException("Failed creating Adyen session", e);
}
}
protected Checkout checkout() {
return new Checkout(new Client(adyenApiKey, testMode ? Environment.TEST : Environment.LIVE,
testMode ? null : liveEndpointUrlPrefix));
}
(async () => {
let configuration = ${partnerJsonParameters?string};
configuration.onPaymentCompleted = function(result, component) {
console.info(result);
if (result.sessionData) {
console.info(result.sessionData);
}
if (result.resultCode) {
console.info(result.resultCode);
}
handleServerResponse(result, configuration);
};
configuration.onError = function(error, component) {
console.error(error, component);
handleServerResponse(result, configuration);
};
let checkout = await AdyenCheckout(configuration);
checkout.create('dropin').mount('#dropin-container');
})();

The sessions request does not perform the payment, but only initiates the payment session with all required parameters and configuration.
The Web drop-in takes care of 'talking' to the Adyen backend and eventually, the payment outcome can be obtained in the frontend using the onPaymentCompleted handler.
onPaymentCompleted: (result, component) => {
console.info("onPaymentCompleted: " + result.resultCode);
...
}
See Use the result code
On the server-side it is possible to get the payment result with a /payments/details call in addition to /sessions if needed.
// get redirectResult appended to the returnUrl
String redirectResult = request.getParameter("redirectResult");
var paymentDetails = new PaymentsDetailsRequest();
paymentDetails.setDetails(Collections.singletonMap("redirectResult", redirectResult));
// use paymentDetails() method
var paymentsDetailsResponse = checkout.paymentsDetails(paymentDetails);
String resultCode = paymentsDetailsResponse.getResultCode();
Note that a synchronous result is not always available, hence relying on the webhook is best.

I'm the (non-developer) colleague testing with PAL enable and sadly the delay is way too long with them as well. It is crucial for us to be able to get trustworthy authorisation and deliver the goods for the customers in (tens of) seconds, not minutes.
Is there any way to achieve fast and trustworthy credit card transactions with your web drop-in without notifications?
This is possible with your soon-to-be-obsoleted HPP integration and I find it unbelievable that you could have impaired, worsen the integration and user experience. So, there must be some kind of misunderstanding and communication breakdown somewhere (I hope). :)
Sorry, could not just comment as I don't have enough reputation...

Related

How to write Junit test case for postAbs method of WebClient in Vert.x?

I recently developed few Verticles from which I needed to make external API calls. To optimize the code, I moved code of calling APIs to one common Helper class. I am also passing Vertx instance from Verticle to Helper class. I am now trying to write Junit test case for the Helper class which is looking like below working code.
public class ServiceExecutionHelper{
public Promise<String> executeService(String requestURI, JsonObject input, MultiMap headers, Vertx vertx){
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(vertx);
client.postAbs(requestURI).timeout(60000).putHeaders(headers)
.sendJsonObject(input, ar -> {
if (ar.succeeded()) {
HttpResponse<Buffer> response = ar.result();
JsonObject serviceRespone = new JsonObject(response.bodyAsString());
JsonArray responseData = serviceRespone.getJsonArray("response_data");
if(responseData != null){
promise.complete("promise_completed");
}else{
promise.fail("promise_failed");
}
}
}
return promise;
}
}
Can anyone please guide how could I write test case for above code?
There are a million ways to do this depending on what exactly you need to test.
Here is one suggestion using junit5 and okhttp's MockWebServer. There are a lot of other conceivable alternatives.
The test verifies:
That you send a POST request using the payload contained in the input parameter.
That your implementation can handle a json response from the webserver.
That your implementation sends exactly one request to the webserver.
That your code completes the Promise if the server's response contains the key "promise_completed"
#ExtendWith(VertxExtension.class)
#Slf4j
public class ServiceExecutionHelperTest {
private ServiceExecutionHelper sut;
private MockWebServer mockWebServer;
#BeforeEach
public void setUp() {
sut = new ServiceExecutionHelper();
mockWebServer = new MockWebServer();
}
#Test
public void testExecuteService(final Vertx vertx, final VertxTestContext testContext) throws InterruptedException {
// given
final JsonObject requestPayload = new JsonObject().put("request", new JsonArray("[]"));
final JsonObject serverResponsePayload = new JsonObject().put("response_data", new JsonArray("[]"));
mockWebServer.enqueue(new MockResponse()
.setBody(serverResponsePayload.encode())
.setResponseCode(200)
.setHeader("content-type", "application/json"));
// when
final Promise<String> stringPromise =
sut.executeService(
mockWebServer.url("/").toString(),
requestPayload,
MultiMap.caseInsensitiveMultiMap(),
vertx);
// then
final RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertEquals("POST", recordedRequest.getMethod());
assertEquals("[text={\"request\":[]}]", recordedRequest.getBody().toString());
assertEquals(1, mockWebServer.getRequestCount());
testContext.assertComplete(stringPromise.future())
.map(val -> {
assertEquals("promise_completed", val);
testContext.completeNow();
return val;
})
.onComplete(onComplete -> {
assertTrue(onComplete.succeeded());
log.info("done");
})
.onFailure(onError -> Assertions.fail());
}
}
Some words from a TDD point of view
Before you start writing tests (and your actual code too, if you ask me), you should clarify your functional and technical requirements.
These should be the basis for your tests. And the tests should be a starting point to implement your code against.
So I cannot promise you that this example is a correct test for your use case. It compiles and and runs. But it should be verified and extended following your actual requirements.
Concerning test coverage
To keep this answer short and concise, I did not write the test to cover all possible branches. The case where the server responds without response_data (i.e. the else branch of your if-clause, where the Promise fails) is not tested.
To cover that case, a second test or the usage of a parameterized test would be necessary.

Dynamic parameter as part of request URI with Apache HttpCore

I am looking for existing solutions to match dynamic parameters with HttpCore. What I have in mind is something similar to constraints in ruby on rails, or dynamic parameters with sails (see here for example).
My objective is to define a REST API where I could easily match requests like GET /objects/<object_id>.
To give a little bit of context, I have an application that creates an HttpServer using the following code
server = ServerBootstrap.bootstrap()
.setListenerPort(port)
.setServerInfo("MyAppServer/1.1")
.setSocketConfig(socketConfig)
.registerHandler("*", new HttpHandler(this))
.create();
And the HttpHandler class that matches the requested URI and dispatches it to the corresponding backend method:
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) {
String method = request.getRequestLine().getMethod().toUpperCase(Locale.ROOT);
// Parameters are ignored for the example
String path = request.getRequestLine().getUri();
if(method.equals("POST") && path.equals("/object/add") {
if(request instanceof HttpEntityEnclosingRequest) {
addObject(((HttpEntityEnclosingRequest)request).getEntity())
}
[...]
For sure I can replace path.equals("/object/add") by something more sophisticated with RegEx to match these dynamic parameters, but before doing so I'd like to know if I am not reinventing the wheel, or if there is an existing lib/class I didn't see in the docs that could help me.
Using HttpCore is a requirement (it is already integrated in the application I am working on), I know some other libraries provide high-level routing mechanisms that support these dynamic parameters, but I can't really afford switching the entire server code to another library.
I am currently using httpcore 4.4.10, but I can upgrade to a newer version of this might help me.
At present HttpCore does not have a fully featured request routing layer. (The reasons for that are more political than technical).
Consider using a custom HttpRequestHandlerMapper to implement your application specific request routing logic.
final HttpServer server = ServerBootstrap.bootstrap()
.setListenerPort(port)
.setServerInfo("Test/1.1")
.setSocketConfig(socketConfig)
.setSslContext(sslContext)
.setHandlerMapper(new HttpRequestHandlerMapper() {
#Override
public HttpRequestHandler lookup(HttpRequest request) {
try {
URI uri = new URI(request.getRequestLine().getUri());
String path = uri.getPath();
// do request routing based on the request path
return new HttpFileHandler(docRoot);
} catch (URISyntaxException e) {
// Provide a more reasonable error handler here
return null;
}
}
})
.setExceptionLogger(new StdErrorExceptionLogger())
.create();

GET,POST,PUT Which should be used for receiving a json object to my rest api and send a json object in response?

I'm writing my first web API using MVC.
I'm aware that POST and PUT are usually implemented to define the HTTP methods for inserting or updating a database behind an API. But all I want to do is receive a JSON object from the caller, do some processing then return another JSON object in response. No database is involved.
I've tested using both POST and GET in my API controller method and they both work ok but which http method should I actually be using for best practice?
eg
public IHttpActionResult Get(ApiEquipment equipment)
{
// Convert equipment to a compatible buffer
return Ok(new ConvertToBuffer(equipment));
}
GET is useful for SAFE(*) operations where you do not need to pass many parameters to the server - all of the parameters must be in the URL as GET operations do not pass data in the HTTP body.
POST is useful for UNSAFE(*) operations or operations where you need to pass large amounts of data to the server as the data can be passed in the HTTP body.
Use GET for simple queries and calculations with few parameters. Use POST for large payloads or operations that change server state (such as updating something or performing complex bussiness operations).
See the HTTP method definitions in RFC 7231 for more in-depth information.
(*) SAFE operations are operations that do not change (visible) server state. UNSAFE operations do change (visible) server state.
GET
It seems that your just want to retrieve data in a meaningful representation (response after processing) from the raw data (request from the caller).
There is no modification / insertion of data, so GET should be use.
The POST verb seems to be what you want.
If I understand correctly, you want to send a JSON from the client to server. Then the server will modify the JSON and return it to the client.
In REST APIs, POST is commonly used to create a new resource. But it's also a catch-all verb for operations that should not be executed using the other methods.
For more details on using POST to invoke arbitrary processing, have a look at this answer.
I would suggest you to use 'HTTPPOST' if you require to process your JSON object else useGETmethod.
Consider this example for using HttpPost method since I process the JSON object to get some info from database.
[HttpPost]
public IHttpActionResult Masters([FromBody]Download_Config_UserInfo Info)
{
List<TestMaster> testMaster = null;
ResponseValidation objValidation = new ResponseValidation();
try
{
#region Validation
objValidation = base.ValidateRequest(Info);
if (!objValidation.isValid)
return base.JsonErrorResult(this.MasterName, objValidation.ErrorCode, objValidation.ErrorDesc);
#endregion
#region Initialization
this.Initialization();
#endregion
#region Functionality
//testMaster = this.GetTestMaster();
testMaster = this.GetTestDateMaster();
if (testMaster == null)
return base.JsonErrorResult(this.MasterName, "E19", "Data Not Available");
var result = (from a in testMaster
select new object[]
{
a.TestId,
a.TestName
});
#endregion
return base.JsonResult(this.MasterName, this.Fields, result);
}
catch (Exception ex)
{
loggerService.Error(Info.ToJSON(), this.MasterName, ex);
return base.JsonErrorResult(this.MasterName, "E20", "Internal Server Error", ex.Message + "_" + ex.StackTrace);
}
finally
{
testMaster = null; objValidation = null; base.UserMaster = null; base.UserPositionMapping = null;
}
}
#endregion
#region Functionality
[NonAction]
public List<TestMaster> GetTestMaster()
{
List<ADM_Retailer> testMaster = null;
try
{
testMaster = this.GetTest();
if (testMaster == null || (testMaster.Count == 0)) { return null; }
string weekNo = base.GetWeekNumber();
return (from a in testMaster
select new TestMaster
{
TestId = a.ARTR_Id,
TestName = a.ARTR_Name,
}).ToList();
}
finally { }
}

What's the best way to migrate to ServiceStack authentication framework when stuck with my_aspnet_* tables

I'm not quite ready to change up all my user/auth tables from the MySQL user/roles/profile provider format, but am moving off of MVC to ServiceStack.
Is there a pre-built IUserAuthRespository and/or CredentialsAuthProvider somewhere that can be used, or do I need to build one to provide this mapping?
If I need to build one, I assume implementing at the IUserAuthRepository level is the cleanest? Is there a minimum set of methods required to implement basic login/logout (and administrative "switch user" impersonation) functionality?
I tried implementing a custom CredentialsAuthProvider, which seems to work, but I'm unable to get local posts for impersonation to use the proper provider. Looking for a solution to that, I realized that maybe its better to implement the repository instead.
EDIT:
My current registration of the custom auth provider is:
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[]
{
container.Resolve<MySqlCredentialsAuthProvider>() //HTML Form post of UserName/Password credentials
}));
And calling code for the local post to the AuthenticateService is:
[RequiredRole(SystemRoles.Administrator)]
public object Any(ImpersonateUser request)
{
using (var service = base.ResolveService<AuthenticateService>()) //In Process
{
//lets us login without a password if we call it internally
var result = service.Post(new Authenticate
{
provider = AuthenticateService.CredentialsProvider,
UserName = request.Username,
//Password = "should-not-matter-since-we-are-posting-locally"
});
return result;
}
}
Integrating with existing User Auth tables
If you want to use your existing User/Auth tables, the easiest solution is to ignore the UserAuth repositories and implement a Custom CredentialsAuthProvider that looks at your existing database tables to return whether their Authentication attempt was successful.
Implement OnAuthenticated() to populate the rest of your typed IAuthSession from your database, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//Alternatively avoid built-in behavior and explicitly save session with
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
Importing existing User Auth tables
If you want to import them into an OrmLite User Auth tables, you would configure to use the OrmLiteAuthRepository in your AppHost:
//Register to use MySql Dialect Provider
container.Register<IDbConnectionFactory>(
new OrmLiteConnectionFactory(dbConnString, MySqlDialect.Provider));
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), //Use your own typed Custom UserSession type
new IAuthProvider[] {
//HTML Form post of UserName/Password credentials
new CredentialsAuthProvider()
}));
//Tell ServiceStack you want to persist User Info in the registered MySql DB above
container.Register<IUserAuthRepository>(c =>
new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>()));
//Resolve instance of configured IUserAuthRepository
var userAuth = container.Resolve<IUserAuthRepository>();
//Create any missing UserAuth RDBMS tables
authRepo.InitSchema();
Then to import your data you can use the above MySQL DB connection to select from your existing tables then use the IUserAuthRepository to create new Users.
// Open DB Connection to RDBMS
using (var db = container.Resolve<IDbConnectionFactory>().Open())
{
//Example of fetching old Users out of a custom table (use your table instead)
var oldUsers = db.Select<OldUserInfo>();
// Clear existing UserAuth tables if you want to replay this import
//db.DeleteAll<UserAuthDetails>();
//db.DeleteAll<UserAuth>();
//Go through and create new User Accounts using Old User Info
foreach (var oldUser in oldUsers)
{
//Create New User Info from Old Info
var newUser = new UserAuth {
UserName = oldUser.UserName,
Email = oldUser.Email,
//...
};
//Create New User Account with oldUser Password
authRepo.CreateUserAuth(newUser, oldUser.Password);
}
}
After this you'll have new User Accounts from your old User Info which you can sign in with.

How to count all HTTP requests sent, retries in?

Some use cases require being able to count the requests sent by the Apache API. For example, when massively requesting a web API, which API requires an authentication through an API key, and which TOS limits the requests count in time for each key.
Being more specific on the case, I'm requesting https://domain1/fooNeedNoKey, and depending on its response analyzed data, I request https://domain2/fooNeedKeyWithRequestsCountRestrictions. All sends of those 1-to-2-requests sequences, are performed through a single org.apache.http.impl.client.FutureRequestExecutionService.
As of now, depending on org.apache.httpcomponents:httpclient:4.3.3, I'm using those API elements:
org.apache.http.impl.client.FutureRequestExecutionService, to perform multi-threaded HTTP requests. It offers time metrics (how much time did an HTTP thread took until terminated), but no requests counter metrics
final CloseableHttpClient httpClient = HttpClients.custom()
// the auto-retry feature of the Apache API will retry up to 5
// times on failure, being also allowed to send again requests
// that were already sent if necessary (I don't really understand
// the purpose of the second parameter below)
.setRetryHandler(new StandardHttpRequestRetryHandler(5, true))
// for HTTP 503 'Service unavailable' errors, also retrying up to
// 5 times, waiting 500ms between each retry. Guessed is that those
// 5 retries are part of the previous "global" 5 retries setting.
// The below setting, when used alone, would allow to only enable
// retries for HTTP 503, or to get a greater count of retries for
// this specific error
.setServiceUnavailableRetryStrategy(new DefaultServiceUnavailableRetryStrategy(5, 500))
.build();, which customizes the Apache API retry behavior
Getting back to the topic :
A request counter could be created by extending the Apache API retry-related classes quoted before
Alternatively, an Apache API support unrelated ticket tends to indicate this requests-counter metrics could be available and forwarded out of the API, into Java NIO
Edit 1:
Looks like the Apache API won't permit this to be done.
Quote from the inside of the API, RetryExec not beeing extendable in the API code I/Os:
package org.apache.http.impl.execchain;
public class RetryExec implements ClientExecChain {
..
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
..
for (int execCount = 1;; execCount++) {
try {
return this.requestExecutor.execute(route, request, context, execAware);
} catch (final IOException ex) {
..
if (retryHandler.retryRequest(ex, execCount, context)) {
..
}
..
}
}
The 'execCount' variable is the needed info, and it can't be accessed since it's only locally used.
As well, one can extend 'retryHandler', and manually count requests in it, but 'retryHandler.retryRequest(ex, execCount, context)' is not provided with the 'request' variable, making it impossible to know on what we're incrementing a counter (one may only want to increment the counter for requests sent to a specific domain).
I'm out of Java ideas for it. A 3rd party alternative: having the Java process polling a file on disk, managed by a shell script counting the desired requests. Sure it will make a lot of disk read-accesses and will be a hardware killer option.
Ok, the work around was easy, the HttpContext class of the API is intended for this:
// optionnally, in case your HttpCLient is configured for retry
class URIAwareHttpRequestRetryHandler extends StandardHttpRequestRetryHandler {
public URIAwareHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled)
{
super(retryCount, requestSentRetryEnabled);
}
#Override
public boolean retryRequest(final IOException exception, final int executionCount, final HttpContext context)
{
final boolean ret = super.retryRequest(exception, executionCount, context);
if (ret) {
doForEachRequestSentOnURI((String) context.getAttribute("requestURI"));
}
return ret;
}
}
// optionnally, in addition to the previous one, in case your HttpClient has specific settings for the 'Service unavailable' errors retries
class URIAwareServiceUnavailableRetryStrategy extends DefaultServiceUnavailableRetryStrategy {
public URIAwareServiceUnavailableRetryStrategy(final int maxRetries, final int retryInterval)
{
super(maxRetries, retryInterval);
}
#Override
public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context)
{
final boolean ret = super.retryRequest(response, executionCount, context);
if (ret) {
doForEachRequestSentOnURI((String) context.getAttribute("requestURI"));
}
return ret;
}
}
// main HTTP querying code: retain the URI in the HttpContext to make it available in the custom retry-handlers code
httpContext.setAttribute("requestURI", httpGET.getURI().toString());
try {
httpContext.setAttribute("requestURI", httpGET.getURI().toString());
httpClient.execute(httpGET, getHTTPResponseHandlerLazy(), httpContext);
// if request got successful with no need of retries, of if it succeeded on the last send: in any cases, this is the last query sent to server and it got successful
doForEachRequestSentOnURI(httpGET.getURI().toString());
} catch (final ClientProtocolException e) {
// if request definitively failed after retries: it's the last query sent to server, and it failed
doForEachRequestSentOnURI(httpGET.getURI().toString());
} catch (final IOException e) {
// if request definitively failed after retries: it's the last query sent to server, and it failed
doForEachRequestSentOnURI(httpGET.getURI().toString());
} finally {
// restoring the context as it was initially
httpContext.removeAttribute("requestURI");
}
Solved.