Closed princefishthrower closed 2 years ago
hi @princefishthrower
the expiry month and expiry year field can be your own <input>
fields. Secure Fields only creates iframes for cardno and cvv.
Also, when using browser autofill you can listen for the autofill
event to automatically fill your own expiry fields:
https://docs.datatrans.ch/docs/secure-fields-events#section-browser-autofill
Hi @dmengelt
Ah I see now those fields are not needed to be through an iframe.
Additionally, from what I understand I would first need a transaction Id to properly initialize the secure fields. But where do I pass this transaction ID? In this.initSecureFields()
, i.e. this.initSecureFields(transactionId)
?
Transaction IDs are not "secret", right? Because I would need to pass that to our web payment page with something like ourwebsite.com/pay?transactionID=abc123
... ultimately we plan to call this web payment portal through a WebView
on a React Native app. Is this a pattern you have seen or could recommend?
Thanks again for the help.
@princefishthrower did you manage to read our documentation on https://docs.datatrans.ch/docs/secure-fields?
You will get the transactionId
from the initial server to server API call and the you would initialize Secure Fields as follows:
var secureFields = new SecureFields();
secureFields.init( {{transactionId}}, {
cardNumber: "cardNumberPlaceholder",
cvv: "cvvPlaceholder",
});
Correct. They are not a secret. No, we are currently not aware of any integrations using Secure Fields within a WebView on a React Native app.
@dmengelt - hmmm... why is it then I only see this.initSecureFields()
in the React example: https://github.com/datatrans/secure-fields-sample/blob/c7ed9a73015bd2faaa1b9de00f8d22774c13c2c1/react-example/src/SecureFields.js#L41
and also this.secureFields.initTokenize()
: https://github.com/datatrans/secure-fields-sample/blob/c7ed9a73015bd2faaa1b9de00f8d22774c13c2c1/react-example/src/SecureFields.js#L91
I can't find documentation on either of these functions, especially the fields
and options
values for initTokenize()
...
Also, is there any example or someone I could talk to about possible integrations with React Native? I also contacted Datatrans through email, the contacts I spoke to are also unaware of advising for React Native...
@princefishthrower
this.initSecureFields
is a class method of our sample React component.
However, sorry for the confusion. In our GitHub examples we use initTokenize
. However you should only use initTokenize
if you are are a PCI-Proxy customer. Is this the case for you? If you want to do actual payments with Secure Fields you should follow https://docs.pci-proxy.com/collect-and-store-cards/capture-iframes
fields
values can be cardNumber
and cvv
:
secureFields.init(
{{transactionId}},
{
cardNumber: "cardNumberPlaceholder",
cvv: {
placeholderElementId: "cvvPlaceholder",
inputType: "tel",
placeholder: "enter your cvv"
}
},{
styles: styles,
focus: "cvv"
}
);
https://docs.datatrans.ch/docs/secure-fields-options#section-further-initialization-settings
options
values can be styles
, focus
and paymentMethods
. You can find some examples here: https://docs.datatrans.ch/docs/secure-fields-options#section-further-initialization-settings
Regarding react native: we don't have any specific examples on how to integrate this properly. I'm sure there are best practices. Please make sure to check whether our Mobile SDK could be helpful in your case.
Do I need to be a customer of PCI-Proxy to use Secure Fields? I assumed secured fields would work out of the box as it is mentioned directly in the Datatrans API docs as a possible method of customer initialized payments.
You don't π
What I'm trying to say is:
PCI-Proxy customer (they mostly "only" do tokenization): Use initTokenize
Datarans Payment customer: 1. initial server to server call to get a transactionId
, 2. Use init(trxId)
Ok, it's clear now! When I run my example locally, I unfortunately get a cross-origin error:
Uncaught Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://reactjs.org/link/crossorigin-error for more information.
at Object.invokeGuardedCallbackDev (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:4005:19)
at invokeGuardedCallback (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:4056:31)
at invokeGuardedCallbackAndCatchFirstError (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:4070:25)
at executeDispatch (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:8243:3)
at processDispatchQueueItemsInOrder (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:8275:7)
at processDispatchQueue (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:8288:5)
at dispatchEventsForPlugins (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:8299:3)
at eval (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:8508:12)
at batchedEventUpdates$1 (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:22391:12)
at batchedEventUpdates (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:3745:12)
I assume that is because I am making requests from localhost
. Is there a sort of dev mode flag I can switch on or will I have to proxy myself?
Furthermore, when it is run even on a real website, I still get a variety of errors:
react-dom.production.min.js:101 Uncaught TypeError: Cannot read properties of undefined (reading 'focus')
at Object.B.focus (secure-fields-2.0.0.min.js:1:4298)
at callback (SecureFields.tsx:191:49)
at onClick (SecureField.tsx:14:93)
at Object.We (react-dom.production.min.js:52:317)
at Ye (react-dom.production.min.js:52:471)
at react-dom.production.min.js:53:35
at _r (react-dom.production.min.js:100:68)
at Cr (react-dom.production.min.js:101:380)
at react-dom.production.min.js:113:65
at De (react-dom.production.min.js:292:189)
when trying to click either the credit card or CVV fields, and when clicking the pay button, I get:
Uncaught TypeError: Cannot read properties of undefined (reading 'postMessage')
at v (secure-fields-2.0.0.min.js:1:4239)
at Object.B.submit (secure-fields-2.0.0.min.js:1:3600)
at onClick (SecureFields.tsx:218:48)
at Object.We (react-dom.production.min.js:52:317)
at Ye (react-dom.production.min.js:52:471)
at react-dom.production.min.js:53:35
at _r (react-dom.production.min.js:100:68)
at Cr (react-dom.production.min.js:101:380)
at react-dom.production.min.js:113:65
at De (react-dom.production.min.js:292:189)
is there an example floating around somewhere that uses both React and the init()
method? Or should it theoretically just be able to swap out of initTokenize
for the init()
function to have it work?
One last requirement I would like is for the customer to have the option to to save their card (in Datatrans lingo I guess make an "alias" for the card). Can this also be done through the secured fields api? Didn't see this option in the docs anywhere. (https://docs.datatrans.ch/docs/secure-fields)
This is quite strange... as a sanity check I copied the src/
folder of react-example
directly to a fresh create-react-app
just to see if it works there. Immediately on page load I get:
Uncaught TypeError: Cannot read properties of undefined (reading 'placeholderElementId')
at b (secure-fields-2.0.0.min.js:1:1702)
at n (secure-fields-2.0.0.min.js:1:1648)
at Object.B.initTokenize (secure-fields-2.0.0.min.js:1:820)
at n.initSecureFields (SecureFields.js:91:23)
at HTMLScriptElement.n.onload (SecureFields.js:48:12)
as well as the same focus
and postMessage
issues above when trying to interact with the form. Looking at the react example provided by you guys, I can't see webpack doing anything special that makes these errors go away, but I guess I need to look harder, as there is obviously a difference between them.
EDIT: found the issue, buried in the example/src/index.js
, I see an example config is passed to the component when calling ReactDOM.render
:
ReactDOM.render(<SecureFields config={{
merchantID: '1100007006',
fields:{
cardNumber: {
placeholderElementId: 'card-number',
inputType: 'tel'
},
cvv: {
placeholderElementId: 'cvv-number',
inputType: 'tel'
}
},
options: {}
}} />, document.getElementById('root'))
Uncaught Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://reactjs.org/link/crossorigin-error for more information.
That sounds like a general problem with React.
What happens if you run the React example app? I just tested it and it works fine for me. Could it be that you're trying to load and initialize the secure fields script on your server vs. in the browser? You should load it only in the browser.
If this doesn't help, please share your code in a repo, gist or similar.
One last requirement I would like is for the customer to have the option to to save their card (in Datatrans lingo I guess make an "alias" for the card). Can this also be done through the secured fields api? Didn't see this option in the docs anywhere.
Good input. We have to add this to our docs.
So once you finished the payment (don't forget to do the final server to server authorization call) you can get the alias by calling our Status API. You will find the alias within the card.alias
property.
@dmengelt - so the alias is generated regardless?
@pstadler - more interesting findings. When I use the example merchant ID (1100007006), both credit card and CVV fields work. However, when I pass ours (1100034100), only the credit card field works. Then I realized this was because we are not PCI Proxy customers, so instead of using initTokenize
,
I passed the following:
this.secureFields.init("220121091819405816", {
cardNumber: "card-number",
cvv: "cvv-number"
})
The credit card field works this way, but I still get the same error why I try to click the CVV field:
Uncaught TypeError: Cannot read properties of undefined (reading 'focus')
at Object.B.focus (secure-fields-2.0.0.min.js:1:4298)
at callback (SecureFields.js:75:1)
at onClick (SecureField.js:8:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:1)
at invokeGuardedCallback (react-dom.development.js:4056:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070:1)
at executeDispatch (react-dom.development.js:8243:1)
at processDispatchQueueItemsInOrder (react-dom.development.js:8275:1)
at processDispatchQueue (react-dom.development.js:8288:1)
This is curious, because upon inspecting my DOM, I indeed have a <div>
with id="cvv-number"
The last thing I can guess is that I'm no longer able to pass the inputType: 'tel'
that was passed in the config for the initTokenize
call, maybe that is causing the problem?
EDIT: Okay, saw that it is possible to pass this input type (and even placholder!) so I tried this:
this.secureFields.init("220121091819405816", {
cardNumber: {
placeholderElementId: "card-number",
inputType: "tel",
placeholder: "Credit card"
},
cvv: {
placeholderElementId: "cvv-number",
inputType: "tel",
placeholder: "CVV"
}
},{
focus: 'cardNumber'
})
Credit card field still fine, CVV still throwing errors. I'm going to just fork the repository and show you what is going on.
so the alias is generated regardless?
Yes.
I just tried with your merchantId. And this works for me:
secureFields.init(
'220121094931809683',
{
cardNumber: {
placeholderElementId: 'card-number',
inputType: "tel",
},
cvv: {
placeholderElementId: 'cvv-number',
inputType: "tel",
}
},
{
focus: "cardNumber"
}
);
@dmengelt Great news for the alias feature!
Hmmm what do you mean "with your merchantId" - is there a place to provide it somewhere in the configuration?
I just did the same (forked from this repository) and it doesn't work. This commit was my only change: https://github.com/princefishthrower/secure-fields-sample/commit/4b459800fe01089dc64da813b31a1a3d58ab311b
If you build it and try it in the browser you'll see the CVV doesn't populate with the placeholder, and clicking it throws an error
@princefishthrower I meant I created a transactionId
by using your merchantId
. Sorry.
For your fork you now need to add the expiry handling.
If I run your fork I get:
Uncaught TypeError: Cannot read properties of undefined (reading 'expy')
Regarding Uncaught TypeError: Cannot read properties of undefined (reading 'focus')
.
This happens when you use secureFields.focus('cvv')
but the transactionId
is already consumed.
Once the form is submitted (secureFields.submit()
) you need to create a new transactionId
.
@dmengelt - thanks for all your help, this is great stuff. I think I'm very close to getting it to work! I will investigate and return shortly...
@dmengelt - thanks again for all your help! got everything working! (at least so far π)
Transactions are showing up in our dashboard π
I have some other technical questions, however. π
As you've told me, these initialized transactions can only be consumed once. Is this also the case for the authenticated transactions, as well as the authorized transactions? If this is the case, this would mean we would have to start all over if there is ever a problem at any point along the path to settlement.
I notice that the data.transactionID
returned in the case of success is identical to the initialized transaction - does this mean the transaction number itself remains the same through all stages of authentication and authorization? Same with the refno
- should we keep this refno
the same at each stage of the transaction process, or generate a new one at each stage (i.e. refno-init-123
, refno-authenticated-123
, refno-authorized-123
)?
The initialized transactions are documented as having a lifetime of only about 30 minutes. I assume the authenticated and authorized transactions are much longer lived?
Would you recommend authorizing the authenticated transactions almost immediately after they are authenticated, or alternatively, authorizing them just before settling (or possibly cancelling) them? (Maybe the timing isn't all that important really, but I at least know from the documentation that a transaction has to be authorized at some point before settling or canceling it)
Again, thank you @dmengelt and @pstadler for all your help, you can probably guess from the spammy messages I'm under a tight deadline and your help has been extremely helpful!
got everything working! (at least so far π)
Sounds good! Could you maybe share a transactionId
for us to verify and maybe also a screenshot of the Secure Fields integration. I'm curious how it looks. π
No it is only about the client side Secure Fields integration. So you can only do secureFields.init(trxId)
once. But retrying (make sure to use idempotency if you can) for example the final server to server authorization call is perfectly fine. If your backend process allows it you can also consider to use "autoSettle": true
for the server to server authorzation call. So then no settlement call is needed.
Yes correct. The transactionId
remains the same throughout all stages. For the refno
I would also keep the same.
Correct. Authenticated and authorized transaction live "forever".
I suggest to authorize almost immediately after the authentication.
Just one question from my side. Did you manage to handle the 3D process?
@dmengelt - sure, here are some transactions from early this morning and more recently today (I haven't authorized any of them yet):
220122153250437362 220122124346213446 220122000520909081
And the screenshot of our actual page:
(Didn't realize until later that the denomination is in cents - so actually it should show "Pay CHF 1,23" to be correct.) It looks very mobile-ish because as I mentioned we open this in a WebView from our mobile app and this keeps the UI experience clean for our customers π
Unfortunately for now I think our business requirements require us to use autoSettle: false
as we need to clear payments based on customer events at a slightly later time. (I'm also aware certain providers only keep pending transactions open for so long - this will be something for us to discover and refine as we go)
I haven't looked in to the 3D process yet. Actually I haven't done much work towards extra steps in general (error message styling, success etc.)- though I am working on this immediate authorization right now. The next step is then to figure out how to take the customer's choice to remember the alias or not - i guess this will be done from handling the success callback - a POST request should be sent to our success URL in that case, correct? Then i'll have to use the refno or something maybe to see if they chose to save the card data...
For the 3D process, Is there a way to know if a provider will trigger the 3D process? or is based on location, timing, amount, etc.?
By the way, I've built out my own initial TypeScript typings for the SecureFields
library and have successfully refactored the SecureFields
usage in a functional component with hooks - was thinking of commenting in the other open issue towards that, I can provide some pointers.
Nice! It looks good!
I'm also aware certain providers only keep pending transactions open for so long - this will be something for us to discover and refine as we go
Exactly. A general rule of thumb for credit cards is: Try to settle before 30 days have passed. Reach out to our support if you need detailed information about this as it could depend on the acquirer you are going to use.
The next step is then to figure out how to take the customer's choice to remember the alias or not - i guess this will be done from handling the success callback - a POST request should be sent to our success URL in that case, correct?
No. You have to do this on your own. A possible process could be:
returnUrl
. However, this returnUrl
is only used when there is 3D involved.secureFields.init(trxId)
redirect
property with a URLreturnUrl
from step 1.card.alias
property.Of course if you are not handling 3D you can omit steps 6. and 7.
For the 3D process, Is there a way to know if a provider will trigger the 3D process? or is based on location, timing, amount, etc.?
We will not return the redirect
property in step 5. if 3D is not needed for the entered card (card not enrolled or 3D2 frictionless flow)
By the way, I've built out my own initial TypeScript typings for the SecureFields library and have successfully refactored the SecureFields usage in a functional component with hooks - was thinking of commenting in the other open issue towards that, I can provide some pointers.
Maybe @pstadler can comment on this.
weβre also using hooks internally. please share your code if you can, always nice to have different angles on a solution! ππ»
@dmengelt , @pstadler - Thanks for all the information again. I will comment in the other thread with the code that I'm using. I'll probably make an example repository based on the react example again.
By now I understand fairly well how accepting payments from customers works, but I'm having trouble seeing how to do the reverse: paying out from our bank account to customer's IBANs. Is the only way of paying out to customers banks through Klarna? If that is the case, I see i need a previously registered alias
for their account - I guess I will also need a secure fields site with an IBAN input, and I guess I could do just a 0 "payment" so really they would just be registered / stored with their IBAN so I can get an alias, right? I guess I don't understand how to tell the API to distinguish between a payment from a customer to us, to a payout from us to a customer.
@dmengelt - one more thing with the 3D process, is the transaction ID also a part of the returnUrl
in addition to the 3D result? Otherwise I guess I'll have to store the transaction ID in localstorage....
We don't have payout APIs. However, you might want to look into our marketplace solution.
one more thing with the 3D process, is the transaction ID also a part of the returnUrl in addition to the 3D result?
Yes. When redirecting back from the 3D process, we will do an application/x-www-form-urlencoded
POST
to the returnUrl
which includes the uppTransactionId
parameter.
@pstadler - ok. For the 3D redirect, I assume in addition to the POST there is a normal GET redirect to returnUrl
? Or what would the users see when the 3D is completed?
There is no additional GET
. They only thing we do is:
<form name="returnForm" action="returnUrl" method="post">
<input type=hidden name="uppTransactionId" value="xxx" />
</form>
So what the user sees at the end of the 3D process is the content of your returnUrl
Interesting... the Secure Fields endpoint /v1/transactions/secureFields
has no options for autoSettle
, language
, allowedPayments
, refno
and so on...
Actually refno
I guess would be most important for us here... is that only something we would set when authorizing the authenticated transaction? Or is there a way to continue using the standard initialization endpoint (which we are currently using) and also provide a returnUrl
?
is that only something we would set when authorizing the authenticated transaction?
Exactly.
autoSettle
: you can define this in the server to server authorization callrefno
: same as for autoSettle
language
: what for? π we don't display anything where we would need the language.allowedPayments
: there is the paymentMethods
array when doing secureFields.init(trxId)
for example:secureFields.init(
{{transactionId}},
{
cardNumber: "cardNumberPlaceholder",
cvv: "cvvPlaceholder"
},{
styles: styles,
paymentMethods: ["ECA", "VIS"] // allowing MC and Visa only
}
);
Or is there a way to continue using the standard initialization endpoint.
No please use the Secure Fields initialization endpoint.
@dmengelt - is the uppTransactionId
the only thing that is POSTed? Don't we also need to know if the 3D process was actually successful or not?
Yes. Sorry I forgot to also mention that. Next to uppTransactionId
we will also post status_3d
to the returnUrl
.
https://docs.datatrans.ch/docs/secure-fields#section-success-3-d-secure.
However theoretically you don't need this parameter as you can always do a Status API call to figure out what the status of the transaction is.
@princefishthrower all good?
@dmengelt Mostly π I haven't had time to check the 3D flow yet, I'm also unsure from these docs: https://docs.datatrans.ch/docs/3d-secure#section-3-d-secure-response what is actually returned, is it the single character (Y
, N
, U
, A
, etc.), or the full code Authenticated
, Enrolled
etc., or both?
Getting the status of a transaction and saving aliases is also working. I have a remaining question here as well though - after we manage to get a card alias, will we be able to do the "authorize" transactions (i.e. the endpoint to issue payments without user interaction), without the user doing 3D again? In otherwords, is the alias enough, or will we always have to direct these customers with 3D credit cards to go through the 3D flow?
One other thing we did notice from our beta testing is that you can use the test cards provided (for example the master card 5200 0000 0000 0007
), but with any CVV and any expiration date (not just the 123
and 06 / 25
respectively), and the payment is still able to be authenticated and authorized successfully. Is this a "feature" instead of a "bug"?
what is actually returned, is it the single character (Y, N, U, A, etc.), or the full code Authenticated, Enrolled etc., or both?
The single character.
In otherwords, is the alias enough, or will we always have to direct these customers with 3D credit cards to go through the 3D flow?
The alias (and the expiry) is enough if you want to do a merchant initiated transaction (MIT) (subscriptions or other use cases where the customer is not in session). If you have the customer "in session" you should trigger the 3D flow again. You would do that by using the "normal" initialisation API (not the Secure Fields one) and submit all the infos you have. alias
, expiry
, paymentMethod
. Then you can redirect the customers browser to the URL we return (check the Location
header of the initialisation API response) and we would automatically redirect to the 3D page of his bank.
Is this a "feature" instead of a "bug"?
Can you share a transactionId
with me so that I can quickly look this up?
@dmengelt sorry for the delay here, we've been waiting on a payment acquirer for card payments in production and as such haven't been doing any further tests. Turns out what I was saying about those authorized payments also wasn't true. I believe there has been more than enough discussion here and hopefully will be a helpful resource for anyone integrating secure fields in the future. If I run into trouble when integrating the 3D flow, I'll open up a separate ticket.
Is there a link to the full documentation for secure fields? I have only found examples with the card number and CVV, but what about the expiration date and month? Are they not needed for this method of payment process?