Introduction

SDK

Lo primero que tenemos que hacer es agregar el SDK de mercado pago a nuestro proyecto. El SDK de MercadoPago nos provee de muchos componentes “prefabricados” que hacen que utilizar MP sea más fácil, además de varios ejemplos de como usarlos. El SDK es una librería, y se puede ver en https://github.com/mercadopago/sdk-android. Es open source, así que se puede customizar según las necesidades que se tengan.

Para agregar el SDK a nuestro proyecto, solo hace falta incluirlo como dependencia:

dependencies {
    // MercadoPago SDK and dependencies
    compile ('com.mercadopago:android-sdk:0.9.9@aar') { transitive = true }
}

A empezar a usarlo

MercadoPago pide que el usuario seleccione el método de pago (la tarjeta de crédito con la cual se va a pagar), para ello nos provee de una activity con todas las tarjetas de crédito disponibles. Lo unico que tenemos que hacer es invocarla y luego manejar su respuesta con un onActivityResult.

// Set the supported payment method types
protected List<string> mSupportedPaymentTypes = new ArrayList<string>(){
    add("credit_card");
    add("debit_card");
    add("prepaid_card");
};


public void getPaymentMethods() {
    new MercadoPago.StartActivityBuilder()
            .setActivity(this)
            .setPublicKey(yourPublicKey)
            .setSupportedPaymentTypes(mSupportedPaymentTypes)
            .startPaymentMethodsActivity();
}

// Process user's choice
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == MercadoPago.PAYMENT_METHODS_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {

            // Set payment method
            PaymentMethod paymentMethod = JsonUtil.getInstance().fromJson(data.getStringExtra("paymentMethod"), PaymentMethod.class);

            // TODO: here call card activity
        }
    }
}

Ahora hay que obtener la información de la tarjeta de crédito seleccionada. Para ello hay que crear una nueva activity (CardActivity) y se puede combinar con el layout activity_card.xml (del SDK) para así obtener todo el formulario de carga de datos de la tarjeta ya implementado.

En el onCreate debemos settear el objeto MercadoPago con la public key que identifica a la app dentro de MP:

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_card);

    // Init MercadoPago object with public key
    mMercadoPago = new MercadoPago.Builder()
                .setContext(mActivity)
                .setPublicKey(mMerchantPublicKey)
                .build();
    // Get identification types
    getIdentificationTypesAsync();

}

Dependiendo del país en el que el usuario de la app este pagando, se debe pedir tipo y numero de documento. Para ello, MP ofrece una llamada a la api. Nosotros debemos invocarla (en el onCreate) y manejar los resultados, de esta manera:

private void getIdentificationTypesAsync() {

    LayoutUtil.showProgress(mActivity);

    mMercadoPago.getIdentificationTypes(new Callback<List<IdentificationType>>() {
        @Override
        public void success(List<IdentificationType> identificationTypes, Response response) {

            mIdentificationType
            .setAdapter(new IdentificationTypesAdapter(mActivity, identificationTypes));

            // Set form "Go" button
            setFormGoButton(mIdentificationNumber);

            LayoutUtil.showRegularLayout(mActivity);
        }

        @Override
        public void failure(RetrofitError error) {

            if (error.getResponse().getStatus() == 404) {

                // No identification type for this country
                mIdentificationLayout.setVisibility(View.GONE);

                // Set form "Go" button
                setFormGoButton(mCardHolderName);

                LayoutUtil.showRegularLayout(mActivity);

            } else {

                MercadoPagoUtil.finishWithApiException(mActivity, error);
            }
        }
    });
}

Para estar seguros de que la información ingresada por el usuario es válida, debemos validar el formulario de carga. Para ello, debemos crear un metodo que valide todos los campos y se llame antes del submit del formulario.

