Add MercadoPago payment method.

This commit is contained in:
jmacxx 2023-09-05 16:20:53 -05:00
parent 5738d07b45
commit dd1c339432
No known key found for this signature in database
GPG key ID: 155297BABFE94A1B
11 changed files with 404 additions and 0 deletions

View file

@ -0,0 +1,107 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.payment;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.payload.MercadoPagoAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.NonNull;
public final class MercadoPagoAccount extends CountryBasedPaymentAccount {
private static final String[] SUPPORTED_COUNTRIES = {"AR"};
// other countries can be added later: "BR", "CL", "CO", "MX", "PE", "UY"
private static final String[] MERCADO_PAGO_SITES = {
"https://www.mercadopago.com.ar/"
// shown when user is prompted to make payment.
// other country specific sites can be added, see https://github.com/bisq-network/growth/issues/278
};
public static String countryToMercadoPagoSite(String countryCode) {
int index = Arrays.stream(SUPPORTED_COUNTRIES).collect(Collectors.toList()).indexOf(countryCode);
return index >= 0 ? MERCADO_PAGO_SITES[index] : Res.get("payment.ask");
}
public static List<Country> getAllMercadoPagoCountries() {
return Arrays.stream(SUPPORTED_COUNTRIES)
.map(CountryUtil::findCountryByCode)
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
public static List<TradeCurrency> SUPPORTED_CURRENCIES() {
return Arrays.stream(SUPPORTED_COUNTRIES)
.map(CurrencyUtil::getCurrencyByCountryCode)
.collect(Collectors.toList());
}
public MercadoPagoAccount() {
super(PaymentMethod.MERCADO_PAGO);
}
@Override
protected PaymentAccountPayload createPayload() {
return new MercadoPagoAccountPayload(paymentMethod.getId(), id);
}
@Override
public @NonNull List<TradeCurrency> getSupportedCurrencies() {
return SUPPORTED_CURRENCIES();
}
public String getMessageForBuyer() {
return "payment.generic.info.buyer";
}
public String getMessageForSeller() {
return "payment.generic.info.seller";
}
public String getMessageForAccountCreation() {
return "payment.mercadoPago.info.account";
}
public String getAccountHolderId() {
return ((MercadoPagoAccountPayload) paymentAccountPayload).getAccountHolderId();
}
public void setAccountHolderId(String id) {
if (id == null) id = "";
((MercadoPagoAccountPayload) paymentAccountPayload).setAccountHolderId(id);
}
public String getAccountHolderName() {
return ((MercadoPagoAccountPayload) paymentAccountPayload).getAccountHolderName();
}
public void setAccountHolderName(String accountHolderName) {
if (accountHolderName == null) accountHolderName = "";
((MercadoPagoAccountPayload) paymentAccountPayload).setAccountHolderName(accountHolderName);
}
}

View file

@ -130,6 +130,8 @@ public class PaymentAccountFactory {
return new DomesticWireTransferAccount();
case PaymentMethod.BSQ_SWAP_ID:
return new BsqSwapAccount();
case PaymentMethod.MERCADO_PAGO_ID:
return new MercadoPagoAccount();
// Cannot be deleted as it would break old trade history entries
case PaymentMethod.OK_PAY_ID:

View file

@ -214,6 +214,8 @@ public class PaymentAccountUtil {
return TransferwiseUsdAccount.SUPPORTED_CURRENCIES;
case VERSE_ID:
return VerseAccount.SUPPORTED_CURRENCIES;
case MERCADO_PAGO_ID:
return MercadoPagoAccount.SUPPORTED_CURRENCIES();
default:
return Collections.emptyList();
}

View file

@ -0,0 +1,103 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.payment.payload;
import bisq.core.locale.Res;
import com.google.protobuf.Message;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@EqualsAndHashCode(callSuper = true)
@ToString
@Setter
@Getter
@Slf4j
public final class MercadoPagoAccountPayload extends CountryBasedPaymentAccountPayload {
private String accountHolderName = "";
private String accountHolderId = "";
public MercadoPagoAccountPayload(String paymentMethod, String id) {
super(paymentMethod, id);
}
private MercadoPagoAccountPayload(String paymentMethod,
String id,
String countryCode,
String accountHolderName,
String accountHolderId,
long maxTradePeriod,
Map<String, String> excludeFromJsonDataMap) {
super(paymentMethod,
id,
countryCode,
maxTradePeriod,
excludeFromJsonDataMap);
this.accountHolderName = accountHolderName;
this.accountHolderId = accountHolderId;
}
@Override
public Message toProtoMessage() {
protobuf.MercadoPagoAccountPayload.Builder builder = protobuf.MercadoPagoAccountPayload.newBuilder()
.setHolderName(accountHolderName)
.setHolderId(accountHolderId);
final protobuf.CountryBasedPaymentAccountPayload.Builder countryBasedPaymentAccountPayload = getPaymentAccountPayloadBuilder()
.getCountryBasedPaymentAccountPayloadBuilder()
.setMercadoPagoAccountPayload(builder);
return getPaymentAccountPayloadBuilder()
.setCountryBasedPaymentAccountPayload(countryBasedPaymentAccountPayload)
.build();
}
public static MercadoPagoAccountPayload fromProto(protobuf.PaymentAccountPayload proto) {
protobuf.CountryBasedPaymentAccountPayload countryBasedPaymentAccountPayload = proto.getCountryBasedPaymentAccountPayload();
protobuf.MercadoPagoAccountPayload mercadoPagoAccountPayloadPB = countryBasedPaymentAccountPayload.getMercadoPagoAccountPayload();
return new MercadoPagoAccountPayload(proto.getPaymentMethodId(),
proto.getId(),
countryBasedPaymentAccountPayload.getCountryCode(),
mercadoPagoAccountPayloadPB.getHolderName(),
mercadoPagoAccountPayloadPB.getHolderId(),
proto.getMaxTradePeriod(),
new HashMap<>(proto.getExcludeFromJsonDataMap()));
}
@Override
public String getPaymentDetails() {
return Res.get(paymentMethodId) + " - " + getPaymentDetailsForTradePopup().replace("\n", ", ");
}
@Override
public String getPaymentDetailsForTradePopup() {
return Res.get("payment.mercadoPago.holderId") + ": " + accountHolderId + "\n" +
Res.get("payment.account.owner") + ": " + accountHolderName;
}
@Override
public byte[] getAgeWitnessInputData() {
String all = this.accountHolderId + this.accountHolderName;
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
}
}

View file

@ -124,6 +124,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
public static final String ACH_TRANSFER_ID = "ACH_TRANSFER";
public static final String DOMESTIC_WIRE_TRANSFER_ID = "DOMESTIC_WIRE_TRANSFER";
public static final String BSQ_SWAP_ID = "BSQ_SWAP";
public static final String MERCADO_PAGO_ID = "MERCADO_PAGO";
// Cannot be deleted as it would break old trade history entries
@Deprecated
@ -187,6 +188,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
public static PaymentMethod ACH_TRANSFER;
public static PaymentMethod DOMESTIC_WIRE_TRANSFER;
public static PaymentMethod BSQ_SWAP;
public static PaymentMethod MERCADO_PAGO;
// Cannot be deleted as it would break old trade history entries
@Deprecated
@ -265,6 +267,9 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
// Australia
AUSTRALIA_PAYID = new PaymentMethod(AUSTRALIA_PAYID_ID, DAY, DEFAULT_TRADE_LIMIT_LOW_RISK),
// Argentina
MERCADO_PAGO = new PaymentMethod(MERCADO_PAGO_ID, DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
// China
ALI_PAY = new PaymentMethod(ALI_PAY_ID, DAY, DEFAULT_TRADE_LIMIT_LOW_RISK),
WECHAT_PAY = new PaymentMethod(WECHAT_PAY_ID, DAY, DEFAULT_TRADE_LIMIT_LOW_RISK),

View file

@ -44,6 +44,7 @@ import bisq.core.payment.payload.ImpsAccountPayload;
import bisq.core.payment.payload.InstantCryptoCurrencyPayload;
import bisq.core.payment.payload.InteracETransferAccountPayload;
import bisq.core.payment.payload.JapanBankAccountPayload;
import bisq.core.payment.payload.MercadoPagoAccountPayload;
import bisq.core.payment.payload.MoneseAccountPayload;
import bisq.core.payment.payload.MoneyBeamAccountPayload;
import bisq.core.payment.payload.MoneyGramAccountPayload;
@ -158,6 +159,8 @@ public class CoreProtoResolver implements ProtoResolver {
return StrikeAccountPayload.fromProto(proto);
case TRANSFERWISE_USD_ACCOUNT_PAYLOAD:
return TransferwiseUsdAccountPayload.fromProto(proto);
case MERCADO_PAGO_ACCOUNT_PAYLOAD:
return MercadoPagoAccountPayload.fromProto(proto);
case IFSC_BASED_ACCOUNT_PAYLOAD:
final protobuf.IfscBasedAccountPayload.MessageCase messageCaseIfsc = proto.getCountryBasedPaymentAccountPayload().getIfscBasedAccountPayload().getMessageCase();
switch (messageCaseIfsc) {

View file

@ -3750,6 +3750,7 @@ payment.account.owner.address=Account owner address
payment.transferwiseUsd.address=(must be US-based, consider using bank address)
payment.amazon.site=Buy giftcard at
payment.mercadoPago.site=MercadoPago site
payment.ask=Ask in Trader Chat
payment.uphold.accountId=Username or email or phone no.
payment.moneyBeam.accountId=Email or phone no.
@ -4115,6 +4116,15 @@ payment.domesticWire.info.buyer=Please ensure you are aware of what it will cost
payment.domesticWire.info.seller=Please ensure you are aware of what it will cost you to receive a wire transfer.\n\n\
When receiving payment, please check that it is received from the BTC Buyer's account.
payment.generic.info.buyer=Please send payment only to the user details provided by the BTC Seller in their Bisq account. Please leave the payment description blank.
payment.generic.info.seller=BTC Sellers should expect to receive payment from the name shown in the BTC Buyer's Bisq account.
payment.mercadoPago.info.account=MercadoPago (Argentina) is a payment app that allows users to send money to other \
MercadoPago accounts instantly and for free in ARS. \n\n\
When setting up your MercadoPago account in Bisq please make sure to include your full name and phone number, email \
or CVU that matches your MercadoPago account. This will ensure that when you send funds they show from the correct \
account and when you receive funds they will be credited to your account.
payment.strike.info.account=Please make sure to include your Strike username.\n\n\
In Bisq, Strike is used for fiat to fiat payments only.\n\n\
Please make sure you are aware of the Strike limits:\n\n\
@ -4229,6 +4239,7 @@ payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send
- try to use creative, believable text for the gift card''s message (e.g., "Happy birthday Susan!") and use trader chat \
to tell your trading peer the reference text you picked so they can verify your payment\n\
- Amazon eGift Cards can only be redeemed on the Amazon website they were purchased on (e.g., a gift card purchased on amazon.it can only be redeemed on amazon.it)
payment.mercado_pago.holderId=UserID linked to financial institution. Like phone number or email or CVU.
# We use constants from the code so we do not use our normal naming convention
# dynamic values are not recognized by IntelliJ
@ -4356,6 +4367,8 @@ ACH_TRANSFER=ACH Transfer
DOMESTIC_WIRE_TRANSFER=Domestic Wire Transfer
# suppress inspection "UnusedProperty"
BSQ_SWAP=BSQ Swap
# suppress inspection "UnusedProperty"
MERCADO_PAGO=MercadoPago
# Deprecated: Cannot be deleted as it would break old trade history entries
# suppress inspection "UnusedProperty"
@ -4452,6 +4465,8 @@ ACH_TRANSFER_SHORT=ACH
DOMESTIC_WIRE_TRANSFER_SHORT=Domestic Wire
# suppress inspection "UnusedProperty"
BSQ_SWAP_SHORT=BSQ Swap
# suppress inspection "UnusedProperty"
MERCADO_PAGO_SHORT=MercadoPago
# Deprecated: Cannot be deleted as it would break old trade history entries
# suppress inspection "UnusedProperty"

View file

@ -0,0 +1,154 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.components.paymentmethods;
import bisq.desktop.components.InputTextField;
import bisq.desktop.util.FormBuilder;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.MercadoPagoAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.MercadoPagoAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.InputValidator;
import bisq.common.UserThread;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import static bisq.desktop.util.FormBuilder.*;
public class MercadoPagoForm extends PaymentMethodForm {
private final MercadoPagoAccount mercadoPagoAccount;
ComboBox<Country> countryCombo;
public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountPayload paymentAccountPayload) {
MercadoPagoAccountPayload mercadoPagoAccountPayload = (MercadoPagoAccountPayload) paymentAccountPayload;
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.owner"),
mercadoPagoAccountPayload.getAccountHolderName());
addCompactTopLabelTextField(gridPane, gridRow, 1, Res.get("shared.country"),
CountryUtil.getNameAndCode(mercadoPagoAccountPayload.getCountryCode()));
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.mercado_pago.holderId"),
mercadoPagoAccountPayload.getAccountHolderId());
addCompactTopLabelTextField(gridPane, gridRow, 1, Res.get("payment.mercadoPago.site"),
MercadoPagoAccount.countryToMercadoPagoSite(mercadoPagoAccountPayload.getCountryCode()));
return gridRow;
}
public MercadoPagoForm(PaymentAccount paymentAccount,
AccountAgeWitnessService accountAgeWitnessService,
InputValidator inputValidator,
GridPane gridPane,
int gridRow,
CoinFormatter formatter) {
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
this.mercadoPagoAccount = (MercadoPagoAccount) paymentAccount;
}
@Override
public void addFormForAddAccount() {
gridRowFrom = gridRow + 1;
InputTextField holderNameInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow,
Res.get("payment.account.owner"));
holderNameInputTextField.setValidator(inputValidator);
holderNameInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
mercadoPagoAccount.setAccountHolderName(newValue);
updateFromInputs();
});
InputTextField mobileNrInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.mercado_pago.holderId"));
mobileNrInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
mercadoPagoAccount.setAccountHolderId(newValue);
updateFromInputs();
});
countryCombo = addComboBox(gridPane, ++gridRow, Res.get("shared.country"));
countryCombo.setPromptText(Res.get("payment.select.country"));
countryCombo.setItems(FXCollections.observableArrayList(MercadoPagoAccount.getAllMercadoPagoCountries()));
TextField ccyField = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.currency"), "").second;
countryCombo.setConverter(new StringConverter<>() {
@Override
public String toString(Country country) {
return country.name + " (" + country.code + ")";
}
@Override
public Country fromString(String s) {
return null;
}
});
countryCombo.setOnAction(e -> {
Country countryCode = countryCombo.getValue();
mercadoPagoAccount.setCountry(countryCode);
TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(countryCode.code);
paymentAccount.setSingleTradeCurrency(currency);
ccyField.setText(currency.getNameAndCode());
updateFromInputs();
});
if (countryCombo.getItems().size() == 1) { // auto select when only one choice
UserThread.runAfter(() -> countryCombo.setValue(countryCombo.getItems().get(0)), 1);
}
addLimitations(false);
addAccountNameTextFieldWithAutoFillToggleButton();
}
@Override
protected void autoFillNameTextField() {
setAccountNameWithString(mercadoPagoAccount.getAccountHolderId());
}
@Override
public void addFormForEditAccount() {
gridRowFrom = gridRow;
addAccountNameTextFieldWithAutoFillToggleButton();
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"),
Res.get(mercadoPagoAccount.getPaymentMethod().getId()));
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.owner"),
mercadoPagoAccount.getAccountHolderName());
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.mercado_pago.holderId"),
mercadoPagoAccount.getAccountHolderId());
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.country"),
mercadoPagoAccount.getCountry() != null ? mercadoPagoAccount.getCountry().name : "");
TradeCurrency singleTradeCurrency = mercadoPagoAccount.getSingleTradeCurrency();
String nameAndCode = singleTradeCurrency != null ? singleTradeCurrency.getNameAndCode() : "null";
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.currency"), nameAndCode);
addLimitations(true);
}
@Override
public void updateAllInputsValid() {
allInputsValid.set(isAccountNameValid()
&& inputValidator.validate(mercadoPagoAccount.getAccountHolderId()).isValid
&& inputValidator.validate(mercadoPagoAccount.getAccountHolderName()).isValid
&& mercadoPagoAccount.getTradeCurrencies().size() > 0);
}
}

