refactor payment account form api to support structured, dynamic forms

This commit is contained in:
woodser 2022-06-16 21:51:22 -04:00
parent 6640dcb2ee
commit b5d4de3528
3 changed files with 442 additions and 25 deletions

View File

@ -5,7 +5,7 @@ import HavenoClient from "./HavenoClient";
import HavenoError from "./utils/HavenoError"; import HavenoError from "./utils/HavenoError";
import HavenoUtils from "./utils/HavenoUtils"; import HavenoUtils from "./utils/HavenoUtils";
import { MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo } from "./protobuf/grpc_pb"; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb import { MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo } from "./protobuf/grpc_pb"; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
import { Attachment, DisputeResult, PaymentMethod, PaymentAccount, MoneroNodeSettings } from "./protobuf/pb_pb"; import { Attachment, DisputeResult, PaymentMethod, PaymentAccountForm, PaymentAccountFormField, PaymentAccount, MoneroNodeSettings} from "./protobuf/pb_pb";
import { XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer } from "./protobuf/grpc_pb"; import { XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer } from "./protobuf/grpc_pb";
import AuthenticationStatus = UrlConnection.AuthenticationStatus; import AuthenticationStatus = UrlConnection.AuthenticationStatus;
import OnlineStatus = UrlConnection.OnlineStatus; import OnlineStatus = UrlConnection.OnlineStatus;
@ -444,7 +444,7 @@ test("Can manage Monero daemon connections", async () => {
await monerod2.stopProcess(); await monerod2.stopProcess();
// test auto switch after periodic connection check // test auto switch after periodic connection check
await wait(TestConfig.daemonPollPeriodMs); await wait(TestConfig.daemonPollPeriodMs * 2);
connection = await charlie.getMoneroConnection(); connection = await charlie.getMoneroConnection();
testConnection(connection!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1); testConnection(connection!, monerodUrl1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
@ -852,6 +852,64 @@ test("Can get payment accounts", async () => {
} }
}); });
// TODO: rename ClearXChange to Zelle
// TODO: FieldId represented as number
test("Can validate payment account forms", async () => {
// supported payment methods
const expectedPaymentMethods = ["REVOLUT", "SEPA", "TRANSFERWISE", "CLEAR_X_CHANGE", "SWIFT", "F2F", "STRIKE"];
// get payment methods
const paymentMethods = await alice.getPaymentMethods();
expect(paymentMethods.length).toEqual(expectedPaymentMethods.length);
for (const paymentMethod of paymentMethods) {
assert(GenUtils.arrayContains(expectedPaymentMethods, paymentMethod.getId()), "Payment method is not expected: " + paymentMethod.getId());
}
// test form for each payment method
for (const paymentMethod of paymentMethods) {
// generate form
const accountForm = await alice.getPaymentAccountForm(paymentMethod.getId());
// complete form, validating each field
for (const field of accountForm.getFieldsList()) {
// validate invalid form field
try {
const invalidInput = getInvalidFormInput(accountForm, field.getId());
await alice.validateFormField(accountForm, field.getId(), invalidInput);
throw new Error("Should have thrown error validating form field '" + field.getId() + "' with invalid value '" + invalidInput + "'");
} catch (err: any) {
if (err.message.indexOf("Not implemented") >= 0) throw err;
if (err.message.indexOf("Should have thrown") >= 0) throw err;
}
// validate valid form field
const validInput = getValidFormInput(field.getId(), accountForm);
await alice.validateFormField(accountForm, field.getId(), validInput);
field.setValue(validInput);
}
// create payment account
const fiatAccount = await alice.createPaymentAccount(accountForm);
// payment account added
let found = false;
for (const paymentAccount of await alice.getPaymentAccounts()) {
if (paymentAccount.getId() === fiatAccount.getId()) {
found = true;
break;
}
}
assert(found, "Payment account not found after adding");
// test payment account
expect(fiatAccount.getPaymentMethod().getId()).toEqual(paymentMethod.getId());
testFiatAccount(fiatAccount, accountForm);
}
});
test("Can create fiat payment accounts", async () => { test("Can create fiat payment accounts", async () => {
// get payment account form // get payment account form
@ -859,18 +917,18 @@ test("Can create fiat payment accounts", async () => {
const accountForm = await alice.getPaymentAccountForm(paymentMethodId); const accountForm = await alice.getPaymentAccountForm(paymentMethodId);
// edit form // edit form
accountForm.tradeCurrencies ="gbp,eur,usd"; HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.ACCOUNT_NAME, "Revolut account " + GenUtils.getUUID(), accountForm);
accountForm.selectedTradeCurrency = "usd"; HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.USER_NAME, "user123", accountForm);
accountForm.accountName = "Revolut account " + GenUtils.getUUID(); HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.TRADE_CURRENCIES, "gbp,eur,usd", accountForm);
accountForm.userName = "user123";
// create payment account // create payment account
const fiatAccount = await alice.createPaymentAccount(accountForm); const fiatAccount = await alice.createPaymentAccount(accountForm);
expect(fiatAccount.getAccountName()).toEqual(accountForm.accountName); expect(fiatAccount.getAccountName()).toEqual(HavenoUtils.getFormValue(PaymentAccountFormField.FieldId.ACCOUNT_NAME, accountForm));
expect(fiatAccount.getSelectedTradeCurrency().getCode()).toEqual("USD");
expect(fiatAccount.getTradeCurrenciesList().length).toBeGreaterThan(0); expect(fiatAccount.getTradeCurrenciesList().length).toBeGreaterThan(0);
expect(fiatAccount.getPaymentAccountPayload()!.getPaymentMethodId()).toEqual(paymentMethodId); expect(fiatAccount.getPaymentAccountPayload()!.getPaymentMethodId()).toEqual(paymentMethodId);
expect(fiatAccount.getPaymentAccountPayload()!.getRevolutAccountPayload()!.getAccountId()).toEqual(accountForm.userName); expect(fiatAccount.getPaymentAccountPayload()!.getRevolutAccountPayload()!.getAccountId()).toEqual(HavenoUtils.getFormValue(PaymentAccountFormField.FieldId.USER_NAME, accountForm)); // TODO: payment payload account id is username?
expect(fiatAccount.getPaymentAccountPayload()!.getRevolutAccountPayload()!.getUserName()).toEqual(accountForm.userName); expect(fiatAccount.getPaymentAccountPayload()!.getRevolutAccountPayload()!.getUserName()).toEqual(HavenoUtils.getFormValue(PaymentAccountFormField.FieldId.USER_NAME, accountForm));
// payment account added // payment account added
let found = false; let found = false;
@ -1085,7 +1143,6 @@ test("Can schedule offers with locked funds", async () => {
expect(BigInt((await charlie.getBalances()).getLockedBalance())).toEqual(BigInt(0)); expect(BigInt((await charlie.getBalances()).getLockedBalance())).toEqual(BigInt(0));
expect(BigInt((await charlie.getBalances()).getReservedOfferBalance())).toEqual(BigInt(0)); expect(BigInt((await charlie.getBalances()).getReservedOfferBalance())).toEqual(BigInt(0));
} catch (err2) { } catch (err2) {
console.log(err2);
err = err2; err = err2;
} }
@ -2125,10 +2182,9 @@ function getCryptoAddress(currencyCode: string): string | undefined {
async function createRevolutPaymentAccount(trader: HavenoClient): Promise<PaymentAccount> { async function createRevolutPaymentAccount(trader: HavenoClient): Promise<PaymentAccount> {
const accountForm = await trader.getPaymentAccountForm('REVOLUT'); const accountForm = await trader.getPaymentAccountForm('REVOLUT');
accountForm.tradeCurrencies ="gbp,eur,usd"; HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.TRADE_CURRENCIES, "gbp,eur,usd", accountForm);
accountForm.selectedTradeCurrency = "usd"; HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.ACCOUNT_NAME, "Revolut account " + GenUtils.getUUID(), accountForm);
accountForm.accountName = "Revolut account " + GenUtils.getUUID(); HavenoUtils.setFormValue(PaymentAccountFormField.FieldId.USER_NAME, "user123", accountForm);
accountForm.userName = "user123";
return trader.createPaymentAccount(accountForm); return trader.createPaymentAccount(accountForm);
} }
@ -2305,3 +2361,305 @@ function testMoneroNodeSettingsEqual(settingsBefore: MoneroNodeSettings, setting
expect(settingsBefore.getBootstrapUrl()).toEqual(settingsAfter.getBootstrapUrl()); expect(settingsBefore.getBootstrapUrl()).toEqual(settingsAfter.getBootstrapUrl());
expect(settingsBefore.getStartupFlagsList()).toEqual(settingsAfter.getStartupFlagsList()); expect(settingsBefore.getStartupFlagsList()).toEqual(settingsAfter.getStartupFlagsList());
} }
function getFormField(form: PaymentAccountForm, fieldId: PaymentAccountFormField.FieldId): PaymentAccountFormField {
for (const field of form.getFieldsList()) {
if (field.getId() == fieldId) return field;
}
throw new Error("Form field not found: " + fieldId);
}
function getValidFormInput(fieldId: PaymentAccountFormField.FieldId, form: PaymentAccountForm): string {
const field = getFormField(form, fieldId);
switch (fieldId) {
case PaymentAccountFormField.FieldId.ACCEPTED_COUNTRY_CODES:
if (form.getId().toString() === "SEPA") return "BE," + field.getSupportedSepaEuroCountriesList().map(country => country.getCode()).join(',');
return field.getSupportedCountriesList().map(country => country.getCode()).join(',');
case PaymentAccountFormField.FieldId.ACCOUNT_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ACCOUNT_NAME:
return form.getId().toString() + " " + GenUtils.getUUID(); // TODO: rename to form.getPaymentMethodId()
case PaymentAccountFormField.FieldId.ACCOUNT_NR:
return "1234567890";
case PaymentAccountFormField.FieldId.ACCOUNT_OWNER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ACCOUNT_TYPE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ANSWER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_NAME:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_NUMBER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_TYPE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ADDRESS:
return "456 example st";
case PaymentAccountFormField.FieldId.BANK_BRANCH:
return "Bank branch XYZ";
case PaymentAccountFormField.FieldId.BANK_BRANCH_CODE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_BRANCH_NAME:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_NAME:
return "Bank XYZ";
case PaymentAccountFormField.FieldId.BANK_SWIFT_CODE:
return "12345678901"; // TODO: use real swift code
case PaymentAccountFormField.FieldId.BENEFICIARY_ACCOUNT_NR:
return "1234567890";
case PaymentAccountFormField.FieldId.BENEFICIARY_ADDRESS:
return "123 example st";
case PaymentAccountFormField.FieldId.BENEFICIARY_CITY:
return "Acme";
case PaymentAccountFormField.FieldId.BENEFICIARY_NAME:
return "Jane Doe";
case PaymentAccountFormField.FieldId.BENEFICIARY_PHONE:
return "123-456-7890";
case PaymentAccountFormField.FieldId.BIC:
return "ATLNFRPP";
case PaymentAccountFormField.FieldId.BRANCH_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.CITY:
return "Atlanta";
case PaymentAccountFormField.FieldId.CONTACT:
return "Email please";
case PaymentAccountFormField.FieldId.COUNTRY:
case PaymentAccountFormField.FieldId.BANK_COUNTRY_CODE:
case PaymentAccountFormField.FieldId.INTERMEDIARY_COUNTRY_CODE:
return field.getSupportedCountriesList().length ? field.getSupportedCountriesList().at(0).getCode() : "FR";
case PaymentAccountFormField.FieldId.EMAIL:
return "jdoe@no.com";
case PaymentAccountFormField.FieldId.EMAIL_OR_MOBILE_NR:
return "876-512-7813";
case PaymentAccountFormField.FieldId.EXTRA_INFO:
return "Please and thanks";
case PaymentAccountFormField.FieldId.HOLDER_ADDRESS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.HOLDER_EMAIL:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.HOLDER_NAME:
return "Alice Doe";
case PaymentAccountFormField.FieldId.HOLDER_TAX_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.IBAN:
return "FR1420041010050500013M02606";
case PaymentAccountFormField.FieldId.IFSC:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.INTERMEDIARY_ADDRESS:
return "123 intermediary example st";
case PaymentAccountFormField.FieldId.INTERMEDIARY_BRANCH:
return "Intermediary bank branch XYZ";
case PaymentAccountFormField.FieldId.INTERMEDIARY_NAME:
return "Intermediary bank XYZ";
case PaymentAccountFormField.FieldId.INTERMEDIARY_SWIFT_CODE:
return "10987654321"; // TODO: use real swift code
case PaymentAccountFormField.FieldId.MOBILE_NR:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.NATIONAL_ACCOUNT_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PAYID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PIX_KEY:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.POSTAL_ADDRESS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PROMPT_PAY_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.QUESTION:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.REQUIREMENTS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.SALT:
return "";
case PaymentAccountFormField.FieldId.SPECIAL_INSTRUCTIONS:
return "asap plz";
case PaymentAccountFormField.FieldId.STATE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.TRADE_CURRENCIES:
return field.getSupportedCurrenciesList().map(currency => currency.getCode()).join(',');
case PaymentAccountFormField.FieldId.USER_NAME:
return "user123";
case PaymentAccountFormField.FieldId.VIRTUAL_PAYMENT_ADDRESS:
throw new Error("Not implemented");
default:
throw new Error("Unhandled form field: " + fieldId);
}
}
// TODO: improve invalid inputs
function getInvalidFormInput(form: PaymentAccountForm, fieldId: PaymentAccountFormField.FieldId): string {
switch (fieldId) {
case PaymentAccountFormField.FieldId.ACCEPTED_COUNTRY_CODES:
return "US,XX";
case PaymentAccountFormField.FieldId.ACCOUNT_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ACCOUNT_NAME:
return "";
case PaymentAccountFormField.FieldId.ACCOUNT_NR:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ACCOUNT_OWNER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ACCOUNT_TYPE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.ANSWER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_NAME:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_NUMBER:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ACCOUNT_TYPE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_ADDRESS:
return "";
case PaymentAccountFormField.FieldId.BANK_BRANCH:
return "A";
case PaymentAccountFormField.FieldId.BANK_BRANCH_CODE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_BRANCH_NAME:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_CODE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_COUNTRY_CODE:
return "A";
case PaymentAccountFormField.FieldId.BANK_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.BANK_NAME:
return "A";
case PaymentAccountFormField.FieldId.BANK_SWIFT_CODE:
return "A";
case PaymentAccountFormField.FieldId.BENEFICIARY_ACCOUNT_NR:
return "1";
case PaymentAccountFormField.FieldId.BENEFICIARY_ADDRESS:
return "";
case PaymentAccountFormField.FieldId.BENEFICIARY_CITY:
return "A";
case PaymentAccountFormField.FieldId.BENEFICIARY_NAME:
return "A";
case PaymentAccountFormField.FieldId.BENEFICIARY_PHONE:
return "1";
case PaymentAccountFormField.FieldId.BIC:
return "123";
case PaymentAccountFormField.FieldId.BRANCH_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.CITY:
return "A";
case PaymentAccountFormField.FieldId.CONTACT:
return "";
case PaymentAccountFormField.FieldId.COUNTRY:
return "abc"
case PaymentAccountFormField.FieldId.EMAIL:
return "@no.com";
case PaymentAccountFormField.FieldId.EMAIL_OR_MOBILE_NR:
return ""; // TODO: validate phone numbers, e.g. 876
case PaymentAccountFormField.FieldId.EXTRA_INFO:
throw new Error("Extra info has no invalid input");
case PaymentAccountFormField.FieldId.HOLDER_ADDRESS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.HOLDER_EMAIL:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.HOLDER_NAME:
return "A";
case PaymentAccountFormField.FieldId.HOLDER_TAX_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.IBAN:
return "abc";
case PaymentAccountFormField.FieldId.IFSC:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.INTERMEDIARY_ADDRESS:
return "";
case PaymentAccountFormField.FieldId.INTERMEDIARY_BRANCH:
return "A";
case PaymentAccountFormField.FieldId.INTERMEDIARY_COUNTRY_CODE:
return "A";
case PaymentAccountFormField.FieldId.INTERMEDIARY_NAME:
return "A";
case PaymentAccountFormField.FieldId.INTERMEDIARY_SWIFT_CODE:
return "A";
case PaymentAccountFormField.FieldId.MOBILE_NR:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.NATIONAL_ACCOUNT_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PAYID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PIX_KEY:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.POSTAL_ADDRESS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.PROMPT_PAY_ID:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.QUESTION:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.REQUIREMENTS:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.SALT:
return "abc";
case PaymentAccountFormField.FieldId.SPECIAL_INSTRUCTIONS:
throw new Error("Special instructions have no invalid input");
case PaymentAccountFormField.FieldId.STATE:
throw new Error("Not implemented");
case PaymentAccountFormField.FieldId.TRADE_CURRENCIES:
return "abc,def";
case PaymentAccountFormField.FieldId.USER_NAME:
return "A";
case PaymentAccountFormField.FieldId.VIRTUAL_PAYMENT_ADDRESS:
throw new Error("Not implemented");
default:
throw new Error("Unhandled form field: " + fieldId);
}
}
function testFiatAccount(account: PaymentAccount, form: PaymentAccountForm) {
expect(account.getAccountName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.ACCOUNT_NAME).getValue()); // TODO: using number as payment method, account payload's account name = user name
const isCountryBased = account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload() !== undefined;
if (isCountryBased) expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getCountryCode()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.COUNTRY).getValue());
switch (form.getId()) {
case PaymentAccountForm.FormId.REVOLUT:
expect(account.getPaymentAccountPayload().getRevolutAccountPayload().getUserName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.USER_NAME).getValue());
expect(account.getTradeCurrenciesList().map(currency => currency.getCode()).join(",")).toEqual(getFormField(form, PaymentAccountFormField.FieldId.TRADE_CURRENCIES).getValue());
break;
case PaymentAccountForm.FormId.SEPA:
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getSepaAccountPayload().getHolderName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.HOLDER_NAME).getValue());
//expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getSepaAccountPayload().getEmail()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.EMAIL).getValue()); // TODO: if this is deprecated, remove from sepa model
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getSepaAccountPayload().getIban()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.IBAN).getValue());
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getSepaAccountPayload().getBic()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BIC).getValue());
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getAcceptedCountryCodesList().join(",")).toEqual(getFormField(form, PaymentAccountFormField.FieldId.ACCEPTED_COUNTRY_CODES).getValue());
break;
case PaymentAccountForm.FormId.TRANSFERWISE:
expect(account.getPaymentAccountPayload().getTransferwiseAccountPayload().getEmail()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.EMAIL).getValue());
break;
case PaymentAccountForm.FormId.CLEAR_X_CHANGE:
expect(account.getPaymentAccountPayload().getClearXchangeAccountPayload().getHolderName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.HOLDER_NAME).getValue());
expect(account.getPaymentAccountPayload().getClearXchangeAccountPayload().getEmailOrMobileNr()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.EMAIL_OR_MOBILE_NR).getValue());
break;
case PaymentAccountForm.FormId.SWIFT:
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBankSwiftCode()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BANK_SWIFT_CODE).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBankCountryCode()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BANK_COUNTRY_CODE).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBankName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BANK_NAME).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBankBranch()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BANK_BRANCH).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBankAddress()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BANK_ADDRESS).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getIntermediarySwiftCode()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.INTERMEDIARY_SWIFT_CODE).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getIntermediaryCountryCode()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.INTERMEDIARY_COUNTRY_CODE).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getIntermediaryName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.INTERMEDIARY_NAME).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getIntermediaryBranch()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.INTERMEDIARY_BRANCH).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getIntermediaryAddress()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.INTERMEDIARY_ADDRESS).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBeneficiaryName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BENEFICIARY_NAME).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBeneficiaryAccountNr()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BENEFICIARY_ACCOUNT_NR).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBeneficiaryAddress()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BENEFICIARY_ADDRESS).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBeneficiaryCity()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BENEFICIARY_CITY).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getBeneficiaryPhone()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.BENEFICIARY_PHONE).getValue());
expect(account.getPaymentAccountPayload().getSwiftAccountPayload().getSpecialInstructions()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.SPECIAL_INSTRUCTIONS).getValue());
break;
case PaymentAccountForm.FormId.F2F:
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getF2fAccountPayload().getCity()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.CITY).getValue());
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getF2fAccountPayload().getContact()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.CONTACT).getValue());
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getF2fAccountPayload().getExtraInfo()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.EXTRA_INFO).getValue());
break;
case PaymentAccountForm.FormId.STRIKE:
expect(account.getPaymentAccountPayload().getCountryBasedPaymentAccountPayload()!.getStrikeAccountPayload().getHolderName()).toEqual(getFormField(form, PaymentAccountFormField.FieldId.HOLDER_NAME).getValue());
break;
default:
throw new Error("Unhandled payment method type: " + form.getId());
}
}