private boolean validateForm(CardToken cardToken) {

    boolean result = true;
    boolean focusSet = false;

    // Validate card number
    try {
        validateCardNumber(cardToken);
        mCardNumber.setError(null);
    }
    catch (Exception ex) {
        mCardNumber.setError(ex.getMessage());
        mCardNumber.requestFocus();
        result = false;
        focusSet = true;
    }
    // Validate security code
    try {
        validateSecurityCode(cardToken);
        mSecurityCode.setError(null);
    }
    catch (Exception ex) {
        mSecurityCode.setError(ex.getMessage());
        if (!focusSet) {
            mSecurityCode.requestFocus();
            focusSet = true;
        }
        result = false;
    }
    // Validate expiry date
    if (!cardToken.validateExpiryDate()) {
        mExpiryDate.setError(getString(com.mercadopago.R.string.invalid_field));
        result = false;
    } else {
        mExpiryDate.setError(null);
    }
    // Validate card holder name
    if (!cardToken.validateCardholderName()) {
        mCardHolderName.setError(getString(com.mercadopago.R.string.invalid_field));
        if (!focusSet) {
            mCardHolderName.requestFocus();
            focusSet = true;
        }
        result = false;
    } else {
        mCardHolderName.setError(null);
    }
    // Validate identification number
    if (getIdentificationType() != null) {
        if (!cardToken.validateIdentificationNumber()) {
            mIdentificationNumber.setError(getString(com.mercadopago.R.string.invalid_field));
            if (!focusSet) {
                mIdentificationNumber.requestFocus();
            }
            result = false;
        } else {
            mIdentificationNumber.setError(null);
        }
    }

    return result;
}

Ahora hay que crear un cardToken (de forma segura) para identificar el pago (es un identificador unico de la tarjeta en mercado pago).

public void submitForm(View view) {

    LayoutUtil.hideKeyboard(mActivity);

    // Set card token
    CardToken cardToken = new CardToken(getCardNumber(), 
                        getMonth(), 
                        getYear(), 
                        getSecurityCode(), 
                        getCardHolderName(),
                            getIdentificationTypeId(getIdentificationType()),
                        getIdentificationNumber());


    if (validateForm(cardToken)) {
        // Create token
        createTokenAsync(cardToken);
    }
}

private void createTokenAsync(CardToken cardToken) {

    LayoutUtil.showProgress(mActivity);

    mMercadoPago.createToken(cardToken, new Callback<Token>() {
        @Override
        public void success(Token token, Response response) {

            Intent returnIntent = new Intent();
            setResult(RESULT_OK, returnIntent);
            returnIntent.putExtra("token", token.getId());
            returnIntent.putExtra("paymentMethod", JsonUtil.getInstance().toJson(mPaymentMethod));
            finish();
        }

        @Override
        public void failure(RetrofitError error) {

            MercadoPagoUtil.finishWithApiException(mActivity, error);
        }
    });
}

Ahora estamos listos para mandarle la info al server. Asi que luego de crear el cardToken debemos hacer la request al server.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    if (requestCode == MercadoPago.PAYMENT_METHODS_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {

          ...
        }
    } else if (requestCode == ExamplesUtils.CARD_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {

            // Create payment
            Server.createPayment(this, 
                    data.getStringExtra("token"),
                    1, 
                    null,
                jsonUtil.getInstance().fromJson(data.getStringExtra("paymentMethod"), PaymentMethod.class)
                    , null);

        } else {

            ...
        }
    }
}

Si todo salió bien al realizar el pago, ya se puede mostrar la pantalla final de éxito:

Payment payment = gson.fromJson(serializedPayment, Payment.class);
new MercadoPago.StartActivityBuilder()
        .setActivity(activity)
        .setPayment(payment)
        .setPaymentMethod(paymentMethod)
        .startCongratsActivity();

La forma rápida

Para tener andando MercadoPago de forma rápida hay que: 1. Agregar el SDK. 2. Agregar la clase CardActivity. 3. Agregar la clase Utils (para no ensuciar la activity). 4. Agregar el manejo de MercadoPago en la activity donde se lo llama.

1- Agregar el SDK

