├── .gitignore
├── config
├── .htaccess
├── timezone.php
├── authentication.ini
└── server.php
├── api
├── threeds2.php
├── originKeys.php
├── payments.details.php
└── payments.php
├── app.json
├── LICENSE
├── payment
└── order.php
├── assets
├── css
│ ├── securedFields.style.css
│ └── style.css
└── js
│ └── main.js
├── README.md
├── CODE_OF_CONDUCT.md
├── index.php
├── Migration.md
└── threeds2
└── main_old_createComponents_flow.js
/.gitignore:
--------------------------------------------------------------------------------
1 | ./idea*
2 | *./idea
3 | *.xml
4 | *.idea
5 | .DS_Store
6 | *.iml
--------------------------------------------------------------------------------
/config/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | order deny,allow
3 | deny from all
4 |
5 |
--------------------------------------------------------------------------------
/config/timezone.php:
--------------------------------------------------------------------------------
1 | true
5 | );
6 |
7 | $browserInfo = array(
8 | 'screenWidth' => 1024,
9 | 'screenHeight' => 500,
10 | 'colorDepth' => 24,
11 | 'userAgent' => 'Chrome',
12 | 'timeZoneOffset' => 0,
13 | 'language' => 'nl-NL',
14 | 'javaEnabled' => true,
15 | 'acceptHeader' => 'http'
16 | );
17 |
18 | return array(
19 | 'additionalData' => $additionalData,
20 | 'browserInfo' => $browserInfo
21 | );
22 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Checkout SecuredFields Example",
3 | "description": "A php server example containing our iframes SecuredFields solution",
4 | "keywords": [
5 | "Adyen",
6 | "payments",
7 | "checkout",
8 | "Javascript",
9 | "SecuredFields"
10 | ],
11 | "website": "https://www.adyen.com/",
12 | "repository": "https://github.com/Adyen/adyen-secured-fields-php-example",
13 | "env": {
14 | "MERCHANT_ACCOUNT": {
15 | "description": "Your merchant account"
16 | },
17 | "CHECKOUT_API_KEY": {
18 | "description": "Your checkout API key"
19 | },
20 | "CLIENT_KEY": {
21 | "description": "Your checkout clientKey"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/config/server.php:
--------------------------------------------------------------------------------
1 | url(),
25 | 'baseURL' => $checkoutBaseURL,
26 | // 'originKeysURL' => $checkoutOriginKeysURL,// Deprecated
27 | 'paymentsURL' => $checkoutPaymentsURL,
28 | 'detailsURL' => $checkoutDetailsURL,
29 | 'returnURL' => $returnURL,
30 | 'shopperIP' => $shopperIP,
31 | );
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) Adyen B.V. (https://www.adyen.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/payment/order.php:
--------------------------------------------------------------------------------
1 | $value,
14 | 'currency'=> $currencyCode,
15 | );
16 |
17 | /** @var $reference - order number */
18 | $reference = 'order_id';
19 |
20 | /** @var $shopperReference - Your shopper reference (id or e-mail are commonly used) */
21 | $shopperReference = 'example_shopper';
22 |
23 | /** @var $shopperLocale - The shopper locale */
24 | $shopperLocale = 'en-US';
25 |
26 | /** @var $countryCode - The countrycode influences the returned payment methods */
27 | $countryCode = 'NL';
28 |
29 | /** @var $channel - the channel influences the returned payment methods (the same server can be used for iOS, Android and Point of sale */
30 | $channel = 'Web';
31 |
32 | /** @var $sessionValidity - the time the offer will be valid for */
33 | $sessionValidity = date('Y-m-d\TH:i:s\Z', strtotime('+1 hour'));
34 |
35 |
36 | return array(
37 | 'amount' => $amount,
38 | 'channel' => $channel,
39 | 'countryCode' => $countryCode,
40 | 'shopperReference' => $shopperReference,
41 | 'shopperLocale' => $shopperLocale,
42 | 'reference' => $reference
43 | );
44 |
--------------------------------------------------------------------------------
/api/originKeys.php:
--------------------------------------------------------------------------------
1 | array($server['origin'])
20 | );
21 |
22 | $setupString = json_encode($request);
23 |
24 | // Initiate curl
25 | $curlAPICall = curl_init();
26 |
27 | // Set to POST
28 | curl_setopt($curlAPICall, CURLOPT_CUSTOMREQUEST, "POST");
29 |
30 | // Add JSON message
31 | curl_setopt($curlAPICall, CURLOPT_POSTFIELDS, $setupString);
32 |
33 | // Will return the response, if false it print the response
34 | curl_setopt($curlAPICall, CURLOPT_RETURNTRANSFER, true);
35 |
36 | // Set the url
37 | curl_setopt($curlAPICall, CURLOPT_URL, $server['originKeysURL']);
38 |
39 | // Api key
40 | curl_setopt($curlAPICall, CURLOPT_HTTPHEADER,
41 | array(
42 | "X-Api-Key: " . $authentication['checkoutAPIkey'],
43 | "Content-Type: application/json",
44 | "Content-Length: " . strlen($setupString)
45 | )
46 | );
47 |
48 | // Execute
49 | $result = curl_exec($curlAPICall);
50 |
51 | // Closing
52 | curl_close($curlAPICall);
53 |
54 | // When this file gets called by javascript or another language, it will respond with a json object
55 | echo $result;
56 | }
57 |
58 | requestOriginKeys($server, $authentication);
59 |
--------------------------------------------------------------------------------
/api/payments.details.php:
--------------------------------------------------------------------------------
1 | $authentication['merchantAccount'],
21 |
22 | 'details' => $_POST['details'],
23 | 'paymentData' => $_POST['paymentData']
24 | );
25 |
26 | $setupString = json_encode($request);
27 |
28 | // Initiate curl
29 | $curlAPICall = curl_init();
30 |
31 | // Set to POST
32 | curl_setopt($curlAPICall, CURLOPT_CUSTOMREQUEST, "POST");
33 |
34 | // Add JSON message
35 | curl_setopt($curlAPICall, CURLOPT_POSTFIELDS, $setupString);
36 |
37 | // Will return the response, if false it print the response
38 | curl_setopt($curlAPICall, CURLOPT_RETURNTRANSFER, true);
39 |
40 | // Set the url
41 | curl_setopt($curlAPICall, CURLOPT_URL, $server['detailsURL']);
42 |
43 | // Api key
44 | curl_setopt($curlAPICall, CURLOPT_HTTPHEADER,
45 | array(
46 | "X-Api-Key: " . $authentication['checkoutAPIkey'],
47 | "Content-Type: application/json",
48 | "Content-Length: " . strlen($setupString)
49 | )
50 | );
51 |
52 | // Execute
53 | $result = curl_exec($curlAPICall);
54 |
55 | // Closing
56 | curl_close($curlAPICall);
57 |
58 | // When this file gets called by javascript or another language, it will respond with a json object
59 | echo $result;
60 | }
61 |
62 | requestPaymentData($server, $authentication);
63 |
--------------------------------------------------------------------------------
/assets/css/securedFields.style.css:
--------------------------------------------------------------------------------
1 | .secured-fields {
2 | position: relative;
3 | font-family: 'Open Sans', sans-serif;
4 | font-size: 14px;
5 | padding: 24px;
6 | }
7 | .pm-image, .pm-image-dual {
8 | background-color: #ffffff;
9 | border-radius: 4px;
10 | -moz-boder-radius: 4px;
11 | -webkit-border-radius: 4px;
12 | float: right;
13 | line-height: 0;
14 | position: relative;
15 | overflow: hidden;
16 | }
17 | .pm-form-label {
18 | float: left;
19 | padding-bottom: 1em;
20 | position: relative;
21 | width: 100%;
22 | }
23 | .pm-form-label--exp-date {
24 | width: 40%;
25 | }
26 | .pm-form-label--exp-year {
27 | width: 40%;
28 | margin-left: 20px;
29 | }
30 | .pm-form-label--cvc {
31 | float: right;
32 | width: 40%;
33 | }
34 | .pm-form-label__text {
35 | color: #00112c;
36 | float: left;
37 | font-size: 0.93333em;
38 | padding-bottom: 6px;
39 | position: relative;
40 | }
41 | .pm-input-field {
42 | background: white;
43 | border: 1px solid #d8d8d8;
44 | -moz-border-radius: 4px;
45 | -webkit-border-radius: 4px;
46 | border-radius: 4px;
47 | box-sizing: border-box;
48 | clear: left;
49 | font-size: 0.93333333333em;
50 | float: left;
51 | padding: 8px;
52 | position: relative;
53 | width: 100%;
54 | height: 35px;
55 | }
56 |
57 | .pm-form-label__error-text {
58 | color: #ff7d00;
59 | display: none;
60 | float: left;
61 | font-size: 13px;
62 | padding-top: 0.4em;
63 | position: relative;
64 | width: 100%;
65 | }
66 |
67 | /* Set dynamically */
68 | .pm-input-field--error {
69 | border: 1px solid #ff7d00;
70 | }
71 |
72 | .pm-input-field--focus {
73 | border: 1px solid #969696;
74 | outline: none;
75 | }
76 | .pm-input-field--error.pm-input-field--focus {
77 | border: 1px solid #ff7d00;
78 | }
79 |
80 | .card-input__spinner__holder{
81 | position: relative;
82 | top: 40px;
83 | }
84 |
85 | .card-input__spinner {
86 | position: absolute;
87 | top: 0;
88 | left: 0;
89 | width: 100%;
90 | height: 100%;
91 | z-index: 1;
92 | display: none;
93 | }
94 |
95 | .card-input__spinner--active {
96 | display: block;
97 | }
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Secured Fields Component PHP server example
2 |
3 | ## This repository is for demo purposes only
4 | This PHP server example is intended to help developers to quickly get up and running with our SecuredFields (aka CustomCard) Component v4.x.
5 | Always ask a back-end developer to create an implementation of this product.
6 |
7 | ## Requirements
8 | To run this Secured Fields example, edit the following variables in the config/authentication.ini file:
9 |
10 | $merchantAccount = "YOUR MERCHANT ACCOUNT".
11 | $checkoutAPIkey = "YOUR CHECKOUT API KEY".
12 | $clientKey = "YOUR CHECKOUT CLIENT KEY".
13 |
14 | These variables can be found in our customer area.
15 | For more information visit our getting started guide .
16 | Also of interest:
17 | Manage you account structure
18 | API credentials .
19 | Client-side authentication .
20 |
21 | ## Installation
22 |
23 | ### Deploying this example to Heroku
24 |
25 | Use this shortcut to deploy to Heroku:
26 |
27 | [](https://heroku.com/deploy?template=https://github.com/Adyen/adyen-secured-fields-sample-code)
28 |
29 | Alternatively, clone this repository and deploy it to your own PHP server
30 |
31 | ## Documentation
32 |
33 | #### SecuredFields 4.x Component
34 | https://docs.adyen.com/payment-methods/cards/custom-card-integration
35 |
36 | #### 3DS 2.0 Example
37 | To see an example of 3DS 2.0 in action run the sample code with the relevant (3DS2) test card numbers, test expiry date and security code credentials.
38 | See: https://docs.adyen.com/classic-integration/3d-secure/native-3ds2/browser-based-integration#testing-3d-secure-2
39 |
40 | #### Migration from CheckoutSecuredFields 1.x
41 | See *Migration.md* (https://github.com/Adyen/adyen-secured-fields-sample-code/blob/main/Migration.md)
42 |
43 | #### Migration from CheckoutSecuredFields 2.x or 3.x
44 | See: https://docs.adyen.com/online-payments/migrate-to-web-4-0-0
45 |
46 | ## License
47 |
48 | This repository is open source and available under the MIT license. For more information, see the LICENSE file.
49 |
--------------------------------------------------------------------------------
/api/payments.php:
--------------------------------------------------------------------------------
1 | $_POST,
23 |
24 | /** All order specific settings can be found in payment/order.php */
25 |
26 | 'amount' => $order['amount'],
27 | 'channel' => $order['channel'],
28 | 'countryCode' => $order['countryCode'],
29 | 'shopperReference' => $order['shopperReference'],
30 | 'shopperLocale' => $order['shopperLocale'],
31 | 'reference' => $order['reference'],
32 |
33 | /** All server specific settings can be found in config/server.php */
34 |
35 | 'origin' => $server['origin'],
36 | 'shopperIP' => $server['shopperIP'],
37 | 'returnUrl' => $server['returnURL'],
38 |
39 | /** All merchant/authentication specific settings can be found in config/authentication.php */
40 |
41 | 'merchantAccount' => $authentication['merchantAccount'],
42 |
43 |
44 | /** All order specific settings can be found in threeds2.php */
45 |
46 | 'additionalData' => $threeds2['additionalData'],
47 | 'browserInfo' => $threeds2['browserInfo']
48 | );
49 |
50 | $setupString = json_encode($request);
51 |
52 | // Initiate curl
53 | $curlAPICall = curl_init();
54 |
55 | // Set to POST
56 | curl_setopt($curlAPICall, CURLOPT_CUSTOMREQUEST, "POST");
57 |
58 | // Add JSON message
59 | curl_setopt($curlAPICall, CURLOPT_POSTFIELDS, $setupString);
60 |
61 | // Will return the response, if false it print the response
62 | curl_setopt($curlAPICall, CURLOPT_RETURNTRANSFER, true);
63 |
64 | // Set the url
65 | curl_setopt($curlAPICall, CURLOPT_URL, $server['paymentsURL']);
66 |
67 | // Api key
68 | curl_setopt($curlAPICall, CURLOPT_HTTPHEADER,
69 | array(
70 | "X-Api-Key: " . $authentication['checkoutAPIkey'],
71 | "Content-Type: application/json",
72 | "Content-Length: " . strlen($setupString)
73 | )
74 | );
75 |
76 | // Execute
77 | $result = curl_exec($curlAPICall);
78 |
79 | // Closing
80 | curl_close($curlAPICall);
81 |
82 | // When this file gets called by javascript or another language, it will respond with a json object
83 | echo $result;
84 | }
85 |
86 | requestPaymentData($order, $server, $authentication, $threeds2);
87 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@adyen.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/assets/css/style.css:
--------------------------------------------------------------------------------
1 | *,
2 | *:after,
3 | *:before {
4 | box-sizing: border-box;
5 | }
6 | html,
7 | body {
8 | font: 16px/1.21 -apple-system, BlinkMacSystemFont, sans-serif;
9 | font-weight: 300;
10 | background: #fcfcfc;
11 | overflow: auto;
12 | margin: 0;
13 | min-height: 100vh;
14 | }
15 |
16 | body {
17 | padding-top: 36px;
18 | }
19 |
20 | @media screen and (min-width: 1240px) {
21 | body {
22 | padding: 0 0 0 320px;
23 | }
24 | }
25 |
26 | header {
27 | background: #fff;
28 | border-bottom: 1px solid #e6e9eb;
29 | left: 0;
30 | position: fixed;
31 | top: 0;
32 | width: 100%;
33 | z-index: 1;
34 | }
35 |
36 | @media screen and (min-width: 1240px) {
37 | header {
38 | position: fixed;
39 | background: #f3f6f9;
40 | height: 100%;
41 | width: 320px;
42 | }
43 | }
44 |
45 | h1 {
46 | font-size: 1.4em;
47 | font-weight: 200;
48 | margin: 0;
49 | padding: 12px;
50 | width: 100%;
51 | }
52 |
53 | @media screen and (min-width: 1240px) {
54 | h1 {
55 | padding: 30px;
56 | }
57 | }
58 |
59 | h1 span {
60 | background: #06f;
61 | border-radius: 3px;
62 | color: #fff;
63 | font-size: 0.45em;
64 | padding: 5px;
65 | position: relative;
66 | top: -5px;
67 | }
68 |
69 | h2 {
70 | font-size: 1.2em;
71 | margin: 0;
72 | }
73 |
74 | nav {
75 | display: none;
76 | width: 100%;
77 | }
78 |
79 | @media screen and (min-width: 1240px) {
80 | nav {
81 | display: block;
82 | }
83 | }
84 |
85 | nav ul {
86 | list-style: none;
87 | margin: 0;
88 | padding: 0 0 0 30px;
89 | width: 100%;
90 | }
91 |
92 | nav ul li {
93 | margin: 20px 0;
94 | }
95 |
96 | nav ul li a {
97 | border-right: 2px solid transparent;
98 | color: #687282;
99 | display: block;
100 | line-height: 1.7em;
101 | text-decoration: none;
102 | transition: all 0.15s ease-out;
103 | }
104 |
105 | nav ul li:hover a {
106 | border-right-color: #06f;
107 | color: #06f;
108 | }
109 |
110 | p {
111 | font-size: 11px;
112 | font-weight: bold;
113 | }
114 |
115 | .explanation {
116 | display: none;
117 | }
118 |
119 | /* Faux SDK */
120 | .merchant-checkout__form,
121 | .merchant-checkout__form *,
122 | .merchant-checkout__form * :before,
123 | .merchant-checkout__form :after {
124 | box-sizing: border-box;
125 | }
126 |
127 | .merchant-checkout__form {
128 | max-width: 600px;
129 | width: 100%;
130 | margin: 30px;
131 | }
132 |
133 | .merchant-checkout__payment-method {
134 | background: #fff;
135 | border: 1px solid #edf0f3;
136 | cursor: pointer;
137 | position: relative;
138 | transition: opacity 0.3s ease-out;
139 | width: 100%;
140 | float: left;
141 | }
142 |
143 | .merchant-checkout__payment-method--hidden {
144 | display: none;
145 | }
146 |
147 | .merchant-checkout__payment-method__header {
148 | cursor: pointer;
149 | align-items: center;
150 | color: #00202e;
151 | display: flex;
152 | font-size: 16px;
153 | font-weight: 400;
154 | padding: 16px;
155 | position: relative;
156 | transition: background 0.1s ease-out;
157 | width: 100%;
158 | }
159 |
160 | .merchant-checkout__payment-method__header h2 {
161 | font-weight: normal;
162 | }
163 |
164 | .merchant-checkout__payment-method--selected .merchant-checkout__payment-method__header h2 {
165 | font-weight: bold;
166 | }
167 |
168 | .merchant-checkout__payment-method__details {
169 | display: none;
170 | padding: 0 16px 16px;
171 | }
172 |
173 | .merchant-checkout__payment-method__details button {
174 | margin: 20px 0 0;
175 | }
176 |
177 | .merchant-checkout__payment-method--selected .merchant-checkout__payment-method__details {
178 | display: block;
179 | }
180 |
181 | .merchant-checkout__payment-method__details__content {
182 | padding: 6px 0 24px;
183 | }
184 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Example PHP checkout
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
To run this securedFields example:
32 |
33 | 1) Edit the following PHP variables in the config/authentication.ini file:
34 | $merchantAccount : 'YOUR MERCHANT ACCOUNT', more information in Manage you account structure .
35 | $checkoutAPIkey : 'YOUR CHECKOUT API KEY', more information in API credentials .
36 | $clientKey : 'YOUR CHECKOUT CLIENT KEY', more information in Client-side authentication .
37 |
38 |
39 | 2) Also make sure that the url function in config/server.php is setting the protocol to the correct value (it might need to be http if you are running these files locally)
40 |
41 |
42 |
43 |
Checkout SecuredFields Component
44 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/Migration.md:
--------------------------------------------------------------------------------
1 | ## Secured Fields v2.0 Migration guide
2 |
3 | This file is meant to help you quickly migrate from a v1.x CheckoutSecuredFields integration
4 | to a v2.x SecuredFields Component integration.
5 |
6 | ### v1.x
7 |
8 | A typical implementation of v1.x CheckoutSecuredFields looked like this:
9 |
10 | #### Markup
11 | ```html
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Card number:
20 |
21 |
22 |
23 | Expiry date:
24 |
25 |
26 |
27 | CVV/CVC:
28 |
29 |
30 |
31 |
32 |
33 |
34 | ...
35 |
36 |
37 |
38 | ```
39 |
40 | A key feature to note in the above markup is the top level container: `
`
41 | which could contain multiple Card Payment Methods - each in their own `
`.
42 | This secondary `
` was expected to have a child element ```
```.
43 |
44 | #### JavaScript
45 | ```javascript
46 | const csfSetupObj = {
47 | rootNode : '.general-payments-container',
48 | configObject : {
49 | originKey: "pub.v2.9915...4010.adkjhgdjhg...",
50 | cardGroupTypes: ['mc', 'visa', 'amex']
51 | },
52 | paymentMethods : {
53 | card : {
54 | sfStyles : {
55 | base: {
56 | color: '#000',
57 | fontSize: '14px',
58 | },
59 | error: {
60 | color: 'red'
61 | },
62 | placeholder: {
63 | color: '#d8d8d8'
64 | },
65 | validated:{
66 | color: 'blue'
67 | }
68 | },
69 | placeholders : {
70 | encryptedCardNumber: '9999 9999 9999 9999',
71 | encryptedSecurityCode: '1234'
72 | }
73 | }
74 | }
75 | };
76 |
77 | const secureFields = window.csf(csfSetupObj);
78 |
79 | // Callbacks
80 | secureFields.onLoad( function(cbObj){} )
81 | .onConfigSuccess( function (cbObj) {} )
82 | .onAllValid( function (cbObj) {} )
83 | .onFieldValid( function (cbObj) {} )
84 | .onBrand( function (cbObj) {} )
85 | .onError( function (cbObj) {} )
86 | .onFocus( function (cbObj) {} )
87 | .onBinValue( function (cbObj) {} );
88 | ```
89 |
90 | A single instance of CheckoutSecuredFields would be initialised with a reference to the top level container: `.general-payments-container`
91 | and would traverse this element looking for all HTML nodes that contained an `
`.
92 | It would create a set of SecuredFields for each discovered HTML node and manage all of these sets.
93 |
94 | ##### Callbacks
95 | Once the instance of CheckoutSecuredFields was created you could then call its callback setters
96 | passing them a function to act as the callback for particular events: `onConfigSuccess`, `onBrand` etc.
97 |
98 | ### v2.x
99 |
100 | A typical implementation of v2.x SecuredFields component looks like this:
101 |
102 | #### Markup
103 | ```html
104 |
105 |
106 |
107 |
108 | Card number:
109 |
110 |
111 |
112 | Expiry date:
113 |
114 |
115 |
116 | CVV/CVC:
117 |
118 |
119 |
120 |
121 |
122 | ...
123 |
124 |
125 | ```
126 | The main difference from v1.x is that there is no longer a top level container `
`
127 | and the element that holds the SecuredFields `
` no longer requires a child element ` -1) {
98 | const newClassName = sfNode.className.replace('pm-input-field--focus', '');
99 | sfNode.className = newClassName.trim();
100 | }
101 | };
102 |
103 | const onError = (cbObj) => {
104 | const sfNode = cbObj.rootNode.querySelector(`[data-cse="${cbObj.fieldType}"]`);
105 | const errorNode = sfNode.parentNode.querySelector('.pm-form-label__error-text');
106 | if (cbObj.error !== '') {
107 | errorNode.style.display = 'block';
108 | errorNode.innerText = cbObj.i18n;
109 | // Add error classes
110 | if (sfNode.className.indexOf('pm-input-field--error') === -1) {
111 | sfNode.className += ' pm-input-field--error';
112 | }
113 | } else if (cbObj.error === '') {
114 | errorNode.style.display = 'none';
115 | errorNode.innerText = '';
116 | // Remove error classes
117 | if (sfNode.className.indexOf('pm-input-field--error') > -1) {
118 | const newClassName = sfNode.className.replace('pm-input-field--error', '');
119 | sfNode.className = newClassName.trim();
120 | }
121 | }
122 | };
123 |
124 | const onFieldValid = (cbObj) => {
125 | // console.log('onFieldValid:: end digits =',cbObj.endDigits);
126 | };
127 |
128 | const onBinValue = (cbObj) => {
129 | // console.log('onBinValue:: bin =',cbObj.binValue);
130 | };
131 |
132 | /////////////////////////////////////////////////////////////////////////////////////////////////////
133 | ///////////////////////////////////// INITIALIZE CHECKOUT CORE //////////////////////////////////////
134 | /////////////////////////////////////////////////////////////////////////////////////////////////////
135 |
136 | window.checkout = new AdyenCheckout({
137 | locale: 'en-US',
138 | originKey,
139 | environment: 'test',
140 | onChange: handleOnChange,
141 | onError: console.error
142 | });
143 |
144 |
145 | /////////////////////////////////////////////////////////////////////////////////////////////////////
146 | //////////////////////////////// INITIALIZE SECURED FIELDS COMPONENT ////////////////////////////////
147 | /////////////////////////////////////////////////////////////////////////////////////////////////////
148 |
149 | window.securedFields = checkout
150 | .create('securedfields', {
151 | type: 'card',
152 | groupTypes: ['mc', 'visa', 'amex', 'bcmc', 'maestro'],
153 | styles,
154 | placeholders,
155 | ariaLabels,
156 | allowedDOMAccess: false, // Whether encrypted blobs will be added to the DOM. OPTIONAL - defaults to false
157 | autoFocus: true, // Whether focus will automatically jump from date to security code fields. OPTIONAL - defaults to true
158 | onConfigSuccess,
159 | onBrand,
160 | onFocus,
161 | onError,
162 | onFieldValid,
163 | onBinValue
164 | })
165 | .mount('.secured-fields');
166 | //--------------------------------------------------------------------------------------------------
167 |
168 |
169 | // Add pay button
170 | payButton = createPayButton('.secured-fields', window.securedFields, 'securedfields');
171 | payButton.setAttribute('disabled', 'true');
172 | }
173 |
174 |
175 | function createPayButton(parent, component, attribute) {
176 |
177 | const payBtn = document.createElement('button');
178 |
179 | payBtn.textContent = 'Pay';
180 | payBtn.name = 'pay';
181 | payBtn.classList.add('adyen-checkout__button', `js-${attribute}`);
182 |
183 | payBtn.addEventListener('click', e => {
184 | e.preventDefault();
185 | startPayment(component);
186 | });
187 |
188 | document.querySelector(parent).appendChild(payBtn);
189 |
190 | return payBtn;
191 | }
192 |
193 | function handleOnChange(state) {
194 |
195 | if (!state.data || !state.data.paymentMethod) return;
196 | // console.log(`${state.data.paymentMethod.type} Component has changed isValid:${state.isValid} state=`, state);
197 |
198 | if(state.isValid){
199 | payButton.removeAttribute('disabled');
200 | }else{
201 | payButton.setAttribute('disabled', 'true');
202 | }
203 | }
204 |
205 | function handlePaymentResult(result) {
206 | console.log('Result: ', result);
207 |
208 | switch (result.resultCode) {
209 | case 'RedirectShopper':
210 | window.location = result.redirect.url;
211 | break;
212 | case 'IdentifyShopper':
213 | case 'ChallengeShopper':
214 | threeDS2(result);
215 | break;
216 | case 'Authorised':
217 | sfText.innerText = result.resultCode;
218 | document.querySelector('.secured-fields').style.display = 'none';
219 | break;
220 | case 'Refused':
221 | sfText.innerText = result.resultCode;
222 | break;
223 | }
224 | }
225 |
226 | function threeDS2(result) {
227 | const button = document.querySelector('.js-securedfields');
228 |
229 | const { authentication, paymentData, resultCode } = result;
230 | const fingerprintToken = authentication['threeds2.fingerprintToken'] || '';
231 | const challengeToken = authentication['threeds2.challengeToken'] || '';
232 | window.paymentData = paymentData;
233 |
234 | if (window.securedFields) {
235 | window.securedFields.unmount();
236 |
237 | const sfNode = document.querySelector('.secured-fields');
238 | while (sfNode.firstChild) {
239 | sfNode.removeChild(sfNode.firstChild);
240 | }
241 | }
242 |
243 | if (button) {
244 | button.remove();
245 | }
246 |
247 | sfText.innerText = resultCode;
248 |
249 | if (resultCode === 'IdentifyShopper') {
250 | const threeds2DeviceFingerprint = checkout
251 | .create('threeDS2DeviceFingerprint', {
252 | fingerprintToken,
253 | paymentData,
254 | onComplete: handle3DS2ComponentResponse,
255 | onError: console.error
256 | })
257 | .mount('.secured-fields');
258 |
259 | window.threeDS2DeviceFingerprint = threeds2DeviceFingerprint;
260 | }
261 |
262 | if (resultCode === 'ChallengeShopper') {
263 | const threeDS2Challenge = checkout
264 | .create('threeDS2Challenge', {
265 | challengeToken,
266 | size: '02', // optional, defaults to '01'
267 | paymentData,
268 | onComplete: handle3DS2ComponentResponse,
269 | onError: console.error
270 | })
271 | .mount('.secured-fields');
272 |
273 | window.threeDS2Challenge = threeDS2Challenge;
274 | }
275 | };
276 |
277 | function handle3DS2ComponentResponse(retrievedData) {
278 |
279 | makeDetailsCall(retrievedData);
280 | };
281 |
282 | /////////////////////////////////////////////////////////////////////////////////////////////////////
283 | //////////////////////////////////////// PERFORM SETUP CALL /////////////////////////////////////////
284 | /////////////////////////////////////////////////////////////////////////////////////////////////////
285 |
286 | // Make 'setup' call with originKeys.php - performs the server call to checkout.adyen.com
287 | function setup(){
288 |
289 | $.ajax({
290 |
291 | url: 'api/originKeys.php',
292 | dataType:'json',
293 | method:'POST',
294 |
295 | success:function(data) {
296 |
297 | // Check that the expected object has been loaded
298 | if(data.hasOwnProperty('originKeys')){
299 |
300 | const protocol = window.location.protocol;
301 | const host = window.location.host;
302 | const url = `${protocol}//${host}`;
303 | const originKey = data.originKeys[url];
304 |
305 | if(originKey){
306 | // Create SecuredFields component
307 | createSecuredFields(originKey);
308 | }else{
309 | showHint = true;
310 | }
311 |
312 | }else{
313 |
314 | // For demo purposes show hint to edit Merchant Account property etc
315 | showHint = true;
316 | }
317 | },
318 |
319 | error : function(){
320 | console.log('Server::originKeys error:: args=', arguments);
321 | showHint = true;
322 | }
323 | });
324 | }
325 |
326 | /////////////////////////////////////////////////////////////////////////////////////////////////////
327 | //////////////////////////////////////// MAKE PAYMENT ///////////////////////////////////////////////
328 | /////////////////////////////////////////////////////////////////////////////////////////////////////
329 |
330 | function startPayment(component){
331 |
332 | payButton.setAttribute('disabled', 'true');
333 |
334 | $.ajax({
335 |
336 | url: './threeds2/payments.php',
337 | dataType:'json',
338 | method:'POST',
339 | data: component.data.paymentMethod,
340 |
341 | success:function(data) {
342 | handlePaymentResult(data);
343 | },
344 |
345 | error : function(){
346 | console.log('Server::startPayment error:: args=', arguments);
347 | }
348 | });
349 | }
350 |
351 | /////////////////////////////////////////////////////////////////////////////////////////////////////
352 | //////////////////////////////////// MAKE 3DS2 DETAILS CALL /////////////////////////////////////////
353 | /////////////////////////////////////////////////////////////////////////////////////////////////////
354 |
355 | function makeDetailsCall({ data }){
356 |
357 | $.ajax({
358 |
359 | url: './threeds2/payments.details.php',
360 | dataType:'json',
361 | method:'POST',
362 | data,
363 |
364 | success:function(resultData) {
365 | handlePaymentResult(resultData);
366 | },
367 |
368 | error : function(){
369 | console.log('Server::makeDetailsCall error:: args=', arguments);
370 | }
371 | });
372 | }
373 |
374 | setup();
375 | });
--------------------------------------------------------------------------------
/assets/js/main.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | // For demo purposes: show hint on how to configure the 'setup' call
3 | let showHint = false;
4 | const explanationDiv = $('.explanation');
5 |
6 | function showExplanation() {
7 | if (showHint) {
8 | explanationDiv.show();
9 | }
10 | }
11 |
12 | window.setTimeout(showExplanation, 5000);
13 | //-----------------------------------------------------------------------
14 |
15 | let hideCVC = false;
16 | let hideDate = false;
17 | let isDualBranding = false;
18 | let payButton = null;
19 | const sfText = document.querySelector('.sf-text');
20 |
21 | function setAttributes(el, attrs) {
22 | for (const key in attrs) {
23 | el.setAttribute(key, attrs[key]);
24 | }
25 | }
26 |
27 | function setLogosActive(rootNode, mode) {
28 | const imageHolder = rootNode.querySelector('.pm-image');
29 | const dualBrandingImageHolder = rootNode.querySelector('.pm-image-dual');
30 |
31 | switch (mode) {
32 | case 'dualBranding_notValid':
33 | Object.assign(imageHolder.style, { display: 'none' });
34 | Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'none', opacity: 0.5 });
35 | break;
36 |
37 | case 'dualBranding_valid':
38 | Object.assign(imageHolder.style, { display: 'none' });
39 | Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'auto', opacity: 1 });
40 | break;
41 |
42 | default:
43 | // reset
44 | Object.assign(imageHolder.style, { display: 'block' });
45 | Object.assign(dualBrandingImageHolder.style, { display: 'none' });
46 | }
47 | }
48 |
49 | function createSecuredFields(clientKey) {
50 |
51 | // Optional SecuredFields styling config
52 | // For more information: https://docs.adyen.com/developers/checkout/api-integration/configure-secured-fields/styling-secured-fields
53 | const styles = {
54 | base: {
55 | color: '#000',
56 | fontSize: '14px',
57 | lineHeight: '14px'
58 | },
59 | error: {
60 | color: 'red'
61 | },
62 | placeholder: {
63 | color: '#d8d8d8'
64 | },
65 | validated:{
66 | color: 'blue'
67 | }
68 | };
69 |
70 | /////////////////////////////////////////////////////////////////////////////////////////////////////
71 | ///////////////////////////// CALLBACK FUNCTIONS FOR SECURED FIELDS /////////////////////////////////
72 | /////////////////////////////////////////////////////////////////////////////////////////////////////
73 |
74 | const onConfigSuccess = (cbObj) => {
75 | document.querySelector('.card-input__spinner__holder').style.display = 'none';
76 |
77 | cbObj.rootNode.style.display = 'block';
78 | cbObj.rootNode.querySelector('.pm-image-dual').style.display = 'none';
79 |
80 | setLogosActive(cbObj.rootNode);
81 | };
82 |
83 | const onBrand = (cbObj) => {
84 | /**
85 | * If not in dual branding mode - add card brand to first image element
86 | */
87 | if (!isDualBranding) {
88 | const brandLogo1 = cbObj.rootNode.querySelector('#pmImage');
89 | setAttributes(brandLogo1, {
90 | src: cbObj.brandImageUrl,
91 | alt: cbObj.brand
92 | });
93 | }
94 |
95 | /**
96 | * Deal with showing/hiding CVC field
97 | */
98 | const cvcNode = cbObj.rootNode.querySelector('.pm-form-label--cvc');
99 |
100 | if (cbObj.cvcPolicy === 'hidden' && !hideCVC) {
101 | hideCVC = true;
102 | cvcNode.style.display = 'none';
103 | }
104 | if (hideCVC && cbObj.cvcPolicy !== 'hidden') {
105 | hideCVC = false;
106 | cvcNode.style.display = 'block';
107 | }
108 |
109 | /**
110 | * Deal with showing/hiding date field(s)
111 | */
112 | const dateNode = cbObj.rootNode.querySelector('.pm-form-label--exp-date');
113 | const monthNode = cbObj.rootNode.querySelector('.pm-form-label.exp-month');
114 | const yearNode = cbObj.rootNode.querySelector('.pm-form-label.exp-year');
115 |
116 | if (cbObj.datePolicy === 'hidden' && !hideDate) {
117 | hideDate = true;
118 | if (dateNode) dateNode.style.display = 'none';
119 | if (monthNode) monthNode.style.display = 'none';
120 | if (yearNode) yearNode.style.display = 'none';
121 | }
122 |
123 | if (hideDate && cbObj.datePolicy !== 'hidden') {
124 | hideDate = false;
125 | if (dateNode) dateNode.style.display = 'block';
126 | if (monthNode) monthNode.style.display = 'block';
127 | if (yearNode) yearNode.style.display = 'block';
128 | }
129 | };
130 |
131 | const onFocus = (cbObj) => {
132 | const sfNode = cbObj.rootNode.querySelector(`[data-cse="${cbObj.fieldType}"]`);
133 | // Add focus
134 | if (cbObj.focus) {
135 | if (sfNode.className.indexOf('pm-input-field--focus') === -1) {
136 | sfNode.className += ' pm-input-field--focus';
137 | }
138 | return;
139 | }
140 | // Remove focus
141 | if (sfNode.className.indexOf('pm-input-field--focus') > -1) {
142 | const newClassName = sfNode.className.replace('pm-input-field--focus', '');
143 | sfNode.className = newClassName.trim();
144 | }
145 | };
146 |
147 | const onError = (cbObj) => {
148 | const sfNode = cbObj.rootNode.querySelector(`[data-cse="${cbObj.fieldType}"]`);
149 | const errorNode = sfNode.parentNode.querySelector('.pm-form-label__error-text');
150 | if (cbObj.error !== '') {
151 | errorNode.style.display = 'block';
152 | errorNode.innerText = cbObj.errorI18n;
153 | // Add error classes
154 | if (sfNode.className.indexOf('pm-input-field--error') === -1) {
155 | sfNode.className += ' pm-input-field--error';
156 | }
157 | } else if (cbObj.error === '') {
158 | errorNode.style.display = 'none';
159 | errorNode.innerText = '';
160 | // Remove error classes
161 | if (sfNode.className.indexOf('pm-input-field--error') > -1) {
162 | const newClassName = sfNode.className.replace('pm-input-field--error', '');
163 | sfNode.className = newClassName.trim();
164 | }
165 | }
166 | };
167 |
168 | const onFieldValid = (cbObj) => {
169 | // console.log('onFieldValid:: end digits =',cbObj.endDigits);
170 | };
171 |
172 | const onBinValue = (cbObj) => {
173 | // console.log('onBinValue:: bin =',cbObj.binValue);
174 | };
175 |
176 | function dualBrandListener(e) {
177 | securedFields.dualBrandingChangeHandler(e);
178 | }
179 |
180 | function resetDualBranding(rootNode) {
181 | isDualBranding = false;
182 |
183 | setLogosActive(rootNode);
184 |
185 | const brandLogo1 = rootNode.querySelector('#pmImageDual1');
186 | brandLogo1.removeEventListener('click', dualBrandListener);
187 |
188 | const brandLogo2 = rootNode.querySelector('#pmImageDual2');
189 | brandLogo2.removeEventListener('click', dualBrandListener);
190 | }
191 |
192 | /**
193 | * Implementing dual branding
194 | */
195 | function onDualBrand(cbObj) {
196 | const brandLogo1 = cbObj.rootNode.querySelector('#pmImageDual1');
197 | const brandLogo2 = cbObj.rootNode.querySelector('#pmImageDual2');
198 |
199 | isDualBranding = true;
200 |
201 | const supportedBrands = cbObj.supportedBrandsRaw;
202 |
203 | /**
204 | * Set first brand icon (and, importantly also add alt &/or data-value attrs); and add event listener
205 | */
206 | setAttributes(brandLogo1, {
207 | src: supportedBrands[0].brandImageUrl,
208 | alt: supportedBrands[0].brand,
209 | 'data-value': supportedBrands[0].brand
210 | });
211 |
212 | brandLogo1.addEventListener('click', dualBrandListener);
213 |
214 | /**
215 | * Set second brand icon (and, importantly also add alt &/or data-value attrs); and add event listener
216 | */
217 | setAttributes(brandLogo2, {
218 | src: supportedBrands[1].brandImageUrl,
219 | alt: supportedBrands[1].brand,
220 | 'data-value': supportedBrands[1].brand
221 | });
222 | brandLogo2.addEventListener('click', dualBrandListener);
223 | }
224 |
225 | function onBinLookup(cbObj) {
226 | /**
227 | * Dual branded result...
228 | */
229 | if (cbObj.supportedBrandsRaw?.length > 1) {
230 | onDualBrand(cbObj);
231 | return;
232 | }
233 |
234 | /**
235 | * ...else - binLookup 'reset' result or binLookup result with only one brand
236 | */
237 | resetDualBranding(cbObj.rootNode);
238 | }
239 |
240 | function onChange(state) {
241 | /**
242 | * If we're in a dual branding scenario & the number field becomes valid or is valid and become invalid
243 | * - set the brand logos to the required 'state'
244 | */
245 | if (isDualBranding) {
246 | const mode = state.valid.encryptedCardNumber ? 'dualBranding_valid' : 'dualBranding_notValid';
247 | setLogosActive(document.querySelector('.secured-fields'), mode);
248 | }
249 |
250 | handleOnChange(state);
251 | }
252 |
253 | /////////////////////////////////////////////////////////////////////////////////////////////////////
254 | ///////////////////////////////////// INITIALIZE CHECKOUT CORE //////////////////////////////////////
255 | /////////////////////////////////////////////////////////////////////////////////////////////////////
256 |
257 | window.checkout = new AdyenCheckout({
258 | locale: 'en-US',
259 | clientKey,
260 | environment: 'test',
261 | onError: console.error,
262 | onAdditionalDetails: makeDetailsCall,
263 | paymentMethodsConfiguration : {
264 | threeDS2: {challengeWindowSize: '04'}
265 | }
266 | });
267 |
268 | /////////////////////////////////////////////////////////////////////////////////////////////////////
269 | //////////////////////////////// INITIALIZE SECURED FIELDS COMPONENT ////////////////////////////////
270 | /////////////////////////////////////////////////////////////////////////////////////////////////////
271 |
272 | window.securedFields = checkout
273 | .create('securedfields', {
274 | type: 'card',
275 | brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro'],
276 | styles,
277 | allowedDOMAccess: false, // Whether encrypted blobs will be added to the DOM. OPTIONAL - defaults to false
278 | autoFocus: true, // Whether focus will automatically jump from date to security code fields. OPTIONAL - defaults to true
279 | onConfigSuccess,
280 | onBrand,
281 | onFocus,
282 | onError,
283 | onFieldValid,
284 | onBinValue,
285 | onBinLookup,
286 | onChange
287 | })
288 | .mount('.secured-fields');
289 | //--------------------------------------------------------------------------------------------------
290 |
291 | // Add pay button
292 | payButton = createPayButton('.secured-fields', window.securedFields, 'securedfields');
293 | payButton.setAttribute('disabled', 'true');
294 | }
295 |
296 | function createPayButton(parent, component, attribute) {
297 | const payBtn = document.createElement('button');
298 |
299 | payBtn.textContent = 'Pay';
300 | payBtn.name = 'pay';
301 | payBtn.classList.add('adyen-checkout__button', `js-${attribute}`);
302 |
303 | payBtn.addEventListener('click', e => {
304 | e.preventDefault();
305 | startPayment(component);
306 | });
307 |
308 | document.querySelector(parent).appendChild(payBtn);
309 |
310 | return payBtn;
311 | }
312 |
313 | function handleOnChange(state) {
314 | if (!state.data || !state.data.paymentMethod) return;
315 |
316 | if(state.isValid){
317 | payButton.removeAttribute('disabled');
318 | }else{
319 | payButton.setAttribute('disabled', 'true');
320 | }
321 | }
322 |
323 | function handlePaymentResult(result) {
324 | console.log('Payment result: ', result);
325 |
326 | switch (result.resultCode) {
327 | case 'RedirectShopper':
328 | window.securedFields.handleAction(result.action);
329 | break;
330 | case 'IdentifyShopper':
331 | case 'ChallengeShopper':
332 | threeDS2(result);
333 | break;
334 | case 'Authorised':
335 | sfText.innerText = result.resultCode;
336 | document.querySelector('.secured-fields').style.display = 'none';
337 | break;
338 | case 'Refused':
339 | sfText.innerText = result.resultCode;
340 | break;
341 | }
342 | }
343 |
344 | function threeDS2(result) {
345 | const button = document.querySelector('.js-securedfields');
346 |
347 | const { resultCode } = result;
348 |
349 | if (window.securedFields) {
350 | window.securedFields.unmount();
351 |
352 | const sfNode = document.querySelector('.secured-fields');
353 | while (sfNode.firstChild) {
354 | sfNode.removeChild(sfNode.firstChild);
355 | }
356 | }
357 |
358 | if (button) {
359 | button.remove();
360 | }
361 |
362 | sfText.innerText = 'Identify and/or Challenge Shopper';
363 |
364 | if (resultCode === 'IdentifyShopper' || resultCode === 'ChallengeShopper') {
365 | // window.checkout.createFromAction(result.action).mount('.secured-fields');
366 | window.securedFields.handleAction(result.action);//, {challengeWindowSize: '01'});
367 | }
368 | }
369 |
370 | /////////////////////////////////////////////////////////////////////////////////////////////////////
371 | //////////////////////////////////////// PERFORM SETUP CALL /////////////////////////////////////////
372 | /////////////////////////////////////////////////////////////////////////////////////////////////////
373 |
374 | function setup(){
375 | if(window.clientKey){
376 | createSecuredFields(window.clientKey);
377 | }else{
378 | // For demo purposes show hint to edit Merchant Account property etc
379 | showHint = true;
380 | }
381 | }
382 |
383 | /////////////////////////////////////////////////////////////////////////////////////////////////////
384 | //////////////////////////////////////// MAKE PAYMENT ///////////////////////////////////////////////
385 | /////////////////////////////////////////////////////////////////////////////////////////////////////
386 |
387 | function startPayment(component){
388 | payButton.setAttribute('disabled', 'true');
389 |
390 | $.ajax({
391 | url: './api/payments.php',
392 | dataType:'json',
393 | method:'POST',
394 | data: component.data.paymentMethod,
395 |
396 | success:function(data) {
397 | handlePaymentResult(data);
398 | },
399 |
400 | error : function(){
401 | console.log('Server::startPayment error:: args=', arguments);
402 | }
403 | });
404 | }
405 |
406 | /////////////////////////////////////////////////////////////////////////////////////////////////////
407 | //////////////////////////////////// MAKE 3DS2 DETAILS CALL /////////////////////////////////////////
408 | /////////////////////////////////////////////////////////////////////////////////////////////////////
409 |
410 | function makeDetailsCall({ data }){
411 | $.ajax({
412 | url: './api/payments.details.php',
413 | dataType:'json',
414 | method:'POST',
415 | data,
416 |
417 | success:function(resultData) {
418 | handlePaymentResult(resultData);
419 | },
420 |
421 | error : function(){
422 | console.log('Server::makeDetailsCall error:: args=', arguments);
423 | }
424 | });
425 | }
426 |
427 | setup();
428 | });
--------------------------------------------------------------------------------