#steven_link-statementdescriptors
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/1387541505073217548
๐ Have more to share? Add more details, code, screenshots, videos, etc. below.
๐ Can I ask for more details to help you? You gave one example and asserted something broke. Can you give a clear recent example where it worked so I can compare?
Can you also explain exactly what "billing descriptors via API" means? That is not a concept we have so I think you are referencing a feature named differently
Okay so statement descriptor. Can you try and provide a clear summary beyond the picture? Like show me an exact PaymentIntent id where this displayed before so I can compare
steven_link-statementdescriptors
Here's a payment-id: pi_3Re0EZDDdhNYBUte0ooVVe2Q
this was displayed on all stripe link payments before today
now none, even old ones return any data
it looks like Stripe made some changes to redact all statemetn descriptors on Link changes today not understanding what this field was being used for
can you clarify what you are using it for? It's just the Dashboard UI so trying to grasp what is blocking you exactly. I'm investigating in parallel but I don't completely grasp what you are describing yet, especially since it's not easy to debug the "absence of information" since you say it was here before but isn't anymore.
Ah yes, so it's nulled out in the API response as well - we have over 4000 Stripe connected merchant accounts and we rely on this field to perform part of our services - reconciling payment data with consumer complaints
Before today this field was available via API however due to some change it's now no longer available, both on new and old charges
A consumer may not know their email used for payment nor card details and the billing descriptor is the only field available, so removing the string from Stripe prevents merchants from reconciling complaints - it's quite bad - our clients are already experiencing an increase in disputes
yeah sorry can I ask you to try and explain this more clearly? IT's really important. You say "it's nulled out in the API" but what exact part is nulled out. Can you share exact JSON response so we're aligned?
Statement descriptors are subtle and there are many different properties in the API across API Resource so I want to make sure we're talking about the same thing here.
I get you're worried about the change, but to help you I do need clear information
Yes one moment
"statement_descriptor_suffix": null,
in the /charges endpoint this field is now null for all Stripe Link charges
thanks, sorry to push but you again said "billing descriptor" which isn't a thing. Now you're talking about an increase in disputes which seems completely unrelated to the ask about the Dashboard showing an empty value. Seems you imply we don't put the statement descriptor when charging the customer and so it's not on their bank statement
Those have always been null in the API unless your code explicitly forced the values in the API, which you aren't doing here since it's an Invoice right?
Do you have an example Event evt_1234 where this was set before so I can look? A few days ago is fine. I just want to compare. Those Events are snapshots and can never be mutated
No, that's not what I'm implying. The statement_descriptor is not available via API (or dashboard) so if a cardholder opens a complaint and is unable to reconcile their account with card numbers etc (which are unavail via link) the descriptor is the only field that can be used
Yeah give me a moment
Sure thing. And to be clear I am not saying we didn't change somethng. I just want to align on the vocabulary to find the discrepancy.
You mentioned "increase in disputes" which confused me
These were never null until today or so
sure, that's where having an exact example where they are not null in the Event will help me track it down
Yes the merchant has no way to refund the cardholder in these cases - hence they will receive more chargebacks
pi_3RVoPBDDdhNYBUte1pzEM6p1
check out this payment
has the statement descriptor not null
sure but I want proof that it was not null before. I need an exact object that has this set and the exact Event id proving it
ok
ah that one has a charge.succeeded event that isn't null https://dashboard.stripe.com/events/evt_3RVoPBDDdhNYBUte1tOeBKqq looking
okay now I have what I need to piece things together. I can see internally that the PaymentIntent you just shared was explicitly confirmed with statement_descriptor: 'xxxxx' and the more recent one (first one) was not.
Trying to figure out the difference
some change definitely went into Stripe - we have several merchant tickets related to the issue being openeed
hum
Are you the developer? I have a hunch but I want to make sure I am talking to the right person
Yes I am, I'm the CTO of Chargeblast
This is from a client's payment volume today - only ~5% of their Link volume have descriptor fields available via API. As of a few days ago it was every transaction
Okay so this will be rapid fire back and forth if that works for you. Let's pause the "impact" for a sec and focus on the implementation
Kk
The PaymentIntent pi_3RVoPBDDdhNYBUte1pzEM6p1 that you mentioned is associated with the Invoice in_1RVnS5DDdhNYBUteaCnB9pSk.
That PaymentIntent and resulting Charge py_3RVoPBDDdhNYBUte1ePHlb9p do have statement_descriptor set (not sharing the value because this thread is public)
Now the value I see on the Charge was added explicitly by your own code as a Connect platform. That one you called the Updated Invoice API and explicitly passed statement_descriptor: 'xxxx'. You even did this multiple times in a row
So first question: does that ring a bell? Do you have code that does this?
let me know if you want to hop on a call or prefer other method of comms.
Yes we do have code that does this
Written text is all I can offer
Okay so you have code that does this. The example you gave me that has a statement descriptor had that code running
The other one you gave me did not
The issue is related to the exposing of the charge data via API - all charge objects (including stripe link payments) will have some statement_descriptor field available in Stripe's internal database. The card holder has to see something on their banking app whether it was through a normal card or Link
Try to focus on my question
We have code that does this yes
Sorry I know it's annoying, I have a weird way of working and I have done this for years. If we run around with alternative "what if" we won't get anywhere I think. Not quickly. But I will touch on all those threads if we get stuck
Okay so can you clarify why that logic/code was never done on the other Invoice that you started with?
To me, all is expected here. If you don't force a statement descriptor value in the API either on the Invoice or the PaymentIntent, the resulting Charge in the API will always have statement_descriptor: null to reflect nothing was passed/forced. It's separate from what we do send the bank, it's purely an API concept
Ok, so I could provide payment ids for Stripe Link payments that have statement_descriptor passed in via non-null, that was set at the sub-account level, however currently have null exposed to us as the connected platform
Previously, Stripe would return the field actually shown to the bank
I don't understand your words ๐ฆ
You are mixing up concepts in our API so I'm confused
So our client - the Stripe sub-merchant (connected account)
sends in their own value via API
it then appears blank when we query that charge
pause here
Statement descriptors are really complex and there are many layers of "where it can be configured". And there's a real difference between what appears in our API in various places and what's sent to the bank.
So focusing purely on the API and code, clarify what you mean?
Do you mean a connected account goes in their Dashboard and configures their default statement descriptor + statement descriptor prefix and you do nothing in the API?
Something else?
give me a moment
For example here:
pi_3RdIlQHCcS7hScp61GmIrH2e
There's a request to update the invoice id with a statement_descriptor however the charge has null value for the statement_descriptor field
Do you mean a connected account goes in their Dashboard and configures their default statement descriptor + statement descriptor prefix and you do nothing in the API?
Connected account can transmit their ownstatement_descriptorfield via API, which is now being lost
Connected account can transmit their own statement_descriptor field via API
what does that sentence mean?
Focus on exact code. What do you do in your code? really sorry this is so confusing to me
yes apologies it's confusing as there's a few moving parts
we have implemented stripe as stripe connect
pause
I understand Connect and your setup. I just need you to be crisp about what your code does. The connected account does nothing since you control all the code.
The PaymentIntent example you gave me was confirmed before the Invoice was updated with a statement descriptor
Ok fair
Our code, and there's a race condition so it only works some % of the time, will set the descriptor for the charge to the merchants public shortened descriptor + a 6 to 8 digit hex cdoe
Just to clarify what I think happens. I am not certain but I am as close to certain as I can be
The Charge object will always have statement_descriptor: null unless your code explicitly forced a statement descriptor on the PaymentIntent or Invoice.
Is there any chance I could DM you non-public info?
So right now, it looks to me like nothing is broken and there is something on your end not setting the statement descriptor properly
And I am not saying it's the case, I'm trying to summarize where we are. Until we find a clear example where it worked before and doesn't now I cant do much
and yes totally fine to DM. I'll talk here but I can read the DMs to piece things together
kk
Okay let me take a wild guess that will likely explain it all
Your code reads a completely different property on Charge called calculated_statement_descriptor and you didn't realize it is only ever set for normal card payments and never for other payment methods including Link.
Can you confirm that's what you do and what populates the data in your screenshots?
one sec
the data populated in my screenshot is just our local db instance of what's in the stripe account under statement_descriptor (corresponds to descriptor in our DB)
that makes no sense ๐
if it was on the Account it would have nothing to do with Link
Sorry you keep going on tangent that aren't relevant
I get it, you are trying to give info but none of that is what I need I am certain of that
You shared a screenshot of rows of payments. Some have an empty descriptor. It'd make no sense that this value came from the Account API Resource acct_12345
It must come from either the Charge, the PaymentIntent or the Invoice.
give me a bit
the issue is the data for descriptors got wiped on our side because we sync state with stripe
so i have to hunt it down in a different silo thats hard to query
I did go and look at one of those accounts and their Charges from a week ago. I spot-checked 20 Charges by hand by looking at the charge.succeeded Event that would not have been mutated in any way and they all have null
I am fairly confident you are mixing up statement_descriptor and calculated_statement_descriptor on Charge for now
Are you able to see if changes went into how statement_descriptor is exposed via API for Link charges within the last 48 hours?
I can run a query that shows this definitely stopped getting exposed - I can show if the statement_descriptor field is null for charges where wallet == link over time
Sadly with the currently description I have no idea where to look
Like this is subtle and you use really specific words but it's unclear if you mean them
Like for example wallet == link to me would mean that you have a Charge object where the PaymentMethod object (pm_123) that was used has type: 'card' and then inside that we model Link as a wallet.
This is completely different from a PaymentMethod that has type: 'link' and those are modeled differently in our API
Okay could you explain the difference between these two?
Here's the query I'm running:
DATE("transaction_date") AS day,
COUNT(*) AS total_count,
COUNT("descriptor") AS descriptor_not_null_count,
COUNT(*) - COUNT("descriptor") AS descriptor_null_count
FROM "direct"."common_charge"
WHERE "wallet" = 'link' AND "status" = 'succeeded'
GROUP BY DATE("transaction_date")
ORDER BY day DESC
LIMIT 300 OFFSET 0;
My understanding is wallet=link would guarantee its a card payment and should have a statement_descriptor (as non-card links could be bank transfers with no descriptor)
(typing, will take a while)
Link is our payment method. We have 3 modes
- Link as a PaymentMethod type, where it has
type: 'link'and abstracts the underlying payment instrument, you don't know if it's a card or a bank account or klarna, you just know they use Link, like with Klarna and such. In that case the Charge haspayment_method_details[type]: 'link' - Link can also be used to prefill card details on websites/merchants that don't accept Link and only accept cards. So those integrations expect
type: 'card'in the API so in that case we still use Link for the consumer paying you but to you it looks like they used their card to pay you. In that case the Charge haspayment_method_details[type]: 'card'andpayment_method_details[card][wallet][type]: 'link' - Link can also be used to pay with bank account details and make it look like a card payment to integrations that only expect cards. In that case the Charge has
payment_method_details[type]: 'card'andpayment_method_details[card][wallet][type]: 'link'andpayment_method_details[card][brand]: 'link'. That one is rare and unlikely to be relevant here
So now in terms of statement descriptor
Option #1 will not have calculated_statement_descriptor
Option #2 will have calculated_statement_descriptor
At this point I would like to see your code that updates the database. I want to see the exact bit of code that reads the Charge API
ok
return CommonChargeDirect(id: id, transactionDate: created, amount: Double(amount ?? 0), currency: currency?.rawValue ?? "usd", refunded: refunded ?? false, descriptor: calculatedStatementDescriptor ?? statementDescriptor ?? "", customer: customer, status: status?.commonStatus ?? .Succeeded, last4: last4, bin: bin, wallet: wallet, source: .Stripe, chargeId: id, brand: brand ?? .Unknown, funding: funding, accountId: accountId, riskScore: riskScore, userId: user.id ?? "", issuer: issuer, customerEmail: customerEmail, authorizationCode: auth, streamId: stream.id,
we use calculatedStatementDescriptor and fallback to statementDescriptor when its null
is it possible that statementDescriptor got nuked?
ding ding ding I was right
descriptor: calculatedStatementDescriptor ?? statementDescriptor ?? "" you are literally starting with calculatedStatementDescriptor which I tried to explain a couple times earlier
It might not be the exact answer and we still need to dig but it's the bit I kept asking about because it's so subtle
Option #2 will have calculated_statement_descriptor```
how about for statement_descriptor?
ok
It's okay we'll get there but that's wy I keep pushing to answer my exact questions and not look at "alternative/unrelated things"
if (paymentMethodDetails?.type == .link) {
wallet = .Link
}
thats how i define if it's link
So your code literally looks at calculated_statement_descriptor by default. This value is always null for Charges that don't have payment_method_details[type]: 'card'.
Always always null.
Now your original question is not that. You are certain we removed the values from old Charges. That is possible though unlikely. And I need a real example that I can look at which for now has not been shared. All the examples you did share had a valid explanation as to why it's null.
ok fair
I fallback to statement_descriptor in the event calculated is null. So I'm guessing calculated was always null and my fallback is no longer being populated
So next step is to find a clear example Charge object that is not for a card and has statement_descriptor currently null but it was not null before.
I believe that is impossible based on this thread and it's more likely that what changed is that something caused your integration to suddenly get the new version of Link
are you able to query a charge_id from the actual statement descriptor if I provide the account_id? That would give us an example
not if it's not a card payment
assuming it's a card payment but in category [1] where the actual method has been abstracted
then no, because then it's not a card payment and I am fairly certain all 3 properties in the API would always be null
calculated_statement_descriptor
statement_descriptor
statement_descriptor_suffix
Hmm, so you're saying all card payments that are Link should have one of these fields non-null, correct?
what does "all card payments that are Link" mean?
prefilled card via link
then yes
Link as a PaymentMethod type, where it has type: 'link' and abstracts the underlying payment instrument, you don't know if it's a card or a bank account or klarna, you just know they use Link, like with Klarna and such. In that case the Charge has payment_method_details[type]: 'link'
``` This with the method being card
๐
you literally quote the one that says it's not card but saying it's card. Also you say "method" do you mean PaymentMethod?
in this quote you are saying the actual funding method is abstracted
which means it could be Klarna, something else or a card
you mean payment instrument not method or funding method? At least in my sentence?
So by funding I mean the funding source of the underlying Link payment is a card (which has been abstracted)
Sorry it's so subtle that I am extremely strict with the vocabulary because it's so hard otherwise since it's all a card to humans thinking about this
In paypal parlance the funding method is what's used to fund the wallet - its abstracted and not exposed to customers
If the Charge has payment_method_details[type]: 'link' then calculated_statement_descriptor will be null. Always has been like this
Yeah sorry I know other companies use different words, that's why I try to outline the exact ones I use so that we're aligned.
don't get me started on calling a payment method Link for example ๐
To explain with more words what I was saying earlier
calculated_statement_descriptoris our own calculation of what was really sent to the card network. It's only set for real card payments where the Charge haspayment_method_details[type]: 'card'. It will always be null for everything else, including payments via Link withpayment_method_details[type]: 'link'even if the payment instrument (what you call a funding source) was a card. We just don't show the calculated value.statement_descriptorthis is only set ifstatement_descriptorwas set explicitly either on the Charge creation, original PaymentIntent or Checkout Session or Invoice, whatever controls the Charge creation. Otherwise it is null, it doesn't inherit the account's default value, it's just null in the API
statement_descriptor_suffixthis is only set ifstatement_descriptorwas set explicitly either on the Charge creation, original PaymentIntent or Checkout Session. Otherwise it is null, it doesn't inherit the account's default value, it's just null in the API. It also can't be controlled with an Invoice
"id": "py_3RdtHODDdhNYBUte1dzotX9E",
"object": "charge",
"amount": 4999,
"amount_captured": 4999,
"application_fee_amount": null,
"balance_transaction": "txn_3RdtHODDdhNYBUte1IU8EfKr",
...
"calculated_statement_descriptor": null,
"fraud_details": {},
"invoice": "in_1RdsKQDDdhNYBUtej6tcwLKE",
"paid": true,
"payment_intent": "pi_3RdtHODDdhNYBUte1g6K1b9r",
"payment_method": "pm_1RbKz7DDdhNYBUtegW92fTBj",
"payment_method_details": {
"link": {
"country": "TH"
},
"type": "link"
},
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}```
So this is definitely a card here, I'm nearly certain
Sure but that's what I keep explaining. It doesn't matter at all that the payment instrument, what you call a funding source, is a card. To the API this is a payment via Link so what I explained above (and said a few times) fully applies
^ ok the above is helpful
so I see part of the "issue" here which makes this very confusing
It is subtle. And at this point I can be candid: it is so frustrating we don't populate calculated_statement_descriptor. There are literally 3 or 4 jiras internally from me begging the product teams to finish this and add it but alas here we are
an API request is made to update the descriptor after the charge is created and so all rebills even on these links will have calculated descriptors
an API request is made to update the descriptor after the charge is created and so all rebills even on these links will have calculated descriptors
what does that mean "all rebills"?
You mean future Invoices for the Subscription?
if the charge was created through a sub update rather than first time purchase the calculated_descriptor field populates
Those future Invoices will also not have anything unless you properly set statement_descriptor on each Invoice before payment is attempted
It is subtle. And at this point I can be candid: it is so frustrating we don't populate calculated_statement_descriptor. There are literally 3 or 4 jiras internally from me begging the product teams to finish this and add it but alas here we are
Anything I can do to push this forward let me know
sadly no, I have a loud voice internally, it's just really hard
Let's refocus: you were certain earlier we nuked the data on existing/previous Charges. Are you mostly convinced that this was incorrect?
It's hard to say
we have several tickets coming in today that were related to this so I assumed somethign chnaged and when I looked at the data it looked very differnt but im not convinced
I'm mostly convinced but not completely
yep that's fair. That's where I am right now ๐
I'm happuy to debug this further. I just want to close down certain parts of the discussion
yep can close this one out. I shot you a DM as well ๐
Right now it could be either
- We changed something, to prove it we need a recent Charge object that is > 30 days old and I can compare the Event and what the API says
- Something changed in your integration and some merchants are now getting
payment_method_details[type]: 'link'and never had that before
Is it easy for you to confirm if it's #2?
Happy to just drop it too if you prefer