support additional info on all offers

This commit is contained in:
woodser 2025-01-25 12:41:20 -05:00
parent a6af1550a4
commit 6c6c6e2dd5
45 changed files with 367 additions and 204 deletions

View File

@ -425,6 +425,7 @@ public class CoreApi {
String paymentAccountId,
boolean isPrivateOffer,
boolean buyerAsTakerWithoutDeposit,
String extraInfo,
Consumer<Offer> resultHandler,
ErrorMessageHandler errorMessageHandler) {
coreOffersService.postOffer(currencyCode,
@ -440,6 +441,7 @@ public class CoreApi {
paymentAccountId,
isPrivateOffer,
buyerAsTakerWithoutDeposit,
extraInfo,
resultHandler,
errorMessageHandler);
}
@ -455,7 +457,8 @@ public class CoreApi {
double securityDepositPct,
PaymentAccount paymentAccount,
boolean isPrivateOffer,
boolean buyerAsTakerWithoutDeposit) {
boolean buyerAsTakerWithoutDeposit,
String extraInfo) {
return coreOffersService.editOffer(offerId,
currencyCode,
direction,
@ -467,7 +470,8 @@ public class CoreApi {
securityDepositPct,
paymentAccount,
isPrivateOffer,
buyerAsTakerWithoutDeposit);
buyerAsTakerWithoutDeposit,
extraInfo);
}
public void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {

View File

@ -178,6 +178,7 @@ public class CoreOffersService {
String paymentAccountId,
boolean isPrivateOffer,
boolean buyerAsTakerWithoutDeposit,
String extraInfo,
Consumer<Offer> resultHandler,
ErrorMessageHandler errorMessageHandler) {
coreWalletsService.verifyWalletsAreAvailable();
@ -204,7 +205,8 @@ public class CoreOffersService {
securityDepositPct,
paymentAccount,
isPrivateOffer,
buyerAsTakerWithoutDeposit);
buyerAsTakerWithoutDeposit,
extraInfo);
verifyPaymentAccountIsValidForNewOffer(offer, paymentAccount);
@ -230,7 +232,8 @@ public class CoreOffersService {
double securityDepositPct,
PaymentAccount paymentAccount,
boolean isPrivateOffer,
boolean buyerAsTakerWithoutDeposit) {
boolean buyerAsTakerWithoutDeposit,
String extraInfo) {
return createOfferService.createAndGetOffer(offerId,
direction,
currencyCode.toUpperCase(),
@ -242,7 +245,8 @@ public class CoreOffersService {
securityDepositPct,
paymentAccount,
isPrivateOffer,
buyerAsTakerWithoutDeposit);
buyerAsTakerWithoutDeposit,
extraInfo);
}
void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {

View File

@ -80,6 +80,7 @@ public class OfferInfo implements Payload {
private final long splitOutputTxFee;
private final boolean isPrivateOffer;
private final String challenge;
private final String extraInfo;
public OfferInfo(OfferInfoBuilder builder) {
this.id = builder.getId();
@ -115,6 +116,7 @@ public class OfferInfo implements Payload {
this.splitOutputTxFee = builder.getSplitOutputTxFee();
this.isPrivateOffer = builder.isPrivateOffer();
this.challenge = builder.getChallenge();
this.extraInfo = builder.getExtraInfo();
}
public static OfferInfo toOfferInfo(Offer offer) {
@ -184,7 +186,8 @@ public class OfferInfo implements Payload {
.withProtocolVersion(offer.getOfferPayload().getProtocolVersion())
.withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress())
.withIsPrivateOffer(offer.isPrivateOffer())
.withChallenge(offer.getChallenge());
.withChallenge(offer.getChallenge())
.withExtraInfo(offer.getCombinedExtraInfo());
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -227,6 +230,7 @@ public class OfferInfo implements Payload {
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash);
Optional.ofNullable(challenge).ifPresent(builder::setChallenge);
Optional.ofNullable(extraInfo).ifPresent(builder::setExtraInfo);
return builder.build();
}
@ -266,6 +270,7 @@ public class OfferInfo implements Payload {
.withSplitOutputTxFee(proto.getSplitOutputTxFee())
.withIsPrivateOffer(proto.getIsPrivateOffer())
.withChallenge(proto.getChallenge())
.withExtraInfo(proto.getExtraInfo())
.build();
}
}

View File

@ -65,6 +65,7 @@ public final class OfferInfoBuilder {
private long splitOutputTxFee;
private boolean isPrivateOffer;
private String challenge;
private String extraInfo;
public OfferInfoBuilder withId(String id) {
this.id = id;
@ -246,6 +247,11 @@ public final class OfferInfoBuilder {
return this;
}
public OfferInfoBuilder withExtraInfo(String extraInfo) {
this.extraInfo = extraInfo;
return this;
}
public OfferInfo build() {
return new OfferInfo(this);
}

View File

@ -103,7 +103,8 @@ public class CreateOfferService {
double securityDepositPct,
PaymentAccount paymentAccount,
boolean isPrivateOffer,
boolean buyerAsTakerWithoutDeposit) {
boolean buyerAsTakerWithoutDeposit,
String extraInfo) {
log.info("create and get offer with offerId={}, " +
"currencyCode={}, " +
"direction={}, " +
@ -114,7 +115,8 @@ public class CreateOfferService {
"minAmount={}, " +
"securityDepositPct={}, " +
"isPrivateOffer={}, " +
"buyerAsTakerWithoutDeposit={}",
"buyerAsTakerWithoutDeposit={}, " +
"extraInfo={}",
offerId,
currencyCode,
direction,
@ -125,7 +127,8 @@ public class CreateOfferService {
minAmount,
securityDepositPct,
isPrivateOffer,
buyerAsTakerWithoutDeposit);
buyerAsTakerWithoutDeposit,
extraInfo);
// verify buyer as taker security deposit
boolean isBuyerMaker = offerUtil.isBuyOffer(direction);
@ -225,7 +228,8 @@ public class CreateOfferService {
Version.TRADE_PROTOCOL_VERSION,
null,
null,
null);
null,
extraInfo);
Offer offer = new Offer(offerPayload);
offer.setPriceFeedService(priceFeedService);
offer.setChallenge(challenge);

View File

@ -421,7 +421,23 @@ public class Offer implements NetworkPayload, PersistablePayload {
return "";
}
public String getExtraInfo() {
public String getCombinedExtraInfo() {
StringBuilder sb = new StringBuilder();
if (getOfferExtraInfo() != null && !getOfferExtraInfo().isEmpty()) {
sb.append(getOfferExtraInfo());
}
if (getPaymentAccountExtraInfo() != null && !getPaymentAccountExtraInfo().isEmpty()) {
if (sb.length() > 0) sb.append("\n\n");
sb.append(getPaymentAccountExtraInfo());
}
return sb.toString();
}
public String getOfferExtraInfo() {
return offerPayload.getExtraInfo();
}
public String getPaymentAccountExtraInfo() {
if (getExtraDataMap() != null && getExtraDataMap().containsKey(OfferPayload.F2F_EXTRA_INFO))
return getExtraDataMap().get(OfferPayload.F2F_EXTRA_INFO);
else if (getExtraDataMap() != null && getExtraDataMap().containsKey(OfferPayload.PAY_BY_MAIL_EXTRA_INFO))

View File

@ -158,6 +158,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
private final boolean isPrivateOffer;
@Nullable
private final String challengeHash;
@Nullable
private final String extraInfo;
///////////////////////////////////////////////////////////////////////////////////////////
@ -201,7 +203,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
int protocolVersion,
@Nullable NodeAddress arbitratorSigner,
@Nullable byte[] arbitratorSignature,
@Nullable List<String> reserveTxKeyImages) {
@Nullable List<String> reserveTxKeyImages,
@Nullable String extraInfo) {
this.id = id;
this.date = date;
this.ownerNodeAddress = ownerNodeAddress;
@ -240,6 +243,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
this.upperClosePrice = upperClosePrice;
this.isPrivateOffer = isPrivateOffer;
this.challengeHash = challengeHash;
this.extraInfo = extraInfo;
}
public byte[] getHash() {
@ -290,7 +294,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
protocolVersion,
arbitratorSigner,
null,
reserveTxKeyImages
reserveTxKeyImages,
null
);
return signee.getHash();
@ -387,6 +392,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
Optional.ofNullable(arbitratorSignature).ifPresent(e -> builder.setArbitratorSignature(ByteString.copyFrom(e)));
Optional.ofNullable(reserveTxKeyImages).ifPresent(builder::addAllReserveTxKeyImages);
Optional.ofNullable(extraInfo).ifPresent(builder::setExtraInfo);
return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build();
}
@ -398,7 +404,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
null : new ArrayList<>(proto.getAcceptedCountryCodesList());
List<String> reserveTxKeyImages = proto.getReserveTxKeyImagesList().isEmpty() ?
null : new ArrayList<>(proto.getReserveTxKeyImagesList());
String challengeHash = ProtoUtil.stringOrNullFromProto(proto.getChallengeHash());
Map<String, String> extraDataMapMap = CollectionUtils.isEmpty(proto.getExtraDataMap()) ?
null : proto.getExtraDataMap();
@ -434,12 +439,13 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
proto.getLowerClosePrice(),
proto.getUpperClosePrice(),
proto.getIsPrivateOffer(),
challengeHash,
ProtoUtil.stringOrNullFromProto(proto.getChallengeHash()),
extraDataMapMap,
proto.getProtocolVersion(),
proto.hasArbitratorSigner() ? NodeAddress.fromProto(proto.getArbitratorSigner()) : null,
ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorSignature()),
reserveTxKeyImages);
reserveTxKeyImages,
ProtoUtil.stringOrNullFromProto(proto.getExtraInfo()));
}
@Override
@ -481,14 +487,15 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
",\r\n lowerClosePrice=" + lowerClosePrice +
",\r\n upperClosePrice=" + upperClosePrice +
",\r\n isPrivateOffer=" + isPrivateOffer +
",\r\n challengeHash='" + challengeHash + '\'' +
",\r\n challengeHash='" + challengeHash +
",\r\n arbitratorSigner=" + arbitratorSigner +
",\r\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
",\r\n extraInfo='" + extraInfo +
"\r\n} ";
}
// For backward compatibility we need to ensure same order for json fields as with 1.7.5. and earlier versions.
// The json is used for the hash in the contract and change of oder would cause a different hash and
// The json is used for the hash in the contract and change of order would cause a different hash and
// therefore a failure during trade.
public static class JsonSerializer implements com.google.gson.JsonSerializer<OfferPayload> {
@Override
@ -525,6 +532,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
object.add("protocolVersion", context.serialize(offerPayload.getProtocolVersion()));
object.add("arbitratorSigner", context.serialize(offerPayload.getArbitratorSigner()));
object.add("arbitratorSignature", context.serialize(offerPayload.getArbitratorSignature()));
object.add("extraInfo", context.serialize(offerPayload.getExtraInfo()));
// reserveTxKeyImages and challengeHash are purposely excluded because they are not relevant to existing trades and would break existing contracts
return object;
}
}

View File

@ -1788,7 +1788,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
protocolVersion,
originalOfferPayload.getArbitratorSigner(),
originalOfferPayload.getArbitratorSignature(),
originalOfferPayload.getReserveTxKeyImages());
originalOfferPayload.getReserveTxKeyImages(),
originalOfferPayload.getExtraInfo());
// Save states from original data to use for the updated
Offer.State originalOfferState = originalOffer.getState();