Seguir los pasos que se indican al inicio del documento.

2- Agregar la clase CardActivity

Crear la clase CardActivity.java (se recomienda que sea dentro del package mercadopago para que asi quede bien diferenciado) que contenga lo siguiente:

public class CardActivity extends AppCompatActivity implements CustomDatePickerDialog.DatePickerDialogListener {

    private PaymentMethod paymentMethod;

    private EditText cardNumber;
    private EditText securityCode;
    private Button expiryDate;
    private EditText cardHolderName;
    private EditText identificationNumber;
    private Spinner identificationType;
    private RelativeLayout identificationLayout;

    private CardToken cardToken;
    private Calendar selectedExpiryDate;
    private int expiryMonth;
    private int expiryYear;

    private Activity activity;
    private String exceptionOnMethod;
    private MercadoPago mercadoPago;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_card);
        activity = this;
        String mMerchantPublicKey = this.getIntent().getStringExtra("merchantPublicKey");
        cardNumber = (EditText) findViewById(R.id.cardNumber);
        securityCode = (EditText) findViewById(R.id.securityCode);
        cardHolderName = (EditText) findViewById(R.id.cardholderName);
        identificationNumber = (EditText) findViewById(R.id.identificationNumber);
        identificationType = (Spinner) findViewById(R.id.identificationType);
        identificationLayout = (RelativeLayout) findViewById(R.id.identificationLayout);
        expiryDate = (Button) findViewById(R.id.expiryDateButton);
        mercadoPago = new MercadoPago.Builder()
                .setContext(activity)
                .setPublicKey(mMerchantPublicKey)
                .build();
        setIdentificationNumberKeyboardBehavior();
        setErrorTextCleaner(cardHolderName);
        paymentMethod = JsonUtil.getInstance().fromJson(this.getIntent().getStringExtra("paymentMethod"), PaymentMethod.class);
        if (paymentMethod.getId() != null) {
            ImageView pmImage = (ImageView) findViewById(com.mercadopago.R.id.pmImage);
            pmImage.setImageResource(MercadoPagoUtil.getPaymentMethodIcon(this, paymentMethod.getId()));
        }
        getIdentificationTypesAsync();
    }

    @Override
    public void onBackPressed() {
        Intent returnIntent = new Intent();
        returnIntent.putExtra("backButtonPressed", true);
        setResult(RESULT_CANCELED, returnIntent);
        finish();
    }

    @Override
    public void onDateSet(DialogFragment dialog, int month, int year) {
        expiryMonth = month;
        expiryYear = year;
        selectedExpiryDate = Calendar.getInstance();
        selectedExpiryDate.set(year, month - 1, Calendar.DAY_OF_WEEK);
        String monthString = month < 10 ? "0" + month : Integer.toString(month);
        String yearString = Integer.toString(year).substring(2);
        expiryDate.setText(new StringBuilder().append(monthString).append(" / ").append(yearString));
        if (CardToken.validateExpiryDate(expiryMonth, expiryYear)) {
            expiryDate.setError(null);
        } else {
            expiryDate.setError(getString(com.mercadopago.R.string.invalid_field));
        }
    }

    public void refreshLayout(View view) {
        if (exceptionOnMethod.equals("getIdentificationTypesAsync")) {
            getIdentificationTypesAsync();
        } else if (exceptionOnMethod.equals("createTokenAsync")) {
            createTokenAsync();
        }
    }

    public void popExpiryDate(View view) {
        DialogFragment newFragment = new CustomDatePickerDialog();
        Bundle args = new Bundle();
        args.putSerializable(CustomDatePickerDialog.CALENDAR, selectedExpiryDate);
        newFragment.setArguments(args);
        newFragment.show(getFragmentManager(), null);
    }

    public void submitForm(View view) {
        LayoutUtil.hideKeyboard(activity);
        cardToken = new CardToken(getCardNumber(), getMonth(), getYear(), getSecurityCode(), getCardHolderName(),
                getIdentificationTypeId(getIdentificationType()), getIdentificationNumber());
        if (validateForm(cardToken)) {
            createTokenAsync();
        }
    }

    private boolean validateForm(CardToken cardToken) {
        boolean result = true;
        boolean focusSet = false;

        try {
            validateCardNumber(cardToken);
            cardNumber.setError(null);
        } catch (Exception ex) {
            cardNumber.setError(ex.getMessage());
            cardNumber.requestFocus();
            result = false;
            focusSet = true;
        }

        try {
            validateSecurityCode(cardToken);
            securityCode.setError(null);
        } catch (Exception ex) {
            securityCode.setError(ex.getMessage());
            if (!focusSet) {
                securityCode.requestFocus();
                focusSet = true;
            }
            result = false;
        }

        if (!cardToken.validateExpiryDate()) {
            expiryDate.setError(getString(com.mercadopago.R.string.invalid_field));
            result = false;
        } else {
            expiryDate.setError(null);
        }

        if (!cardToken.validateCardholderName()) {
            cardHolderName.setError(getString(com.mercadopago.R.string.invalid_field));
            if (!focusSet) {
                cardHolderName.requestFocus();
                focusSet = true;
            }
            result = false;
        } else {
            cardHolderName.setError(null);
        }

        if (getIdentificationType() != null) {
            if (!cardToken.validateIdentificationNumber()) {
                identificationNumber.setError(getString(com.mercadopago.R.string.invalid_field));
                if (!focusSet) {
                    identificationNumber.requestFocus();
                }
                result = false;
            } else {
                identificationNumber.setError(null);
            }
        }
        return result;
    }

    protected void validateCardNumber(CardToken cardToken) throws Exception {
        cardToken.validateCardNumber(this, paymentMethod);
    }

    protected void validateSecurityCode(CardToken cardToken) throws Exception {
        cardToken.validateSecurityCode(this, paymentMethod);
    }

    private void getIdentificationTypesAsync() {
        LayoutUtil.showProgressLayout(activity);
        mercadoPago.getIdentificationTypes(new Callback<List<IdentificationType>>() {
            @Override
            public void success(List<IdentificationType> identificationTypes, Response response) {
                identificationType.setAdapter(new IdentificationTypesAdapter(activity, identificationTypes));
                setFormGoButton(identificationNumber);
                LayoutUtil.showRegularLayout(activity);
            }

            @Override
            public void failure(RetrofitError error) {
                if ((error.getResponse() != null) && (error.getResponse().getStatus() == 404)) {
                    identificationLayout.setVisibility(View.GONE);
                    setFormGoButton(cardHolderName);
                    LayoutUtil.showRegularLayout(activity);
                } else {
                    exceptionOnMethod = "getIdentificationTypesAsync";
                    ApiUtil.finishWithApiException(activity, error);
                }
            }
        });
    }

    private void createTokenAsync() {
        LayoutUtil.showProgressLayout(activity);
        mercadoPago.createToken(cardToken, new Callback<Token>() {
            @Override
            public void success(Token token, Response response) {
                Intent returnIntent = new Intent();
                setResult(RESULT_OK, returnIntent);
                returnIntent.putExtra("token", token.getId());
                returnIntent.putExtra("paymentMethod", JsonUtil.getInstance().toJson(paymentMethod));
                finish();
            }

            @Override
            public void failure(RetrofitError error) {
                exceptionOnMethod = "createTokenAsync";
                ApiUtil.finishWithApiException(activity, error);
            }
        });
    }

    private void setFormGoButton(final EditText editText) {
        editText.setOnKeyListener(new View.OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // If the event is a key-down event on the "enter" button
                if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
                        (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    submitForm(v);
                }
                return false;
            }
        });
    }

    private void setIdentificationNumberKeyboardBehavior() {
        identificationType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                IdentificationType identificationType = getIdentificationType();
                if (identificationType != null) {
                    if (identificationType.getType().equals("number")) {
                        identificationNumber.setInputType(InputType.TYPE_CLASS_NUMBER);
                    } else {
                        identificationNumber.setInputType(InputType.TYPE_CLASS_TEXT);
                    }
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
            }
        });
    }

    private void setErrorTextCleaner(final EditText editText) {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            public void afterTextChanged(Editable edt) {
                if (editText.getText().length() > 0) {
                    editText.setError(null);
                }
            }
        });
    }

    private String getCardNumber() {
        return this.cardNumber.getText().toString();
    }

    private String getSecurityCode() {
        return this.securityCode.getText().toString();
    }

    private Integer getMonth() {
        return expiryMonth;
    }

    private Integer getYear() {
        return expiryYear;
    }

    private String getCardHolderName() {
        return this.cardHolderName.getText().toString();
    }

    private IdentificationType getIdentificationType() {
        return (IdentificationType) identificationType.getSelectedItem();
    }

    private String getIdentificationTypeId(IdentificationType identificationType) {
        if (identificationType != null) {
            return identificationType.getId();
        } else {
            return null;
        }
    }

    private String getIdentificationNumber() {
        if (!this.identificationNumber.getText().toString().equals("")) {
            return this.identificationNumber.getText().toString();
        } else {
            return null;
        }
    }
}

