Skip to content

Commit

Permalink
overhauled our IT setup to create Stripe payments/subscriptions on de…
Browse files Browse the repository at this point in the history
…mand
  • Loading branch information
brmeyer committed Sep 4, 2023
1 parent b1d45bd commit 2c2891f
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 875 deletions.
13 changes: 13 additions & 0 deletions src/main/java/com/impactupgrade/nucleus/client/StripeClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.stripe.param.CustomerSearchParams;
import com.stripe.param.CustomerUpdateParams;
import com.stripe.param.PaymentIntentCreateParams;
import com.stripe.param.PaymentIntentListParams;
import com.stripe.param.PaymentIntentUpdateParams;
import com.stripe.param.PaymentSourceCollectionCreateParams;
import com.stripe.param.PayoutListParams;
Expand Down Expand Up @@ -83,6 +84,11 @@ protected Charge retrieve() throws StripeException {
}.result();
}

public List<Charge> getChargesFromCustomer(String customerId) throws StripeException {
ChargeListParams params = ChargeListParams.builder().setCustomer(customerId).build();
return Charge.list(params, requestOptions).getData();
}

public Invoice getInvoice(String id) throws StripeException {
Map<String, Object> params = new HashMap<>();
List<String> expand = new ArrayList<>();
Expand Down Expand Up @@ -148,6 +154,11 @@ protected PaymentIntent retrieve() throws StripeException {
}.result();
}

public List<PaymentIntent> getPaymentIntentsFromCustomer(String customerId) throws StripeException {
PaymentIntentListParams params = PaymentIntentListParams.builder().setCustomer(customerId).build();
return PaymentIntent.list(params, requestOptions).getData();
}

