├── .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 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 |
45 |
46 |

SecuredFields

47 |
48 |
49 | 86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
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 | 22 | 26 | 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 | 111 | 115 | 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 | }); --------------------------------------------------------------------------------