Customizar según corresponda.

3- Agregar la clase Utils

Crear la clase Utils.java (se recomienda que sea dentro del package mercadopago para que asi quede bien diferenciado) que contenga lo siguiente:

public class Utils {

    public static final int CARD_REQUEST_CODE = 13;

    //Custom your MercadoPago PUBLIC_KEY and ACCESS_TOKEN
    public static String PUBLIC_KEY = "TEST-d840bf4c-ee1e-4563-8813-7707e73e1c05";
    public static String ACCESS_TOKEN = "TEST-2919128858572145-060511-c777c7825bfb2a8c43627d88f4bbcff1__LC_LD__-184272638";

    //Custom your BASE_URL and CREATE_PAYMENT_URI if you dont have an api
    public static final String BASE_URL = "https://www.mercadopago.com";
    public static final String CREATE_PAYMENT_URI = "/checkout/examples/doPayment";

    //Only for test
    public static final String DUMMY_ITEM_ID = "id1";
    public static final Integer DUMMY_ITEM_QUANTITY = 1;
    public static final BigDecimal DUMMY_ITEM_UNIT_PRICE = new BigDecimal("100");

    public static void startCardActivity(Activity activity, String publicKey, PaymentMethod paymentMethod) {
        Intent cardIntent = new Intent(activity, CardActivity.class);
        cardIntent.putExtra("merchantPublicKey", publicKey);
        cardIntent.putExtra("paymentMethod", JsonUtil.getInstance().toJson(paymentMethod));
        activity.startActivityForResult(cardIntent, CARD_REQUEST_CODE);
    }