View File

@ -93,7 +93,7 @@ public final class F2FAccount extends CountryBasedPaymentAccount {
if (field.getId() == PaymentAccountFormField.FieldId.TRADE_CURRENCIES) field.setComponent(PaymentAccountFormField.Component.SELECT_ONE);
if (field.getId() == PaymentAccountFormField.FieldId.CITY) field.setLabel(Res.get("payment.f2f.city"));
if (field.getId() == PaymentAccountFormField.FieldId.CONTACT) field.setLabel(Res.get("payment.f2f.contact"));
if (field.getId() == PaymentAccountFormField.FieldId.EXTRA_INFO) field.setLabel(Res.get("payment.shared.extraInfo.prompt"));
if (field.getId() == PaymentAccountFormField.FieldId.EXTRA_INFO) field.setLabel(Res.get("payment.shared.extraInfo.prompt.paymentAccount"));
return field;
}
}

View File

@ -3028,7 +3028,10 @@ payment.f2f.city=City for 'Face to face' meeting
payment.f2f.city.prompt=The city will be displayed with the offer
payment.shared.optionalExtra=Optional additional information
payment.shared.extraInfo=Additional information
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Additional offer information
payment.shared.extraInfo.prompt.paymentAccount=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.prompt.offer=Define any special terms, conditions, or details you would like to be displayed with your offer.
payment.shared.extraInfo.noDeposit=Contact details and offer terms
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\n\
The main differences are:\n\
The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n\
@ -3042,7 +3045,7 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ
recommendations at: [HYPERLINK:https://docs.haveno.exchange/the-project/payment_methods/F2F]
payment.f2f.info.openURL=Open web page
payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Additional information: {0}
payment.shared.extraInfo.tooltip=Additional information: {0}
payment.ifsc=IFS Code
payment.ifsc.validation=IFSC format: XXXX0999999

View File

@ -3028,7 +3028,10 @@ payment.f2f.city=Město pro setkání 'tváří v tvář'
payment.f2f.city.prompt=Město se zobrazí s nabídkou
payment.shared.optionalExtra=Volitelné další informace
payment.shared.extraInfo=Další informace
payment.shared.extraInfo.prompt=Uveďte jakékoli speciální požadavky, podmínky a detaily, které chcete zobrazit u vašich nabídek s tímto platebním účtem. (Uživatelé uvidí tyto informace předtím, než akceptují vaši nabídku.)
payment.shared.extraInfo.offer=Další informace o nabídce
payment.shared.extraInfo.prompt.paymentAccount=Uveďte jakékoli speciální požadavky, podmínky a detaily, které chcete zobrazit u vašich nabídek s tímto platebním účtem. (Uživatelé uvidí tyto informace předtím, než akceptují vaši nabídku.)
payment.shared.extraInfo.prompt.offer=Definujte jakékoli speciální podmínky, podmínky nebo detaily, které chcete zobrazit u své nabídky.
payment.shared.extraInfo.noDeposit=Kontaktní údaje a podmínky nabídky
payment.f2f.info=Obchody 'tváří v tvář' mají různá pravidla a přicházejí s jinými riziky než online transakce.\n\n\
Hlavní rozdíly jsou:\n\
Obchodní partneři si musí vyměňovat informace o místě a čase schůzky pomocí poskytnutých kontaktních údajů.\n\
@ -3042,7 +3045,7 @@ payment.f2f.info=Obchody 'tváří v tvář' mají různá pravidla a přicháze
na adrese: [HYPERLINK:https://docs.haveno.exchange/the-project/payment_methods/F2F]
payment.f2f.info.openURL=Otevřít webovou stránku
payment.f2f.offerbook.tooltip.countryAndCity=Země a město: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Další informace: {0}
payment.shared.extraInfo.tooltip=Další informace: {0}
payment.ifsc=IFS kód
payment.ifsc.validation=IFSC formát: XXXX0999999

View File

@ -2032,11 +2032,14 @@ payment.f2f.city=Stadt für ein "Angesicht zu Angesicht" Treffen
payment.f2f.city.prompt=Die Stadt wird mit dem Angebot angezeigt
payment.shared.optionalExtra=Freiwillige zusätzliche Informationen
payment.shared.extraInfo=Zusätzliche Informationen
payment.shared.extraInfo.prompt=Gib spezielle Bedingungen, Abmachungen oder Details die bei ihren Angeboten unter diesem Zahlungskonto angezeigt werden sollen an. Nutzer werden diese Informationen vor der Annahme des Angebots sehen.
payment.shared.extraInfo.offer=Zusätzliche Angebotsinformationen
payment.shared.extraInfo.prompt.paymentAccount=Gib spezielle Bedingungen, Abmachungen oder Details die bei ihren Angeboten unter diesem Zahlungskonto angezeigt werden sollen an. Nutzer werden diese Informationen vor der Annahme des Angebots sehen.
payment.shared.extraInfo.prompt.offer=Definieren Sie alle speziellen Begriffe, Bedingungen oder Details, die Sie mit Ihrem Angebot anzeigen möchten.
payment.shared.extraInfo.noDeposit=Kontaktdaten und Angebotsbedingungen
payment.f2f.info=Persönliche 'Face to Face' Trades haben unterschiedliche Regeln und sind mit anderen Risiken verbunden als gewöhnliche Online-Trades.\n\nDie Hauptunterschiede sind:\n● Die Trading Partner müssen die Kontaktdaten und Informationen über den Ort und die Uhrzeit des Treffens austauschen.\n● Die Trading Partner müssen ihre Laptops mitbringen und die Bestätigung der "gesendeten Zahlung" und der "erhaltenen Zahlung" am Treffpunkt vornehmen.\n● Wenn ein Ersteller eines Angebots spezielle "Allgemeine Geschäftsbedingungen" hat, muss er diese im Textfeld "Zusatzinformationen" des Kontos angeben.\n● Mit der Annahme eines Angebots erklärt sich der Käufer mit den vom Anbieter angegebenen "Allgemeinen Geschäftsbedingungen" einverstanden.\n● Im Konfliktfall kann der Mediator oder Arbitrator nicht viel tun, da es in der Regel schwierig ist zu bestimmen, was beim Treffen passiert ist. In solchen Fällen können die Monero auf unbestimmte Zeit oder bis zu einer Einigung der Trading Peers gesperrt werden.\n\nUm sicherzustellen, dass Sie die Besonderheiten der persönlichen 'Face to Face' Trades vollständig verstehen, lesen Sie bitte die Anweisungen und Empfehlungen unter: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Webseite öffnen
payment.f2f.offerbook.tooltip.countryAndCity=Land und Stadt: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Zusätzliche Informationen: {0}
payment.shared.extraInfo.tooltip=Zusätzliche Informationen: {0}
payment.japan.bank=Bank
payment.japan.branch=Filiale

View File

@ -2033,11 +2033,14 @@ payment.f2f.city=Ciudad para la reunión 'cara a cara'
payment.f2f.city.prompt=La ciudad se mostrará con la oferta
payment.shared.optionalExtra=Información adicional opcional
payment.shared.extraInfo=Información adicional
payment.shared.extraInfo.prompt=Defina cualquier término especial, condiciones o detalles que quiera mostrar junto a sus ofertas para esta cuenta de pago (otros usuarios podrán ver esta información antes de aceptar las ofertas).
payment.shared.extraInfo.offer=Información adicional de la oferta
payment.shared.extraInfo.prompt.paymentAccount=Defina cualquier término especial, condiciones o detalles que quiera mostrar junto a sus ofertas para esta cuenta de pago (otros usuarios podrán ver esta información antes de aceptar las ofertas).
payment.shared.extraInfo.prompt.offer=Defina cualquier término, condición o detalle especial que le gustaría mostrar con su oferta.
payment.shared.extraInfo.noDeposit=Detalles de contacto y términos de la oferta
payment.f2f.info=Los intercambios 'Cara a Cara' tienen diferentes reglas y riesgos que las transacciones en línea.\n\nLas principales diferencias son:\n● Los pares de intercambio necesitan intercambiar información acerca del punto de reunión y la hora usando los detalles de contacto proporcionados.\n● Los pares de intercambio tienen que traer sus portátiles y hacer la confirmación de 'pago enviado' y 'pago recibido' en el lugar de reunión.\n● Si un creador tiene 'términos y condiciones' especiales necesita declararlos en el campo de texto 'información adicional' en la cuenta.\n● Tomando una oferta el tomador está de acuerdo con los 'términos y condiciones' declarados por el creador.\n● En caso de disputa el árbitro no puede ayudar mucho ya que normalmente es complicado obtener evidencias no manipulables de lo que ha pasado en una reunión. En estos casos los fondos XMR pueden bloquearse indefinidamente o hasta que los pares lleguen a un acuerdo.\n\nPara asegurarse de que comprende las diferencias con los intercambios 'Cara a Cara' por favor lea las instrucciones y recomendaciones en: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Abrir paǵina web
payment.f2f.offerbook.tooltip.countryAndCity=País y ciudad: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Información adicional: {0}
payment.shared.extraInfo.tooltip=Información adicional: {0}
payment.japan.bank=Banco
payment.japan.branch=Branch

View File

@ -2007,11 +2007,14 @@ payment.f2f.city=شهر جهت ملاقات 'رو در رو'
payment.f2f.city.prompt=نام شهر به همراه پیشنهاد نمایش داده خواهد شد
payment.shared.optionalExtra=اطلاعات اضافی اختیاری
payment.shared.extraInfo=اطلاعات اضافی
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=اطلاعات اضافی پیشنهاد
payment.shared.extraInfo.prompt.paymentAccount=هرگونه اصطلاحات، شرایط یا جزئیات خاصی که می‌خواهید همراه با پیشنهادات شما برای این حساب پرداخت نمایش داده شود را تعریف کنید (کاربران قبل از پذیرش پیشنهادات این اطلاعات را مشاهده خواهند کرد).
payment.shared.extraInfo.prompt.offer=هر اصطلاح، شرایط یا جزئیات خاصی که مایلید همراه با پیشنهاد خود نمایش داده شود را تعریف کنید.
payment.shared.extraInfo.noDeposit=جزئیات تماس و شرایط پیشنهاد
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=باز کردن صفحه وب
payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1}
payment.f2f.offerbook.tooltip.extra=اطلاعات اضافی: {0}
payment.shared.extraInfo.tooltip=اطلاعات اضافی: {0}
payment.japan.bank=بانک
payment.japan.branch=Branch

View File

@ -2034,11 +2034,14 @@ payment.f2f.city=Ville pour la rencontre en face à face
payment.f2f.city.prompt=La ville sera affichée en même temps que l'ordre
payment.shared.optionalExtra=Informations complémentaires facultatives
payment.shared.extraInfo=Informations complémentaires
payment.shared.extraInfo.prompt=Définissez n'importe quels termes spécifiques, conditons ou détails que vous souhaiteriez voir affichés avec vos offres pour ce compte de paiement (les utilisateurs verront ces informations avant d'accepter les offres).
payment.shared.extraInfo.offer=Informations supplémentaires sur l'offre
payment.shared.extraInfo.prompt.paymentAccount=Définissez n'importe quels termes spécifiques, conditons ou détails que vous souhaiteriez voir affichés avec vos offres pour ce compte de paiement (les utilisateurs verront ces informations avant d'accepter les offres).
payment.shared.extraInfo.prompt.offer=Définissez tous les termes, conditions ou détails spéciaux que vous souhaitez afficher avec votre offre.
payment.shared.extraInfo.noDeposit=Coordonnées et conditions de l'offre
payment.f2f.info=Les transactions en 'face à face' ont des règles différentes et comportent des risques différents de ceux des transactions en ligne.\n\nLes principales différences sont les suivantes:\n● Les pairs de trading doivent échanger des informations sur le lieu et l'heure de la réunion en utilisant les coordonnées de contanct qu'ils ont fournies.\n● Les pairs de trading doivent apporter leur ordinateur portable et faire la confirmation du 'paiement envoyé' et du 'paiement reçu' sur le lieu de la réunion.\n● Si un maker a des 'termes et conditions' spéciaux, il doit les indiquer dans le champ 'Informations supplémentaires' dans le compte.\n● En acceptant une offre, le taker accepte les 'termes et conditions' du maker.\n● En cas de litige, le médiateur ou l'arbitre ne peut pas beaucoup aider car il est généralement difficile d'obtenir des preuves irréfutables de ce qui s'est passé lors de la réunion. Dans ce cas, les fonds en XMR peuvent être bloqué s indéfiniment tant que les pairs ne parviennent pas à un accord.\n\nPour vous assurer de bien comprendre les spécificités des transactions 'face à face', veuillez lire les instructions et les recommandations à [LIEN:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Ouvrir la page web
payment.f2f.offerbook.tooltip.countryAndCity=Pays et ville: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Informations complémentaires: {0}
payment.shared.extraInfo.tooltip=Informations complémentaires: {0}
payment.japan.bank=Banque
payment.japan.branch=Filiale

