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