#nerder_best-practices
1 messages ¡ Page 1 of 1 (latest)
đ 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.
- nerder_error, 1 day ago, 62 messages
hey @lean harbor
hi! not that's not possible, you can only clone a card from a platform to a connected account.
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:
- create a payment method (in the platform)
- 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
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?
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
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?
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
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.
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
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
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
then it should just work. So when you have an error, what's the request ID req_xxx of it?
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:
- The config in the frontend is
$publishableKey/$connectedAccountId - 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!
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
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
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
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
I think you still do need to pass https://docs.stripe.com/js/payment_intents/confirm_payment#confirm_payment_intent-options-confirmParams-payment_method (which is what you're doing in params in the first example, I think) , as otherwise I think what can happen is confirmPayment will try to create a new PaymentMethod from the Elements UI, but I'm not 100% sure on that
but yeah either way this should all work
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
oh yeah I forgot this was mobile