View File

@ -2010,11 +2010,14 @@ payment.f2f.city=Città per l'incontro 'Faccia a faccia'
payment.f2f.city.prompt=La città verrà visualizzata con l'offerta
payment.shared.optionalExtra=Ulteriori informazioni opzionali
payment.shared.extraInfo=Informazioni aggiuntive
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Informazioni aggiuntive sull'offerta
payment.shared.extraInfo.prompt.paymentAccount=Definisci eventuali termini, condizioni o dettagli speciali che desideri vengano visualizzati con le tue offerte per questo account di pagamento (gli utenti vedranno queste informazioni prima di accettare le offerte).
payment.shared.extraInfo.prompt.offer=Definisci eventuali termini, condizioni o dettagli speciali che desideri mostrare con la tua offerta.
payment.shared.extraInfo.noDeposit=Dettagli di contatto e termini dell'offerta
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Apri sito web
payment.f2f.offerbook.tooltip.countryAndCity=Paese e città: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Ulteriori informazioni: {0}
payment.shared.extraInfo.tooltip=Ulteriori informazioni: {0}
payment.japan.bank=Banca
payment.japan.branch=Filiale

View File

@ -2032,11 +2032,14 @@ payment.f2f.city=「対面」で会うための市区町村
payment.f2f.city.prompt=オファーとともに市区町村が表示されます
payment.shared.optionalExtra=オプションの追加情報
payment.shared.extraInfo=追加情報
payment.shared.extraInfo.prompt=この支払いアカウントのオファーと一緒に表示したい特別な契約条件または詳細を定義して下さい(オファーを受ける前に、ユーザはこの情報を見れます)。
payment.shared.extraInfo.offer=追加のオファー情報
payment.shared.extraInfo.prompt.paymentAccount=この支払いアカウントのオファーと一緒に表示したい特別な契約条件または詳細を定義して下さい(オファーを受ける前に、ユーザはこの情報を見れます)。
payment.shared.extraInfo.prompt.offer=提供内容と共に表示したい特別な用語、条件、または詳細を定義してください。
payment.shared.extraInfo.noDeposit=連絡先詳細およびオファー条件
payment.f2f.info=「対面」トレードには違うルールがあり、オンライントレードとは異なるリスクを伴います。\n\n主な違いは以下の通りです。\n●取引者は、提供される連絡先の詳細を使用して、出会う場所と時間に関する情報を交換する必要があります。\n●取引者は自分のートパソコンを持ってきて、集合場所で「送金」と「入金」の確認をする必要があります。\n●メイカーに特別な「取引条件」がある場合は、アカウントの「追加情報」テキストフィールドにその旨を記載する必要があります。\n●オファーを受けると、テイカーはメイカーの「トレード条件」に同意したものとします。\n●係争が発生した場合、集合場所で何が起きたのかについての改ざん防止証明を入手することは通常困難であるため、調停者や調停人はあまりサポートをできません。このような場合、XMRの資金は無期限に、または取引者が合意に達するまでロックされる可能性があります。\n\n「対面」トレードでの違いを完全に理解しているか確認するためには、次のURLにある手順と推奨事項をお読みください[HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Webページを開く
payment.f2f.offerbook.tooltip.countryAndCity=国と都市: {0} / {1}
payment.f2f.offerbook.tooltip.extra=追加情報: {0}
payment.shared.extraInfo.tooltip=追加情報: {0}
payment.japan.bank=銀行
payment.japan.branch=支店

View File

@ -2017,11 +2017,14 @@ payment.f2f.city=Cidade para se encontrar 'Cara-a-cara'
payment.f2f.city.prompt=A cidade será exibida na oferta
payment.shared.optionalExtra=Informações adicionais opcionais
payment.shared.extraInfo=Informações adicionais
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Informações adicionais sobre a oferta
payment.shared.extraInfo.prompt.paymentAccount=Defina quaisquer termos, condições ou detalhes especiais que você gostaria que fossem exibidos com suas ofertas para esta conta de pagamento (os usuários verão estas informações antes de aceitar as ofertas).
payment.shared.extraInfo.prompt.offer=Defina quaisquer termos, condições ou detalhes especiais que você gostaria de exibir com sua oferta.
payment.shared.extraInfo.noDeposit=Detalhes de contato e termos da oferta
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Abrir site
payment.f2f.offerbook.tooltip.countryAndCity=País e cidade: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Informações adicionais: {0}
payment.shared.extraInfo.tooltip=Informações adicionais: {0}
payment.japan.bank=Banco
payment.japan.branch=Ramo

View File

@ -2007,11 +2007,14 @@ payment.f2f.city=Cidade para o encontro 'Face à face'
payment.f2f.city.prompt=A cidade será exibida com a oferta
payment.shared.optionalExtra=Informação adicional opcional
payment.shared.extraInfo=Informação adicional
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Informações adicionais sobre a oferta
payment.shared.extraInfo.prompt.paymentAccount=Defina quaisquer termos especiais, condições ou detalhes que você gostaria de exibir com suas ofertas para esta conta de pagamento (os usuários verão essas informações antes de aceitar as ofertas).
payment.shared.extraInfo.prompt.offer=Defina quaisquer termos, condições ou detalhes especiais que você gostaria de exibir com sua oferta.
payment.shared.extraInfo.noDeposit=Detalhes de contato e termos da oferta
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Abrir página web
payment.f2f.offerbook.tooltip.countryAndCity=País e cidade: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Informação adicional: {0}
payment.shared.extraInfo.tooltip=Informação adicional: {0}
payment.japan.bank=Banco
payment.japan.branch=Agência

View File

@ -2008,11 +2008,14 @@ payment.f2f.city=Город для личной встречи
payment.f2f.city.prompt=Город будет указан в предложении
payment.shared.optionalExtra=Дополнительная необязательная информация
payment.shared.extraInfo=Дополнительная информация
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Дополнительная информация о предложении
payment.shared.extraInfo.prompt.paymentAccount=Определите любые специальные термины, условия или детали, которые вы хотите, чтобы отображались с вашими предложениями для этого платежного аккаунта (пользователи увидят эту информацию перед принятием предложений).
payment.shared.extraInfo.prompt.offer=Определите любые специальные условия, требования или детали, которые вы хотели бы указать в своем предложении.
payment.shared.extraInfo.noDeposit=Контактные данные и условия предложения
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Открыть веб-страницу
payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Дополнительная информация: {0}
payment.shared.extraInfo.tooltip=Дополнительная информация: {0}
payment.japan.bank=Банк
payment.japan.branch=Branch

View File

@ -2008,11 +2008,14 @@ payment.f2f.city=เมืองสำหรับการประชุมแ
payment.f2f.city.prompt=ชื่อเมืองจะแสดงพร้อมกับข้อเสนอ
payment.shared.optionalExtra=ข้อมูลตัวเลือกเพิ่มเติม
payment.shared.extraInfo=ข้อมูลเพิ่มเติม
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=ข้อมูลเพิ่มเติมเกี่ยวกับข้อเสนอ
payment.shared.extraInfo.prompt.paymentAccount=กำหนดคำศัพท์ เงื่อนไข หรือรายละเอียดพิเศษใดๆ ที่คุณต้องการให้แสดงพร้อมข้อเสนอของคุณสำหรับบัญชีการชำระเงินนี้ (ผู้ใช้จะเห็นข้อมูลนี้ก่อนที่จะยอมรับข้อเสนอ)
payment.shared.extraInfo.prompt.offer=กำหนดเงื่อนไขพิเศษ ข้อกำหนด หรือรายละเอียดใด ๆ ที่คุณต้องการแสดงพร้อมกับข้อเสนอของคุณ
payment.shared.extraInfo.noDeposit=รายละเอียดการติดต่อและเงื่อนไขข้อเสนอ
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=เปิดหน้าเว็บ
payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1}
payment.f2f.offerbook.tooltip.extra=ข้อมูลเพิ่มเติม: {0}
payment.shared.extraInfo.tooltip=ข้อมูลเพิ่มเติม: {0}
payment.japan.bank=ธนาคาร
payment.japan.branch=Branch

