/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.portfolio.pendingtrades.steps.buyer;
import io.bitsquare.app.DevFlags;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.gui.components.BusyAnimation;
import io.bitsquare.gui.components.TextFieldWithCopyIcon;
import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.components.paymentmethods.*;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.payment.*;
import io.bitsquare.trade.Trade;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import static io.bitsquare.gui.util.FormBuilder.*;
public class BuyerStep2View extends TradeStepView {
private Button confirmButton;
private Label statusLabel;
private BusyAnimation busyAnimation;
private Subscription tradeStatePropertySubscription;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialisation
///////////////////////////////////////////////////////////////////////////////////////////
public BuyerStep2View(PendingTradesViewModel model) {
super(model);
}
@Override
public void activate() {
super.activate();
//TODO we get called twice, check why
if (tradeStatePropertySubscription == null) {
tradeStatePropertySubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) {
PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData();
String key = "startPayment" + trade.getId();
String message = "";
if (paymentAccountContractData instanceof CryptoCurrencyAccountContractData)
message = "Your trade has reached at least one blockchain confirmation.\n" +
"(You can wait for more confirmations if you want - 6 confirmations are considered as very secure.)\n\n" +
"Please transfer from your external " +
CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()) + " wallet\n" +
model.formatter.formatVolumeWithCode(trade.getTradeVolume()) + " to the BTC seller.\n\n" +
"Here are the trading account details of the bitcoin seller:\n" +
"" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n\n" +
"(You can copy & paste the values from the main screen after closing that popup.)";
else if (paymentAccountContractData != null)
if (paymentAccountContractData instanceof CashDepositAccountContractData)
message = "Your trade has reached at least one blockchain confirmation.\n" +
"(You can wait for more confirmations if you want - 6 confirmations are considered as very secure.)\n\n" +
"Please go to a bank and pay " +
model.formatter.formatVolumeWithCode(trade.getTradeVolume()) + " to the BTC seller.\n\n" +
"Here are the trading account details of the BTC seller:\n" +
"" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n" +
"(You can copy & paste the values from the main screen after closing that popup.)\n\n" +
"Please don't forget to add the trade ID \"" + trade.getShortId() +
"\" as \"reason for payment\" so the receiver can assign your payment to this trade.\n\n" +
"DO NOT use any additional notice in the \"reason for payment\" text like " +
"Bitcoin, Btc or Bitsquare.\n\n" +
"If your bank charges fees you have to cover those fees.\n\n" +
"IMPORTANT REQUIREMENT:\n" +
"After you have done the payment write on the paper receipt: NO REFUNDS.\n" +
"Then tear it in 2 parts, make a photo and send it to the BTC seller's email address.";
else if (paymentAccountContractData instanceof USPostalMoneyOrderAccountContractData)
message = "Your trade has reached at least one blockchain confirmation.\n" +
"(You can wait for more confirmations if you want - 6 confirmations are considered as very secure.)\n\n" +
"Please send " +
model.formatter.formatVolumeWithCode(trade.getTradeVolume()) + " by \"US Postal Money Order\" to the BTC seller.\n\n" +
"Here are the trading account details of the BTC seller:\n" +
"" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n" +
"(You can copy & paste the values from the main screen after closing that popup.)\n\n" +
"Please don't forget to add the trade ID \"" + trade.getShortId() +
"\" as \"reason for payment\" so the receiver can assign your payment to this trade.\n\n" +
"DO NOT use any additional notice in the \"reason for payment\" text like " +
"Bitcoin, Btc or Bitsquare.";
else
message = "Your trade has reached at least one blockchain confirmation.\n" +
"(You can wait for more confirmations if you want - 6 confirmations are considered as very secure.)\n\n" +
"Please go to your online banking web page and pay " +
model.formatter.formatVolumeWithCode(trade.getTradeVolume()) + " to the BTC seller.\n\n" +
"Here are the trading account details of the BTC seller:\n" +
"" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n" +
"(You can copy & paste the values from the main screen after closing that popup.)\n\n" +
"Please don't forget to add the trade ID \"" + trade.getShortId() +
"\" as \"reason for payment\" so the receiver can assign your payment to this trade.\n\n" +
"DO NOT use any additional notice in the \"reason for payment\" text like " +
"Bitcoin, Btc or Bitsquare.\n\n" +
"If your bank charges fees you have to cover those fees.";
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
preferences.dontShowAgain(key, true);
new Popup().headLine("Attention required for trade with ID " + trade.getShortId())
.attention(message)
.show();
}
} else if (state == Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED && confirmButton.isDisabled()) {
showStatusInfo();
} else if (state == Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG) {
hideStatusInfo();
}
});
}
}
@Override
public void deactivate() {
super.deactivate();
hideStatusInfo();
if (tradeStatePropertySubscription != null) {
tradeStatePropertySubscription.unsubscribe();
tradeStatePropertySubscription = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Content
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void addContent() {
addTradeInfoBlock();
PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData();
String paymentMethodName = paymentAccountContractData != null ? paymentAccountContractData.getPaymentMethodName() : "";
TitledGroupBg accountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 1,
"Start payment using " + BSResources.get(paymentMethodName),
Layout.GROUP_DISTANCE);
TextFieldWithCopyIcon field = addLabelTextFieldWithCopyIcon(gridPane, gridRow, "Amount to transfer:",
model.getFiatVolume(),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
field.setCopyWithoutCurrencyPostFix(true);
switch (paymentMethodName) {
case PaymentMethod.OK_PAY_ID:
gridRow = OKPayForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.PERFECT_MONEY_ID:
gridRow = PerfectMoneyForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.SEPA_ID:
gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.FASTER_PAYMENTS_ID:
gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.NATIONAL_BANK_ID:
gridRow = NationalBankForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.SAME_BANK_ID:
gridRow = SameBankForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.SPECIFIC_BANKS_ID:
gridRow = SpecificBankForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.SWISH_ID:
gridRow = SwishForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.ALI_PAY_ID:
gridRow = AliPayForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.CLEAR_X_CHANGE_ID:
gridRow = ClearXchangeForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.CHASE_QUICK_PAY_ID:
gridRow = ChaseQuickPayForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.INTERAC_E_TRANSFER_ID:
gridRow = InteracETransferForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.US_POSTAL_MONEY_ORDER_ID:
gridRow = USPostalMoneyOrderForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.CASH_DEPOSIT_ID:
gridRow = CashDepositForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData);
break;
case PaymentMethod.BLOCK_CHAINS_ID:
String labelTitle = "Sellers " + CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()) + " address:";
gridRow = CryptoCurrencyForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData, labelTitle);
break;
default:
log.error("Not supported PaymentMethod: " + paymentMethodName);
}
if (!(paymentAccountContractData instanceof CryptoCurrencyAccountContractData))
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Reason for payment:", model.dataModel.getReference());
GridPane.setRowSpan(accountTitledGroupBg, gridRow - 3);
Tuple3<Button, BusyAnimation, Label> tuple3 = addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow, "Payment started");
confirmButton = tuple3.first;
confirmButton.setOnAction(e -> onPaymentStarted());
busyAnimation = tuple3.second;
statusLabel = tuple3.third;
hideStatusInfo();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Warning
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected String getWarningText() {
setWarningHeadline();
return "You still have not done your " + model.dataModel.getCurrencyCode() + " payment!\n" +
"Please note that the trade has to be completed until " +
model.getDateForOpenDispute() +
" otherwise the trade will be investigated by the arbitrator.";
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dispute
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected String getOpenForDisputeText() {
return "You have not completed your payment!\n" +
"The max. period for the trade has elapsed.\n" +
"\nPlease contact the arbitrator for opening a dispute.";
}
@Override
protected void applyOnDisputeOpened() {
confirmButton.setDisable(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI Handlers
///////////////////////////////////////////////////////////////////////////////////////////
private void onPaymentStarted() {
if (model.p2PService.isBootstrapped()) {
if (model.dataModel.getSellersPaymentAccountContractData() instanceof CashDepositAccountContractData) {
String key = "confirmPaperReceiptSent";
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
Popup popup = new Popup();
popup.headLine("Did you sent the paper receipt to the BTC seller?")
.feedback("Remember:\n" +
"You need to write on the paper receipt: NO REFUNDS.\n" +
"Then tear it in 2 parts, make a photo and send it to the BTC seller's email address.")
.actionButtonText("Yes, I have sent the paper receipt")
.onAction(this::showConfirmPaymentStartedPopup)
.closeButtonText("No")
.onClose(popup::hide)
.dontShowAgainId(key, preferences)
.show();
} else {
showConfirmPaymentStartedPopup();
}
} else {
showConfirmPaymentStartedPopup();
}
} else {
new Popup().information("You need to wait until you are fully connected to the network.\n" +
"That might take up to about 2 minutes at startup.").show();
}
}
private void showConfirmPaymentStartedPopup() {
String key = "confirmPaymentStarted";
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
Popup popup = new Popup();
popup.headLine("Confirm that you have started the payment")
.confirmation("Did you initiate the " + CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()) +
" payment to your trading partner?")
.width(700)
.actionButtonText("Yes, I have started the payment")
.onAction(this::confirmPaymentStarted)
.closeButtonText("No")
.onClose(popup::hide)
.dontShowAgainId(key, preferences)
.show();
} else {
confirmPaymentStarted();
}
}
private void confirmPaymentStarted() {
confirmButton.setDisable(true);
showStatusInfo();
model.dataModel.onPaymentStarted(() -> {
// In case the first send failed we got the support button displayed.
// If it succeeds at a second try we remove the support button again.
//TODO check for support. in case of a dispute we dont want to hide the button
//if (notificationGroup != null)
// notificationGroup.setButtonVisible(false);
}, errorMessage -> {
confirmButton.setDisable(false);
hideStatusInfo();
new Popup().warning("Sending message to your trading partner failed.\n" +
"Please try again and if it continue to fail report a bug.").show();
});
}
private void showStatusInfo() {
busyAnimation.play();
statusLabel.setText("Sending confirmation...");
}
private void hideStatusInfo() {
busyAnimation.stop();
statusLabel.setText("");
}
}