    public static void createPayment(final Activity activity, String token, final Integer installments, Long cardIssuerId, BigDecimal price,
                                     final PaymentMethod paymentMethod, Discount discount) {

        if (paymentMethod != null) {
            Item item = new Item(DUMMY_ITEM_ID, DUMMY_ITEM_QUANTITY, price);

            final String paymentMethodId = paymentMethod.getId();
            Long campaignId = (discount != null) ? discount.getId() : null;
            MerchantPayment merchantPayment = new MerchantPayment(item, installments, cardIssuerId, token, paymentMethodId, campaignId, ACCESS_TOKEN);

            //Custom your payment method api call
            //In this case:
            App.service.payOrder(order.id, 
                token, 
                installments, 
                paymentMethodId, 
                email, 
                new Callback<HashMap<String, Object>>() {
                @Override
                public void success(HashMap<String, Object> hashMap, Response response) {

                    Integer status = Integer.parseInt((String) hashMap.get("status"));
                    if (status == 200 || status == 201){
                        Gson gson = new GsonBuilder()
                                .serializeNulls()
                                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
                                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                                .setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                                .create();
                        String serializedPayment = gson.toJson(hashMap.get("response"));
                        Payment payment = gson.fromJson(serializedPayment, Payment.class);
                        new MercadoPago.StartActivityBuilder()
                                .setActivity(activity)
                                .setPayment(payment)
                                .setPaymentMethod(paymentMethod)
                                .startCongratsActivity();

                    } else {
                        Toast.makeText(activity, "Ocurrió un error al procesar el pago", Toast.LENGTH_SHORT).show();
                    }
                }

                @Override
                public void failure(RetrofitError error) {
                    LayoutUtil.showRegularLayout(activity);
                    Toast.makeText(activity, "Ocurrió un error al procesar el pago", Toast.LENGTH_LONG).show();
                }

            });
        } else {
            Toast.makeText(activity, "Invalid payment method", Toast.LENGTH_LONG).show();
        }
    }

}