View File

@ -4,8 +4,8 @@ import HavenoUtils from "./utils/HavenoUtils";
import TaskLooper from "./utils/TaskLooper"; import TaskLooper from "./utils/TaskLooper";
import type * as grpcWeb from "grpc-web"; import type * as grpcWeb from "grpc-web";
import { GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient, MoneroNodeClient } from './protobuf/GrpcServiceClientPb'; import { GetVersionClient, AccountClient, MoneroConnectionsClient, DisputesClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient, MoneroNodeClient } from './protobuf/GrpcServiceClientPb';
import { GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetMyOfferRequest, GetMyOfferReply, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentMethodsRequest, GetPaymentMethodsReply, GetPaymentAccountFormRequest, CreatePaymentAccountRequest, CreatePaymentAccountReply, GetPaymentAccountFormReply, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetXmrSeedRequest, GetXmrSeedReply, GetXmrPrimaryAddressRequest, GetXmrPrimaryAddressReply, GetXmrNewSubaddressRequest, GetXmrNewSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply, GetDisputeRequest, GetDisputeReply, GetDisputesRequest, GetDisputesReply, OpenDisputeRequest, ResolveDisputeRequest, SendDisputeChatMessageRequest, SendChatMessageRequest, GetChatMessagesRequest, GetChatMessagesReply, StartMoneroNodeRequest, StopMoneroNodeRequest, IsMoneroNodeRunningRequest, IsMoneroNodeRunningReply, GetMoneroNodeSettingsRequest, GetMoneroNodeSettingsReply } from "./protobuf/grpc_pb"; import { GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetMyOfferRequest, GetMyOfferReply, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentMethodsRequest, GetPaymentMethodsReply, GetPaymentAccountFormRequest, CreatePaymentAccountRequest, ValidateFormFieldRequest, CreatePaymentAccountReply, GetPaymentAccountFormReply, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetXmrSeedRequest, GetXmrSeedReply, GetXmrPrimaryAddressRequest, GetXmrPrimaryAddressReply, GetXmrNewSubaddressRequest, GetXmrNewSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply, GetDisputeRequest, GetDisputeReply, GetDisputesRequest, GetDisputesReply, OpenDisputeRequest, ResolveDisputeRequest, SendDisputeChatMessageRequest, SendChatMessageRequest, GetChatMessagesRequest, GetChatMessagesReply, StartMoneroNodeRequest, StopMoneroNodeRequest, IsMoneroNodeRunningRequest, IsMoneroNodeRunningReply, GetMoneroNodeSettingsRequest, GetMoneroNodeSettingsReply } from "./protobuf/grpc_pb";
import { PaymentMethod, PaymentAccount, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage, MoneroNodeSettings } from "./protobuf/pb_pb"; import { PaymentMethod, PaymentAccountForm, PaymentAccountFormField, PaymentAccount, AvailabilityResult, Attachment, DisputeResult, Dispute, ChatMessage, MoneroNodeSettings } from "./protobuf/pb_pb";
/** /**
* Haveno daemon client. * Haveno daemon client.
@ -1024,14 +1024,39 @@ export default class HavenoClient {
/** /**
* Get a form for the given payment method to complete and create a new payment account. * Get a form for the given payment method to complete and create a new payment account.
* *
* @return {object} the payment account form as JSON * @param {string} paymentMethodId - the id of the payment method
* @return {PaymentAccountForm} the payment account form
*/ */
async getPaymentAccountForm(paymentMethodId: string): Promise<any> { async getPaymentAccountForm(paymentMethodId: string): Promise<PaymentAccountForm> {
try { try {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
this._paymentAccountsClient.getPaymentAccountForm(new GetPaymentAccountFormRequest().setPaymentMethodId(paymentMethodId), {password: this._password}, function(err: grpcWeb.RpcError, response: GetPaymentAccountFormReply) { this._paymentAccountsClient.getPaymentAccountForm(new GetPaymentAccountFormRequest().setPaymentMethodId(paymentMethodId), {password: this._password}, function(err: grpcWeb.RpcError, response: GetPaymentAccountFormReply) {
if (err) reject(err); if (err) reject(err);
else resolve(JSON.parse(response.getPaymentAccountFormJson())); else resolve(response.getPaymentAccountForm()!);
});
});
} catch (e: any) {
throw new HavenoError(e.message, e.code);
}
}
/*
* Validate a form field.
*
* @param {object} form - form context to validate the given value
* @param {PaymentAccountFormField.FieldId} fieldId - id of the field to validate
* @param {string} value - input value to validate
*/
async validateFormField(form: PaymentAccountForm, fieldId: PaymentAccountFormField.FieldId, value: string): Promise<void> {
const request = new ValidateFormFieldRequest()
.setForm(form)
.setFieldId(fieldId)
.setValue(value);
try {
await new Promise<void>((resolve, reject) => {
this._paymentAccountsClient.validateFormField(request, {password: this._password}, function(err: grpcWeb.RpcError) {
if (err) reject(err);
else resolve();
}); });
}); });
} catch (e: any) { } catch (e: any) {
@ -1042,13 +1067,13 @@ export default class HavenoClient {
/** /**
* Create a payment account. * Create a payment account.
* *
* @param {object} paymentAccountForm - the completed form as JSON to create the payment account * @param {PaymentAccountForm} paymentAccountForm - the completed form to create the payment account
* @return {PaymentAccount} the created payment account * @return {PaymentAccount} the created payment account
*/ */
async createPaymentAccount(paymentAccountForm: any): Promise<PaymentAccount> { async createPaymentAccount(paymentAccountForm: PaymentAccountForm): Promise<PaymentAccount> {
try { try {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(JSON.stringify(paymentAccountForm)), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) { this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(paymentAccountForm), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getPaymentAccount()!); else resolve(response.getPaymentAccount()!);
}); });

View File

@ -1,5 +1,6 @@
import assert from "assert"; import assert from "assert";
import console from 'console'; import console from "console";
import { PaymentAccountForm, PaymentAccountFormField } from "../protobuf/pb_pb";
/** /**
* Collection of utilities for working with Haveno. * Collection of utilities for working with Haveno.
@ -83,4 +84,37 @@ export default class HavenoUtils {
static centinerosToAtomicUnits(centineros: number): bigint { static centinerosToAtomicUnits(centineros: number): bigint {
return BigInt(centineros) * BigInt(HavenoUtils.centinerosToAUMultiplier); return BigInt(centineros) * BigInt(HavenoUtils.centinerosToAUMultiplier);
} }
/**
* Get a form field value.
*
* @param {PaymentAccountFormField.FieldId} fieldId - id of the field to get the value from
* @param {PaymentAccountForm} form - form to get the field value from
* @return {string} the form field value
*/
// TODO: attach getter and setter to PaymentAccountForm prototype in typescript?
static getFormValue(fieldId: PaymentAccountFormField.FieldId, form: PaymentAccountForm): string {
for (const field of form.getFieldsList()) {
if (field.getId() === fieldId) return field.getValue();
}
throw new Error("PaymentAccountForm does not have field " + fieldId);
}
/**
* Set a form field value.
*
* @param {PaymentAccountFormField.FieldId} fieldId - id of the field to set the value of
* @param {string} value - field value to set
* @param {PaymentAccountForm} form - form to get the field from
* @return {string} the form field value
*/
static setFormValue(fieldId: PaymentAccountFormField.FieldId, value: string, form: PaymentAccountForm): void {
for (const field of form.getFieldsList()) {
if (field.getId() === fieldId) {
field.setValue(value);
return;
}
}
throw new Error("PaymentAccountForm does not have field " + fieldId);
}
} }