View File

@ -3015,7 +3015,10 @@ payment.f2f.city='Yüz yüze' buluşma için şehir
payment.f2f.city.prompt=Şehir teklifle birlikte gösterilecektir
payment.shared.optionalExtra=İsteğe bağlı ek bilgi
payment.shared.extraInfo=Ek bilgi
payment.shared.extraInfo.prompt=Bu ödeme hesabınız için tekliflerinize eklemek istediğiniz özel şart, koşul veya detayları tanımlayın (kullanıcılar bu bilgileri teklifleri kabul etmeden önce görecektir).
payment.shared.extraInfo.offer=Ek teklif bilgileri
payment.shared.extraInfo.prompt.paymentAccount=Bu ödeme hesabınız için tekliflerinize eklemek istediğiniz özel şart, koşul veya detayları tanımlayın (kullanıcılar bu bilgileri teklifleri kabul etmeden önce görecektir).
payment.shared.extraInfo.prompt.offer=Teklifinizle birlikte göstermek istediğiniz özel terimleri, koşulları veya detayları tanımlayın.
payment.shared.extraInfo.noDeposit=İletişim detayları ve teklif şartları
payment.f2f.info='Yüz Yüze' ticaretler farklı kurallara sahiptir ve çevrimiçi işlemlerden farklı riskler içerir.\n\n\
Başlıca farklar şunlardır:\n\
Ticaret eşleri, sağlanan iletişim bilgilerini kullanarak buluşma yeri ve zamanını paylaşmalıdır.\n\
@ -3029,7 +3032,7 @@ payment.f2f.info='Yüz Yüze' ticaretler farklı kurallara sahiptir ve çevrimi
ve tavsiyeleri okuyun: [HYPERLINK:https://docs.haveno.exchange/the-project/payment_methods/F2F]
payment.f2f.info.openURL=Web sayfasını
payment.f2f.offerbook.tooltip.countryAndCity=Ülke ve şehir: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Ek bilgi: {0}
payment.shared.extraInfo.tooltip=Ek bilgi: {0}
payment.ifsc=IFS Kodu
payment.ifsc.validation=IFSC formatı: XXXX0999999

View File

@ -2010,11 +2010,14 @@ payment.f2f.city=Thành phố để gặp mặt trực tiếp
payment.f2f.city.prompt=Thành phố sẽ được hiển thị cùng báo giá
payment.shared.optionalExtra=Thông tin thêm tuỳ chọn.
payment.shared.extraInfo=thông tin thêm
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=Thông tin bổ sung về ưu đãi
payment.shared.extraInfo.prompt.paymentAccount=Xác định bất kỳ điều khoản, điều kiện hoặc chi tiết đặc biệt nào bạn muốn hiển thị cùng với các ưu đãi của mình cho tài khoản thanh toán này (người dùng sẽ thấy thông tin này trước khi chấp nhận các ưu đãi).
payment.shared.extraInfo.prompt.offer=Xác định bất kỳ thuật ngữ, điều kiện hoặc chi tiết đặc biệt nào bạn muốn hiển thị cùng với đề nghị của mình.
payment.shared.extraInfo.noDeposit=Chi tiết liên hệ và điều khoản ưu đãi
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\nThe main differences are:\n● The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n● The trading peers need to bring their laptops and do the confirmation of 'payment sent' and 'payment received' at the meeting place.\n● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to an agreement.\n\nTo be sure you fully understand the differences with 'Face to Face' trades please read the instructions and recommendations at: [HYPERLINK:https://docs.haveno.exchange/trading-rules.html#f2f-trading]
payment.f2f.info.openURL=Mở trang web
payment.f2f.offerbook.tooltip.countryAndCity=Country and city: {0} / {1}
payment.f2f.offerbook.tooltip.extra=Thông tin thêm: {0}
payment.shared.extraInfo.tooltip=Thông tin thêm: {0}
payment.japan.bank=Ngân hàng
payment.japan.branch=Branch

View File

@ -2017,11 +2017,14 @@ payment.f2f.city=“面对面”会议的城市
payment.f2f.city.prompt=城市将与报价一同显示
payment.shared.optionalExtra=可选的附加信息
payment.shared.extraInfo=附加信息
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=附加报价信息
payment.shared.extraInfo.prompt.paymentAccount=定义您希望在此支付账户的报价中显示的任何特殊术语、条件或细节(用户在接受报价之前将看到这些信息)。
payment.shared.extraInfo.prompt.offer=定义您希望随您的报价一起显示的任何特殊条款、条件或详细信息。
payment.shared.extraInfo.noDeposit=联系方式和优惠条款
payment.f2f.info=与网上交易相比,“面对面”交易有不同的规则,也有不同的风险。\n\n主要区别是\n●交易伙伴需要使用他们提供的联系方式交换关于会面地点和时间的信息。\n●交易双方需要携带笔记本电脑在会面地点确认“已发送付款”和“已收到付款”。\n●如果交易方有特殊的“条款和条件”他们必须在账户的“附加信息”文本框中声明这些条款和条件。\n●在发生争议时调解员或仲裁员不能提供太多帮助因为通常很难获得有关会面上所发生情况的篡改证据。在这种情况下XMR 资金可能会被无限期锁定,或者直到交易双方达成协议。\n\n为确保您完全理解“面对面”交易的不同之处请阅读以下说明和建议“https://docs.haveno.exchange/trading-rules.html#f2f-trading”
payment.f2f.info.openURL=打开网页
payment.f2f.offerbook.tooltip.countryAndCity=国家或地区及城市:{0} / {1}
payment.f2f.offerbook.tooltip.extra=附加信息:{0}
payment.shared.extraInfo.tooltip=附加信息:{0}
payment.japan.bank=银行
payment.japan.branch=分行

View File

@ -2011,11 +2011,14 @@ payment.f2f.city=“面對面”會議的城市
payment.f2f.city.prompt=城市將與報價一同顯示
payment.shared.optionalExtra=可選的附加信息
payment.shared.extraInfo=附加信息
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.shared.extraInfo.offer=額外的優惠資訊
payment.shared.extraInfo.prompt.paymentAccount=定義您希望在此付款帳戶的報價中顯示的任何特殊術語、條件或細節(用戶在接受報價之前將看到這些資訊)。
payment.shared.extraInfo.prompt.offer=定義您希望在您的報價中顯示的任何特殊條款、條件或詳細資訊。
payment.shared.extraInfo.noDeposit=聯絡詳情及優惠條款
payment.f2f.info=與網上交易相比,“面對面”交易有不同的規則,也有不同的風險。\n\n主要區別是\n●交易夥伴需要使用他們提供的聯繫方式交換關於會面地點和時間的信息。\n●交易雙方需要攜帶筆記本電腦在會面地點確認“已發送付款”和“已收到付款”。\n●如果交易方有特殊的“條款和條件”他們必須在賬户的“附加信息”文本框中聲明這些條款和條件。\n●在發生爭議時調解員或仲裁員不能提供太多幫助因為通常很難獲得有關會面上所發生情況的篡改證據。在這種情況下XMR 資金可能會被無限期鎖定,或者直到交易雙方達成協議。\n\n為確保您完全理解“面對面”交易的不同之處請閲讀以下説明和建議“https://docs.haveno.exchange/trading-rules.html#f2f-trading”
payment.f2f.info.openURL=打開網頁
payment.f2f.offerbook.tooltip.countryAndCity=國家或地區及城市:{0} / {1}
payment.f2f.offerbook.tooltip.extra=附加信息:{0}
payment.shared.extraInfo.tooltip=附加信息:{0}
payment.japan.bank=銀行
payment.japan.branch=分行

View File

@ -73,7 +73,8 @@ public class OfferMaker {
0,
null,
null,
null));
null,
"My extra info"));
public static final Maker<Offer> btcUsdOffer = a(Offer);
}

