omise / omise-android

Omise Android SDK
https://docs.opn.ooo
Other
50 stars 32 forks source link

Testing 3d secure on test account. #45

Closed hamzabinamin closed 6 years ago

hamzabinamin commented 6 years ago

Hi there, I recently got my app approved from the omise review team and they suggested that I should add 3d secure as well. They mentioned that they have activated 3d secure on my account. I have a few questions:

  1. How can I make sure that 3d secure is enabled on my test and live account? I don't see any information on the dashboard that states 3d secure is enabled? My omise account is waseem@globalngn.com, can you please check if its enabled on test and live accounts?
  2. I have read the flow of 3d secure and in my app when the authorization activity gets called, it just displays the return_uri webpage (after displaying the message "Please wait your payment is being processed"). There is no screen where the bank otp is requested to be entered? Is there any way to test that under test account?
  3. I retrieved the charge afterwards and saw that the paid field was set to true, does that mean the charge got authorized and 3d secure worked fine?
  4. Is there a way that I can keep the return uri empty and bring the user inside the native android app? I don't want to lead them to a webpage.
  5. In onActivityResult I'm receiving the result code of 0, why doesn't it return the result as true? How is it determining if the result is true or false?

@fred @sirn @chakrit @robinclart @SullysMustyRuby

chakrit commented 6 years ago

@hamzabinamin Let me investigate for 1. and 2. However, for these queries, I recommend to ask via our official support channel directly as we cannot reveal some account-specific information on public forums.

3) -> Yes paid=true means the charge is complete. 4) -> Not with empty return_uri but we have mechanism in the SDK to detect redirection to a specific URI and does the callback dance for you.

cc: @Namiii @nuxzero @pitiphong-p for 4 (how to) and 5 🙏

chakrit commented 6 years ago

For 4. you should always handle these at the web server level anyway as it is possible for the end-user to modify the return-uri and trick the app into thinking that you are paid.

nuxzero commented 6 years ago

For 5. Android used setResult(int, intent) to pass the result back to onActivityResult(int requestCode, int resultCode, Intent data). You can use CreditCardActivity.RESULT_CANCEL or CreditCardActivity.RESULT_OK to handle result back from startActivityForResult like this.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  switch (requestCode) {
    case REQUEST_CC:
      if (resultCode == CreditCardActivity.RESULT_CANCEL) {
        return;
      }

      Token token = data.getParcelableExtra(CreditCardActivity.EXTRA_TOKEN_OBJECT);
      // process your token here.

    default:
      super.onActivityResult(requestCode, resultCode, data);
  }
}
chakrit commented 6 years ago

@hamzabinamin your account is 3ds-enabled already.

hamzabinamin commented 6 years ago

@nuxzero @chakrit How can I test the otp scenario on the test account? For test account, the authorization activity processes payment and displays the return URL, there's no way to determine if the user entered correct otp and paid the payment. Also will the live account work with testing cards mentioned on your website?

This is how I am calling the authorization acitivty

public static int AUTHORIZING_PAYMENT_REQUEST_CODE = 111;

// customer card already saved, so calling the charge API, the charge API returns the authorized URL

  `CustomerCalls.chargeCustomer(PaymentFragment.this, customer.omiseID);`
if(success) {
 String authorizedURL = charge.get("Authorized_URL").getAsString();
showAuthorizingPaymentForm(authorizedURL);
}
public void showAuthorizingPaymentForm(String authorizedURL) {
        String[] array = {"https://www.mywebsite.com"};
        Intent intent = new Intent(getContext(), AuthorizingPaymentActivity.class);
        intent.putExtra(AuthorizingPaymentActivity.EXTRA_AUTHORIZED_URLSTRING, authorizedURL);
        intent.putExtra(AuthorizingPaymentActivity.EXTRA_EXPECTED_RETURN_URLSTRING_PATTERNS, array);
        startActivityForResult(intent, AUTHORIZING_PAYMENT_REQUEST_CODE);
    }
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        System.out.println("onActivityResult got called");
        if (requestCode == AUTHORIZING_PAYMENT_REQUEST_CODE && CreditCardActivity.resultCode == RESULT_OK) {
            String url = data.getStringExtra(AuthorizingPaymentActivity.EXTRA_RETURNED_URLSTRING);
            System.out.println("Result OK, returning URL: " + url);
        }
        else {
            System.out.println("Got in else, returning URL: " + resultCode);
        }
    }

