@Test(groups = "slow")
  public void testAccountPaymentsWithRefund() throws Exception {
    final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();

    // Verify payments
    final InvoicePayments objFromJson =
        killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(objFromJson.size(), 1);
  }
  @Test(groups = "slow", description = "Verify no PII data is required")
  public void testEmptyAccount() throws Exception {
    final Account emptyAccount = new Account();

    final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment);
    Assert.assertNotNull(account.getExternalKey());
    Assert.assertNull(account.getName());
    Assert.assertNull(account.getEmail());
  }
  @Test(groups = "slow", description = "Can retrieve the account balance")
  public void testAccountWithBalance() throws Exception {
    final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();

    final Account accountWithBalance =
        killBillClient.getAccount(accountJson.getAccountId(), true, false);
    final BigDecimal accountBalance = accountWithBalance.getAccountBalance();
    Assert.assertTrue(accountBalance.compareTo(BigDecimal.ZERO) > 0);
  }
  @Test(groups = "slow", description = "Add custom fields to account")
  public void testCustomFields() throws Exception {
    final Account accountJson = createAccount();
    assertNotNull(accountJson);

    final Collection<CustomField> customFields = new LinkedList<CustomField>();
    customFields.add(
        new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "1", "value1", null));
    customFields.add(
        new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "2", "value2", null));
    customFields.add(
        new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "3", "value3", null));

    killBillClient.createAccountCustomFields(
        accountJson.getAccountId(), customFields, createdBy, reason, comment);

    final List<CustomField> accountCustomFields =
        killBillClient.getAccountCustomFields(accountJson.getAccountId());
    assertEquals(accountCustomFields.size(), 3);

    // Delete all custom fields for account
    killBillClient.deleteAccountCustomFields(
        accountJson.getAccountId(), createdBy, reason, comment);

    final List<CustomField> remainingCustomFields =
        killBillClient.getAccountCustomFields(accountJson.getAccountId());
    assertEquals(remainingCustomFields.size(), 0);
  }
  @Test(groups = "slow", description = "Add tags to account")
  public void testTags() throws Exception {
    final Account input = createAccount();
    // Use tag definition for AUTO_PAY_OFF
    final UUID autoPayOffId = new UUID(0, 1);

    // Add a tag
    killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);

    // Retrieves all tags
    final List<Tag> tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
    Assert.assertEquals(tags1.size(), 1);
    Assert.assertEquals(tags1.get(0).getTagDefinitionId(), autoPayOffId);

    // Verify adding the same tag a second time doesn't do anything
    killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);

    // Retrieves all tags again
    killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
    final List<Tag> tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
    Assert.assertEquals(tags2, tags1);

    // Verify audit logs
    Assert.assertEquals(tags2.get(0).getAuditLogs().size(), 1);
    final AuditLog auditLogJson = tags2.get(0).getAuditLogs().get(0);
    Assert.assertEquals(auditLogJson.getChangeType(), "INSERT");
    Assert.assertEquals(auditLogJson.getChangedBy(), createdBy);
    Assert.assertEquals(auditLogJson.getReasonCode(), reason);
    Assert.assertEquals(auditLogJson.getComments(), comment);
    Assert.assertNotNull(auditLogJson.getChangeDate());
    Assert.assertNotNull(auditLogJson.getUserToken());
  }
  @Test(groups = "slow", description = "Verify external key is unique")
  public void testUniqueExternalKey() throws Exception {
    // Verify the external key is not mandatory
    final Account inputWithNoExternalKey =
        getAccount(UUID.randomUUID().toString(), null, UUID.randomUUID().toString());
    Assert.assertNull(inputWithNoExternalKey.getExternalKey());

    final Account account =
        killBillClient.createAccount(inputWithNoExternalKey, createdBy, reason, comment);
    Assert.assertNotNull(account.getExternalKey());

    final Account inputWithSameExternalKey =
        getAccount(
            UUID.randomUUID().toString(), account.getExternalKey(), UUID.randomUUID().toString());
    try {
      killBillClient.createAccount(inputWithSameExternalKey, createdBy, reason, comment);
      Assert.fail();
    } catch (final KillBillClientException e) {
      Assert.assertEquals(
          e.getBillingException().getCode(), (Integer) ErrorCode.ACCOUNT_ALREADY_EXISTS.getCode());
    }
  }
  @Test(groups = "slow", description = "refresh payment methods")
  public void testRefreshPaymentMethods() throws Exception {
    Account account = createAccountWithDefaultPaymentMethod("someExternalKey");

    final PaymentMethods paymentMethodsBeforeRefreshing =
        killBillClient.getPaymentMethodsForAccount(account.getAccountId());
    assertEquals(paymentMethodsBeforeRefreshing.size(), 1);
    assertEquals(paymentMethodsBeforeRefreshing.get(0).getExternalKey(), "someExternalKey");

    // WITH NAME OF AN EXISTING PLUGIN
    killBillClient.refreshPaymentMethods(
        account.getAccountId(),
        PLUGIN_NAME,
        ImmutableMap.<String, String>of(),
        createdBy,
        reason,
        comment);

    final PaymentMethods paymentMethodsAfterExistingPluginCall =
        killBillClient.getPaymentMethodsForAccount(account.getAccountId());

    assertEquals(paymentMethodsAfterExistingPluginCall.size(), 1);
    assertEquals(paymentMethodsAfterExistingPluginCall.get(0).getExternalKey(), "someExternalKey");

    // WITHOUT PLUGIN NAME
    killBillClient.refreshPaymentMethods(
        account.getAccountId(), ImmutableMap.<String, String>of(), createdBy, reason, comment);

    final PaymentMethods paymentMethodsAfterNoPluginNameCall =
        killBillClient.getPaymentMethodsForAccount(account.getAccountId());
    assertEquals(paymentMethodsAfterNoPluginNameCall.size(), 1);
    assertEquals(paymentMethodsAfterNoPluginNameCall.get(0).getExternalKey(), "someExternalKey");

    // WITH WRONG PLUGIN NAME
    try {
      killBillClient.refreshPaymentMethods(
          account.getAccountId(),
          "GreatestPluginEver",
          ImmutableMap.<String, String>of(),
          createdBy,
          reason,
          comment);
      Assert.fail();
    } catch (KillBillClientException e) {
      Assert.assertEquals(
          e.getBillingException().getCode(),
          (Integer) ErrorCode.PAYMENT_NO_SUCH_PAYMENT_PLUGIN.getCode());
    }
  }
  @Test(groups = "slow", description = "Can create, retrieve, search and update accounts")
  public void testAccountOk() throws Exception {
    final Account input = createAccount();

    // Retrieves by external key
    final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey());
    Assert.assertTrue(retrievedAccount.equals(input));

    // Try search endpoint
    searchAccount(input, retrievedAccount);

    // Update Account
    final Account newInput =
        new Account(
            input.getAccountId(),
            "zozo",
            4,
            input.getExternalKey(),
            "*****@*****.**",
            18,
            "USD",
            null,
            "UTC",
            "bl1",
            "bh2",
            "",
            "",
            "ca",
            "San Francisco",
            "usa",
            "en",
            "415-255-2991",
            false,
            false,
            null,
            null);
    final Account updatedAccount =
        killBillClient.updateAccount(newInput, createdBy, reason, comment);
    Assert.assertTrue(updatedAccount.equals(newInput));

    // Try search endpoint
    searchAccount(input, null);
  }
  private void searchAccount(final Account input, @Nullable final Account output) throws Exception {
    // Search by id
    if (output != null) {
      doSearchAccount(input.getAccountId().toString(), output);
    }

    // Search by name
    doSearchAccount(input.getName(), output);

    // Search by email
    doSearchAccount(input.getEmail(), output);

    // Search by company name
    doSearchAccount(input.getCompany(), output);

    // Search by external key.
    // Note: we will always find a match since we don't update it
    final List<Account> accountsByExternalKey =
        killBillClient.searchAccounts(input.getExternalKey());
    Assert.assertEquals(accountsByExternalKey.size(), 1);
    Assert.assertEquals(accountsByExternalKey.get(0).getAccountId(), input.getAccountId());
    Assert.assertEquals(accountsByExternalKey.get(0).getExternalKey(), input.getExternalKey());
  }
  @Test(groups = "slow")
  public void testFailedPaymentWithPerTenantRetryConfig() throws Exception {
    // Create the tenant
    final String apiKeyTenant1 = "tenantSuperTuned";
    final String apiSecretTenant1 = "2367$$ffr79";
    loginTenant(apiKeyTenant1, apiSecretTenant1);
    final Tenant tenant1 = new Tenant();
    tenant1.setApiKey(apiKeyTenant1);
    tenant1.setApiSecret(apiSecretTenant1);
    killBillClient.createTenant(tenant1, createdBy, reason, comment);

    // Configure our plugin to fail
    mockPaymentProviderPlugin.makeAllInvoicesFailWithError(true);

    // Upload the config
    final ObjectMapper mapper = new ObjectMapper();
    final HashMap<String, String> perTenantProperties = new HashMap<String, String>();
    perTenantProperties.put("org.killbill.payment.retry.days", "1,1,1");
    final String perTenantConfig = mapper.writeValueAsString(perTenantProperties);

    final TenantKey tenantKey =
        killBillClient.postConfigurationPropertiesForTenant(perTenantConfig, basicRequestOptions());

    final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();

    final Payments payments = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments.size(), 1);
    Assert.assertEquals(payments.get(0).getTransactions().size(), 1);

    //
    // Because we have specified a retry interval of one day we should see the new attempt after
    // moving clock 1 day (and not 8 days which is default)
    //

    //
    // Now unregister special per tenant config and we the first retry occurs one day after (and
    // still fails), it now sets a retry date of 8 days
    //
    killBillClient.unregisterConfigurationForTenant(basicRequestOptions());
    // org.killbill.tenant.broadcast.rate has been set to 1s
    crappyWaitForLackOfProperSynchonization(2000);

    clock.addDays(1);

    Awaitility.await()
        .atMost(4, TimeUnit.SECONDS)
        .pollInterval(Duration.ONE_SECOND)
        .until(
            new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {

                return killBillClient
                        .getPaymentsForAccount(accountJson.getAccountId())
                        .get(0)
                        .getTransactions()
                        .size()
                    == 2;
              }
            });
    final Payments payments2 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments2.size(), 1);
    Assert.assertEquals(payments2.get(0).getTransactions().size(), 2);
    Assert.assertEquals(
        payments2.get(0).getTransactions().get(0).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments2.get(0).getTransactions().get(1).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());

    clock.addDays(1);
    crappyWaitForLackOfProperSynchonization(3000);

    // No retry with default config
    final Payments payments3 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments3.size(), 1);
    Assert.assertEquals(payments3.get(0).getTransactions().size(), 2);

    mockPaymentProviderPlugin.makeAllInvoicesFailWithError(false);
    clock.addDays(7);

    Awaitility.await()
        .atMost(4, TimeUnit.SECONDS)
        .pollInterval(Duration.ONE_SECOND)
        .until(
            new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {
                return killBillClient
                        .getPaymentsForAccount(accountJson.getAccountId())
                        .get(0)
                        .getTransactions()
                        .size()
                    == 3;
              }
            });
    final Payments payments4 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments4.size(), 1);
    Assert.assertEquals(payments4.get(0).getTransactions().size(), 3);
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(0).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(1).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(2).getStatus(), TransactionStatus.SUCCESS.name());
  }
  @Test(groups = "slow", description = "Can CRUD payment methods")
  public void testAccountPaymentMethods() throws Exception {
    final Account accountJson = createAccount();
    assertNotNull(accountJson);

    final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
    info.setProperties(getPaymentMethodCCProperties());
    PaymentMethod paymentMethodJson =
        new PaymentMethod(
            null,
            UUID.randomUUID().toString(),
            accountJson.getAccountId(),
            true,
            PLUGIN_NAME,
            info);
    final PaymentMethod paymentMethodCC =
        killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
    assertTrue(paymentMethodCC.getIsDefault());

    //
    // Add another payment method
    //
    final PaymentMethodPluginDetail info2 = new PaymentMethodPluginDetail();
    info2.setProperties(getPaymentMethodPaypalProperties());
    paymentMethodJson =
        new PaymentMethod(
            null,
            UUID.randomUUID().toString(),
            accountJson.getAccountId(),
            false,
            PLUGIN_NAME,
            info2);
    final PaymentMethod paymentMethodPP =
        killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
    assertFalse(paymentMethodPP.getIsDefault());

    //
    // FETCH ALL PAYMENT METHODS
    //
    List<PaymentMethod> paymentMethods =
        killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
    assertEquals(paymentMethods.size(), 2);

    //
    // CHANGE DEFAULT
    //
    assertTrue(
        killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());
    assertFalse(
        killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
    killBillClient.updateDefaultPaymentMethod(
        accountJson.getAccountId(),
        paymentMethodPP.getPaymentMethodId(),
        createdBy,
        reason,
        comment);
    assertTrue(
        killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
    assertFalse(
        killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());

    //
    // DELETE NON DEFAULT PM
    //
    killBillClient.deletePaymentMethod(
        paymentMethodCC.getPaymentMethodId(), false, createdBy, reason, comment);

    //
    // FETCH ALL PAYMENT METHODS
    //
    paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
    assertEquals(paymentMethods.size(), 1);

    //
    // DELETE DEFAULT PAYMENT METHOD (without special flag first)
    //
    try {
      killBillClient.deletePaymentMethod(
          paymentMethodPP.getPaymentMethodId(), false, createdBy, reason, comment);
      fail();
    } catch (final KillBillClientException e) {
    }

    //
    // RETRY TO DELETE DEFAULT PAYMENT METHOD (with special flag this time)
    //
    killBillClient.deletePaymentMethod(
        paymentMethodPP.getPaymentMethodId(), true, createdBy, reason, comment);

    // CHECK ACCOUNT IS NOW AUTO_PAY_OFF
    final List<Tag> tagsJson = killBillClient.getAccountTags(accountJson.getAccountId());
    Assert.assertEquals(tagsJson.size(), 1);
    final Tag tagJson = tagsJson.get(0);
    Assert.assertEquals(tagJson.getTagDefinitionName(), "AUTO_PAY_OFF");
    Assert.assertEquals(tagJson.getTagDefinitionId(), new UUID(0, 1));

    // FETCH ACCOUNT AGAIN AND CHECK THERE IS NO DEFAULT PAYMENT METHOD SET
    final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId());
    Assert.assertEquals(updatedAccount.getAccountId(), accountJson.getAccountId());
    Assert.assertNull(updatedAccount.getPaymentMethodId());

    //
    // FINALLY TRY TO REMOVE AUTO_PAY_OFF WITH NO DEFAULT PAYMENT METHOD ON ACCOUNT
    //
    try {
      killBillClient.deleteAccountTag(
          accountJson.getAccountId(), new UUID(0, 1), createdBy, reason, comment);
    } catch (final KillBillClientException e) {
    }
  }