View File

@ -156,6 +156,7 @@ class GrpcOffersService extends OffersImplBase {
req.getPaymentAccountId(),
req.getIsPrivateOffer(),
req.getBuyerAsTakerWithoutDeposit(),
req.getExtraInfo(),
offer -> {
// This result handling consumer's accept operation will return
// the new offer to the gRPC client after async placement is done.

View File

@ -91,7 +91,7 @@ public class AustraliaPayidForm extends PaymentMethodForm {
});
TextArea extraTextArea = addTopLabelTextArea(gridPane, ++gridRow,
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt")).second;
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt.paymentAccount")).second;
extraTextArea.setMinHeight(70);
((JFXTextArea) extraTextArea).setLabelFloat(false);
extraTextArea.textProperty().addListener((ov, oldValue, newValue) -> {

View File

@ -79,7 +79,7 @@ public class CashAppForm extends PaymentMethodForm {
});
TextArea extraTextArea = addTopLabelTextArea(gridPane, ++gridRow,
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt")).second;
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt.paymentAccount")).second;
extraTextArea.setMinHeight(70);
((JFXTextArea) extraTextArea).setLabelFloat(false);
extraTextArea.textProperty().addListener((ov, oldValue, newValue) -> {

View File

@ -65,7 +65,7 @@ public class F2FForm extends PaymentMethodForm {
textArea.setMinHeight(70);
textArea.setEditable(false);
textArea.setId("text-area-disabled");
textArea.setText(offer.getExtraInfo());
textArea.setText(offer.getPaymentAccountExtraInfo());
return gridRow;
}
@ -106,7 +106,7 @@ public class F2FForm extends PaymentMethodForm {
});
TextArea extraTextArea = addTopLabelTextArea(gridPane, ++gridRow,
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt")).second;
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt.paymentAccount")).second;
extraTextArea.setMinHeight(70);
((JFXTextArea) extraTextArea).setLabelFloat(false);
//extraTextArea.setValidator(f2fValidator);

View File

@ -79,7 +79,7 @@ public class PayPalForm extends PaymentMethodForm {
});
TextArea extraTextArea = addTopLabelTextArea(gridPane, ++gridRow,
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt")).second;
Res.get("payment.shared.optionalExtra"), Res.get("payment.shared.extraInfo.prompt.paymentAccount")).second;
extraTextArea.setMinHeight(70);
((JFXTextArea) extraTextArea).setLabelFloat(false);
extraTextArea.textProperty().addListener((ov, oldValue, newValue) -> {

View File

@ -105,6 +105,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
protected final ObjectProperty<Price> price = new SimpleObjectProperty<>();
protected final ObjectProperty<Volume> volume = new SimpleObjectProperty<>();
protected final ObjectProperty<Volume> minVolume = new SimpleObjectProperty<>();
protected final ObjectProperty<String> extraInfo = new SimpleObjectProperty<>();
// Percentage value of buyer security deposit. E.g. 0.01 means 1% of trade amount
protected final DoubleProperty securityDepositPct = new SimpleDoubleProperty();
@ -305,7 +306,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
securityDepositPct.get(),
paymentAccount,
buyerAsTakerWithoutDeposit.get(), // private offer if buyer as taker without deposit
buyerAsTakerWithoutDeposit.get());
buyerAsTakerWithoutDeposit.get(),
extraInfo.get());
}
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
@ -583,6 +585,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
this.amount.set(amount);
}
protected void setMinAmount(BigInteger minAmount) {
this.minAmount.set(minAmount);
}
protected void setPrice(Price price) {
this.price.set(price);
}
@ -595,6 +601,22 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
this.securityDepositPct.set(value);
}
public void setMarketPriceAvailable(boolean marketPriceAvailable) {
this.marketPriceAvailable = marketPriceAvailable;
}
public void setTriggerPrice(long triggerPrice) {
this.triggerPrice = triggerPrice;
}
public void setReserveExactAmount(boolean reserveExactAmount) {
this.reserveExactAmount = reserveExactAmount;
}
protected void setExtraInfo(String extraInfo) {
this.extraInfo.set(extraInfo);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
@ -627,10 +649,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
return buyerAsTakerWithoutDeposit;
}
protected void setMinAmount(BigInteger minAmount) {
this.minAmount.set(minAmount);
}
public ReadOnlyStringProperty getTradeCurrencyCode() {
return tradeCurrencyCode;
}
@ -670,10 +688,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
return totalToPay;
}
public void setMarketPriceAvailable(boolean marketPriceAvailable) {
this.marketPriceAvailable = marketPriceAvailable;
}
public BigInteger getMaxMakerFee() {
return HavenoUtils.multiply(amount.get(), buyerAsTakerWithoutDeposit.get() ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT);
}
@ -687,11 +701,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
return getSecurityDeposit().compareTo(Restrictions.getMinSecurityDeposit()) <= 0;
}
public void setTriggerPrice(long triggerPrice) {
this.triggerPrice = triggerPrice;
}
public void setReserveExactAmount(boolean reserveExactAmount) {
this.reserveExactAmount = reserveExactAmount;
public ReadOnlyObjectProperty<String> getExtraInfo() {
return extraInfo;
}
}