Customizar según corresponda. Esta clase va a permitir tener ciertas herramientas útiles separadas del flujo principal para no contaminarlo y que sea mas claro.

4- Agregar el manejo de MercadoPago en la activity donde se lo llama

En la activity desde la cual deseamos realizar el pago (supongamos que es SummaryActivity), debemos agregar:

public class SummaryActivity extends Activity {

    //Son los metodos de pago que queremos que admita MercadoPago a la hora de pagar
    private List<String> supportedPaymentTypes = new ArrayList<String>() {
        add("credit_card");
    };

    //Este método debe ser llamado en el onClick de algun boton que dé inicio al proceso de pago
    public void send(View view) {
        new MercadoPago.StartActivityBuilder()
                .setActivity(SummaryActivity.this)
                .setPublicKey(Utils.PUBLIC_KEY)
                .setSupportedPaymentTypes(supportedPaymentTypes)
                .startPaymentMethodsActivity();
    }

    //Este método es el encargado de manejar las respuestas de las activities que usa MercadoPago en el proceso
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == MercadoPago.PAYMENT_METHODS_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                PaymentMethod paymentMethod = JsonUtil.getInstance().fromJson(data.getStringExtra("paymentMethod"), PaymentMethod.class);
                Utils.startCardActivity(this, Utils.PUBLIC_KEY, paymentMethod);
            } else {
                if ((data != null) && (data.getStringExtra("apiException") != null)) {
                    Toast.makeText(getApplicationContext(), data.getStringExtra("apiException"), Toast.LENGTH_LONG).show();
                }
            }
        } else if (requestCode == Utils.CARD_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                try {
                    cardToken = data.getStringExtra("token");
                    Utils.createPayment(this, 
                        data.getStringExtra("token"), 
                        1, //Custom installments
                        null, 
                        BigDecimal.valueOf(100), //Custom price
                        JsonUtil.getInstance().fromJson(data.getStringExtra("paymentMethod"), PaymentMethod.class), null);
                } catch (Exception exc) {
                    exc.printStackTrace();
                    Toast.makeText(this, "Ocurrio un error al procesar al pago", Toast.LENGTH_SHORT).show();
                }
            } else {
                if (data != null) {
                    if (data.getStringExtra("apiException") != null) {
                        Toast.makeText(getApplicationContext(), data.getStringExtra("apiException"), Toast.LENGTH_LONG).show();
                    } else if (data.getBooleanExtra("backButtonPressed", false)) {
                        new MercadoPago.StartActivityBuilder()
                                .setActivity(this)
                                .setPublicKey(Utils.PUBLIC_KEY)
                                .setSupportedPaymentTypes(supportedPaymentTypes)
                                .startPaymentMethodsActivity();
                    }
                }
            }
        } else if (requestCode == MercadoPago.CONGRATS_REQUEST_CODE) {
            LayoutUtil.showRegularLayout(this);

            Intent intent = new Intent(SummaryActivity.this, HomeActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
            finish();

        }
    }

}

Customizar según corresponda.

Last updated