#nerder_best-practices

1 messages ¡ Page 1 of 1 (latest)

keen raftBOT
#

👋 Welcome to your new thread!

⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1308786875007242392

📝 Have more to share? Add more details, code, screenshots, videos, etc. below.

Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.

warped socket
#

hey @lean harbor

lean harbor
#

hi! not that's not possible, you can only clone a card from a platform to a connected account.

warped socket
#

ok, i'm asking this because i'm a bit stuck with my implementation for Google/Apple pay

#

the problem is that if I create the token for the connected account, then I can't use it to create a payment method in the platform later on (because the token will be invalid)

#

and if i create the payment without the $connectedAccount in the frontend I can't confirm it

#

in my current implementation the user does the following:

  1. create a payment method (in the platform)
  2. purchase a subscription in the connected account
#

in that case I check and clone the payment method

#

but for Google/Apple pay, the payment method is attached contextually to the purchase

#

so I can either create the payment intent for the connected account, but then also the token will be related to the connected account

#

You think that my only option would be to simply create the pm only in the connected account and avoid all this cloining around?

#

Also another interesting thing might be to clone from connected account to connected account

lean harbor
#

that's impossible too

#

I don't follow. Why can't you just clone the Apple/Google PaymentMethod from the platform to the connected account and use it there/attach to a customer/confirm the PaymentIntent? why does that not work exactly?

warped socket
#

Ok let me show you some quick code to explain better

#

For Google pay in the fronted i do the following:

    if (walletType == WalletType.google_pay) {
      final publishableKey = jsonConfig['data']['allowedPaymentMethods'][0]
          ['tokenizationSpecification']['parameters']['stripe:publishableKey'];
      jsonConfig['data']['allowedPaymentMethods'][0]['tokenizationSpecification']['parameters']
          ['stripe:publishableKey'] = '$publishableKey/${widget.gym.stripeAccountId}';
    }

And the pass this json as a paymentConfig into the GooglePayButton

#

this will create the PI for the connected account and in the result i'll get a token.id

#

i then send this token.id to my server to create a subscription and return back a client_secret

#

ideally tho, to create a subscription i either fetch the payment method from the connected account (if present) or clone it from the platform and return it

#
    const selectedPaymentMethod = await this._fetchOrClonePaymentMethod(
      gym,
      accountInPlatform,
      customerFromConnectedAccount,
      paymentMethodIdFromConnectedAccount, //nullable
      token, //nullable
    );

and here the impl of fetchOrClone:

    let selectedPaymentMethod: PaymentMethod;

    if (paymentMethodId) {
      selectedPaymentMethod = await this.customerRepository.fetchPaymentMethodFromGym(
        gym,
        customerFromGym,
        paymentMethodId,
      );
    }

    if (!selectedPaymentMethod) {
      let reusablePaymentMethod: PaymentMethod;

      if (token) {
        reusablePaymentMethod = await this.paymentMethodRepository.createReusableCardFromToken(account, token);
      } else {
        reusablePaymentMethod = await this.customerRepository.fetchPaymentMethodFromPlatform(account, paymentMethodId);
      }
      selectedPaymentMethod = await this.paymentMethodRepository.cloneCardFromPlatform(
        gym,
        account,
        customerFromGym,
        reusablePaymentMethod,
      );
    }
#

the idea in a nutshell is that if I have the PM already it means i'm reusing a pm previously created in the connected account

#

if i don't i either need to fetch it from the platform, or i need to create it from a token (in the platform)

#
  async createReusableCardFromToken(account: Account, token: string): Promise<PaymentMethod> {
    this.logger.log(`Creating payment method from token for customer [${account.customerId}]`);
    try {
      const paymentMethod = await this.stripe.paymentMethods.create({
        type: 'card',
        card: { token },
      });

      this.logger.log(`Attaching payment method [${paymentMethod.id}] to customer [${account.customerId}]`);
      await this.stripe.paymentMethods.attach(paymentMethod.id, { customer: account.customerId });

      return PaymentMethod.of(paymentMethod.id);
    } catch (e) {
      this.logger.error(`Error creating payment method from token for customer [${account.customerId}]`, e);
      throw convertStripeErrorToPaymentSystemError(e as Error);
    }
  }
#

the problem with this tho, is that the token the UI sends (since has been created for the connected account) is invalid here, because i'm trying to create it in the platform now