View File

@ -44,6 +44,7 @@ import haveno.desktop.components.AutoTooltipLabel;
import haveno.desktop.components.BalanceTextField;
import haveno.desktop.components.BusyAnimation;
import haveno.desktop.components.FundsTextField;
import haveno.desktop.components.HavenoTextArea;
import haveno.desktop.components.InfoInputTextField;
import haveno.desktop.components.InputTextField;
import haveno.desktop.components.TitledGroupBg;
@ -75,6 +76,7 @@ import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
@ -126,7 +128,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private ScrollPane scrollPane;
protected GridPane gridPane;
private TitledGroupBg payFundsTitledGroupBg, setDepositTitledGroupBg, paymentTitledGroupBg;
private TitledGroupBg payFundsTitledGroupBg, setDepositTitledGroupBg, extraInfoTitledGroupBg, paymentTitledGroupBg;
protected TitledGroupBg amountTitledGroupBg;
private BusyAnimation waitingForFundsSpinner;
private AutoTooltipButton nextButton, cancelButton1, cancelButton2, placeOfferButton, fundFromSavingsWalletButton;
@ -138,6 +140,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private BalanceTextField balanceTextField;
private ToggleButton reserveExactAmountSlider;
private ToggleButton buyerAsTakerWithoutDepositSlider;
protected TextArea extraInfoTextArea;
private FundsTextField totalToPayTextField;
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
@ -156,10 +159,10 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
securityDepositFocusedListener, priceFocusedListener, placeOfferCompletedListener,
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
isMinSecurityDepositListener, buyerAsTakerWithoutDepositListener, triggerPriceFocusedListener;
isMinSecurityDepositListener, buyerAsTakerWithoutDepositListener, triggerPriceFocusedListener, extraInfoFocusedListener;
private ChangeListener<BigInteger> missingCoinListener;
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
marketPriceMarginListener, volumeListener, securityDepositInXMRListener;
marketPriceMarginListener, volumeListener, securityDepositInXMRListener, extraInfoListener;
private ChangeListener<Number> marketPriceAvailableListener;
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler, paymentAccountsComboBoxSelectionHandler;
private OfferView.CloseHandler closeHandler;
@ -202,6 +205,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
addPaymentGroup();
addAmountPriceGroup();
addOptionsGroup();
addExtraInfoGroup();
addNextButtons();
addFundingGroup();
createListeners();
@ -257,6 +262,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
triggerPriceInfoInputTextField.setContentForPopOver(popOverLabel, AwesomeIcon.SHIELD);
buyerAsTakerWithoutDepositSlider.setSelected(model.dataModel.getBuyerAsTakerWithoutDeposit().get());
extraInfoTextArea.setText(model.dataModel.extraInfo.get());
}
}
@ -389,6 +396,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
buyerAsTakerWithoutDepositSlider.setVisible(false);
buyerAsTakerWithoutDepositSlider.setManaged(false);
extraInfoTitledGroupBg.setVisible(false);
extraInfoTitledGroupBg.setManaged(false);
extraInfoTextArea.setVisible(false);
extraInfoTextArea.setManaged(false);
updateQrCode();
model.onShowPayFundsScreen(() -> {
@ -571,6 +583,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
securityDepositLabel.textProperty().bind(model.securityDepositLabel);
tradeFeeInXmrLabel.textProperty().bind(model.tradeFeeInXmrWithFiat);
tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription);
extraInfoTextArea.textProperty().bindBidirectional(model.extraInfo);
// Validation
amountTextField.validationResultProperty().bind(model.amountValidationResult);
@ -621,6 +634,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
tradeFeeDescriptionLabel.textProperty().unbind();
tradeFeeInXmrLabel.visibleProperty().unbind();
tradeFeeDescriptionLabel.visibleProperty().unbind();
extraInfoTextArea.textProperty().unbindBidirectional(model.extraInfo);
// Validation
amountTextField.validationResultProperty().unbind();
@ -694,11 +708,14 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
model.onFocusOutSecurityDepositTextField(oldValue, newValue);
securityDepositInputTextField.setText(model.securityDeposit.get());
};
triggerPriceFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutTriggerPriceTextField(oldValue, newValue);
triggerPriceInputTextField.setText(model.triggerPrice.get());
};
extraInfoFocusedListener = (observable, oldValue, newValue) -> {
model.onFocusOutExtraInfoTextField(oldValue, newValue);
extraInfoTextArea.setText(model.extraInfo.get());
};
errorMessageListener = (o, oldValue, newValue) -> {
if (model.createOfferCanceled) return;
@ -822,6 +839,12 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
buyerAsTakerWithoutDepositListener = ((observable, oldValue, newValue) -> {
updateSecurityDepositLabels();
});
extraInfoListener = (observable, oldValue, newValue) -> {
if (newValue != null && !newValue.equals("")) {
// no action
}
};
}
private void updateSecurityDepositLabels() {
@ -881,6 +904,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
model.securityDepositInXMR.addListener(securityDepositInXMRListener);
model.isMinSecurityDeposit.addListener(isMinSecurityDepositListener);
model.getDataModel().buyerAsTakerWithoutDeposit.addListener(buyerAsTakerWithoutDepositListener);
model.getDataModel().extraInfo.addListener(extraInfoListener);
// focus out
amountTextField.focusedProperty().addListener(amountFocusedListener);
@ -890,6 +914,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
securityDepositInputTextField.focusedProperty().addListener(securityDepositFocusedListener);
extraInfoTextArea.focusedProperty().addListener(extraInfoFocusedListener);
// notifications
model.getDataModel().getShowWalletFundedNotification().addListener(getShowWalletFundedNotificationListener);
@ -914,6 +939,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
model.securityDepositInXMR.removeListener(securityDepositInXMRListener);
model.isMinSecurityDeposit.removeListener(isMinSecurityDepositListener);
model.getDataModel().buyerAsTakerWithoutDeposit.removeListener(buyerAsTakerWithoutDepositListener);
model.getDataModel().extraInfo.removeListener(extraInfoListener);
// focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener);
@ -923,6 +949,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
securityDepositInputTextField.focusedProperty().removeListener(securityDepositFocusedListener);
extraInfoTextArea.focusedProperty().removeListener(extraInfoFocusedListener);
// notifications
model.getDataModel().getShowWalletFundedNotification().removeListener(getShowWalletFundedNotificationListener);
@ -1062,7 +1089,30 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
});
GridPane.setHalignment(buyerAsTakerWithoutDepositSlider, HPos.LEFT);
GridPane.setMargin(buyerAsTakerWithoutDepositSlider, new Insets(0, 0, 0, 0));
}
private void addExtraInfoGroup() {
extraInfoTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 1,
Res.get("payment.shared.optionalExtra"), 25 + heightAdjustment);
GridPane.setColumnSpan(extraInfoTitledGroupBg, 3);
extraInfoTextArea = new HavenoTextArea();
extraInfoTextArea.setPromptText(Res.get("payment.shared.extraInfo.prompt.offer"));
extraInfoTextArea.getStyleClass().add("text-area");
extraInfoTextArea.setWrapText(true);
extraInfoTextArea.setPrefHeight(75);
extraInfoTextArea.setMinHeight(75);
extraInfoTextArea.setMaxHeight(75);
GridPane.setRowIndex(extraInfoTextArea, gridRow);
GridPane.setColumnSpan(extraInfoTextArea, GridPane.REMAINING);
GridPane.setColumnIndex(extraInfoTextArea, 0);
GridPane.setHalignment(extraInfoTextArea, HPos.LEFT);
GridPane.setMargin(extraInfoTextArea, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
gridPane.getChildren().add(extraInfoTextArea);
}
private void addNextButtons() {
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
Res.get("shared.nextStep"), Res.get("shared.cancel"));
nextButton = (AutoTooltipButton) tuple.first;
@ -1105,16 +1155,26 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
protected void hideOptionsGroup() {
setDepositTitledGroupBg.setVisible(false);
setDepositTitledGroupBg.setManaged(false);
nextButton.setVisible(false);
nextButton.setManaged(false);
cancelButton1.setVisible(false);
cancelButton1.setManaged(false);
securityDepositAndFeeBox.setVisible(false);
securityDepositAndFeeBox.setManaged(false);
buyerAsTakerWithoutDepositSlider.setVisible(false);
buyerAsTakerWithoutDepositSlider.setManaged(false);
}
protected void hideExtraInfoGroup() {
extraInfoTitledGroupBg.setVisible(false);
extraInfoTitledGroupBg.setManaged(false);
extraInfoTextArea.setVisible(false);
extraInfoTextArea.setManaged(false);
}
protected void hideNextButtons() {
nextButton.setVisible(false);
nextButton.setManaged(false);
cancelButton1.setVisible(false);
cancelButton1.setManaged(false);
}
private VBox getSecurityDepositBox() {
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
Res.get("createOffer.securityDeposit.prompt"));