public Refund getRefund(String id) throws StripeException {
return new Retriever<Refund>() {
@Override
Expand Down Expand Up @@ -307,6 +318,8 @@ private List<BalanceTransaction> getBalanceTransactions(Payout payout, BalanceTr
transactionExpand.add("data.source");
// also include the customer -- need it as a fallback for campaigns
transactionExpand.add("data.source.customer");
// and the charge
transactionExpand.add("data.source.charge");
// and the payment intent
transactionExpand.add("data.source.payment_intent");
transactionParams.put("expand", transactionExpand);
Expand Down
33 changes: 13 additions & 20 deletions src/test/java/com/impactupgrade/nucleus/it/AbstractIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package com.impactupgrade.nucleus.it;

import com.google.common.base.Strings;
import com.impactupgrade.integration.hubspot.AssociationSearchResult;
import com.impactupgrade.integration.hubspot.AssociationSearchResults;
import com.impactupgrade.integration.hubspot.Company;
Expand Down Expand Up @@ -105,11 +106,11 @@ protected Application configure() {
return new ResourceConfig();
}

protected void clearSfdc() throws Exception {
clearSfdcByName("Tester");
}
protected void clearSfdc(String name) throws Exception {
if (Strings.isNullOrEmpty(name)) {
return;
}

protected void clearSfdcByName(String name) throws Exception {
SfdcClient sfdcClient = env.sfdcClient();

List<SObject> existingAccounts = sfdcClient.getAccountsByName(name);
Expand All @@ -133,11 +134,11 @@ protected void clearSfdcByName(String name) throws Exception {
assertEquals(0, sfdcClient.getAccountsByName(name).size());
}

protected void clearHubspot() throws Exception {
clearHubspotByName("Tester");
}
protected void clearHubspot(String name) throws Exception {
if (Strings.isNullOrEmpty(name)) {
return;
}

protected void clearHubspotByName(String name) throws Exception {
HubSpotCrmV3Client hsClient = HubSpotClientFactory.crmV3Client(env);

CompanyResults existingAccounts = hsClient.company().searchByName(name, Collections.emptyList());
Expand Down Expand Up @@ -167,19 +168,11 @@ protected void clearHubspotByName(String name) throws Exception {
assertEquals(0, hsClient.company().searchByName(name, Collections.emptyList()).getResults().size());
}

protected void clearDonorwrangler() throws Exception {
clearDonorwranglerByName("Tester");
}

protected void clearDonorwranglerByName(String name) throws Exception {
// TODO
}

protected void clearVirtuous() throws Exception {
clearVirtuousByName("Tester");
}
protected void clearVirtuous(String name) throws Exception {
if (Strings.isNullOrEmpty(name)) {
return;
}

protected void clearVirtuousByName(String name) throws Exception {
CrmService crmService = env.crmService("virtuous");
VirtuousClient virtuousClient = env.virtuousClient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@

package com.impactupgrade.nucleus.it;

import com.google.common.io.Resources;
import com.impactupgrade.nucleus.App;
import com.impactupgrade.nucleus.client.SfdcClient;
import com.impactupgrade.nucleus.client.StripeClient;
import com.impactupgrade.nucleus.it.util.StripeUtil;
import com.impactupgrade.nucleus.model.ContactSearch;
import com.sforce.soap.partner.sobject.SObject;
import com.stripe.model.Charge;
import com.stripe.param.ChargeCreateParams;
import org.junit.jupiter.api.Test;

import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -29,110 +34,77 @@ protected CustomDonationsToSfdcIT() {

@Test
public void coreOneTime() throws Exception {
clearSfdc();

// play a Stripe webhook, captured directly from our Stripe account itself
String json = Resources.toString(Resources.getResource("custom-donations-charge-success.json"), StandardCharsets.UTF_8);
String nowDate = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());

String randomFirstName = StripeUtil.generateName();
String randomLastName = StripeUtil.generateName();
String randomEmail = StripeUtil.generateName().toLowerCase() + "@test.com";

// Custom Donations uses metadata instead of Customers.
StripeClient stripeClient = env.stripeClient();
Map<String, String> metadata = Map.of(
"First Name", randomFirstName,
"Last Name", randomLastName,
"Donor Email", randomEmail,
"Phone Number", "260-123-4567",
"Street Address", "123 Somewhere St",
"City", "Fort Wayne",
"State", "IN",
"Postal Code", "46814",
"Country", "US"
);
ChargeCreateParams.Builder chargeBuilder = ChargeCreateParams.builder()
.setSource("tok_visa")
.setAmount(100L)
.setCurrency("USD")
.putAllMetadata(metadata);
Charge charge = stripeClient.createCharge(chargeBuilder);
String json = StripeUtil.createEventJson("charge.succeeded", charge.getRawJsonObject(), charge.getCreated());

// play as a Stripe webhook
Response response = target("/api/stripe/webhook").request().post(Entity.json(json));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());

SfdcClient sfdcClient = env.sfdcClient();

// verify ContactService -> SfdcCrmService
Optional<SObject> contactO = sfdcClient.searchContacts(ContactSearch.byEmail("[email protected]")).getSingleResult();
Optional<SObject> contactO = sfdcClient.searchContacts(ContactSearch.byEmail(randomEmail)).getSingleResult();
assertTrue(contactO.isPresent());
SObject contact = contactO.get();
String accountId = contact.getField("AccountId").toString();
Optional<SObject> accountO = sfdcClient.getAccountById(accountId);
assertTrue(accountO.isPresent());
SObject account = accountO.get();
assertEquals("Integration Tester", account.getField("Name"));
assertEquals("13022 Redding Drive", account.getField("BillingStreet"));
assertEquals(randomFirstName + " " + randomLastName, account.getField("Name"));
assertEquals("123 Somewhere St", account.getField("BillingStreet"));
assertEquals("Fort Wayne", account.getField("BillingCity"));
assertEquals("IN", account.getField("BillingState"));
assertEquals("46814", account.getField("BillingPostalCode"));
assertEquals("US", account.getField("BillingCountry"));
assertEquals("Integration", contact.getField("FirstName"));
assertEquals("Tester", contact.getField("LastName"));
assertEquals("[email protected]", contact.getField("Email"));
assertEquals("260-349-5732", contact.getField("MobilePhone"));
assertEquals(randomFirstName, contact.getField("FirstName"));
assertEquals(randomLastName, contact.getField("LastName"));
assertEquals(randomEmail, contact.getField("Email"));
assertEquals("260-123-4567", contact.getField("MobilePhone"));

// verify DonationService -> SfdcCrmService
List<SObject> opps = sfdcClient.getDonationsByAccountId(accountId);
assertEquals(1, opps.size());
SObject opp = opps.get(0);
assertNull(opp.getField("Npe03__Recurring_Donation__c"));
assertEquals("Stripe", opp.getField("Payment_Gateway_Name__c"));
assertEquals("ch_3JWulhHAwJOu5brr1EEbHa4z", opp.getField("Payment_Gateway_Transaction_Id__c"));
assertEquals(charge.getId(), opp.getField("Payment_Gateway_Transaction_Id__c"));
assertNull(opp.getField("Payment_Gateway_Customer_Id__c"));
assertEquals("Closed Won", opp.getField("StageName"));
assertEquals("2021-09-07", opp.getField("CloseDate"));
assertEquals("Integration Tester Donation", opp.getField("Name"));
assertEquals("104.92", opp.getField("Amount"));
assertEquals(nowDate, opp.getField("CloseDate"));
assertEquals(randomFirstName + " " + randomLastName + " Donation", opp.getField("Name"));
assertEquals("1.0", opp.getField("Amount"));

// TODO: Allocation Name (singular) and Allocations List (plural)
// TODO: Campaign
}

@Test
public void coreSubscription() throws Exception {
clearSfdc();

// play a Stripe webhook, captured directly from our Stripe account itself
String json = Resources.toString(Resources.getResource("custom-donations-subscription-success.json"), StandardCharsets.UTF_8);
Response response = target("/api/stripe/webhook").request().post(Entity.json(json));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());

SfdcClient sfdcClient = env.sfdcClient();

// verify DonorService -> SfdcCrmService
Optional<SObject> contactO = sfdcClient.searchContacts(ContactSearch.byEmail("[email protected]")).getSingleResult();
assertTrue(contactO.isPresent());
SObject contact = contactO.get();
String accountId = contact.getField("AccountId").toString();
Optional<SObject> accountO = sfdcClient.getAccountById(accountId);
assertTrue(accountO.isPresent());
SObject account = accountO.get();
assertEquals("Integration Tester", account.getField("Name"));
assertEquals("13022 Redding Drive", account.getField("BillingStreet"));
assertEquals("Fort Wayne", account.getField("BillingCity"));
assertEquals("IN", account.getField("BillingState"));
assertEquals("46814", account.getField("BillingPostalCode"));
assertEquals("US", account.getField("BillingCountry"));
assertEquals("Integration", contact.getField("FirstName"));
assertEquals("Tester", contact.getField("LastName"));
assertEquals("[email protected]", contact.getField("Email"));
assertEquals("260-349-5732", contact.getField("MobilePhone"));

// verify the RD
List<SObject> rds = sfdcClient.getRecurringDonationsByAccountId(accountId);
assertEquals(1, rds.size());
SObject rd = rds.get(0);
assertEquals("52.62", rd.getField("npe03__Amount__c"));
assertEquals("Open", rd.getField("npe03__Open_Ended_Status__c"));
assertEquals("Multiply By", rd.getField("npe03__Schedule_Type__c"));
assertEquals("Monthly", rd.getField("npe03__Installment_Period__c"));
assertEquals("2021-09-07", rd.getField("npe03__Date_Established__c"));
// TODO: periodically fails -- may have a timing issue where this isn't being updated fast enough
assertEquals("2021-09-07", rd.getField("npe03__Next_Payment_Date__c"));
assertEquals("Stripe", rd.getField("Payment_Gateway_Name__c"));
assertEquals("cus_KBHqPAS8xHRjcM", rd.getField("Payment_Gateway_Customer_Id__c"));
assertEquals("sub_KBHqz0v4OgY68l", rd.getField("Payment_Gateway_Subscription_Id__c"));

// verify the Closed Won opp
List<SObject> opps = sfdcClient.getDonationsByAccountId(accountId);
assertEquals(1, opps.size());
SObject opp = opps.get(0);
assertEquals(rd.getId(), opp.getField("npe03__Recurring_Donation__c"));
assertEquals("Stripe", opp.getField("Payment_Gateway_Name__c"));
assertEquals("pi_3JWvHFHAwJOu5brr0jae8t9x", opp.getField("Payment_Gateway_Transaction_Id__c"));
assertEquals("cus_KBHqPAS8xHRjcM", rd.getField("Payment_Gateway_Customer_Id__c"));
assertEquals("Closed Won", opp.getField("StageName"));
assertEquals("2021-09-07", opp.getField("CloseDate"));
assertEquals("Integration Tester Donation", opp.getField("Name"));
assertEquals("52.62", opp.getField("Amount"));

// TODO: Allocation Name (singular) and Allocations List (plural)
// TODO: Campaign
// only delete if the test passed -- keep failures in SFDC for analysis
clearSfdc(randomLastName);
}

// Custom Donations handles recurring gifts with standard Customer/Subscription data. No need to retest that!
}

This file was deleted.

Loading

0 comments on commit 2c2891f

Please sign in to comment.