In this case I always get result code as 0. Why is that?

Also I have noticed today that none of the testing card listed on (https://www.omise.co/api-testing) are working. On every card I am getting "Payment was rejected by the issuer or acquirer with no specific reason". All these cards were working fine before I submitted this ticket, is this because of 3DS?

nuxzero commented 6 years ago

@hamzabinamin 1) We don’t provide the real OTP scenario for test account however you can test the flow by setting the return_url and go through authorized_url comes with the charge data. Also, you can not use the card number on live mode too.

2) The code bug

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        System.out.println("onActivityResult got called");
        if (requestCode == AUTHORIZING_PAYMENT_REQUEST_CODE && CreditCardActivity.resultCode == RESULT_OK) {
            ...
    }

Follow your code, you should use resultCode in onActivityResult() not from CreditCardActivity.resultCode. And make sure you use RESULT_OK from android.app.Activity. The 0 code meant RESULT_CANCELED please make sure have correct request.

3) On the payment rejected error, please make sure you test with the test account’s key.

Hope this should solve your problem.

hamzabinamin commented 6 years ago

@nuxzero

  1. I was using the test card for successful charge. It's number is 4111-1111-1111-1111. I am also getting the transaction ID as null. All of these cards were working fine before 3ds got enabled.

  2. Is it possible that I can use real cards on test account without having the charge deducting money from the card? That way I can test the otp scenario I believe.

  3. Also I think I found out the problem, when authorization activity finishes processing the payment, it displays the URL given in return_url, but then I press the back button and I get the result code as 0.

Now the confusion is, when the return url is displayed, how do I determine if the otp was correct and payment as successful at that point? And then depending on the scenario return back to the app? Can you provide a code example to do that?

nuxzero commented 6 years ago
  1. Please check the status property of the charge. If the charge needs to be authorized with OTP then the status will be other status than successful which the charge may not have the transaction yet.

  2. No, the test account will be run in the contained environment which doesn’t connect to the bank. But you can test on the live account and refund after created charge.

  3. That right, onActivityResult() will return RESULT_CANCELED when press back.

how do I determine if the OTP was correct and payment as successful at that point? You need to have your backend to fetch the updated charge data from Omise and communicate back to your mobile client. The AuthorizingPaymentActivity will detect if the flow direct to url same as you sent AuthorizingPaymentActivity.EXTRA_AUTHORIZED_URLSTRING to AuthorizingPaymentActivity. Then will auto direct back to the place that you call startActivityForResult() with result RESUL_OK and AuthorizingPaymentActivity.EXTRA_RETURNED_URLSTRING. However by redirecting back to return URL doesn’t mean if the charge was succeeded or failed or anything other than the customer finished the authorization process. You still need to have you backend to fetch the updated status of charge and communicate back to your mobile client