View File

@ -144,6 +144,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
final StringProperty waitingForFundsText = new SimpleStringProperty("");
final StringProperty triggerPriceDescription = new SimpleStringProperty("");
final StringProperty percentagePriceDescription = new SimpleStringProperty("");
final StringProperty extraInfo = new SimpleStringProperty("");
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty();
@ -166,6 +167,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
private ChangeListener<String> priceStringListener, marketPriceMarginStringListener;
private ChangeListener<String> volumeStringListener;
private ChangeListener<String> securityDepositStringListener;
private ChangeListener<String> extraInfoStringListener;
private ChangeListener<BigInteger> amountListener;
private ChangeListener<BigInteger> minAmountListener;
@ -238,6 +240,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
dataModel.calculateTotalToPay();
updateButtonDisableState();
updateSpinnerInfo();
setExtraInfoToModel();
}, 100, TimeUnit.MILLISECONDS);
}
@ -498,6 +501,14 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
updateButtonDisableState();
};
extraInfoStringListener = (ov, oldValue, newValue) -> {
if (newValue != null) {
extraInfo.set(newValue);
} else {
extraInfo.set("");
}
};
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
@ -542,6 +553,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
dataModel.getUseMarketBasedPrice().addListener(useMarketBasedPriceListener);
volume.addListener(volumeStringListener);
securityDeposit.addListener(securityDepositStringListener);
extraInfo.addListener(extraInfoStringListener);
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
dataModel.getAmount().addListener(amountListener);
@ -550,6 +562,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
dataModel.getVolume().addListener(volumeListener);
dataModel.getSecurityDepositPct().addListener(securityDepositAsDoubleListener);
dataModel.getBuyerAsTakerWithoutDeposit().addListener(buyerAsTakerWithoutDepositListener);
dataModel.getExtraInfo().addListener(extraInfoStringListener);
// dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
dataModel.getIsXmrWalletFunded().addListener(isWalletFundedListener);
@ -565,6 +578,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
dataModel.getUseMarketBasedPrice().removeListener(useMarketBasedPriceListener);
volume.removeListener(volumeStringListener);
securityDeposit.removeListener(securityDepositStringListener);
extraInfo.removeListener(extraInfoStringListener);
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
dataModel.getAmount().removeListener(amountListener);
@ -827,6 +841,12 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
}
}
public void onFocusOutExtraInfoTextField(boolean oldValue, boolean newValue) {
if (oldValue && !newValue) {
dataModel.setExtraInfo(extraInfo.get());
}
}
void onFocusOutTriggerPriceTextField(boolean oldValue, boolean newValue) {
if (oldValue && !newValue) {
onTriggerPriceTextFieldChanged();
@ -1233,6 +1253,14 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
}
}
private void setExtraInfoToModel() {
if (extraInfo.get() != null && !extraInfo.get().isEmpty()) {
dataModel.setExtraInfo(extraInfo.get());
} else {
dataModel.setExtraInfo(null);
}
}
private void validateAndSetSecurityDepositToModel() {
// If the security deposit in the model is not valid percent
String value = FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get());

View File

@ -483,8 +483,6 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
if (countryCode != null) {
result += "\n" + Res.get("payment.f2f.offerbook.tooltip.countryAndCity",
CountryUtil.getNameByCode(countryCode), offer.getF2FCity());
result += "\n" + Res.get("payment.f2f.offerbook.tooltip.extra", offer.getExtraInfo());
}
} else {
if (countryCode != null) {
@ -514,6 +512,8 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
result += "\n" + Res.getWithCol("shared.acceptedBanks") + " " + Joiner.on(", ").join(acceptedBanks);
}
}
if (offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty())
result += "\n" + Res.get("payment.shared.extraInfo.tooltip", offer.getCombinedExtraInfo());
}
return result;
}

View File

