async createSubscriptionWithPMI()

in packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts [651:806]


  async createSubscriptionWithPMI(request: AuthRequest): Promise<{
    sourceCountry: string | null;
    subscription: DeepPartial<Stripe.Subscription>;
  }> {
    this.log.begin('subscriptions.createSubscriptionWithPMI', request);
    const { uid, email, account } = await handleAuth(
      this.db,
      request.auth,
      true
    );
    await this.customs.check(request, email, 'createSubscriptionWithPMI');

    const taxAddress = buildTaxAddress(
      this.log,
      request.app.clientAddress,
      request.app.geo.location
    );

    const countryCode = taxAddress?.countryCode;
    if (countryCode && this.unsupportedLocations.includes(countryCode)) {
      throw error.unsupportedLocation(countryCode);
    }

    try {
      const customer = await this.stripeHelper.fetchCustomer(uid, ['tax']);
      if (!customer) {
        throw error.unknownCustomer(uid);
      }

      const {
        priceId,
        paymentMethodId,
        promotionCode: promotionCodeFromRequest,
        metricsContext,
      } = request.payload as Record<string, string>;

      // Make sure to clean up any subscriptions that may be hanging with no payment
      const existingSubscription =
        this.stripeHelper.findCustomerSubscriptionByPlanId(customer, priceId);
      if (existingSubscription?.status === 'incomplete') {
        await this.stripeHelper.cancelSubscription(existingSubscription.id);
      }

      // Validate that the user doesn't have conflicting subscriptions, for instance via IAP
      const { subscriptionEligibilityResult } =
        await this.capabilityService.getPlanEligibility(
          customer.metadata.userid,
          priceId
        );
      if (
        subscriptionEligibilityResult !== SubscriptionEligibilityResult.CREATE
      ) {
        throw error.userAlreadySubscribedToProduct();
      }

      const promotionCode: Stripe.PromotionCode | undefined =
        await this.extractPromotionCode(promotionCodeFromRequest, priceId);

      let paymentMethod: Stripe.PaymentMethod | undefined;

      const planCurrency = (await this.stripeHelper.findAbbrevPlanById(priceId))
        .currency;

      const automaticTax =
        this.stripeHelper.isCustomerTaxableWithSubscriptionCurrency(
          customer,
          planCurrency
        );

      // Skip the payment source check if there's no payment method id.
      if (paymentMethodId) {
        paymentMethod =
          await this.stripeHelper.getPaymentMethod(paymentMethodId);
        const paymentMethodCountry = paymentMethod.card?.country;
        if (
          !this.stripeHelper.currencyHelper.isCurrencyCompatibleWithCountry(
            planCurrency,
            paymentMethodCountry
          )
        ) {
          throw error.currencyCountryMismatch(
            planCurrency,
            paymentMethodCountry
          );
        }
        if (!this.stripeHelper.customerTaxId(customer)) {
          await this.stripeHelper.addTaxIdToCustomer(customer, planCurrency);
        }
      }

      const subscription: any =
        await this.stripeHelper.createSubscriptionWithPMI({
          customerId: customer.id,
          priceId,
          paymentMethodId,
          promotionCode: promotionCode,
          automaticTax,
        });

      if (!automaticTax) {
        this.log.warn(
          'subscriptions.createSubscriptionWithPMI.automatic_tax_failed',
          {
            uid,
            automatic_tax: customer.tax?.automatic_tax,
          }
        );
      }

      const sourceCountry =
        this.stripeHelper.extractSourceCountryFromSubscription(subscription);

      await this.customerChanged(request, uid, email);

      this.log.info('subscriptions.createSubscriptionWithPMI.success', {
        uid,
        subscriptionId: subscription.id,
      });

      await sendFinishSetupEmailForStubAccount({
        uid,
        account,
        subscription,
        stripeHelper: this.stripeHelper,
        mailer: this.mailer,
        subscriptionAccountReminders: this.subscriptionAccountReminders,
        metricsContext,
      });

      return {
        sourceCountry,
        subscription: filterSubscription(subscription),
      };
    } catch (err) {
      try {
        if (account.verifierSetAt <= 0) {
          await deleteAccountIfUnverified(
            this.db,
            this.stripeHelper,
            this.log,
            request,
            email
          );
        }
      } catch (deleteAccountError) {
        if (
          deleteAccountError.errno !== error.ERRNO.ACCOUNT_EXISTS &&
          deleteAccountError.errno !==
            error.ERRNO.VERIFIED_SECONDARY_EMAIL_EXISTS
        ) {
          throw deleteAccountError;
        }
      }
      throw err;
    }
  }