11package com .trynoice .api .subscription ;
22
3+ import com .stripe .exception .StripeException ;
4+ import com .trynoice .api .contracts .AccountServiceContract ;
35import com .trynoice .api .identity .entities .AuthUser ;
46import com .trynoice .api .subscription .entities .Customer ;
57import com .trynoice .api .subscription .entities .CustomerRepository ;
68import com .trynoice .api .subscription .entities .Subscription ;
79import com .trynoice .api .subscription .entities .SubscriptionPlan ;
810import com .trynoice .api .subscription .entities .SubscriptionRepository ;
911import com .trynoice .api .subscription .payload .GooglePlayDeveloperNotification ;
10- import com .trynoice .api .subscription .payload .GooglePlaySubscriptionPurchase ;
12+ import com .trynoice .api .subscription .upstream .AndroidPublisherApi ;
13+ import com .trynoice .api .subscription .upstream .StripeApi ;
14+ import com .trynoice .api .subscription .upstream .models .GooglePlaySubscriptionPurchase ;
1115import lombok .NonNull ;
1216import lombok .val ;
1317import org .junit .jupiter .api .Test ;
1721import org .springframework .beans .factory .annotation .Autowired ;
1822import org .springframework .boot .test .context .SpringBootTest ;
1923import org .springframework .boot .test .mock .mockito .MockBean ;
24+ import org .springframework .context .ApplicationEventPublisher ;
2025import org .springframework .transaction .annotation .Transactional ;
2126
2227import javax .persistence .EntityManager ;
2328import java .time .OffsetDateTime ;
2429import java .util .UUID ;
30+ import java .util .stream .Collectors ;
31+ import java .util .stream .IntStream ;
2532import java .util .stream .Stream ;
2633
2734import static com .trynoice .api .subscription .SubscriptionTestUtils .buildSubscriptionPlan ;
@@ -50,6 +57,12 @@ public class SubscriptionServiceTest {
5057 @ MockBean
5158 private AndroidPublisherApi androidPublisherApi ;
5259
60+ @ MockBean
61+ private StripeApi stripeApi ;
62+
63+ @ Autowired
64+ private ApplicationEventPublisher eventPublisher ;
65+
5366 @ Autowired
5467 private SubscriptionService service ;
5568
@@ -65,8 +78,9 @@ void handleGooglePlayWebhookEvent(
6578 ) throws Exception {
6679 val purchaseToken = UUID .randomUUID ().toString ();
6780 val authUser = createAuthUser (entityManager );
81+ val customer = buildCustomer (authUser , null );
6882 val plan = buildSubscriptionPlan (entityManager , SubscriptionPlan .Provider .GOOGLE_PLAY , purchase .getProductId ());
69- val subscription = buildSubscription (authUser , plan , wasActive , wasPaymentPending , wasActive ? purchaseToken : null );
83+ val subscription = buildSubscription (customer , plan , wasActive , wasPaymentPending , wasActive ? purchaseToken : null );
7084 purchase = purchase .withObfuscatedExternalAccountId (String .valueOf (subscription .getId ()));
7185 when (androidPublisherApi .getSubscriptionPurchase (purchaseToken ))
7286 .thenReturn (purchase );
@@ -157,7 +171,8 @@ void handleGooglePlayWebhookEvent_planUpgrade() throws Exception {
157171 val oldPurchaseToken = UUID .randomUUID ().toString ();
158172 val newPurchaseToken = UUID .randomUUID ().toString ();
159173 val authUser = createAuthUser (entityManager );
160- val subscription = buildSubscription (authUser , oldPlan , true , false , oldPurchaseToken );
174+ val customer = buildCustomer (authUser , null );
175+ val subscription = buildSubscription (customer , oldPlan , true , false , oldPurchaseToken );
161176 val purchase = GooglePlaySubscriptionPurchase .builder ()
162177 .productId (newPlan .getProvidedId ())
163178 .startTimeMillis (System .currentTimeMillis ())
@@ -188,8 +203,9 @@ void handleGooglePlayWebhookEvent_doublePurchase() throws Exception {
188203 val authUser = createAuthUser (entityManager );
189204 val plan = buildSubscriptionPlan (entityManager , SubscriptionPlan .Provider .GOOGLE_PLAY , "test-plan" );
190205 val purchaseToken = UUID .randomUUID ().toString ();
191- val subscription1 = buildSubscription (authUser , plan , true , false , UUID .randomUUID ().toString ());
192- val subscription2 = buildSubscription (authUser , plan , false , false , null );
206+ val customer = buildCustomer (authUser , null );
207+ val subscription1 = buildSubscription (customer , plan , true , false , UUID .randomUUID ().toString ());
208+ val subscription2 = buildSubscription (customer , plan , false , false , null );
193209 val purchase = GooglePlaySubscriptionPurchase .builder ()
194210 .productId (plan .getProvidedId ())
195211 .startTimeMillis (System .currentTimeMillis ())
@@ -213,21 +229,56 @@ void handleGooglePlayWebhookEvent_doublePurchase() throws Exception {
213229 verify (androidPublisherApi , times (0 )).acknowledgePurchase (plan .getProvidedId (), purchaseToken );
214230 }
215231
232+ @ Test
233+ void onUserDeleted () throws StripeException {
234+ val plan = buildSubscriptionPlan (entityManager , SubscriptionPlan .Provider .STRIPE , "stripe-test-plan" );
235+ val deletedCustomers = IntStream .range (0 , 5 )
236+ .mapToObj (i -> buildCustomer (createAuthUser (entityManager ), "stripe-customer-" + i ))
237+ .collect (Collectors .toUnmodifiableList ());
238+
239+ val activeCustomers = IntStream .range (5 , 10 )
240+ .mapToObj (i -> buildCustomer (createAuthUser (entityManager ), "stripe-customer-" + i ))
241+ .collect (Collectors .toUnmodifiableList ());
242+
243+ val activeSubscriptions = IntStream .range (0 , 5 )
244+ .mapToObj (i -> i % 2 == 0 ? deletedCustomers .get (i ) : activeCustomers .get (i ))
245+ .map (c -> buildSubscription (c , plan , true , false , "stripe-subscription-" + c .getUserId ()))
246+ .collect (Collectors .toUnmodifiableList ());
247+
248+ // TODO: how to test it using ApplicationEventPublisher#publishEvent? Since the following is
249+ // a TransactionalEventListener, it probably gets invoked after the test has finished.
250+ deletedCustomers .forEach (c -> service .onUserDeleted (new AccountServiceContract .UserDeletedEvent (c .getUserId ())));
251+
252+ for (val deletedCustomer : deletedCustomers ) {
253+ verify (stripeApi , times (1 )).resetCustomerNameAndEmail (deletedCustomer .getStripeId ());
254+ }
255+
256+ for (val activeCustomer : activeCustomers ) {
257+ verify (stripeApi , times (0 )).resetCustomerNameAndEmail (activeCustomer .getStripeId ());
258+ }
259+
260+ activeSubscriptions .stream ()
261+ .filter (s -> deletedCustomers .contains (s .getCustomer ()))
262+ .map (s -> subscriptionRepository .findById (s .getId ()).orElseThrow ())
263+ .forEach (s -> assertFalse (s .isActive ()));
264+
265+ activeSubscriptions .stream ()
266+ .filter (s -> activeCustomers .contains (s .getCustomer ()))
267+ .map (s -> subscriptionRepository .findById (s .getId ()).orElseThrow ())
268+ .forEach (s -> assertTrue (s .isActive ()));
269+ }
270+
216271 @ NonNull
217272 private Subscription buildSubscription (
218- @ NonNull AuthUser owner ,
273+ @ NonNull Customer customer ,
219274 @ NonNull SubscriptionPlan plan ,
220275 boolean isActive ,
221276 boolean isPaymentPending ,
222277 String providedId
223278 ) {
224279 return subscriptionRepository .save (
225280 Subscription .builder ()
226- .customer (
227- customerRepository .save (
228- Customer .builder ()
229- .userId (owner .getId ())
230- .build ()))
281+ .customer (customer )
231282 .plan (plan )
232283 .providedId (providedId )
233284 .isPaymentPending (isPaymentPending )
@@ -236,6 +287,15 @@ private Subscription buildSubscription(
236287 .build ());
237288 }
238289
290+ @ NonNull
291+ private Customer buildCustomer (@ NonNull AuthUser user , String stripeCustomerId ) {
292+ return customerRepository .save (
293+ Customer .builder ()
294+ .userId (user .getId ())
295+ .stripeId (stripeCustomerId )
296+ .build ());
297+ }
298+
239299 @ NonNull
240300 private static GooglePlayDeveloperNotification buildGooglePlayDeveloperNotification (int type , @ NonNull String purchaseToken ) {
241301 return GooglePlayDeveloperNotification .builder ()
0 commit comments