@ -69,6 +69,7 @@ import static haveno.desktop.util.FormBuilder.add2ButtonsWithBox;
import static haveno.desktop.util.FormBuilder.addAddressTextField;
import static haveno.desktop.util.FormBuilder.addBalanceTextField;
import static haveno.desktop.util.FormBuilder.addComboBoxTopLabelTextField;
import static haveno.desktop.util.FormBuilder.addCompactTopLabelTextArea;
import static haveno.desktop.util.FormBuilder.addFundsTextfield;
import static haveno.desktop.util.FormBuilder.addTitledGroupBg;
import static haveno.desktop.util.FormBuilder.getEditableValueBox;
@ -98,6 +99,7 @@ import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
@ -125,6 +127,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private GridPane gridPane;
private TitledGroupBg noFundingRequiredTitledGroupBg;
private Label noFundingRequiredLabel;
private int lastGridRowNoFundingRequired;
private TitledGroupBg payFundsTitledGroupBg;
private TitledGroupBg advancedOptionsGroup;
private VBox priceAsPercentageInputBox, amountRangeBox;
@ -132,6 +135,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
priceAsPercentageValueCurrencyBox, minAmountValueCurrencyBox, advancedOptionsBox,
takeOfferBox, buttonBox, firstRowHBox;
private ComboBox<PaymentAccount> paymentAccountsComboBox;
private TextArea extraInfoTextArea;
private Label amountDescriptionLabel,
paymentMethodLabel,
priceCurrencyLabel, priceAsPercentageLabel,
@ -160,7 +164,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private int gridRow = 0;
private final HashMap<String, Boolean> paymentAccountWarningDisplayed = new HashMap<>();
private boolean offerDetailsWindowDisplayed, zelleWarningDisplayed, fasterPaymentsWarningDisplayed,
private boolean offerDetailsWindowDisplayed, extraInfoPopupDisplayed, zelleWarningDisplayed, fasterPaymentsWarningDisplayed,
takeOfferFromUnsignedAccountWarningDisplayed, payByMailWarningDisplayed, cashAtAtmWarningDisplayed,
australiaPayidWarningDisplayed, paypalWarningDisplayed, cashAppWarningDisplayed, F2FWarningDisplayed;
private SimpleBooleanProperty errorPopupDisplayed;
@ -267,16 +271,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
balanceTextField.setTargetAmount(model.dataModel.getTotalToPay().get());
maybeShowExtraInfoPopup(model.dataModel.getOffer());
maybeShowTakeOfferFromUnsignedAccountWarning(model.dataModel.getOffer());
maybeShowZelleWarning(lastPaymentAccount);
maybeShowFasterPaymentsWarning(lastPaymentAccount);
maybeShowAccountWarning(lastPaymentAccount, model.dataModel.isBuyOffer());
maybeShowPayByMailWarning(lastPaymentAccount, model.dataModel.getOffer());
maybeShowCashAtAtmWarning(lastPaymentAccount, model.dataModel.getOffer());
maybeShowAustraliaPayidWarning(lastPaymentAccount, model.dataModel.getOffer());
maybeShowPayPalWarning(lastPaymentAccount, model.dataModel.getOffer());
maybeShowCashAppWarning(lastPaymentAccount, model.dataModel.getOffer());
maybeShowF2FWarning(lastPaymentAccount, model.dataModel.getOffer());
if (!model.isRange()) {
nextButton.setVisible(false);
@ -358,6 +357,26 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
new Popup().warning(Res.get("takeOffer.noPriceFeedAvailable"))
.onClose(() -> close(false))
.show();
if (offer.hasBuyerAsTakerWithoutDeposit() && offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty()) {
// attach extra info text area
extraInfoTextArea = addCompactTopLabelTextArea(gridPane, ++lastGridRowNoFundingRequired, Res.get("payment.shared.extraInfo.noDeposit"), "").second;
extraInfoTextArea.setText(offer.getCombinedExtraInfo());
extraInfoTextArea.getStyleClass().add("text-area");
extraInfoTextArea.setWrapText(true);
extraInfoTextArea.setPrefHeight(75);
extraInfoTextArea.setMinHeight(75);
extraInfoTextArea.setMaxHeight(150);
extraInfoTextArea.setEditable(false);
GridPane.setRowIndex(extraInfoTextArea, lastGridRowNoFundingRequired);
GridPane.setColumnSpan(extraInfoTextArea, GridPane.REMAINING);
GridPane.setColumnIndex(extraInfoTextArea, 0);
// move up take offer buttons
GridPane.setRowIndex(takeOfferBox, lastGridRowNoFundingRequired + 1);
GridPane.setMargin(takeOfferBox, new Insets(15, 0, 0, 0));
}
}
@Override
@ -871,6 +890,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
noFundingRequiredLabel.setPadding(new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
GridPane.setHalignment(noFundingRequiredLabel, HPos.LEFT);
gridPane.getChildren().add(noFundingRequiredLabel);
lastGridRowNoFundingRequired = gridRow;
// funding title
payFundsTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 3,
@ -1121,6 +1141,21 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private void maybeShowExtraInfoPopup(Offer offer) {
if (offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty() && !extraInfoPopupDisplayed) {
extraInfoPopupDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getCombinedExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowTakeOfferFromUnsignedAccountWarning(Offer offer) {
// warn if you are selling BTC to unsigned account (#5343)
@ -1151,108 +1186,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
OfferViewUtil.showPaymentAccountWarning(msgKey, paymentAccountWarningDisplayed);
}
private void maybeShowPayByMailWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.PAY_BY_MAIL_ID) &&
!payByMailWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
payByMailWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowCashAtAtmWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.CASH_AT_ATM_ID) &&
!cashAtAtmWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
cashAtAtmWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowAustraliaPayidWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.AUSTRALIA_PAYID_ID) &&
!australiaPayidWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
australiaPayidWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowPayPalWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.PAYPAL_ID) &&
!paypalWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
paypalWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowCashAppWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.CASH_APP_ID) &&
!cashAppWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
cashAppWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private void maybeShowF2FWarning(PaymentAccount paymentAccount, Offer offer) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.F2F_ID) &&
!F2FWarningDisplayed && !offer.getExtraInfo().isEmpty()) {
F2FWarningDisplayed = true;
UserThread.runAfter(() -> {
new GenericMessageWindow()
.preamble(Res.get("payment.tradingRestrictions"))
.instruction(offer.getExtraInfo())
.actionButtonText(Res.get("shared.iConfirm"))
.closeButtonText(Res.get("shared.close"))
.width(Layout.INITIAL_WINDOW_WIDTH)
.onClose(() -> close(false))
.show();
}, 500, TimeUnit.MILLISECONDS);
}
}
private Tuple2<Label, VBox> getTradeInputBox(HBox amountValueBox, String promptText) {
Label descriptionLabel = new AutoTooltipLabel(promptText);
descriptionLabel.setId("input-description-label");

View File

@ -177,19 +177,14 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
List<String> acceptedCountryCodes = offer.getAcceptedCountryCodes();
boolean showAcceptedCountryCodes = acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty();
boolean isF2F = offer.getPaymentMethod().equals(PaymentMethod.F2F);
boolean showExtraInfo = offer.getPaymentMethod().equals(PaymentMethod.F2F) ||
offer.getPaymentMethod().equals(PaymentMethod.PAY_BY_MAIL) ||
offer.getPaymentMethod().equals(PaymentMethod.AUSTRALIA_PAYID)||
offer.getPaymentMethod().equals(PaymentMethod.PAYPAL)||
offer.getPaymentMethod().equals(PaymentMethod.CASH_APP) ||
offer.getPaymentMethod().equals(PaymentMethod.CASH_AT_ATM);
boolean showOfferExtraInfo = offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty();
if (!takeOfferHandlerOptional.isPresent())
rows++;
if (showAcceptedBanks)
rows++;
if (showAcceptedCountryCodes)
rows++;
if (showExtraInfo)
if (showOfferExtraInfo)
rows++;
if (isF2F)
rows++;
@ -320,9 +315,9 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
if (isF2F) {
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("payment.f2f.city"), offer.getF2FCity());
}
if (showExtraInfo) {
if (showOfferExtraInfo) {
TextArea textArea = addConfirmationLabelTextArea(gridPane, ++rowIndex, Res.get("payment.shared.extraInfo"), "", 0).second;
textArea.setText(offer.getExtraInfo());
textArea.setText(offer.getCombinedExtraInfo());
textArea.setMaxHeight(200);
textArea.sceneProperty().addListener((o, oldScene, newScene) -> {
if (newScene != null) {

View File

@ -38,6 +38,7 @@ import haveno.core.xmr.wallet.BtcWalletService;
import haveno.desktop.components.HavenoTextArea;
import haveno.desktop.main.MainView;
import haveno.desktop.main.overlays.Overlay;
import haveno.desktop.util.CssTheme;
import haveno.desktop.util.DisplayUtils;
import static haveno.desktop.util.DisplayUtils.getAccountWitnessDescription;
import static haveno.desktop.util.FormBuilder.add2ButtonsWithBox;
@ -47,6 +48,8 @@ import static haveno.desktop.util.FormBuilder.addLabelTxIdTextField;
import static haveno.desktop.util.FormBuilder.addTitledGroupBg;
import haveno.desktop.util.Layout;
import haveno.network.p2p.NodeAddress;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
@ -165,9 +168,12 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
// second group
rows = 7;
if (offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty())
rows++;
PaymentAccountPayload buyerPaymentAccountPayload = null;
PaymentAccountPayload sellerPaymentAccountPayload = null;
if (contract != null) {
rows++;
@ -219,6 +225,29 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradePeersOnion"),
trade.getTradePeerNodeAddress().getFullAddress());
if (offer.getCombinedExtraInfo() != null && !offer.getCombinedExtraInfo().isEmpty()) {
TextArea textArea = addConfirmationLabelTextArea(gridPane, ++rowIndex, Res.get("payment.shared.extraInfo.offer"), "", 0).second;
textArea.setText(offer.getCombinedExtraInfo());
textArea.setMaxHeight(200);
textArea.sceneProperty().addListener((o, oldScene, newScene) -> {
if (newScene != null) {
// avoid javafx css warning
CssTheme.loadSceneStyles(newScene, CssTheme.CSS_THEME_LIGHT, false);
textArea.applyCss();
var text = textArea.lookup(".text");
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
return textArea.getFont().getSize() + text.getBoundsInLocal().getHeight();
}, text.boundsInLocalProperty()));
text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
Platform.runLater(() -> textArea.requestLayout());
});
}
});
textArea.setEditable(false);
}
if (contract != null) {
buyersAccountAge = getAccountWitnessDescription(accountAgeWitnessService, offer.getPaymentMethod(), buyerPaymentAccountPayload, contract.getBuyerPubKeyRing());
sellersAccountAge = getAccountWitnessDescription(accountAgeWitnessService, offer.getPaymentMethod(), sellerPaymentAccountPayload, contract.getSellerPubKeyRing());

View File

@ -166,6 +166,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
if (offer.isUseMarketBasedPrice()) {
setMarketPriceMarginPct(offer.getMarketPriceMarginPct());
}
setExtraInfo(offer.getOfferExtraInfo());
}
public void onStartEditOffer(ErrorMessageHandler errorMessageHandler) {
@ -216,7 +217,8 @@ class EditOfferDataModel extends MutableOfferDataModel {
offerPayload.getProtocolVersion(),
offerPayload.getArbitratorSigner(),
offerPayload.getArbitratorSignature(),
offerPayload.getReserveTxKeyImages());
offerPayload.getReserveTxKeyImages(),
newOfferPayload.getExtraInfo());
final Offer editedOffer = new Offer(editedPayload);
editedOffer.setPriceFeedService(priceFeedService);

View File

@ -90,6 +90,7 @@ public class EditOfferView extends MutableOfferView<EditOfferViewModel> {
addBindings();
hideOptionsGroup();
hideNextButtons();
// Lock amount field as it would require bigger changes to support increased amount values.
amountTextField.setDisable(true);
@ -178,7 +179,7 @@ public class EditOfferView extends MutableOfferView<EditOfferViewModel> {
private void addConfirmEditGroup() {
int tmpGridRow = 4;
int tmpGridRow = 6;
final Tuple4<Button, BusyAnimation, Label, HBox> editOfferTuple = addButtonBusyAnimationLabelAfterGroup(gridPane, tmpGridRow++, Res.get("editOffer.confirmEdit"));
final HBox editOfferConfirmationBox = editOfferTuple.fourth;

View File

@ -94,6 +94,7 @@ public class TradesChartsViewModelTest {
0,
null,
null,
null,
null);
@BeforeEach

View File

@ -632,6 +632,7 @@ public class OfferBookViewModelTest {
0,
null,
null,
null,
null));
}
}

View File

@ -111,6 +111,7 @@ public class OfferMaker {
lookup.valueOf(protocolVersion, 0),
getLocalHostNodeWithPort(99999),
null,
null,
null));
public static final Maker<Offer> xmrUsdOffer = a(Offer);

View File

@ -527,6 +527,7 @@ message PostOfferRequest {
string payment_account_id = 11;
bool is_private_offer = 12;
bool buyer_as_taker_without_deposit = 13;
string extra_info = 14;
}
message PostOfferReply {
@ -574,6 +575,7 @@ message OfferInfo {
uint64 split_output_tx_fee = 31 [jstype = JS_STRING];
bool is_private_offer = 32;
string challenge = 33;
string extra_info = 34;
}
message AvailabilityResultWithDescription {

View File

@ -657,6 +657,7 @@ message OfferPayload {
NodeAddress arbitrator_signer = 36;
bytes arbitrator_signature = 37;
repeated string reserve_tx_key_images = 38;
string extra_info = 39;
}
enum OfferDirection {