For sample:


 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == MainActivity.AUTHORIZING_PAYMENT_REQUEST_CODE && resultCode == RESULT_OK) {
            String url = data.getStringExtra(AuthorizingPaymentActivity.EXTRA_RETURNED_URLSTRING);
            Toast.makeText(getApplicationContext(), url, Toast.LENGTH_LONG).show();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }```
hamzabinamin commented 6 years ago

@nuxzero Thanks for explaining the flow. I have one last confusion.

When the return URL is displayed, what trigger should be there that can close the authorization activity and send the result code of 1? Because after the payment gets processed, the authorization activity displays the return URL that I provided but it doesn't get closed by itself and if I press the back key it returns a result of 0.

pitiphong-p commented 6 years ago

@hamzabinamin the AuthorizingPaymentActivity will return the result when its web view was redirected to a URL that matches the expected URL patterns argument. Please check your expected URL patterns argument if it’s the prefix of the returned URL

hamzabinamin commented 6 years ago

@pitiphong-p thank you for providing that information. I see, that EXPECTED_URL_PATTERNS takes an array of URL strings, what will be the use case in which we might need more than one return URL?

pitiphong-p commented 6 years ago

@hamzabinamin You can have only 1 return URL per charge but the reason that EXPECTED_URL_PATTERNS is an array is for the case that you have multiple variant of URL patterns

hamzabinamin commented 6 years ago

@pitiphong-p Do you have an example for this?

pitiphong-p commented 6 years ago

An example for which case?

hamzabinamin commented 6 years ago

@pitiphong-p An example for the scenario where we might have multiple variants of URL patterns?

pitiphong-p commented 6 years ago

We don’t have any concrete example here. This feature is provided in case where a business requirement need to have multiple return URLs patterns. For example a business may have many lines of products and would like to have multiple returned URLs patterns for each product lines

hamzabinamin commented 6 years ago

@pitiphong-p While I was able to solve my initial problem and now I do get the successful result in onAcitvityResult. But I am not been able to retrieve the charge ID after the authorization process. The way my code is structured, I somehow need a way to pass some data(charge id, customer id etc) to onAcitivtyResult so that I can perform a call to my backend. I tried doing the following but its returning null for charge id

Intent intent = new Intent(getContext(), AuthorizingPaymentActivity.class);
        intent.putExtra(AuthorizingPaymentActivity.EXTRA_AUTHORIZED_URLSTRING, authorizedURL);
        intent.putExtra(AuthorizingPaymentActivity.EXTRA_EXPECTED_RETURN_URLSTRING_PATTERNS, array);
        intent.putExtra("Charge ID", "234");
startActivityForResult(intent, AUTHORIZING_PAYMENT_REQUEST_CODE);
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        System.out.println("onActivityResult got called");
        Utilities.hideHUD(hud);
        if (requestCode == AUTHORIZING_PAYMENT_REQUEST_CODE && resultCode == RESULT_OK) {
            String url = data.getStringExtra(AuthorizingPaymentActivity.EXTRA_RETURNED_URLSTRING);
            String chargeID =  data.getExtras().getString("Charge ID"); // null
            String chargeID2 =  data.getStringExtra("Charge ID"); // null
}
...
}

If I can't send data through intent, is there any other way?

pitiphong-p commented 6 years ago

@hamzabinamin We can't provide more information from our Activity. However the practice that many merchants do is that the return URL contains some information related to the order/charge which you can parse the returned URL and asks for related information from your server based on the parameter in the returned URL

hamzabinamin commented 6 years ago

@pitiphong-p sending get parameters in the return URL is safe?

How to place the charge id as parameter in the return url? The following doesn't seem to work;

$charge = OmiseCharge::create(array(
        'amount'   => $amount,
        'currency' => 'THB',
        'customer' => $omiseid,
        'card'     => $cardid,
        'return_uri' => 'http://www.buddiesmart.com'
    ));

$charge['return_uri'] = 'http://www.buddiesmart.com/chargeid=$charge['id']';

Any way I can access the charge id while the charge object is being created?

Something like the following:

$charge = OmiseCharge::create(array(
        'amount'   => $amount,
        'currency' => 'THB',
        'customer' => $omiseid,
        'card'     => $cardid,
        'return_uri' => 'http://www.buddiesmart.com/chargeid=$charge['id']'
    ));
pitiphong-p commented 6 years ago

You shouldn't send the Omise charge id back with the returned URL. The data that we recommend to put in the returned URL is a piece of data that you can look up back to your order/data in your system that is associated with an Omise charge. For example your order id or a piece of an order that unique for that order and you can look up for an order with that data.