View file

@ -39,6 +39,7 @@ import bisq.desktop.components.paymentmethods.HalCashForm;
import bisq.desktop.components.paymentmethods.ImpsForm;
import bisq.desktop.components.paymentmethods.InteracETransferForm;
import bisq.desktop.components.paymentmethods.JapanBankTransferForm;
import bisq.desktop.components.paymentmethods.MercadoPagoForm;
import bisq.desktop.components.paymentmethods.MoneseForm;
import bisq.desktop.components.paymentmethods.MoneyBeamForm;
import bisq.desktop.components.paymentmethods.MoneyGramForm;
@ -690,6 +691,8 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
return new AchTransferForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
case PaymentMethod.DOMESTIC_WIRE_TRANSFER_ID:
return new DomesticWireTransferForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
case PaymentMethod.MERCADO_PAGO_ID:
return new MercadoPagoForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
default:
log.error("Not supported PaymentMethod: " + paymentMethod);
return null;

View file

@ -42,6 +42,7 @@ import bisq.desktop.components.paymentmethods.HalCashForm;
import bisq.desktop.components.paymentmethods.ImpsForm;
import bisq.desktop.components.paymentmethods.InteracETransferForm;
import bisq.desktop.components.paymentmethods.JapanBankTransferForm;
import bisq.desktop.components.paymentmethods.MercadoPagoForm;
import bisq.desktop.components.paymentmethods.MoneseForm;
import bisq.desktop.components.paymentmethods.MoneyBeamForm;
import bisq.desktop.components.paymentmethods.MoneyGramForm;
@ -426,6 +427,9 @@ public class BuyerStep2View extends TradeStepView {
case PaymentMethod.DOMESTIC_WIRE_TRANSFER_ID:
gridRow = DomesticWireTransferForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
break;
case PaymentMethod.MERCADO_PAGO_ID:
gridRow = MercadoPagoForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
break;
default:
log.error("Not supported PaymentMethod: " + paymentMethodId);
}

View file

@ -1140,6 +1140,7 @@ message CountryBasedPaymentAccountPayload {
StrikeAccountPayload strike_account_payload = 16;
TikkieAccountPayload tikkie_account_payload = 17;
TransferwiseUsdAccountPayload transferwise_usd_account_payload = 18;
MercadoPagoAccountPayload mercado_pago_account_payload = 19;
}
}
@ -1427,6 +1428,11 @@ message VerseAccountPayload {
string holder_name = 1;
}
message MercadoPagoAccountPayload {
string holder_name = 1;
string holder_id = 2;
}
message SwiftAccountPayload {
string beneficiary_name = 1;
string beneficiary_account_nr = 2;