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.editOffer=Edit offer
|
||||||
shared.duplicateOffer=Duplicate offer
|
shared.duplicateOffer=Duplicate offer
|
||||||
shared.openLargeQRWindow=Open large QR code window
|
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.chooseTradingAccount=Choose trading account
|
||||||
shared.faq=Visit FAQ page
|
shared.faq=Visit FAQ page
|
||||||
shared.yesCancel=Yes, cancel
|
shared.yesCancel=Yes, cancel
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package bisq.desktop.components.paymentmethods;
|
package bisq.desktop.components.paymentmethods;
|
||||||
|
|
||||||
|
import bisq.desktop.components.AutoTooltipButton;
|
||||||
import bisq.desktop.components.AutoTooltipCheckBox;
|
import bisq.desktop.components.AutoTooltipCheckBox;
|
||||||
|
import bisq.desktop.main.overlays.windows.QRCodeWindow;
|
||||||
import bisq.desktop.util.FormBuilder;
|
import bisq.desktop.util.FormBuilder;
|
||||||
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
import bisq.desktop.util.Layout;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.locale.Country;
|
import bisq.core.locale.Country;
|
||||||
|
import bisq.core.locale.CountryUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.payment.CountryBasedPaymentAccount;
|
import bisq.core.payment.CountryBasedPaymentAccount;
|
||||||
@ -14,9 +19,13 @@ import bisq.core.util.validation.InputValidator;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.JFXComboBox;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
|
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
@ -25,18 +34,50 @@ import javafx.scene.layout.FlowPane;
|
|||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||||
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
|
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public abstract class GeneralSepaForm extends PaymentMethodForm {
|
public abstract class GeneralSepaForm extends PaymentMethodForm {
|
||||||
|
|
||||||
static final String BIC = "BIC";
|
static final String BIC = "BIC";
|
||||||
static final String IBAN = "IBAN";
|
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) {
|
GeneralSepaForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService, InputValidator inputValidator, GridPane gridPane, int gridRow, CoinFormatter formatter) {
|
||||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||||
}
|
}
|
||||||
@ -134,4 +175,14 @@ public abstract class GeneralSepaForm extends PaymentMethodForm {
|
|||||||
abstract boolean isCountryAccepted(String countryCode);
|
abstract boolean isCountryAccepted(String countryCode);
|
||||||
|
|
||||||
protected abstract String getIban();
|
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 java.util.Optional;
|
||||||
|
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
|
||||||
|
|
||||||
public class SepaForm extends GeneralSepaForm {
|
public class SepaForm extends GeneralSepaForm {
|
||||||
|
|
||||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||||
PaymentAccountPayload paymentAccountPayload) {
|
PaymentAccountPayload paymentAccountPayload, String amount) {
|
||||||
SepaAccountPayload sepaAccountPayload = (SepaAccountPayload) paymentAccountPayload;
|
SepaAccountPayload sepaAccountPayload = (SepaAccountPayload) paymentAccountPayload;
|
||||||
|
return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount,
|
||||||
final String title = Res.get("payment.account.owner");
|
sepaAccountPayload.getCountryCode(),
|
||||||
final String value = sepaAccountPayload.getHolderName();
|
sepaAccountPayload.getHolderName(),
|
||||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value);
|
sepaAccountPayload.getBic(),
|
||||||
|
sepaAccountPayload.getIban());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SepaAccount sepaAccount;
|
private final SepaAccount sepaAccount;
|
||||||
|
@ -45,25 +45,17 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
|
||||||
|
|
||||||
public class SepaInstantForm extends GeneralSepaForm {
|
public class SepaInstantForm extends GeneralSepaForm {
|
||||||
|
|
||||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||||
PaymentAccountPayload paymentAccountPayload) {
|
PaymentAccountPayload paymentAccountPayload, String amount) {
|
||||||
SepaInstantAccountPayload sepaInstantAccountPayload = (SepaInstantAccountPayload) paymentAccountPayload;
|
SepaInstantAccountPayload sepaInstantAccountPayload = (SepaInstantAccountPayload) paymentAccountPayload;
|
||||||
|
return GeneralSepaForm.addFormForBuyer(gridPane, gridRow, amount,
|
||||||
final String title = Res.get("payment.account.owner");
|
sepaInstantAccountPayload.getCountryCode(),
|
||||||
final String value = sepaInstantAccountPayload.getHolderName();
|
sepaInstantAccountPayload.getHolderName(),
|
||||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, title, value);
|
sepaInstantAccountPayload.getBic(),
|
||||||
|
sepaInstantAccountPayload.getIban());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SepaInstantAccount sepaInstantAccount;
|
private final SepaInstantAccount sepaInstantAccount;
|
||||||
|
@ -27,32 +27,21 @@ import net.glxn.qrgen.image.ImageType;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
||||||
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
|
private ImageView qrCodeImageView;
|
||||||
private final ImageView qrCodeImageView;
|
|
||||||
private final String bitcoinAddressOrURI;
|
private final String bitcoinAddressOrURI;
|
||||||
|
private boolean showTextRepresentation = true;
|
||||||
|
private double prefWidth = 500, prefHeight = 500;
|
||||||
|
|
||||||
public QRCodeWindow(String bitcoinAddressOrURI) {
|
public QRCodeWindow(String bitcoinAddressOrURI) {
|
||||||
this.bitcoinAddressOrURI = 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;
|
type = Type.Information;
|
||||||
width = 468;
|
|
||||||
headLine(Res.get("qRCodeWindow.headline"));
|
headLine(Res.get("qRCodeWindow.headline"));
|
||||||
message(Res.get("qRCodeWindow.msg"));
|
message(Res.get("qRCodeWindow.msg"));
|
||||||
}
|
}
|
||||||
@ -60,8 +49,23 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
|||||||
@Override
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
createGridPane();
|
createGridPane();
|
||||||
|
gridPane.setPrefWidth(prefWidth);
|
||||||
|
gridPane.setMinHeight(prefHeight);
|
||||||
addHeadLine();
|
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.setRowIndex(qrCodeImageView, ++rowIndex);
|
||||||
GridPane.setColumnSpan(qrCodeImageView, 2);
|
GridPane.setColumnSpan(qrCodeImageView, 2);
|
||||||
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
|
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
|
||||||
@ -69,11 +73,24 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
|
|||||||
|
|
||||||
message = bitcoinAddressOrURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
|
message = bitcoinAddressOrURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
|
||||||
setTruncatedMessage();
|
setTruncatedMessage();
|
||||||
addMessage();
|
if (showTextRepresentation) {
|
||||||
GridPane.setHalignment(messageLabel, HPos.CENTER);
|
addMessage();
|
||||||
|
GridPane.setHalignment(messageLabel, HPos.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
addButtons();
|
addButtons();
|
||||||
applyStyles();
|
applyStyles();
|
||||||
display();
|
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);
|
gridRow = PerfectMoneyForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||||
break;
|
break;
|
||||||
case PaymentMethod.SEPA_ID:
|
case PaymentMethod.SEPA_ID:
|
||||||
gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume());
|
||||||
break;
|
break;
|
||||||
case PaymentMethod.SEPA_INSTANT_ID:
|
case PaymentMethod.SEPA_INSTANT_ID:
|
||||||
gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
gridRow = SepaInstantForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, model.getFiatVolume());
|
||||||
break;
|
break;
|
||||||
case PaymentMethod.FASTER_PAYMENTS_ID:
|
case PaymentMethod.FASTER_PAYMENTS_ID:
|
||||||
gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||||
|
@ -743,6 +743,20 @@ public class GUIUtil {
|
|||||||
return t.cast(parent);
|
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() {
|
public static void showTakeOfferFromUnsignedAccountWarning() {
|
||||||
String key = "confirmTakeOfferFromUnsignedAccount";
|
String key = "confirmTakeOfferFromUnsignedAccount";
|
||||||
new Popup().warning(Res.get("payment.takeOfferFromUnsignedAccount.warning"))
|
new Popup().warning(Res.get("payment.takeOfferFromUnsignedAccount.warning"))
|
||||||
|
Loading…
Reference in New Issue
Block a user