mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge pull request #7005 from jmacxx/sepa_qr_code
Add SEPA QR Code for buyer payment
This commit is contained in:
commit
ee987bef1c
@ -101,6 +101,8 @@ shared.dontRemoveOffer=Don't remove offer
|
||||
shared.editOffer=Edit offer
|
||||
shared.duplicateOffer=Duplicate offer
|
||||
shared.openLargeQRWindow=Open large QR code window
|
||||
shared.showSepaQRCode=Show SEPA QR Code
|
||||
shared.maximizedToProtectPrivacy=In order to protect your privacy, the QR Code will be shown maximized over the application window.
|
||||
shared.chooseTradingAccount=Choose trading account
|
||||
shared.faq=Visit FAQ page
|
||||
shared.yesCancel=Yes, cancel
|
||||
|
@ -1,10 +1,15 @@
|
||||
package bisq.desktop.components.paymentmethods;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.AutoTooltipCheckBox;
|
||||
import bisq.desktop.main.overlays.windows.QRCodeWindow;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.Country;
|
||||
import bisq.core.locale.CountryUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.CountryBasedPaymentAccount;
|
||||
@ -14,9 +19,13 @@ import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.utils.MaterialDesignIconFactory;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.TextField;
|
||||
@ -25,18 +34,50 @@ import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
|
||||
|
||||
@Slf4j
|
||||
public abstract class GeneralSepaForm extends PaymentMethodForm {
|
||||
|
||||
static final String BIC = "BIC";
|
||||
static final String IBAN = "IBAN";
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow, String amount,
|
||||
String countryCode, String recipient, String bic, String iban) {
|
||||
Button showQrCodeButton = new AutoTooltipButton(Res.get("shared.showSepaQRCode"),
|
||||
MaterialDesignIconFactory.get().createIcon(MaterialDesignIcon.QRCODE, "2.0em"));
|
||||
GridPane.setRowIndex(showQrCodeButton, gridRow);
|
||||
GridPane.setColumnIndex(showQrCodeButton, 0);
|
||||
gridPane.getChildren().add(showQrCodeButton);
|
||||
GridPane.setMargin(showQrCodeButton, new Insets(66 + Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0));
|
||||
showQrCodeButton.setStyle("-fx-pref-height: 27; -fx-padding: 4 4 4 4;");
|
||||
GridPane.setColumnIndex(showQrCodeButton, 1);
|
||||
showQrCodeButton.setOnMouseClicked(e -> GUIUtil.showMaximizedToProtectPrivacyMessage(() ->
|
||||
new QRCodeWindow(constructQRCodeString(bic, iban, recipient, amount))
|
||||
.withoutText()
|
||||
.setWindowDimensions(gridPane.getScene().getWindow().getWidth() * 1.05,
|
||||
gridPane.getScene().getWindow().getHeight() * 1.05)
|
||||
.show()));
|
||||
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.account.owner"), recipient);
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, bic);
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow,
|
||||
Res.get("payment.bank.country"), CountryUtil.getNameAndCode(countryCode));
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, IBAN, iban);
|
||||
|
||||
return gridRow;
|
||||
}
|
||||
|
||||
GeneralSepaForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService, InputValidator inputValidator, GridPane gridPane, int gridRow, CoinFormatter formatter) {
|
||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||
}
|
||||
@ -134,4 +175,14 @@ public abstract class GeneralSepaForm extends PaymentMethodForm {
|
||||
abstract boolean isCountryAccepted(String countryCode);
|
||||
|
||||
protected abstract String getIban();
|
||||
|
||||
// see https://en.wikipedia.org/wiki/EPC_QR_code
|
||||
private static String constructQRCodeString(String bic, String iban, String recipient, String amountCcy) {
|
||||
String paymentBase = "BCD\n001\n1\nSCT\n" + bic + "\n" + recipient + "\n" + iban;
|
||||
String[] amountSplit = amountCcy.split(" ");
|
||||
if (amountSplit.length == 2) {
|
||||
return paymentBase + "\n" + amountSplit[1] + amountSplit[0]; // ccy and amount combined, EPC_QR_code spec
|
||||
}
|
||||
return paymentBase;
|
||||
}
|
||||
}
|
||||
|
@ -45,25 +45,17 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||
|
||||
public class SepaForm extends GeneralSepaForm {
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||
PaymentAccountPayload paymentAccountPayload) {
|
||||
PaymentAccountPayload paymentAccountPayload, String amount) {
|
||||
SepaAccountPayload sepaAccountPayload = (SepaAccountPayload) paymentAccountPayload;
|
||||
|
||||
final String title = Res.get("payment.account.owner");
|
||||
final String value = sepaAccountPayload.getHolderName();
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value);
|
||||
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1,
|
||||
Res.get("payment.bank.country"),
|
||||
CountryUtil.getNameAndCode(sepaAccountPayload.getCountryCode()));
|
||||
// IBAN, BIC will not be translated
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, IBAN, sepaAccountPayload.getIban());
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, sepaAccountPayload.getBic());
|
||||
return gridRow;
|
||||
return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount,
|
||||
sepaAccountPayload.getCountryCode(),
|
||||
sepaAccountPayload.getHolderName(),
|
||||
sepaAccountPayload.getBic(),
|
||||
sepaAccountPayload.getIban());
|
||||
}
|
||||
|
||||
private final SepaAccount sepaAccount;
|
||||
|
@ -45,25 +45,17 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||
|
||||
public class SepaInstantForm extends GeneralSepaForm {
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||
PaymentAccountPayload paymentAccountPayload) {
|
||||
PaymentAccountPayload paymentAccountPayload, String amount) {
|
||||
SepaInstantAccountPayload sepaInstantAccountPayload = (SepaInstantAccountPayload) paymentAccountPayload;
|
||||
|
||||
final String title = Res.get("payment.account.owner");
|
||||
final String value = sepaInstantAccountPayload.getHolderName();
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value);
|
||||
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1,
|
||||
Res.get("payment.bank.country"),
|
||||
CountryUtil.getNameAndCode(sepaInstantAccountPayload.getCountryCode()));
|
||||
// IBAN, BIC will not be translated
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, IBAN, sepaInstantAccountPayload.getIban());
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, BIC, sepaInstantAccountPayload.getBic());
|
||||
return gridRow;
|
||||
return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount,
|
||||
sepaInstantAccountPayload.getCountryCode(),
|
||||
sepaInstantAccountPayload.getHolderName(),
|
||||
sepaInstantAccountPayload.getBic(),
|
||||
sepaInstantAccountPayload.getIban());
|
||||
}
|
||||
|
||||
private final SepaInstantAccount sepaInstantAccount;
|
||||
|
@ -27,32 +27,21 @@ import net.glxn.qrgen.image.ImageType;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
|
||||
private final ImageView qrCodeImageView;
|
||||
private ImageView qrCodeImageView;
|
||||
private final String bitcoinAddressOrURI;
|
||||
private boolean showTextRepresentation = true;
|
||||
private double prefWidth = 500, prefHeight = 500;
|
||||
|
||||
public QRCodeWindow(String bitcoinAddressOrURI) {
|
||||
this.bitcoinAddressOrURI = bitcoinAddressOrURI;
|
||||
final byte[] imageBytes = QRCode
|
||||
.from(bitcoinAddressOrURI)
|
||||
.withSize(250, 250)
|
||||
.to(ImageType.PNG)
|
||||
.stream()
|
||||
.toByteArray();
|
||||
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
|
||||
qrCodeImageView = new ImageView(qrImage);
|
||||
|
||||
type = Type.Information;
|
||||
width = 468;
|
||||
headLine(Res.get("qRCodeWindow.headline"));
|
||||
message(Res.get("qRCodeWindow.msg"));
|
||||
}
|
||||
@ -60,8 +49,23 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
@Override
|
||||
public void show() {
|
||||
createGridPane();
|
||||
gridPane.setPrefWidth(prefWidth);
|
||||
gridPane.setMinHeight(prefHeight);
|
||||
addHeadLine();
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setMinHeight(prefHeight / 8);
|
||||
gridPane.add(spacer, 0, ++rowIndex);
|
||||
|
||||
final byte[] imageBytes = QRCode
|
||||
.from(bitcoinAddressOrURI)
|
||||
.withSize((int) prefWidth / 2, (int) prefHeight / 2)
|
||||
.to(ImageType.PNG)
|
||||
.stream()
|
||||
.toByteArray();
|
||||
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
|
||||
qrCodeImageView = new ImageView(qrImage);
|
||||
|
||||
GridPane.setRowIndex(qrCodeImageView, ++rowIndex);
|
||||
GridPane.setColumnSpan(qrCodeImageView, 2);
|
||||
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
|
||||
@ -69,11 +73,24 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||
|
||||
message = bitcoinAddressOrURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
|
||||
setTruncatedMessage();
|
||||
if (showTextRepresentation) {
|
||||
addMessage();
|
||||
GridPane.setHalignment(messageLabel, HPos.CENTER);
|
||||
}
|
||||
|
||||
addButtons();
|
||||
applyStyles();
|
||||
display();
|
||||
}
|
||||
|
||||
public QRCodeWindow withoutText() {
|
||||
showTextRepresentation = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QRCodeWindow setWindowDimensions(double width, double height) {
|
||||
this.prefWidth = width;
|
||||
this.prefHeight = height;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -283,10 +283,10 @@ public class BuyerStep2View extends TradeStepView {
|
||||
gridRow = PerfectMoneyForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
break;
|
||||
case PaymentMethod.SEPA_ID:
|
||||
gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume());
|
||||
break;
|
||||
case PaymentMethod.SEPA_INSTANT_ID:
|
||||
gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume());
|
||||
break;
|
||||
case PaymentMethod.FASTER_PAYMENTS_ID:
|
||||
gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
|
@ -743,6 +743,20 @@ public class GUIUtil {
|
||||
return t.cast(parent);
|
||||
}
|
||||
|
||||
public static void showMaximizedToProtectPrivacyMessage(Runnable runnable) {
|
||||
String msg = Res.get("shared.maximizedToProtectPrivacy");
|
||||
String id = "shared.maximizedToProtectPrivacy";
|
||||
if (preferences.showAgain(id)) {
|
||||
new Popup().information(msg)
|
||||
.onClose(runnable)
|
||||
.useIUnderstandButton()
|
||||
.show();
|
||||
DontShowAgainLookup.dontShowAgain(id, true);
|
||||
} else {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void showTakeOfferFromUnsignedAccountWarning() {
|
||||
String key = "confirmTakeOfferFromUnsignedAccount";
|
||||
new Popup().warning(Res.get("payment.takeOfferFromUnsignedAccount.warning"))
|
||||
|
Loading…
Reference in New Issue
Block a user