lean harbor
#

I'm still a bit lost. If this works for non-wallet cards it will work for wallets too overall.

#

like I don't get why you think there's something different that happens only for wallets.

for non-wallets, in your existing working integration:

  • a user fills in their card details on the form. what account do you create the PaymentMethod on?
warped socket
#

In that case I create it in the platform

#

then when they press "buy"

#

I clone it into the connected account and create the subscription

lean harbor
#

cool. So why can't you just do the same here? create the token on the platform (i.e. do not pass ${widget.gym.stripeAccountId} in the config). Create the PaymentMethod (your createReusableCardFromToken function) on the platform too. All the rest of your logic just flows normally on the existing paths from there.

warped socket
#

because if I don't pass ${widget.gym.stripeAccountId} in the config it fails in confirming the payment intent

#

because the client secret is generated for the connected account not the platform (as the sub is created in the connected account not the platform)

#

basically i'm stuck, either way i get an Invalid token id: tok_xxx

#

wait a sec, let me try one thing. EDIT: nothing i've tested an assumption and doesn't work

lean harbor
#

in the config it fails in confirming the payment intent
because the client secret is generated for the connected account not the platform (as the sub is created in the connected account not the platform)
then why isn't that a problem for other cards?

#

you need to confirm the PaymentIntent with the cloned PaymentMethod object

#

maybe you're getting mixed up at some point and you try to confirm the PaymentIntent with the original token/PM object from the platform instead of the cloned one on the connected account

warped socket
#

no, in the code is correct I'm passing the correct pm

#

is not a problem for card because it happens in 2 moments in time

#

while here it happens contextually

lean harbor
#

then it should just work. So when you have an error, what's the request ID req_xxx of it?

warped socket
#

Let me generate the 2 failing scenario and i'll send you the req

#

Ok super weird

#

i see the failure in the console, but no req

#

Invalid token id: tok_1QNEbcLAVB3C1lDIhzNd0cgm

#

this was the error

#

k found it sorry

#

req_AhVfvYzmAK9lca

#

this is the first scenario:

  1. The config in the frontend is $publishableKey/$connectedAccountId
  2. In the backend i'm attempting to create a Payment Method from token in platform
#

OOOK I think i've manage to make it work!

lean harbor
#

so that error is expected, the token is on the connected account

#

hence, "do not pass ${widget.gym.stripeAccountId} in the config" as mentioned earlier to create the token on the platform instead, and then that request would work

warped socket
#

Ok, yes

#

but there was another small thing to do

#

before when i removed the stripeAccountId in the config

#

i also remove the stripeAccountId in the cofirmPayment (the thing that confirm the PI with client_secret in the UI)

#

so i got into another error becuase the PI was invalid (since it was created for the connected)

#

Now I've understood

lean harbor
#

yes, that should be retained.
the flow is
create token+PM on platform -> clone them to connected account -> create and process PaymentIntent on connected account(stripeAccount passed in front and backend) using the cloned IDs

warped socket
#

ok make sense

#

this should be simpliefied

  @override
  Future<void> confirmPaymentWithWallet({
    required String clientSecret,
    required String token,
    String? onBehalfOf,
  }) async {
    _setupConnectedAccount(onBehalfOf);
    final params = PaymentMethodParams.cardFromToken(
      paymentMethodData: PaymentMethodDataCardFromToken(
        token: token,
      ),
    );
    await _stripe.confirmPayment(
      paymentIntentClientSecret: clientSecret,
      data: params,
    );
  }
#

into this:

  @override
  Future<void> confirmPayment({
    required String clientSecret,
    String? onBehalfOf,
  }) async {
    _setupConnectedAccount(onBehalfOf);
    try {
      await _stripe.confirmPayment(
        paymentIntentClientSecret: clientSecret,
      );
    } on StripeException catch (e) {
      throw PaymentSystemRepositoryException(e.error.localizedMessage ?? e.error.message);
    } catch (e) {
      rethrow;
    }
  }
#

as in this case I don't need to create the pm from the client as i'm doing everything in the backend (cloning and all)

#

amazing, thank you so much for your help

lean harbor
#

but yeah either way this should all work

warped socket
#

in the flutter_stripe library that is done implicitly when you are not passing the params

#

so no need to handle that I guess

#

and i'm seeing it working properly

lean harbor
#

oh yeah I forgot this was mobile