├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── docs
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── _config.yml
├── _data
│ ├── buttons.yml
│ ├── details.yml
│ ├── params.yml
│ └── response.yml
├── _includes
│ ├── button.html
│ ├── checkout.html
│ └── fondy.svg
├── config.rb
├── docs
│ ├── getting-started.md
│ ├── index.md
│ ├── installation.md
│ ├── request-parameters.md
│ └── test-payment-details.md
├── examples
│ ├── button.html
│ ├── checkout.html
│ └── index.md
├── favicon.ico
├── index.html
└── static
│ ├── checkout.js
│ ├── checkout.min.js
│ └── checkout.min.js.map
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── checkout.js
├── core
│ ├── api.js
│ ├── class.js
│ ├── component.js
│ ├── config.js
│ ├── connector.js
│ ├── deferred.js
│ ├── event.js
│ ├── google
│ │ └── pay.js
│ ├── modal.js
│ ├── model.js
│ ├── module.js
│ ├── payment
│ │ ├── button.js
│ │ ├── container.js
│ │ ├── element.js
│ │ └── request.js
│ ├── response.js
│ ├── template.js
│ ├── utils.js
│ └── widget
│ │ ├── button.js
│ │ ├── form.js
│ │ └── index.js
└── views
│ ├── index.js
│ └── template
│ ├── acs.js
│ └── acs_styles.js
└── tests
├── index.html
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .wrangler
3 | .well-known
4 | node_modules
5 | dist
6 | showcase
7 | src/worker
8 | npm-debug.log
9 | wrangler.toml
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "semi": false,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Fondy
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | IPSP JS SDK
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Installation
20 |
21 | ### Node
22 |
23 | If you’re using [Npm](https://npmjs.com/) in your project, you can add `ipsp-js-sdk` dependency to `package.json`
24 | with following command:
25 |
26 | ```cmd
27 | npm i --save ipsp-js-sdk
28 | ```
29 |
30 | or add dependency manually:
31 |
32 | ```json
33 | {
34 | "dependency": {
35 | "ipsp-js-sdk":"^2.0"
36 | }
37 | }
38 | ```
39 |
40 | ### Bower
41 |
42 | If you’re using [Bower](https://bower.io/) in your project, you can run the following command:
43 |
44 | ```cmd
45 | bower install ipsp-js-sdk
46 | ```
47 |
48 | ### Manual installation
49 |
50 | If you do not use NodeJS, you can download the
51 | [latest release](https://github.com/cloudipsp/ipsp-js-sdk/releases).
52 | Or clone from GitHub the latest developer version
53 | ```cmd
54 | git clone git@github.com:cloudipsp/ipsp-js-sdk.git
55 | ```
56 |
57 |
58 | ## Quick start
59 |
60 | ```html
61 |
62 | ```
63 |
64 | ## Basic template
65 |
66 | ```html
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
85 |
86 |
87 | ```
88 |
89 | ## PaymentButton template (ApplePay/GooglePay)
90 |
91 | ```html
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
116 |
117 |
118 | ```
119 |
120 | ## Parameters
121 |
122 | ### Host-to-host token
123 |
124 |
125 | ```json
126 | {
127 | "payment_system":"Supported payment systems: card, p24",
128 | "token":"host-to-host generated token",
129 | "card_number":"16/19-digits number",
130 | "expiry_date":"Supported formats: MM/YY, MM/YYYY, MMYY, MMYYYY",
131 | "cvv2":"3-digits number"
132 | }
133 | ```
134 |
135 | Where token is value, returned in paymentgateway response from API enpoint /api/checkout/token (more details in API documentation: https://docs.fondy.eu/docs/page/3/ )
136 |
137 | request example:
138 |
139 | ```
140 | curl -i -X POST \
141 | -H "Content-Type:application/json" \
142 | -d \
143 | '{
144 | "request": {
145 | "server_callback_url": "http://myshop/callback/",
146 | "order_id": "TestOrder1",
147 | "currency": "USD",
148 | "merchant_id": 1396424,
149 | "order_desc": "Test payment",
150 | "amount": 1000
151 | }
152 | }' \
153 | 'https://api.fondy.eu/api/checkout/token'
154 | ```
155 |
156 | response example:
157 | ```json
158 | {
159 | "response": {
160 | "response_status": "success",
161 | "token": "7ddb3fbb03d60787b3972ef8d6fad0f97f7d2f86"
162 | }
163 | }
164 | ```
165 |
166 |
167 |
168 | ### Client-side merchant ID
169 |
170 | ```json
171 | {
172 | "payment_system":"Supported payment systems: card, p24",
173 | "merchant_id":"1396424",
174 | "currency":"USD",
175 | "amount":"100.20",
176 | "card_number":"16/19-digits number",
177 | "expiry_date":"Supported formats: MM/YY, MM/YYYY, MMYY, MMYYYY",
178 | "cvv2":"3-digits number"
179 | }
180 | ```
181 |
182 | optional merchant parameters:
183 |
184 |
185 | ```json
186 | {
187 | "email":"customer email address",
188 | "phone":"customer phone number"
189 | }
190 | ```
191 | ## JS SDK usage example
192 |
193 | https://jsfiddle.net/fondyeu/jdc9o5cx/
194 |
195 |
196 | ## License
197 |
198 | [MIT](https://github.com/cloudipsp/ipsp-js-sdk/blob/HEAD/LICENSE)
199 |
200 | ## Author
201 |
202 | [](https://kosatyi.com/)
203 | [](https://fondy.io/)
204 |
205 |
206 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .bundle
3 | .sass-cache
4 | Gemfile.lock
5 | _site
6 | *.gem
7 |
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'github-pages', group: :jekyll_plugins
3 | gem 'kramdown'
--------------------------------------------------------------------------------
/docs/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Parker Moore
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.
22 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: kosatyi/sjt
2 |
3 | navigation: true
4 | projects: true
5 |
6 | exclude:
7 | - .idea
8 | - node_modules
9 | - package.json
10 | - package-lock.json
11 | - Gemfile
12 | - Gemfil.lock
13 | - gulpfile.js
14 |
15 | plugins:
16 | - jekyll-seo-tag
17 | - jekyll-sitemap
18 | - jekyll-feed
19 | - jekyll-paginate
20 | - jekyll-redirect-from
21 |
22 | baseurl: /ipsp-js-sdk
23 |
24 | brand: IPSP JS SDK
25 | title: IPSP JS SDK
26 | description: Fondy Payment System
27 | logo: fondy.svg
28 |
29 | author:
30 | name: Fondy Developer Team
31 | url: https://fondy.io/
32 |
33 | toolbar:
34 | -
35 | name: Docs
36 | link: /docs/
37 | -
38 | name: Examples
39 | link: /examples/
40 |
41 |
42 | defaults:
43 | -
44 | scope:
45 | path: ""
46 | type: "pages"
47 | values:
48 | layout: default
49 |
--------------------------------------------------------------------------------
/docs/_data/buttons.yml:
--------------------------------------------------------------------------------
1 | defaults:
2 | title: Black Button
3 | summary:
4 | style:
5 | type: long
6 | color: black
7 | height: 46
8 | mode: default
9 |
10 | #white:
11 | # title: White Button
12 | # summary:
13 | # style:
14 | # type: long
15 | # color: white
16 | # height: 46
17 | # mode: default
18 |
19 | #buy:
20 | # title: Buy Button
21 | # summary:
22 | # style:
23 | # type: long
24 | # color: black
25 | # height: 46
26 | # mode: buy
27 | #
28 | #book:
29 | # title: Book Button
30 | # summary:
31 | # style:
32 | # type: long
33 | # color: black
34 | # height: 46
35 | # mode: book
36 | #
37 | #checkout:
38 | # title: Checkout Button
39 | # summary:
40 | # style:
41 | # type: long
42 | # color: black
43 | # height: 46
44 | # mode: checkout
45 | #
46 | #donate:
47 | # title: Donate Button
48 | # summary:
49 | # style:
50 | # type: long
51 | # color: black
52 | # height: 46
53 | # mode: donate
54 | #
55 | #order:
56 | # title: Order Button
57 | # summary:
58 | # style:
59 | # type: long
60 | # color: black
61 | # height: 46
62 | # mode: order
63 | #
64 | #pay:
65 | # title: Pay Button
66 | # summary:
67 | # style:
68 | # type: long
69 | # color: black
70 | # height: 46
71 | # mode: pay
72 | #
73 | #subscribe:
74 | # title: Subscribe Button
75 | # summary:
76 | # style:
77 | # type: long
78 | # color: black
79 | # height: 46
80 | # mode: subscribe
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/_data/details.yml:
--------------------------------------------------------------------------------
1 | params:
2 | -
3 | name: merchant id
4 | value: 1396424
5 | -
6 | name: transaction password
7 | value: test
8 | -
9 | name: currency
10 | value: EUR, USD, GBP, RUB, UAH
11 |
12 | cards:
13 | -
14 | number: 4444555566661111
15 | date: any
16 | cvv2: any
17 | 3ds: yes
18 | response: approve
19 | -
20 | number: 4444111166665555
21 | date: any
22 | cvv2: any
23 | 3ds: yes
24 | response: decline
25 | -
26 | number: 4444555511116666
27 | date: any
28 | cvv2: any
29 | 3ds: no
30 | response: approve
31 | -
32 | number: 4444111155556666
33 | date: any
34 | cvv2: any
35 | 3ds: no
36 | response: decline
37 |
--------------------------------------------------------------------------------
/docs/_data/params.yml:
--------------------------------------------------------------------------------
1 | card_number:
2 | type: integer(19)
3 | descr: Visa/MC card number mandatory
4 | sample:
5 | mandatory: true
6 |
7 | cvv2:
8 | type: integer(4)
9 | descr: Card CVV2/CVC2 code
10 | sample:
11 | mandatory: true
12 |
13 | expiry_date:
14 | type: integer(4)
15 | descr: Card expiry date in format MMYY
16 | mandatory: true
17 |
18 | client_ip:
19 | type: integer(15)
20 | descr: Client IP
21 | mandatory: true
22 |
23 | comment:
24 | type: string(1024)
25 | descr: Merchant comment on reversal reason UTF-8
26 | sample: Customer returned product back
27 | mandatory: true
28 |
29 | order_id:
30 | type: string(1024)
31 | descr: Order ID which is generated by merchant.
32 | sample: ID1234
33 | mandatory: true
34 |
35 | merchant_id:
36 | type: integer(12)
37 | descr: Merchant unique ID. Generated by FONDY during merchant registration.
38 | sample: 1
39 | mandatory: true
40 |
41 | order_desc:
42 | type: string(1024)
43 | descr: Order description. Generated by merchant in UTF-8.
44 | sample: Hotel booking №1234 Antalia Resort
45 | mandatory: true
46 |
47 | signature:
48 | type: string(40)
49 | descr: Order signature. Required to verify merchant request consistency and authenticity.
50 | Signature generation algorithm please see at Signature generation for request and response
51 | sample: 1
52 | mandatory: true
53 |
54 | amount:
55 | type: integer(12)
56 | descr: Order amount in cents without separator
57 | sample: 1020 (means 10 euros and 20 cents)
58 | mandatory: true
59 |
60 | currency:
61 | type: string(3)
62 | descr: Order currency. Supported values (UAH, RUB, USD, EUR, GBP)
63 | sample: EUR
64 | mandatory: true
65 |
66 | date_from:
67 | type: string(10)
68 | descr: Settlement date in fromat DD.MM.YYYY
69 | sample: 15.04.2014
70 | mandatory: true
71 |
72 | date_to:
73 | type: string(10)
74 | descr: Settlement date in fromat DD.MM.YYYY
75 | sample: 16.04.2014
76 | mandatory: true
77 |
78 | version:
79 | type: string(10)
80 | descr: Protocol version.
81 | default: 1.0
82 | sample: 1.0
83 |
84 | response_url:
85 | type: string(2048)
86 | descr: Merchant site URL, where customer will be redirected after payment completion.
87 | default: https://api.fondy.eu/checkout/responsepage
88 | sample: http://example.com/response
89 |
90 | server_callback_url:
91 | type: string(2048)
92 | descr: Merchant site URL, where host-to-host callback will be send after payment completion.
93 | sample: http://example.com/callback
94 |
95 | payment_systems:
96 | type: string(1024)
97 | descr: Payment systems which can be used for payment by customer at FONDY payment page. Systems must be separated by a comma or semicolon.
98 | default: is set from merchant settings in FONDY merchant portal
99 | sample: sepa,card,direct_debit
100 |
101 | default_payment_system:
102 | type: string(25)
103 | descr: Payment system which will be shown to customer at FONDY payment page first.
104 | sample: card
105 |
106 | lifetime:
107 | type: integer(6)
108 | descr: Order lifetime in seconds. After this time, the order will be given the status of 'expired' if the client has not paid it
109 | default: 36000
110 | sample: 600
111 |
112 |
113 | merchant_data:
114 | type: string(2048)
115 | descr: Any arbitrary set of data that a merchant wants to get back in the response to response_url or/and server_callback_url, and also in reports
116 | sample:
117 |
118 | preauth:
119 | type: string(1)
120 | descr: Parameter supported only for Visa/MasterCard payment method
121 | N — amount is debited from the customer's card immediately and settled to the merchant account, in accordance with the rules of settlements.
122 | Y — amount holded on the customer card and not charged until the merchant sends a 'capture' request to confirm
123 |
124 | default: N
125 | sample: Y
126 |
127 | sender_email:
128 | type: string(254)
129 | descr: Customer email
130 | sample: customer@example.com
131 |
132 | delayed:
133 | type: string(1)
134 | descr: Delayed order flag.
135 | Y — allows customer to pay the order during period sent by merchant in lifetime parameter. Merchant must expect several host-to-host callbacks and browser redirects at the same order. Customer will have possibility to try to pay the same order_id, if previous attempt failed
136 | N — after payment is declined order_id customer will be redirected to merchant site to recreate the order. In this case only one callback will be sent to server_callback_url
137 | default: Y
138 | sample: N
139 |
140 | lang:
141 | type: string(2)
142 | descr: Payment page language. Supported values (ru,uk,en,lv,fr,cs)
143 | sample: en
144 |
145 | product_id:
146 | type: string(1024)
147 | descr: Merchant product or service id
148 | sample: en
149 |
150 | required_rectoken:
151 | type: string(1)
152 | descr: Flag which indicates whether FONDY must return card token — token to access card funds without cardholder interaction
153 | default: N
154 | sample: Y
155 |
156 | verification:
157 | type: string(1)
158 | descr: If Y order will be automatically reversed by FONDY after successful approval
159 | default: N
160 | sample: Y
161 |
162 | verification_type:
163 | type: string(25)
164 | descr: amount - amount submitted from merchant will be hold on card
165 | code - amount submitted from merchant will be hold on card. Also cardholder have to enter 4-characters code to pass verification
166 | default: amount
167 | sample: code
168 |
169 | rectoken:
170 | type: string(40)
171 | descr: Card token — token to access card funds without cardholder interaction
172 | sample: 544d3f86237886b6404d8b000f6a7d71c45410b7
173 |
174 | receiver_rectoken:
175 | type: string(40)
176 | descr: Card token — token to credit card without transfering full card number
177 | sample: 544d3f86237886b6404d8b000f6a7d71c45410b7
178 |
179 | design_id:
180 | type: integer(6)
181 | descr: ID of design which is set in merchant portal
182 | sample: 123
183 |
184 | subscription:
185 | type: string(1)
186 | descr: Y - enable scheduled payments
187 | N - by default, disable scheduled payments
188 | default: N
189 | sample: Y
190 |
191 | subscription_callback_url:
192 | type: string(2048)
193 | descr: Merchant site URL, where host-to-host callback will be send after scheduled payment completion
194 | sample: http://example.com/subscription_callback
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/docs/_data/response.yml:
--------------------------------------------------------------------------------
1 | order_id:
2 | type: string(1024)
3 | descr: Order ID which is generated by merchant.
4 |
5 | merchant_id:
6 | type: integer(12)
7 | descr: Merchant unique ID. Generated by FONDY during merchant registration.
8 | sample:
9 |
10 | amount:
11 | type: integer(12)
12 | descr: Order amount in cents without separator
13 | sample: 1020 (EUR) (means 10 euros and 20 cents)
14 |
15 | currency:
16 | type: string(3)
17 | descr: Order currency. Supported values ( UAH, RUB, USD, EUR, GBP)
18 | sample: EUR
19 |
20 | order_status:
21 | type: string(50)
22 | descr: Order processing status. Can contain next values >
23 | created — order has been created, but the customer has not entered payment details yet; merchant must continue to request the status of the order
24 | processing — order is still in processing by payment gateway; merchant must continue to request the status of the order
25 | declined — order is declined by FONDY payment gateway or by bank or by external payment system
26 | approved — order completed successfully, funds are hold on the payer's account and soon will be credited of the merchant; merchant can provide the service or ship goods
27 | expired — order lifetime expired.
28 | reversed — previously approved transaction was fully or partially reversed. In this case parameter reversal_amount will be > 0
29 |
30 | response_status:
31 | type: string(50)
32 | descr: Request processing status. If parameters sent by merchant did not pass validation then failure, else success
33 |
34 | signature:
35 | type: string(40)
36 | descr: Order signature. Required to verify merchant request consistency and authenticity. Signature generation algorithm please see at Signature generation for request and response
37 | sample: 1773cf135bd89656131134b98637894dad42f808
38 |
39 | tran_type:
40 | type: string(50)
41 | descr: Supported values > (purchase, reverse)
42 |
43 | sender_cell_phone:
44 | type: string(16)
45 | descr: Customer mobile phone number
46 |
47 | sender_account:
48 | type: string(50)
49 | descr: Customer payment account
50 |
51 | masked_card:
52 | type: string(19)
53 | descr: Masked card number
54 | sample: 444444XXXXXX5555
55 |
56 | card_bin:
57 | type: integer(6)
58 | descr: Card bin — usually first 6 digits
59 | sample: 444444
60 |
61 | card_type:
62 | type: string(50)
63 | descr: Supported values > (VISA, MasterCard)
64 |
65 | rrn:
66 | type: string(50)
67 | descr: Commonly not unique transaction ID returned by bank.
68 |
69 | approval_code:
70 | type: string(6)
71 | descr: Commonly not unique authorization code returned by bank.
72 |
73 | response_code:
74 | type: integer(4)
75 | descr: Order decline response code. Possible codes see in Response codes
76 |
77 | response_description:
78 | type: string(1024)
79 | descr: Order response code description, see Response codes
80 |
81 | reversal_amount:
82 | type: integer(12)
83 | descr: Total amount of all reversals for current order
84 |
85 | settlement_amount:
86 | type: integer(12)
87 | descr: Settlement amount for current order
88 |
89 | settlement_amount:
90 | type: integer(12)
91 | descr: Settlement amount for current order
92 |
93 | settlement_currency:
94 | type: string(3)
95 | descr: Currency of order settlement
96 |
97 | order_time:
98 | type: string(19)
99 | descr: Order creation date DD.MM.YYYY hh:mm:ss
100 | sample: 21.12.2014 11:21:30
101 |
102 | settlement_date:
103 | type: string(10)
104 | descr: Settlement date in format DD.MM.YYYY
105 | sample: 21.12.2014
106 |
107 | eci:
108 | type: integer(2)
109 | descr: Ecommerce Indicator - parameter specifies whether 3DSecure authentication was performed or not. Supported values >
110 | 5 — full 3DSecure authentication performed
111 | 6 — merchant supports 3DSecure, but issuing bunk does not
112 | 7 — neither merchant nor issuing bank supports 3DSecure
113 |
114 | fee:
115 | type: integer(12)
116 | descr: Fee charged by FONDY
117 |
118 | payment_system:
119 | type: string(50)
120 | descr: Payment system which was used for payment. Supported payment systems list see Supported payment systems
121 | sample: Qiwi
122 |
123 | sender_email:
124 | type: string(254)
125 | descr: Customer email
126 |
127 | payment_id:
128 | type: integer(19)
129 | descr: Unique paymnet ID generated by FONDY payment gateway
130 |
131 | actual_amount:
132 | type: integer(12)
133 | descr: Actual amount after all reversals.
134 |
135 | actual_currency:
136 | type: string(3)
137 | descr: Actual currency
138 |
139 | product_id:
140 | type: string(1024)
141 | descr: Merchant product or service ID
142 |
143 | merchant_data:
144 | type: string(2048)
145 | descr: Any arbitrary set of data that a merchant sends in request
146 |
147 | verification_status:
148 | type: string(50)
149 | descr: Code verification result
150 | Supported values
151 | verified — card successfully verified with code
152 | incorrect — incorrect code entered but limit not exceeded yet
153 | failed — allowed number of invalid attempts to enter code exceeded
154 | created — verification code created but not entered yet
155 |
156 | rectoken:
157 | type: string(40)
158 | descr: Flag which indicates whether FONDY must return card token — token to access card funds without cardholder interaction
159 | sample: da39a3ee5e6b4b0d3255bfef95601890afd80709
160 |
161 | rectoken_lifetime:
162 | type: string(19)
163 | descr: Token lifetime in format DD.MM.YYYY hh:mm:ss
164 | sample: 01.01.2018 00:00:00
165 |
166 | checkout_url:
167 | type: string(2048)
168 | descr: FONDY payment page URL where merchant site must redirect customer to enter payment details
169 | sample: https://api.fondy.eu/checkout?token=e0a5d4f331806d1e2feb80353b4c44bf6751fc8c
170 |
171 | payment_id:
172 | type: integer(19)
173 | descr: Unique payment ID generated by FONDY payment gateway
174 |
175 | error_code:
176 | type: integer(4)
177 | descr: Response decline code. Supported values see Response codes
178 |
179 | error_message:
180 | type: string(1024)
181 | descr: Response code description. See Response codes
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/docs/_includes/button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
32 |
33 |
74 |
75 | {% assign examples = site.data.buttons %}
76 | {% for item in examples %}
77 | {% assign name = item[0] %}
78 | {% assign options = item[1] %}
79 |
80 |
{{ options.title }}
81 | {% highlight json %}{{ options.style | jsonify }}{% endhighlight %}
82 |
83 |
84 |
85 | {% endfor %}
86 |
--------------------------------------------------------------------------------
/docs/_includes/checkout.html:
--------------------------------------------------------------------------------
1 |
85 |
86 |
87 |
88 |
128 |
129 |
140 |
--------------------------------------------------------------------------------
/docs/_includes/fondy.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/config.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudipsp/ipsp-js-sdk/59e69bfc7dd6cde4dc15af31f6adbd314da6fe91/docs/config.rb
--------------------------------------------------------------------------------
/docs/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting Started
3 | headline: Getting Started
4 | sort: 2
5 | ---
6 |
7 | ## Quick start
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
29 |
30 | ```
31 |
32 | ## Parameters
33 |
34 | ### Host-to-host token
35 |
36 | For server-side token generation see [ipsp php sdk](https://ipsp-php.com/) project.
37 |
38 | ```json
39 | {
40 | "payment_system":"Supported payment systems: card, p24",
41 | "token":"host-to-host generated token",
42 | "card_number":"16/19-digits number",
43 | "expiry_date":"Supported formats: MM/YY, MM/YYYY, MMYY, MMYYYY",
44 | "cvv2":"3-digits number"
45 | }
46 | ```
47 |
48 | ### Client-side merchant ID
49 |
50 |
51 | ```json
52 | {
53 | "payment_system":"Supported payment systems: card, p24",
54 | "merchant_id":"1396424",
55 | "currency":"USD",
56 | "amount":"100.20",
57 | "card_number":"16/19-digits number",
58 | "expiry_date":"Supported formats: MM/YY, MM/YYYY, MMYY, MMYYYY",
59 | "cvv2":"3-digits number"
60 | }
61 | ```
62 |
63 | optional merchant parameters:
64 |
65 |
66 | ```json
67 | {
68 | "email":"customer email address",
69 | "phone":"customer phone number"
70 | }
71 | ```
--------------------------------------------------------------------------------
/docs/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | sort: 1
3 | ---
4 |
5 | # Documentation
6 |
7 | Download and installation, API methods description, frequently asked questions , organizing and releasing your source project.
8 |
9 | ## Initialize Api Component
10 |
11 | ### Singleton usage
12 |
13 | ```javascript
14 | var checkout = $checkout('Api');
15 | ```
16 |
17 | Use **$checkout('Api')** instance in inside nested scope
18 |
19 | ```javascript
20 | var result = (function(instance){
21 | return $checkout('Api') === instance; // ==> their instances are equal
22 | })(checkout);
23 | ```
24 |
25 | ### New Instance usage
26 |
27 | Create new iframe connector for every Checkout.Api instance
28 |
29 | ```javascript
30 | var checkout = $checkout.get('Api');
31 | ```
32 |
33 | ## Send Request
34 |
35 | ### Method **.request()**
36 |
37 | ```javascript
38 | $checkout('Api').request('api.checkout.form',{ params }); // return Defer object
39 | ```
40 |
41 | ### Basic params example
42 |
43 | #### Token param (host-to-host)
44 |
45 | ```javascript
46 | $checkout('Api').request('api.checkout.form',{
47 | token:'host-to-host-token',
48 | card_number:'4444555566661111',
49 | expire_date:'12/30',
50 | cvv2:'111'
51 | });
52 | ```
53 |
54 | #### Generation Params
55 |
56 | ```javascript
57 | $checkout('Api').request('api.checkout.form',{
58 | merchant_id:123456,
59 | amount:250,
60 | currency:'USD',
61 | card_number:'4444555566661111',
62 | expire_date:'12/30',
63 | cvv2:'111'
64 | });
65 | ```
66 |
67 | > [Additional Generation Params](request-parameters.md)
68 |
69 | #### Custom user field object
70 |
71 | Add user field object to property **custom<Object>** with field name as object key.
72 | Mandatory properties for child object is **label** and **value**.
73 |
74 |
75 | ```javascript
76 | $checkout('Api').request('api.checkout.form',{
77 | merchant_id:123456,
78 | amount:250,
79 | currency:'USD',
80 | card_number:'4444555566661111',
81 | expire_date:'12/30',
82 | cvv2:'111',
83 | custom: {
84 | customer: {
85 | label: 'Customer',
86 | value: 'John Doe'
87 | },
88 | user_id: {
89 | label: 'User ID',
90 | value: 1234567890
91 | }
92 | }
93 | });
94 | ```
95 |
96 | You can use form nested name values to serve correct structure for custom field object as well.
97 |
98 | Example:
99 | ```html
100 |
101 |
102 |
103 |
104 |
105 |
106 | ```
107 |
108 | ## Response model
109 |
110 | ### Methods
111 |
112 | #### attr('property')
113 |
114 | #### sendResponse()
115 |
116 | #### submitToMerchant()
117 |
118 | #### waitOn3dsDecline()
119 |
120 | #### submit3dsForm()
121 |
122 | #### needVerifyCode()
123 |
124 | ### Properties
125 |
126 | #### model.attr('info')
127 |
128 | #### model.attr('order')
129 |
130 |
131 | ## Handle Response
132 |
133 | ### 3d secure modal window
134 |
135 | 
136 | {:.text-center}
137 |
138 | ### 3d secure timeout request
139 |
140 | ```javascript
141 | $checkout('Api').request('api.checkout.form',{ params }).notify(function(model){
142 | if(model.waitOn3dsDecline()){
143 | // show timer for next 3ds modal window
144 | setTimeout(function(){
145 | model.submit3dsForm();
146 | },model.waitOn3dsDecline());
147 | }
148 | });
149 | ```
150 |
151 | 
152 | {:.text-center}
153 |
154 | ### Submit to result page
155 |
156 | Use method **submitToMerchant()** if **response_url** parameter is specified to redirect user on merchant page with
157 | order data object.
158 |
159 | ```javascript
160 | $checkout('Api').request('api.checkout.form',{
161 | ...
162 | response_url:'https://example.com/'
163 | ...
164 | }).done(function(model){
165 | model.submitToMerchant();
166 | });
167 | ```
168 |
169 | ### Handle gateway errors
170 |
171 | ```javascript
172 | $checkout('Api').request('api.checkout.form',{ params }).fail(function(model){
173 | model.attr('error.code');
174 | model.attr('error.message');
175 | });
176 | ```
177 |
178 |
181 |
--------------------------------------------------------------------------------
/docs/docs/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | headline: Installation
4 | sort: 1
5 | ---
6 |
7 | ## Node
8 |
9 | If you’re using [Npm](https://npmjs.com/) in your project, you can add `ipsp-js-sdk` dependency to `package.json`
10 | with following command:
11 |
12 | ```cmd
13 | npm i --save ipsp-js-sdk
14 | ```
15 |
16 | or add dependency manually:
17 |
18 | ```json
19 | {
20 | "dependency": {
21 | "ipsp-js-sdk":"^1.0"
22 | }
23 | }
24 | ```
25 |
26 | ## Bower
27 |
28 | If you’re using [Bower](https://bower.io/) in your project, you can run the following command:
29 |
30 | ```cmd
31 | bower install ipsp-js-sdk
32 | ```
33 |
34 | ## Manual installation
35 |
36 | If you do not use NodeJS, you can download the
37 | [latest release](https://github.com/cloudipsp/ipsp-js-sdk/releases).
38 | Or clone from GitHub the latest developer version
39 | ```cmd
40 | git clone git@github.com:cloudipsp/ipsp-js-sdk.git
41 | ```
42 |
43 | ## CDN
44 |
45 | ```html
46 |
47 | ```
--------------------------------------------------------------------------------
/docs/docs/request-parameters.md:
--------------------------------------------------------------------------------
1 | ---
2 | sort: 5
3 | ---
4 |
5 | # Request Parameters
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Parameter |
17 | Type |
18 | Description |
19 |
20 |
21 |
22 | {% for row in site.data.params %}
23 |
24 | {{ row[0] }} |
25 | {{ row[1].type }} |
26 | {{ row[1].descr }} |
27 |
28 | {% endfor %}
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/docs/test-payment-details.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Test Payment Details
3 | headline: Test Payment Details
4 | sort: 6
5 | ---
6 |
7 | ## Merchant parameters
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Parameter |
18 | Value |
19 |
20 |
21 |
22 | {% for detail in site.data.details.params %}
23 |
24 | {{ detail.name }} |
25 | {{ detail.value }} |
26 |
27 | {% endfor %}
28 |
29 |
30 |
31 |
32 | ## Test card numbers
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Card number |
43 | Expiry date |
44 | CVV2 |
45 | 3DSecure |
46 | Response type |
47 |
48 |
49 |
50 | {% for card in site.data.paymentdetails.cards %}
51 |
52 | {{ card['number'] }} |
53 | {{ card['date'] }} |
54 | {{ card['cvv2'] }} |
55 | {{ card['3ds'] }} |
56 | {{ card['response'] }} |
57 |
58 | {% endfor %}
59 |
60 |
61 |
--------------------------------------------------------------------------------
/docs/examples/button.html:
--------------------------------------------------------------------------------
1 | ---
2 | sort: 2
3 | title: Apple/Google Pay Button
4 | ---
5 |
6 | Apple/Google Pay Button
7 |
8 | {% include button.html %}
9 |
--------------------------------------------------------------------------------
/docs/examples/checkout.html:
--------------------------------------------------------------------------------
1 | ---
2 | sort: 1
3 | title: Checkout Form
4 | ---
5 |
6 | Checkout Form
7 |
8 | {% include checkout.html %}
9 |
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | sort: 2
3 | ---
4 |
5 | # Examples
6 |
7 |
10 |
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudipsp/ipsp-js-sdk/59e69bfc7dd6cde4dc15af31f6adbd314da6fe91/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: IPSP JS SDK
3 | description: Start e-commerce on your website.
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Project section
18 |
19 |
22 |
--------------------------------------------------------------------------------
/docs/static/checkout.min.js:
--------------------------------------------------------------------------------
1 | !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).$checkout=t()}(function(){return function i(o,s,r){function a(e,t){if(!s[e]){if(!o[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(c)return c(e,!0);throw(n=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",n}n=s[e]={exports:{}},o[e][0].call(n.exports,function(t){return a(o[e][1][t]||t)},n,n.exports,i,o,s,r)}return s[e].exports}for(var c="function"==typeof require&&require,t=0;t/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},h=/(.)^/,u={"'":"'","\\":"\\","\r":"r","\n":"n","\t":"t","\u2028":"u2028","\u2029":"u2029"},d=/\\|'|\r|\n|\t|\u2028|\u2029/g,r={"&":"&","<":"<",">":">",'"':""","'":"'"},a=new RegExp("[&<>\"']","g"),l=0,t=s.extend({utils:t,init:function(t){this.name=t,this.view={},this.output()},output:function(){this.view.source=o[this.name],this.view.output=function(s){var e,t=new RegExp([(c.escape||h).source,(c.interpolate||h).source,(c.evaluate||h).source].join("|")+"|$","g"),r=0,a="__p+='";s.replace(t,function(t,e,n,i,o){return a+=s.slice(r,o).replace(d,function(t){return"\\"+u[t]}),e&&(a+="'+\n((__t=("+e+"))==null?'':escapeExpr(__t))+\n'"),n&&(a+="'+\n((__t=("+n+"))==null?'':__t)+\n'"),i&&(a+="';\n"+i+"\n__p+='"),r=o+t.length,t}),a+="';\n",a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+(a=!c.variable?"with(obj||{}){\n"+a+"}\n":a)+"return __p;\n//# sourceURL=/tmpl/source["+l+++"]";try{e=new Function(c.variable||"obj","escapeExpr",a)}catch(t){throw t.source=a,t}t=function(t){return e.call(this,t,i)};return t.source="function("+(c.variable||"obj")+"){\n"+a+"}",t}(this.view.source)},render:function(t){return this.data=t,this.view.output.call(this,this)},include:function(t,e){return this.instance(t).render(this.utils.extend(this.data,e))}});e.exports=t},{"../views":22,"./class":3,"./utils":18}],18:[function(t,e,n){var s={getType:function(t){return{}.toString.call(t).match(/\s([a-zA-Z]+)/)[1].toLowerCase()},isObject:function(t){return"object"===this.getType(t)},isPlainObject:function(t){return!!t&&"object"==typeof t&&t.constructor===Object},isFunction:function(t){return"function"===this.getType(t)},isRegexp:function(t){return"regexp"===this.getType(t)},isArguments:function(t){return"arguments"===this.getType(t)},isError:function(t){return"error"===this.getType(t)},isArray:function(t){return"array"===this.getType(t)},isDate:function(t){return"date"===this.getType(t)},isString:function(t){return"string"===this.getType(t)},isNumber:function(t){return"number"===this.getType(t)},isElement:function(t){return t&&1===t.nodeType},toArray:function(t){return[].slice.call(t)},querySelectorAll:function(t,e){return this.toArray((e||document).querySelectorAll(t))},querySelector:function(t,e){return(e||document).querySelector(t)},hasProp:function(t,e){return t&&t.hasOwnProperty(e)},forEach:function(t,e,n){for(var i in t)this.hasProp(t,i)&&e.call(n||null,t[i],i)},map:function(t,e,n){var i,o,s=[];for(i in t)this.hasProp(t,i)&&void 0!==(o=e.call(n||null,t[i],i))&&(s[i]=o);return s},cleanObject:function(t){for(var e in t)this.hasProp(t,e)&&(0===t[e].length?(this.isArray(t)&&t.splice(e,1),this.isPlainObject(t)&&delete t[e]):this.isPlainObject(t[e])&&this.cleanObject(t[e]));return t},param:function(t){function e(t,e){e=null==(e="function"==typeof e?e():e)?"":e,n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)}var n=[],o=function(i,t){return i?s.isArray(t)?s.forEach(t,function(t,e){var n;o(i+"["+(n=e,"object"==typeof(e=t)&&e?n:"")+"]",t)}):s.isObject(t)?s.forEach(t,function(t,e){o(i+"["+e+"]",t)}):e(i,t):s.isArray(t)?s.forEach(t,function(t){e(t.name,t.value)}):s.forEach(t,function(t,e){o(e,t)}),n};return o("",t).join("&")},removeElement:function(t){t.parentNode.removeChild(t)},createElement:function(t){return document.createElement(t)},getStyle:function(t,e,n){return((n=window.getComputedStyle)?n(t):t.currentStyle)[e.replace(/-(\w)/gi,function(t,e){return e.toUpperCase()})]},extend:function(n){return this.forEach([].slice.call(arguments,1),function(t){null!==t&&this.forEach(t,function(t,e){this.isPlainObject(t)?n[e]=this.extend(n[e]||{},t):n[e]=t},this)},this),n},uuid:function(){for(var t=0,e="";t++<36;)e+=51*t&52?(15^t?8^Math.random()*(20^t?16:4):4).toString(16):"-";return e},getPath:function(t){var e=t.split("."),t=e.shift(),n=null;return this.hasProp(window,t)&&(n=window[t],this.forEach(e,function(t){n=this.hasProp(n,t)?n[t]:null},this)),n},stringFormat:function(t,n){return(t||"").replace(/{(.+?)}/g,function(t,e){return n[e]||t})}};e.exports=s},{}],19:[function(t,e,n){t=t("./index").extend({attributes:{},initElement:function(t){this.utils.isPlainObject(this.params.attributes)&&this.utils.extend(this.attributes,this.params.attributes),this.addSelectorEvent(t,"click","sendRequest")},getRequestParams:function(t){return this.utils.extend({},this.params.options,this.getElementData(t))},getElementData:function(n){var i={};return this.utils.forEach(this.attributes,function(t,e){n.hasAttribute(e)&&(i[t]=n.getAttribute(e))}),i}});e.exports=t},{"./index":21}],20:[function(t,e,n){var i=t("../module"),t=t("./index"),o=i.extend({init:function(t){this.setFormElement(t)},setFormElement:function(t){this.utils.isElement(t)&&(this.form=t)},getData:function(t){var e=this.deparam(this.serializeArray());return!0===t?this.utils.cleanObject(e):e},serializeArray:function(){var t=this.utils.toArray(this.form.elements);return this.utils.map(t,function(t){if(!t.disabled&&""!==t.name&&(!t.type.match("checkbox|radio")||t.checked))return{name:t.name,value:t.value}})},serializeAndEncode:function(){return this.utils.map(this.serializeArray(),function(t){return[t.name,encodeURIComponent(t.value)].join("=")}).join("&")},deparam:function(t){var e,r={},n=/[^\[\]]+|\[\]$/g;for(e in t)t.hasOwnProperty(e)&&function(t,e){for(var n=r,i=t.pop(),o=t.length,s=0;s\n\n\n',e.exports["styles.ejs"]='\n',e.exports["trustly.ejs"]='<%=include(\'styles.ejs\')%>\n\n'},{}]},{},[1])(1)});
2 | //# sourceMappingURL=checkout.min.js.map
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ipsp-js-sdk",
3 | "title": "Fondy Checkout SDK",
4 | "description": "Fondy payment service provider javascript sdk",
5 | "version": "2.0.12",
6 | "license": "MIT",
7 | "homepage": "https://fondy.eu",
8 | "main": "src/checkout.js",
9 | "unpkg": "dist/checkout.min.js",
10 | "browser": "dist/checkout.js",
11 | "author": {
12 | "name": "CloudIPSP JS Team",
13 | "url": "https://github.com/orgs/cloudipsp/teams/js-team"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/cloudipsp/ipsp-js-sdk.git"
18 | },
19 | "scripts": {
20 | "dev": "npm-run-all --parallel dev:wrangler dev:rollup",
21 | "dev:wrangler": "wrangler dev",
22 | "dev:rollup": "./node_modules/.bin/rollup -c -w --bundleConfigAsCjs",
23 | "build": "npm install && ./node_modules/.bin/rollup -c --bundleConfigAsCjs",
24 | "prepublishOnly": "npm install && ./node_modules/.bin/rollup -c --bundleConfigAsCjs",
25 | "version": "npm run build",
26 | "postversion": "git push && git push --tags"
27 | },
28 | "files": [
29 | "dist",
30 | "src"
31 | ],
32 | "devDependencies": {
33 | "@babel/preset-env": "^7.22.4",
34 | "@rollup/plugin-babel": "^6.0.3",
35 | "@rollup/plugin-commonjs": "^24.0.1",
36 | "@rollup/plugin-json": "^6.0.0",
37 | "@rollup/plugin-node-resolve": "^15.0.1",
38 | "@rollup/plugin-terser": "^0.4.0",
39 | "hono": "^3.2.3",
40 | "npm-run-all": "^4.1.5",
41 | "qunit": "^2.14.1",
42 | "rollup": "^3.23.0",
43 | "rollup-plugin-html": "^0.2.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel'
2 | import resolve from '@rollup/plugin-node-resolve'
3 | import commonjs from '@rollup/plugin-commonjs'
4 | import terser from '@rollup/plugin-terser'
5 | import json from '@rollup/plugin-json';
6 | import pkg from './package.json'
7 |
8 | export default {
9 | input: pkg.main,
10 | output: [
11 | {
12 | file: 'dist/checkout.js',
13 | format: 'umd',
14 | name: '$checkout'
15 | },
16 | {
17 | file: 'dist/checkout.min.js',
18 | format: 'umd',
19 | sourcemap: true,
20 | name: '$checkout',
21 | plugins:[
22 | terser()
23 | ]
24 | },
25 | ],
26 | plugins: [
27 | commonjs(),
28 | resolve({}),
29 | json(),
30 | babel({
31 | presets: ['@babel/preset-env'],
32 | babelHelpers: 'bundled'
33 | })
34 | ]
35 | }
--------------------------------------------------------------------------------
/src/checkout.js:
--------------------------------------------------------------------------------
1 | const {Component} = require('./core/component');
2 | const Utils = require('./core/utils');
3 | const Config = require('./core/config');
4 | const {Api} = require('./core/api');
5 | const {Module} = require('./core/module');
6 | const {Connector} = require('./core/connector');
7 | const {Response} = require('./core/response');
8 | const {PaymentButton} = require('./core/payment/button');
9 | const {PaymentRequestApi} = require('./core/payment/request');
10 | const {PaymentElement} = require('./core/payment/element');
11 | const {PaymentContainer} = require('./core/payment/container');
12 | const {WidgetForm} = require('./core/widget/form');
13 | const {WidgetButton} = require('./core/widget/button');
14 |
15 | Component.add('Api', Api);
16 | Component.add('Connector', Connector);
17 | Component.add('PaymentButton', PaymentButton);
18 | Component.add('PaymentRequestApi', PaymentRequestApi);
19 | Component.add('PaymentElement', PaymentElement);
20 | Component.add('FormWidget', WidgetForm);
21 | Component.add('ButtonWidget', WidgetButton);
22 |
23 | Component.Utils = exports.Utils = Utils
24 | Component.Config = exports.Config = Config
25 | Component.Api = exports.Api = Api;
26 | Component.Module = exports.Module = Module;
27 | Component.Connector = exports.Connector = Connector;
28 | Component.PaymentRequestApi = exports.PaymentRequestApi = PaymentRequestApi;
29 | Component.PaymentContainer = exports.PaymentContainer = PaymentContainer;
30 | Component.PaymentElement = exports.PaymentElement = PaymentElement;
31 | Component.PaymentButton = exports.PaymentButton = PaymentButton;
32 | Component.Response = exports.Response = Response;
33 |
34 | module.exports = Component;
--------------------------------------------------------------------------------
/src/core/api.js:
--------------------------------------------------------------------------------
1 | const { Deferred } = require('./deferred')
2 | const { Module } = require('./module')
3 | const { Connector } = require('./connector')
4 | const { Modal } = require('./modal')
5 | const { Response } = require('./response')
6 | const { ApiFrameCss, ApiOrigin, ApiEndpoint } = require('./config')
7 |
8 | exports.Api = Module.extend({
9 | defaults: {
10 | origin: ApiOrigin,
11 | endpoint: ApiEndpoint,
12 | messages: {
13 | modalHeader:
14 | 'Now you will be redirected to your bank 3DSecure. If you are not redirected please refer',
15 | modalLinkLabel: 'link',
16 | },
17 | },
18 | init(params) {
19 | this.initParams(params)
20 | },
21 | url(type, url) {
22 | return [
23 | this.params.origin,
24 | this.params.endpoint[type] || '/',
25 | url || '',
26 | ].join('')
27 | },
28 | extendParams(params) {
29 | this.utils.extend(this.params, params)
30 | return this
31 | },
32 | initParams(params) {
33 | this.params = this.utils.extend({}, this.defaults)
34 | this.extendParams(params)
35 | this.setOrigin(this.params.origin)
36 | this.loaded = false
37 | this.created = false
38 | },
39 | setOrigin(origin) {
40 | if (this.utils.isString(origin)) {
41 | this.params.origin = origin
42 | }
43 | return this
44 | },
45 | scope(callback) {
46 | callback = this.proxy(callback)
47 | if (this._createFrame().loaded === true) {
48 | callback()
49 | } else {
50 | this.on('checkout.api', callback)
51 | }
52 | },
53 | request(model, method, params) {
54 | const defer = Deferred()
55 | const data = {
56 | uid: this.connector.getUID(),
57 | action: model,
58 | method: method,
59 | params: params || {},
60 | }
61 | this.connector.send('request', data)
62 | this.connector.on(
63 | data.uid,
64 | this.proxy(function (ev, response, model, action) {
65 | const responseModel = new Response(response)
66 | responseModel.setUID(data.uid)
67 | responseModel.setConnector(this.connector)
68 | action = 'resolveWith'
69 | if (responseModel.attr('submit3ds')) {
70 | action = 'notifyWith'
71 | }
72 | if (responseModel.attr('error')) {
73 | action = 'rejectWith'
74 | }
75 | defer[action](this, [responseModel])
76 | })
77 | )
78 | return defer
79 | },
80 | _loadFrame(url) {
81 | this.iframe = this.utils.createElement('iframe')
82 | this.addAttr(this.iframe, {
83 | allowtransparency: true,
84 | frameborder: 0,
85 | scrolling: 'no',
86 | })
87 | this.addAttr(this.iframe, { src: url })
88 | this.addCss(this.iframe, ApiFrameCss)
89 | this.body = this.utils.querySelector('body')
90 | if (this.body.firstChild) {
91 | this.body.insertBefore(this.iframe, this.body.firstChild)
92 | } else {
93 | this.body.appendChild(this.iframe)
94 | }
95 | return this.iframe
96 | },
97 | _createFrame() {
98 | if (this.created === false) {
99 | this.created = true
100 | this.iframe = this._loadFrame(this.url('gateway'))
101 | this.connector = new Connector({
102 | target: this.iframe.contentWindow,
103 | origin: this.params.origin,
104 | })
105 | this.connector.on('load', this.proxy('_onLoadConnector'))
106 | this.connector.on('modal', this.proxy('_onOpenModal'))
107 | }
108 | return this
109 | },
110 | _onOpenModal(xhr, model) {
111 | this.modal = new Modal({
112 | checkout: this,
113 | model: model,
114 | })
115 | this.modal.on('close', this.proxy('_onCloseModal'))
116 | },
117 | _onCloseModal(modal, data) {
118 | this.trigger('modal.close', modal, data)
119 | },
120 | _onLoadConnector() {
121 | this.loaded = true
122 | this.connector.off('load')
123 | this.trigger('checkout.api')
124 | this.off('checkout.api')
125 | },
126 | })
127 |
--------------------------------------------------------------------------------
/src/core/class.js:
--------------------------------------------------------------------------------
1 | let init = false
2 | const fnTest = /xyz/.test(
3 | function () {
4 | return 'xyz'
5 | }.toString()
6 | )
7 | ? /\b_super\b/
8 | : /.*/
9 |
10 | function ClassObject() {}
11 |
12 | ClassObject.prototype._super = function () {}
13 |
14 | ClassObject.prototype.instance = function (params) {
15 | return new this.constructor(params)
16 | }
17 |
18 | ClassObject.prototype.proxy = function (fn) {
19 | fn = typeof fn == 'string' ? this[fn] : fn
20 | return (function (cx, cb) {
21 | return function () {
22 | return cb.apply(
23 | cx,
24 | [this].concat(Array.prototype.slice.call(arguments))
25 | )
26 | }
27 | })(this, fn)
28 | }
29 |
30 | function superMethod(parent, name, method) {
31 | return function () {
32 | let temp = this._super,
33 | result
34 | this._super = parent[name]
35 | result = method.apply(this, arguments)
36 | this._super = temp
37 | return result
38 | }
39 | }
40 |
41 | function assign(target, instance) {
42 | let prop,
43 | proto,
44 | parent = target.prototype
45 | init = true
46 | proto = new target()
47 | init = false
48 | for (prop in instance) {
49 | if (instance.hasOwnProperty(prop)) {
50 | if (
51 | typeof parent[prop] == 'function' &&
52 | typeof instance[prop] == 'function' &&
53 | fnTest.test(instance[prop])
54 | ) {
55 | proto[prop] = superMethod(parent, prop, instance[prop])
56 | } else {
57 | proto[prop] = instance[prop]
58 | }
59 | }
60 | }
61 | return proto
62 | }
63 |
64 | ClassObject.extend = function extend(instance) {
65 | function Class() {
66 | if (!init && this) this['init'].apply(this, arguments)
67 | }
68 | Class.prototype = assign(this, instance)
69 | Class.prototype.constructor = Class
70 | Class.extend = extend
71 | return Class
72 | }
73 |
74 | exports.ClassObject = ClassObject
75 |
--------------------------------------------------------------------------------
/src/core/component.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Modules registry
3 | * @type {Object}
4 | */
5 | const modules = {}
6 | /**
7 | * @type {Object}
8 | */
9 | const instance = {}
10 | /**
11 | * @param {string} name
12 | * @param {Object} [params]
13 | * @returns {{}}
14 | */
15 | const newModule = function (name, params) {
16 | if (!modules[name]) {
17 | throw Error(['module is undefined', name].join(' '))
18 | }
19 | return new modules[name](params || {})
20 | }
21 |
22 | const addModule = function (name, module) {
23 | if (modules[name]) {
24 | throw Error(['module already added', name].join(' '))
25 | }
26 | modules[name] = module
27 | }
28 |
29 | function Component(name, params) {
30 | if (instance[name]) return instance[name]
31 | return (instance[name] = newModule(name, params))
32 | }
33 |
34 | Component.get = function (name, params) {
35 | return newModule(name, params)
36 | }
37 |
38 | Component.add = function (name, module) {
39 | addModule(name, module)
40 | return this
41 | }
42 |
43 | exports.Component = Component
44 |
--------------------------------------------------------------------------------
/src/core/config.js:
--------------------------------------------------------------------------------
1 | exports.ApiOrigin = 'https://api.fondy.eu'
2 |
3 | exports.ApiEndpoint = {
4 | gateway: '/checkout/v2/index.html',
5 | element: '/checkout/v2/button/element.html',
6 | }
7 |
8 | exports.GooglePayApi = 'https://pay.google.com/gp/p/js/pay.js'
9 |
10 | exports.GoogleBaseRequest = {
11 | apiVersion: 2,
12 | apiVersionMinor: 0,
13 | allowedPaymentMethods: [
14 | {
15 | type: 'CARD',
16 | parameters: {
17 | allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
18 | allowedCardNetworks: [
19 | 'AMEX',
20 | 'DISCOVER',
21 | 'INTERAC',
22 | 'JCB',
23 | 'MASTERCARD',
24 | 'VISA',
25 | ],
26 | },
27 | },
28 | ],
29 | }
30 |
31 | exports.GooglePayLanguages = [
32 | 'ar',
33 | 'bg',
34 | 'ca',
35 | 'zh',
36 | 'hr',
37 | 'cs',
38 | 'da',
39 | 'nl',
40 | 'en',
41 | 'et',
42 | 'fi',
43 | 'fr',
44 | 'de',
45 | 'el',
46 | 'id',
47 | 'it',
48 | 'ja',
49 | 'ko',
50 | 'ms',
51 | 'no',
52 | 'pl',
53 | 'pt',
54 | 'ru',
55 | 'sr',
56 | 'sk',
57 | 'sl',
58 | 'es',
59 | 'sv',
60 | 'th',
61 | 'tr',
62 | 'uk',
63 | ]
64 |
65 | exports.PaymentRequestOptions = {}
66 |
67 | exports.PaymentRequestDetails = {
68 | total: {
69 | label: 'Total',
70 | amount: {
71 | currency: 'USD',
72 | value: '0.00',
73 | },
74 | },
75 | }
76 |
77 | exports.ApiFrameCss = {
78 | width: '1px !important',
79 | height: '1px !important',
80 | left: '1px !important',
81 | bottom: '1px !important',
82 | position: 'fixed !important',
83 | border: '0px !important',
84 | }
85 |
86 | exports.ButtonFrameCss = {
87 | border: 'none !important',
88 | margin: '0 !important',
89 | padding: '0 !important',
90 | display: 'block !important',
91 | width: '1px !important',
92 | 'min-width': '100% !important',
93 | background: 'transparent !important',
94 | position: 'relative !important',
95 | opacity: '0 !important',
96 | overflow: 'hidden !important',
97 | height: '100% !important',
98 | outline: 'none !important',
99 | 'z-index': '1 !important',
100 | }
101 |
102 | exports.ButtonFrameAttrs = {
103 | tabindex: '-1',
104 | scrolling: 'no',
105 | frameborder: 0,
106 | allowtransparency: true,
107 | allowpaymentrequest: true,
108 | }
109 |
110 | exports.ButtonCoverCss = {
111 | 'z-index': '2 !important',
112 | position: 'absolute !important',
113 | border: 'none !important',
114 | background: 'transparent !important',
115 | left: '0 !important',
116 | top: '0 !important',
117 | cursor: 'pointer !important',
118 | outline: 'none !important',
119 | width: '100% !important',
120 | height: '100% !important',
121 | }
122 |
123 | exports.ButtonCoverAttrs = {
124 | role: 'button',
125 | 'aria-pressed': 'false',
126 | }
127 |
128 | exports.ButtonContainerCss = {
129 | border: '0 !important',
130 | margin: '0 !important',
131 | padding: '0 !important',
132 | display: 'block !important',
133 | background: 'transparent !important',
134 | 'user-select': 'none !important',
135 | overflow: 'hidden !important',
136 | position: 'relative !important',
137 | opacity: '1 !important',
138 | height: '0 !important',
139 | width: '100% !important',
140 | outline: 'none !important',
141 | }
142 |
143 | exports.ButtonDefaultColor = 'dark'
144 |
145 | exports.ButtonColorMap = {
146 | dark: 'dark',
147 | light: 'light',
148 | black: 'dark',
149 | white: 'light',
150 | }
151 |
152 | exports.ButtonLabelMap = {
153 | ar: '',
154 | bg: '',
155 | ca: '',
156 | zh: '',
157 | hr: '',
158 | cs: '',
159 | da: '',
160 | nl: '',
161 | en: 'Pay with',
162 | et: '',
163 | es: 'Comprar con',
164 | el: '',
165 | fi: '',
166 | fr: 'Acheter avec',
167 | de: 'Zahlen über',
168 | id: '',
169 | it: 'Acquista con',
170 | ja: '',
171 | ko: '',
172 | ms: '',
173 | no: '',
174 | pl: 'Zapłać przez',
175 | pt: '',
176 | ru: 'Оплатить через',
177 | sr: '',
178 | sk: 'Zaplatiť cez',
179 | sl: '',
180 | sv: '',
181 | th: '',
182 | tr: '',
183 | uk: 'Оплатити через',
184 | }
185 |
--------------------------------------------------------------------------------
/src/core/connector.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('./module')
2 |
3 | exports.Connector = Module.extend({
4 | ns: 'crossDomain',
5 | origin: '*',
6 | uniqueId: 1,
7 | signature: null,
8 | init(params) {
9 | this.setTarget(params.target)
10 | this.setOrigin(params.origin)
11 | this.create()
12 | },
13 | create() {
14 | this.addEvent(window, 'message', 'router')
15 | },
16 | setOrigin(origin) {
17 | this.origin = origin || '*'
18 | },
19 | setTarget(target) {
20 | this.target = target
21 | return this
22 | },
23 | getUID() {
24 | return ++this.uniqueId
25 | },
26 | destroy() {
27 | this.removeEvent(window, 'message', 'router')
28 | this._super()
29 | },
30 | router(window, ev, response) {
31 | if (this.target !== ev.source) return false
32 | try {
33 | response = JSON.parse(ev.data)
34 | } catch (e) {}
35 | if (response && response.action && response.data) {
36 | this.trigger(response.action, response.data)
37 | }
38 | },
39 | send(action, data, request, options) {
40 | if (!this.target) {
41 | return
42 | }
43 | request = JSON.stringify({
44 | action: action,
45 | data: data,
46 | })
47 | options = {
48 | targetOrigin: this.origin,
49 | delegate: 'payment',
50 | }
51 | try {
52 | this.target.postMessage(request, options)
53 | } catch (e) {
54 | this.target.postMessage(request, this.origin, [])
55 | }
56 | },
57 | })
58 |
--------------------------------------------------------------------------------
/src/core/deferred.js:
--------------------------------------------------------------------------------
1 | const { isArray, isFunction, hasProp, forEach } = require('./utils')
2 | const PENDING = 0
3 | const RESOLVED = 1
4 | const REJECTED = 2
5 | /**
6 | * @name Deferred
7 | * @param [fn]
8 | * @return {Deferred}
9 | */
10 | exports.Deferred = function Deferred(fn) {
11 | const doneFuncs = []
12 | const failFuncs = []
13 | const progressFuncs = []
14 | let status = PENDING
15 | let resultArgs = null
16 | /**
17 | * @lends Deferred.prototype
18 | */
19 | const promise = {
20 | done(fn) {
21 | if (status === RESOLVED) {
22 | fn.apply(this, resultArgs)
23 | }
24 | doneFuncs.push(fn)
25 | return this
26 | },
27 | fail(fn) {
28 | if (status === RESOLVED) {
29 | fn.apply(this, resultArgs)
30 | }
31 | failFuncs.push(fn)
32 | return this
33 | },
34 | always(fn) {
35 | return this.done(fn).fail(fn)
36 | },
37 | progress(fn) {
38 | if (status === PENDING) {
39 | progressFuncs.push(fn)
40 | }
41 | return this
42 | },
43 | then(done, fail, progress) {
44 | if (done) {
45 | this.done(done)
46 | }
47 | if (fail) {
48 | this.fail(fail)
49 | }
50 | if (progress) {
51 | this.progress(progress)
52 | }
53 | return this
54 | },
55 | promise(object) {
56 | if (object === null) {
57 | return promise
58 | }
59 | forEach(promise, function (value, name) {
60 | object[name] = value
61 | })
62 | return object
63 | },
64 | state() {
65 | return status
66 | },
67 | isPending() {
68 | return status === PENDING
69 | },
70 | isRejected() {
71 | return status === REJECTED
72 | },
73 | isResolved() {
74 | return status === RESOLVED
75 | },
76 | }
77 | /**
78 | * @lends Deferred.prototype
79 | */
80 | const deferred = {
81 | resolveWith(context, params) {
82 | if (status === PENDING) {
83 | status = RESOLVED
84 | let args = (resultArgs = params || [])
85 | for (let i = 0; i < doneFuncs.length; i++) {
86 | doneFuncs[i].apply(context, args)
87 | }
88 | }
89 | return this
90 | },
91 | rejectWith(context, params) {
92 | if (status === PENDING) {
93 | status = REJECTED
94 | let args = (resultArgs = params || [])
95 | for (let i = 0; i < failFuncs.length; i++) {
96 | failFuncs[i].apply(context, args)
97 | }
98 | }
99 | return this
100 | },
101 | notifyWith(context, params) {
102 | if (status === PENDING) {
103 | let args = (resultArgs = params || [])
104 | for (let i = 0; i < progressFuncs.length; i++) {
105 | progressFuncs[i].apply(context, args)
106 | }
107 | }
108 | return this
109 | },
110 | resetState() {
111 | status = PENDING
112 | return this
113 | },
114 | resolve() {
115 | return this.resolveWith(this, arguments)
116 | },
117 | reject() {
118 | return this.rejectWith(this, arguments)
119 | },
120 | notify() {
121 | return this.notifyWith(this, arguments)
122 | },
123 | }
124 | const obj = promise.promise(deferred)
125 | if (isFunction(fn)) {
126 | fn.call(obj, obj)
127 | }
128 | return obj
129 | }
130 |
--------------------------------------------------------------------------------
/src/core/event.js:
--------------------------------------------------------------------------------
1 | const { ClassObject } = require('./class')
2 |
3 | exports.Event = ClassObject.extend({
4 | init() {
5 | this.events = {}
6 | this.empty = []
7 | },
8 | on(type, callback) {
9 | ;(this.events[type] = this.events[type] || []).push(callback)
10 | return this
11 | },
12 | off(type, callback) {
13 | type || (this.events = {})
14 | let list = this.events[type] || this.empty,
15 | i = (list.length = callback ? list.length : 0)
16 | while (i--) callback === list[i] && list.splice(i, 1)
17 | return this
18 | },
19 | trigger(type) {
20 | let e = this.events[type] || this.empty,
21 | list = e.length > 0 ? e.slice(0, e.length) : e,
22 | i = 0,
23 | j
24 | while ((j = list[i++])) j.apply(j, this.empty.slice.call(arguments, 1))
25 | return this
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/src/core/google/pay.js:
--------------------------------------------------------------------------------
1 | const { Deferred } = require('../deferred')
2 | const { Module } = require('../module')
3 | const { GooglePayApi, GoogleBaseRequest } = require('../config')
4 |
5 | const GooglePay = Module.extend({
6 | id: 'google-payments-api',
7 | init() {
8 | this.client = null
9 | this.wrapper = this.utils.querySelector('head')
10 | this.defer = Deferred()
11 | },
12 | load() {
13 | if (this.utils.getPath('google.payments.api.PaymentsClient')) {
14 | return this.defer.resolveWith(this)
15 | }
16 | if (this.utils.querySelector('#'.concat(this.id))) {
17 | return this.defer
18 | }
19 | this.script = this.utils.createElement('script')
20 | this.addAttr(this.script, {
21 | id: this.id,
22 | async: true,
23 | src: GooglePayApi,
24 | })
25 | this.utils.isElement(this.wrapper) &&
26 | this.wrapper.appendChild(this.script)
27 | this.addEvent(this.script, 'load', 'onLoadSuccess')
28 | this.addEvent(this.script, 'error', 'onLoadError')
29 | return this.defer
30 | },
31 | show(methods) {
32 | const method = methods.find(function (item) {
33 | return item.supportedMethods === 'https://google.com/pay'
34 | })
35 | const client = this.getClient({ environment: method.data.environment })
36 | return client.loadPaymentData(method.data)
37 | },
38 | readyToPay(cx, response) {
39 | if (response.result) {
40 | this.defer.resolveWith(this)
41 | }
42 | },
43 | onError(cx, error) {
44 | this.defer.rejectWith(this, error)
45 | },
46 | onLoadSuccess() {
47 | const client = this.getClient()
48 | if (client)
49 | client
50 | .isReadyToPay(GoogleBaseRequest)
51 | .then(this.proxy('readyToPay'))
52 | .catch(this.proxy('onError'))
53 | },
54 | onLoadError() {
55 | this.defer.rejectWith(this)
56 | },
57 | getClient(options) {
58 | if (options || this.client === null) {
59 | const PaymentClient = this.utils.getPath(
60 | 'google.payments.api.PaymentsClient'
61 | )
62 | if (PaymentClient) {
63 | this.client = new PaymentClient(options)
64 | } else {
65 | this.onError(null, new Error('Google Client Error'))
66 | }
67 | }
68 | return this.client
69 | },
70 | })
71 |
72 | exports.GooglePay = new GooglePay()
73 |
--------------------------------------------------------------------------------
/src/core/modal.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('./module')
2 | const { Connector } = require('./connector')
3 | const { Template } = require('./template')
4 |
5 | exports.Modal = Module.extend({
6 | init(data) {
7 | this.checkout = data.checkout
8 | this.model = data.model || {}
9 | this.messages = data.checkout.params.messages || {}
10 | this.template = new Template('acs')
11 | this.body = this.utils.querySelector('body')
12 | this.initModal()
13 | this.initConnector()
14 | },
15 | initModal() {
16 | this.name = ['modal-iframe', this.getRandomNumber()].join('-')
17 | this.modal = this.utils.createElement('div')
18 | this.modal.innerHTML = this.template.render({
19 | model: this.model,
20 | messages: this.messages,
21 | })
22 | this.iframe = this.find('.ipsp-modal-iframe')
23 | this.addAttr(this.iframe, { name: this.name, id: this.name })
24 | if (this.model['send_data']) {
25 | this.form = this.prepareForm(
26 | this.model['url'],
27 | this.model['send_data'],
28 | this.name
29 | )
30 | this.modal.appendChild(this.form)
31 | } else {
32 | this.iframe.src = this.model['url']
33 | }
34 | this.addEvent(this.find('.ipsp-modal-close'), 'click', 'closeModal')
35 | this.addEvent(this.find('.ipsp-modal-title a'), 'click', 'submitForm')
36 | this.initScrollbar()
37 | this.body.appendChild(this.modal)
38 | if (this.form) {
39 | this.form.submit()
40 | }
41 | },
42 | measureScrollbar() {
43 | const scrollDiv = document.createElement('div')
44 | scrollDiv.className = 'modal-scrollbar-measure'
45 | this.body.appendChild(scrollDiv)
46 | this.utils.removeElement(scrollDiv)
47 | return scrollDiv.offsetWidth - scrollDiv.clientWidth
48 | },
49 | checkScrollbar() {
50 | let documentElementRect
51 | let fullWindowWidth = window.innerWidth
52 | if (!fullWindowWidth) {
53 | documentElementRect =
54 | document.documentElement.getBoundingClientRect()
55 | fullWindowWidth =
56 | documentElementRect.right - Math.abs(documentElementRect.left)
57 | }
58 | this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
59 | this.scrollbarWidth = this.measureScrollbar()
60 | },
61 | initScrollbar() {
62 | this.checkScrollbar()
63 | this.bodyPad = parseInt(
64 | this.utils.getStyle(this.body, 'padding-right') || 0,
65 | 10
66 | )
67 | this.originalBodyPad = document.body.style.paddingRight || ''
68 | this.originalOverflow = document.body.style.overflow || ''
69 | if (this.bodyIsOverflowing) {
70 | this.addCss(this.body, {
71 | paddingRight: [this.bodyPad + this.scrollbarWidth, 'px'].join(
72 | ''
73 | ),
74 | overflow: 'hidden',
75 | })
76 | }
77 | },
78 | resetScrollbar() {
79 | this.addCss(this.body, {
80 | paddingRight: this.originalBodyPad
81 | ? [this.originalBodyPad, 'px'].join('')
82 | : '',
83 | overflow: this.originalOverflow,
84 | })
85 | },
86 | getRandomNumber() {
87 | return Math.round(Math.random() * 1000000000)
88 | },
89 | find(selector) {
90 | return this.utils.querySelector(selector, this.modal)
91 | },
92 | closeModal(el, ev) {
93 | ev.preventDefault()
94 | this.trigger('close', this.data)
95 | this.removeModal()
96 | },
97 | submitForm(el, ev) {
98 | ev.preventDefault()
99 | this.trigger('submit', this.data)
100 | this.addAttr(this.form, {
101 | target: '_blank',
102 | })
103 | this.form.submit()
104 | },
105 | removeModal() {
106 | this.destroy()
107 | },
108 | destroy() {
109 | this.utils.removeElement(this.modal)
110 | this.resetScrollbar()
111 | this.connector.destroy()
112 | this._super()
113 | },
114 | initConnector() {
115 | this.connector = new Connector({ target: this.iframe.contentWindow })
116 | this.connector.on('response', this.proxy('onResponse'))
117 | },
118 | onResponse(ev, data) {
119 | this.sendResponse(data)
120 | this.removeModal()
121 | },
122 | sendResponse(data) {
123 | this.checkout.connector.send('request', {
124 | uid: data.uid,
125 | action: 'api.checkout.proxy',
126 | method: 'send',
127 | params: data,
128 | })
129 | },
130 | })
131 |
--------------------------------------------------------------------------------
/src/core/model.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('./module')
2 |
3 | exports.Model = Module.extend({
4 | init(data) {
5 | this.data = data || {}
6 | this.create()
7 | },
8 | create() {},
9 | eachProps(args) {
10 | const name = args[1] ? args[0] : null
11 | const callback = args[1] ? args[1] : args[0]
12 | const list = name ? this.alt(name, []) : this.data
13 | return {
14 | list: list,
15 | callback: callback,
16 | }
17 | },
18 | each() {
19 | const props = this.eachProps(arguments)
20 | for (let prop in props.list) {
21 | if (props.list.hasOwnProperty(prop)) {
22 | props.callback(
23 | this.instance(props.list[prop]),
24 | props.list[prop],
25 | prop
26 | )
27 | }
28 | }
29 | },
30 | filter() {
31 | const props = this.eachProps(arguments)
32 | for (let prop in props.list) {
33 | if (props.list.hasOwnProperty(prop)) {
34 | const item = this.instance(props.list[prop])
35 | if (props.callback(item, props.list[prop], prop)) {
36 | return props.list[prop]
37 | }
38 | }
39 | }
40 | },
41 | find() {
42 | const props = this.eachProps(arguments)
43 | for (let prop in props.list) {
44 | if (props.list.hasOwnProperty(prop)) {
45 | const item = this.instance(props.list[prop])
46 | if (props.callback(item, props.list[prop], prop)) {
47 | return item
48 | }
49 | }
50 | }
51 | return false
52 | },
53 | alt(prop, defaults) {
54 | prop = this.attr(prop)
55 | return typeof prop === 'undefined' ? defaults : prop
56 | },
57 | attr(key, value) {
58 | let i = 0,
59 | data = this.data,
60 | name = (key || '').split('.'),
61 | prop = name.pop(),
62 | len = arguments.length
63 | for (; i < name.length; i++) {
64 | if (data && data.hasOwnProperty(name[i])) {
65 | data = data[name[i]]
66 | } else {
67 | if (len === 2) {
68 | data = data[name[i]] = {}
69 | } else {
70 | break
71 | }
72 | }
73 | }
74 | if (len === 1) {
75 | return data ? data[prop] : undefined
76 | }
77 | if (len === 2) {
78 | data[prop] = value
79 | }
80 | return this
81 | },
82 | stringify() {
83 | return JSON.stringify(this.serialize())
84 | },
85 | serialize() {
86 | return this.data
87 | },
88 | })
89 |
--------------------------------------------------------------------------------
/src/core/module.js:
--------------------------------------------------------------------------------
1 | const { ClassObject } = require('./class')
2 | const { Event } = require('./event')
3 | const Utils = require('./utils')
4 |
5 | exports.Module = ClassObject.extend({
6 | utils: Utils,
7 | getListener() {
8 | if (!this._listener_) this._listener_ = new Event()
9 | return this._listener_
10 | },
11 | destroy() {
12 | this.off()
13 | },
14 | on(type, callback) {
15 | this.getListener().on(type, callback)
16 | return this
17 | },
18 | off(type, callback) {
19 | this.getListener().off(type, callback)
20 | return this
21 | },
22 | proxy(fn) {
23 | if (!this._proxy_cache_) this._proxy_cache_ = {}
24 | if (this.utils.isString(fn)) {
25 | if (!this._proxy_cache_[fn]) {
26 | this._proxy_cache_[fn] = this._super(fn)
27 | }
28 | return this._proxy_cache_[fn]
29 | }
30 | return this._super(fn)
31 | },
32 | trigger() {
33 | this.getListener().trigger.apply(this.getListener(), arguments)
34 | return this
35 | },
36 | each(ob, cb) {
37 | this.utils.forEach(ob, this.proxy(cb))
38 | },
39 | addAttr(el, ob) {
40 | if (!this.utils.isElement(el)) return false
41 | this.utils.forEach(ob, function (v, k) {
42 | el.setAttribute(k, v)
43 | })
44 | },
45 | addCss(el, ob) {
46 | if (!this.utils.isElement(el)) return false
47 | this.utils.forEach(
48 | ob,
49 | function (v, k) {
50 | this.addCssProperty(el, k, v)
51 | },
52 | this
53 | )
54 | },
55 | addCssProperty(el, style, value) {
56 | let result = el.style.cssText.match(
57 | new RegExp(
58 | '(?:[;\\s]|^)(' +
59 | style.replace('-', '\\-') +
60 | '\\s*:(.*?)(;|$))'
61 | )
62 | ),
63 | idx
64 | if (result) {
65 | idx = result.index + result[0].indexOf(result[1])
66 | el.style.cssText =
67 | el.style.cssText.substring(0, idx) +
68 | style +
69 | ': ' +
70 | value +
71 | ';' +
72 | el.style.cssText.substring(idx + result[1].length)
73 | } else {
74 | el.style.cssText += ' ' + style + ': ' + value + ';'
75 | }
76 | },
77 | addEvent(el, ev, cb) {
78 | if (!el || !ev || !cb) return false
79 | cb = this.proxy(cb)
80 | if (el.addEventListener) el.addEventListener(ev, cb)
81 | else if (el['attachEvent']) el['attachEvent']('on' + ev, cb)
82 | },
83 | removeEvent(el, ev, cb) {
84 | if (!el || !ev || !cb) return false
85 | cb = this.proxy(cb)
86 | if (el.removeEventListener) el.removeEventListener(ev, cb, false)
87 | else if (el['detachEvent']) el['detachEvent']('on' + ev, cb)
88 | },
89 | prepareForm(url, data, target, method) {
90 | const form = this.utils.createElement('form')
91 | this.addAttr(form, {
92 | action: url,
93 | target: target || '_self',
94 | method: method || 'POST',
95 | })
96 | this.addCss(form, {
97 | display: 'none',
98 | })
99 | this.utils.forEach(
100 | data,
101 | function (v, k, el) {
102 | el = this.utils.createElement('input')
103 | el.type = 'hidden'
104 | el.name = k
105 | el.value = v
106 | form.appendChild(el)
107 | },
108 | this
109 | )
110 | return form
111 | },
112 | })
113 |
--------------------------------------------------------------------------------
/src/core/payment/button.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../module')
2 | const { Api } = require('../api')
3 | const { PaymentRequestApi } = require('./request')
4 | const { PaymentElement } = require('./element')
5 | const { forEach } = require('../utils')
6 | const { ApiOrigin, ApiEndpoint } = require('../config')
7 |
8 | exports.PaymentButton = Module.extend({
9 | defaults: {
10 | origin: ApiOrigin,
11 | endpoint: ApiEndpoint,
12 | methods: ['apple', 'google'],
13 | element: null,
14 | style: {
15 | height: 38,
16 | mode: 'default',
17 | type: 'long',
18 | color: 'black',
19 | },
20 | data: {
21 | lang: 'en',
22 | },
23 | before(defer) {
24 | defer.resolve()
25 | },
26 | },
27 | init(params) {
28 | this.elements = {}
29 | this.supported = false
30 | this.params = {
31 | element: params.element,
32 | methods: params.methods || this.defaults.methods,
33 | origin: params.origin || this.defaults.origin,
34 | endpoint: this.utils.extend(
35 | {},
36 | this.defaults.endpoint,
37 | params.endpoint
38 | ),
39 | style: this.utils.extend({}, this.defaults.style, params.style),
40 | before: params.before || this.defaults.before,
41 | data: this.utils.extend({}, this.defaults.data, params.data),
42 | }
43 | this.initApi(params.api)
44 | this.initPaymentRequestApi()
45 | this.initElements()
46 | this.update()
47 | },
48 | initApi(api) {
49 | if (api instanceof Api) {
50 | this.api = api
51 | } else {
52 | this.api = new Api({
53 | origin: this.params.origin,
54 | endpoint: this.params.endpoint,
55 | })
56 | }
57 | },
58 | initPaymentRequestApi() {
59 | this.request = new PaymentRequestApi()
60 | this.request.setApi(this.api)
61 | this.request.setMerchant(this.params.data.merchant_id)
62 | this.request.setBeforeCallback(this.params.before)
63 | this.request.on('details', this.proxy('onDetails'))
64 | this.request.on('error', this.proxy('onError'))
65 | },
66 | initElements() {
67 | const style = this.params.style
68 | const data = this.params.data
69 | const origin = this.params.origin
70 | const appendTo = this.params.element
71 | const endpoint = this.params.endpoint.element
72 | const request = this.request
73 | this.container = this.utils.querySelector(this.params.element)
74 | this.addCss(this.container, {
75 | display: 'flex',
76 | gap: '1rem',
77 | 'flex-direction': 'column',
78 | })
79 | forEach(this.params.methods, function (method) {
80 | const element = new PaymentElement({
81 | origin: origin,
82 | endpoint: endpoint,
83 | method: method,
84 | appendTo: appendTo,
85 | color: style.color,
86 | mode: style.mode,
87 | lang: data.lang,
88 | height: style.height,
89 | })
90 | element.setPaymentRequest(request)
91 | })
92 | request.getSupportedMethods()
93 | },
94 | update(data) {
95 | return this.request.update(
96 | this.utils.extend(this.params.data, data || {})
97 | )
98 | },
99 | onDetails(cx, data) {
100 | this.api.scope(() => {
101 | this.api
102 | .request(
103 | 'api.checkout.form',
104 | 'request',
105 | this.utils.extend({}, this.params.data, data)
106 | )
107 | .done(this.proxy('onSuccess'))
108 | .fail(this.proxy('onError'))
109 | })
110 | },
111 | onSuccess(cx, data) {
112 | this.trigger('success', data)
113 | },
114 | onError(cx, data) {
115 | this.trigger('error', data)
116 | },
117 | })
118 |
--------------------------------------------------------------------------------
/src/core/payment/container.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../module')
2 | const { Connector } = require('../connector')
3 | const { PaymentRequestApi } = require('./request')
4 | const {
5 | GooglePayLanguages,
6 | ButtonColorMap,
7 | ButtonDefaultColor,
8 | ButtonLabelMap,
9 | } = require('../config')
10 |
11 | const parseConfig = (config) => {
12 | const data = {
13 | payment_system: config.payment_system,
14 | provider: {},
15 | }
16 | const methods = config.methods || []
17 | const details = config.details || {}
18 | const options = config.options || {}
19 | const regex = new RegExp('//([a-z]+)\\.com/')
20 | methods.forEach(function (item) {
21 | const match = regex.exec(item.supportedMethods)
22 | if (match === null) return
23 | data.provider[match[1]] = {
24 | methods: [item],
25 | details: details,
26 | options: options,
27 | }
28 | })
29 | return data
30 | }
31 |
32 | exports.PaymentContainer = Module.extend({
33 | defaults: {
34 | element: null,
35 | method: 'card',
36 | data: {
37 | lang: 'en',
38 | },
39 | style: {},
40 | },
41 | init(params) {
42 | this.initParams(params)
43 | this.initEvents()
44 | },
45 | initParams(params) {
46 | this.params = this.utils.extend({}, this.defaults, params)
47 | this.element = this.utils.querySelector(this.params.element)
48 | this.connector = new Connector({
49 | target: window.parent,
50 | })
51 | this.payment = new PaymentRequestApi({
52 | embedded: true,
53 | })
54 | },
55 | extendParams(params) {
56 | this.utils.extend(this.params, {
57 | method: params.method,
58 | style: params.style,
59 | data: params.data,
60 | css: params.css,
61 | })
62 | },
63 | getGoogleLangSupport(lang, defaults) {
64 | return GooglePayLanguages.indexOf(lang) !== -1 ? lang : defaults
65 | },
66 | getButtonColor(color) {
67 | return ButtonColorMap[color] || ButtonDefaultColor
68 | },
69 | getGoogleSvg(color, lang, mode) {
70 | const params = {
71 | endpoint: 'https://www.gstatic.com/instantbuy/svg',
72 | color: this.getButtonColor(color),
73 | mode: mode || 'plain',
74 | lang: lang || 'en',
75 | }
76 | let format = 'url("{endpoint}/{color}/{mode}/{lang}.svg")'
77 | if (mode === 'plain') {
78 | format = 'url("{endpoint}/{color}_gpay.svg")'
79 | }
80 | if (mode === 'buy') {
81 | format = 'url("{endpoint}/{color}/{lang}.svg")'
82 | }
83 | return this.utils.stringFormat(format, params)
84 | },
85 | getAppleSvg(color) {
86 | const format = 'url("svg/apple-pay-{color}.svg")'
87 | const params = {
88 | color: this.getButtonColor(color),
89 | }
90 | return this.utils.stringFormat(format, params)
91 | },
92 | getAppleLabel(lang) {
93 | return ButtonLabelMap[lang || 'en']
94 | },
95 | addFrameImage() {
96 | const frame =
97 | this.utils.querySelector('iframe', this.element) ||
98 | this.utils.createElement('iframe')
99 | const url = 'https://pay.google.com/gp/p/generate_gpay_btn_img'
100 | const style = this.params.style || {}
101 | const lang = this.getGoogleLangSupport(this.params.data.lang, 'en')
102 | const query = {
103 | buttonColor: style.color || 'black',
104 | browserLocale: lang,
105 | buttonSizeMode: 'fill',
106 | }
107 | const src = [url, this.utils.param(query)].join('?')
108 | this.addAttr(frame, {
109 | scrolling: 'no',
110 | frameborder: 0,
111 | src: src,
112 | })
113 | this.element.appendChild(frame)
114 | this.element.classList.remove('short', 'long')
115 | },
116 | styleButton() {
117 | let element = this.element
118 | let params = this.params
119 | let method = params.method
120 | let style = params.style || {}
121 | let lang = params.data.lang || 'en'
122 | let css = params.css || {}
123 | element.setAttribute('class', '')
124 | element.classList.add('button', 'pending')
125 | if (method === 'card') method = 'google'
126 | if (method) {
127 | element.classList.add(method)
128 | }
129 | if (lang) {
130 | element.classList.add(lang)
131 | }
132 | if (style.type) {
133 | element.classList.add(style.type)
134 | }
135 | if (style.mode) {
136 | element.classList.add(style.mode)
137 | }
138 | if (style.color) {
139 | element.classList.add(style.color)
140 | }
141 | if (method === 'google') {
142 | if (style.type === 'short') {
143 | style.mode = 'plain'
144 | }
145 | if (style.mode === 'default') {
146 | this.addFrameImage()
147 | } else {
148 | css.image = this.getGoogleSvg(style.color, lang, style.mode)
149 | }
150 | }
151 | if (method === 'apple') {
152 | css.image = this.getAppleSvg(style.color)
153 | css.label = this.getAppleLabel(lang, style.mode)
154 | }
155 | if (css) {
156 | this.utils.forEach(css, function (value, name) {
157 | element.style.setProperty(['--', name].join(''), value)
158 | })
159 | }
160 | },
161 | initEvents() {
162 | this.payment.on(
163 | 'details',
164 | this.proxy(function (cx, data) {
165 | this.connector.send('details', data)
166 | this.connector.send('complete', data)
167 | })
168 | )
169 | this.payment.on(
170 | 'reload',
171 | this.proxy(function (cx, data) {
172 | this.connector.send('reload', data)
173 | })
174 | )
175 | this.payment.on(
176 | 'error',
177 | this.proxy(function (cx, data) {
178 | this.connector.send('error', data)
179 | })
180 | )
181 | this.connector.on(
182 | 'options',
183 | this.proxy(function (cx, data) {
184 | this.extendParams(data)
185 | this.styleButton()
186 | })
187 | )
188 | this.connector.on(
189 | 'pay',
190 | this.proxy(function () {
191 | if (!this.element.classList.contains('pending')) {
192 | if (this.params.method === 'apple') {
193 | const payload = this.payment.payload || {}
194 | const apple = payload.provider['apple'] || {}
195 | this.connector.send('pay', {
196 | payment_system: payload.payment_system,
197 | methods: apple.methods,
198 | details: apple.details,
199 | options: apple.options,
200 | })
201 | } else {
202 | this.payment
203 | .setSupported({
204 | fallback: false,
205 | provider: ['google'],
206 | })
207 | .pay('google')
208 | }
209 | }
210 | })
211 | )
212 | this.connector.on(
213 | 'config',
214 | this.proxy(function (cx, data) {
215 | this.payment.setPayload((data = parseConfig(data)))
216 | if (
217 | data.payment_system &&
218 | Object.keys(data.provider).length > 0
219 | ) {
220 | this.element.classList.remove('pending')
221 | this.element.classList.add('ready')
222 | this.connector.send('show', {})
223 | } else {
224 | this.connector.send('hide', {})
225 | }
226 | })
227 | )
228 | this.connector.on(
229 | 'event',
230 | this.proxy(function (cx, data) {
231 | if (data.type === 'mouseenter') {
232 | this.element.classList.add('hover')
233 | }
234 | if (data.type === 'mouseleave') {
235 | this.element.classList.remove('hover')
236 | }
237 | if (data.type === 'focusin') {
238 | this.element.classList.add('active')
239 | }
240 | if (data.type === 'focusout') {
241 | this.element.classList.remove('active')
242 | }
243 | })
244 | )
245 | },
246 | })
247 |
--------------------------------------------------------------------------------
/src/core/payment/element.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../module')
2 | const { Connector } = require('../connector')
3 | const {
4 | ButtonContainerCss,
5 | ButtonCoverCss,
6 | ButtonCoverAttrs,
7 | ButtonFrameCss,
8 | ButtonFrameAttrs,
9 | } = require('../config')
10 |
11 | const { PaymentRequestApi } = require('./request')
12 |
13 | exports.PaymentElement = Module.extend({
14 | defaults: {
15 | method: null,
16 | appendTo: null,
17 | className: 'payment-element',
18 | origin: 'https://pay.fondy.eu',
19 | endpoint: '/checkout/v2/button/element.html',
20 | mode: 'plain',
21 | color: 'black',
22 | lang: 'en',
23 | height: 38,
24 | },
25 | init(params) {
26 | this.params = {}
27 | this.utils.extend(this.params, this.defaults)
28 | this.utils.extend(this.params, params)
29 | this.initElement()
30 | },
31 | getElementUrl() {
32 | return [this.params.origin, this.params.endpoint].join('')
33 | },
34 | getElementOptions() {
35 | return this.utils.param({
36 | method: this.params.method,
37 | mode: this.params.mode,
38 | style: this.params.style,
39 | color: this.params.color,
40 | lang: this.params.lang,
41 | })
42 | },
43 | initElement() {
44 | this.element = this.utils.createElement('div')
45 | this.iframe = this.utils.createElement('iframe')
46 | this.button = this.utils.createElement('a')
47 | this.addCss(this.element, ButtonContainerCss)
48 | this.addCss(this.button, ButtonCoverCss)
49 | this.addAttr(this.button, ButtonCoverAttrs)
50 | this.addCss(this.iframe, ButtonFrameCss)
51 | this.addAttr(this.iframe, ButtonFrameAttrs)
52 | this.addAttr(this.iframe, {
53 | src: [this.getElementUrl(), this.getElementOptions()].join('?'),
54 | })
55 | this.addEvent(this.iframe, 'load', 'onloadConnector')
56 | this.addEvent(this.iframe, 'error', 'errorConnector')
57 | this.addAttr(this.element, {
58 | class: this.params.className,
59 | })
60 | this.element.appendChild(this.iframe)
61 | this.element.appendChild(this.button)
62 | },
63 | errorConnector() {
64 | log('frame load error')
65 | },
66 | onloadConnector() {
67 | this.connector = new Connector({
68 | target: this.iframe.contentWindow,
69 | origin: this.params.origin,
70 | })
71 | },
72 | send(action, data) {
73 | if (this.connector) {
74 | this.connector.send(action, data)
75 | }
76 | },
77 | onEvent(cx, ev) {
78 | ev.preventDefault()
79 | if (this.pending) return false
80 | this.send('event', { type: ev.type })
81 | },
82 | onClick() {
83 | if (this.pending) return false
84 | this.request
85 | .before()
86 | .done(this.proxy('onClickDone'))
87 | .fail(this.proxy('onClickFail'))
88 | },
89 | onClickDone() {
90 | this.request.pay(this.params.method)
91 | },
92 | onClickFail() {},
93 | onSupported(cx, supported) {
94 | if (supported.provider.includes(this.params.method)) {
95 | this.mount()
96 | }
97 | },
98 | onPayload(cx, payload) {
99 | if (payload.allowed.includes(this.params.method)) {
100 | this.show()
101 | }
102 | },
103 | onPending(cx, state) {
104 | this.pending = state
105 | this.send('pending', { state: state })
106 | },
107 | initEvents() {
108 | this.addEvent(this.button, 'mouseenter', 'onEvent')
109 | this.addEvent(this.button, 'mouseleave', 'onEvent')
110 | this.addEvent(this.button, 'blur', 'onEvent')
111 | this.addEvent(this.button, 'focus', 'onEvent')
112 | this.addEvent(this.button, 'click', 'onClick')
113 | },
114 | setPaymentRequest(request) {
115 | if (!(request instanceof PaymentRequestApi))
116 | throw Error('request is not instance of PaymentRequestApi')
117 | this.request = request
118 | const onSupported = this.proxy('onSupported')
119 | const onPayload = this.proxy('onPayload')
120 | const onPending = this.proxy('onPending')
121 | this.request.off('supported', onSupported).on('supported', onSupported)
122 | this.request.off('payload', onPayload).on('payload', onPayload)
123 | this.request.off('pending', onPending).on('pending', onPending)
124 | return this
125 | },
126 | appendTo(appendTo) {
127 | const container = this.utils.querySelector(appendTo)
128 | if (container) container.appendChild(this.element)
129 | this.initEvents()
130 | return this
131 | },
132 | mount() {
133 | this.appendTo(this.params.appendTo)
134 | },
135 | isMounted() {
136 | return document.body.contains(this.element)
137 | },
138 | show() {
139 | if (this.isMounted() === false) return this
140 | this.addCss(this.iframe, {
141 | transition: 'opacity 0.6s 0.4s ease-out',
142 | opacity: this.utils.cssUnit(1),
143 | })
144 | this.addCss(this.element, {
145 | transition: 'height 0.2s ease-out',
146 | height: this.utils.cssUnit(this.params.height, 'px'),
147 | })
148 | this.trigger('show', {})
149 | return this
150 | },
151 | hide() {
152 | if (this.isMounted() === false) return this
153 | this.addCss(this.iframe, {
154 | transition: 'opacity 0.4s ease-out',
155 | opacity: this.utils.cssUnit(0),
156 | })
157 | this.addCss(this.element, {
158 | transition: 'height 0.2s 0.4s ease-out',
159 | height: this.utils.cssUnit(0, 'px'),
160 | })
161 | this.trigger('hide', {})
162 | return this
163 | },
164 | })
165 |
--------------------------------------------------------------------------------
/src/core/payment/request.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../module')
2 | const { Api } = require('../api')
3 | const { GooglePay } = require('../google/pay')
4 | const { Deferred } = require('../deferred')
5 | const { getPaymentRequest, hasProp, isFunction } = require('../utils')
6 | const { GoogleBaseRequest, PaymentRequestDetails } = require('../config')
7 |
8 | const getPaymentMethods = () => {
9 | return [
10 | [
11 | 'google',
12 | {
13 | supportedMethods: 'https://google.com/pay',
14 | data: GoogleBaseRequest,
15 | },
16 | ],
17 | [
18 | 'apple',
19 | {
20 | supportedMethods: 'https://apple.com/apple-pay',
21 | },
22 | () => hasProp(window, 'ApplePaySession'),
23 | ],
24 | ]
25 | }
26 |
27 | let requestDeferred = null
28 |
29 | let requestSupported = {
30 | fallback: false,
31 | provider: [],
32 | }
33 |
34 | const getSupportedMethods = () => {
35 | if (requestDeferred) return requestDeferred
36 | requestDeferred = Deferred()
37 | const methods = getPaymentMethods()
38 | const details = PaymentRequestDetails
39 | ;(function check() {
40 | const item = methods.shift()
41 | if (item === undefined) {
42 | if (requestSupported.provider.indexOf('google') === -1) {
43 | requestSupported.fallback = true
44 | requestSupported.provider.push('google')
45 | }
46 | return requestDeferred.resolve(requestSupported)
47 | }
48 | const method = item.shift()
49 | const config = item.shift()
50 | const callback = item.shift()
51 | if (isFunction(callback) && callback() === false) {
52 | setTimeout(check, 25)
53 | return false
54 | }
55 | const request = getPaymentRequest([config], details, {})
56 | if (request) {
57 | request
58 | .canMakePayment()
59 | .then(function (status) {
60 | if (status === true) requestSupported.provider.push(method)
61 | setTimeout(check, 25)
62 | })
63 | .catch(function () {
64 | setTimeout(check, 25)
65 | })
66 | } else {
67 | setTimeout(check, 25)
68 | }
69 | })()
70 | return requestDeferred
71 | }
72 |
73 | const PaymentRequestInterface = Module.extend({
74 | config: {
75 | payment_system: '',
76 | fallback: false,
77 | methods: [],
78 | details: {},
79 | options: {},
80 | },
81 | supported: {
82 | fallback: false,
83 | provider: [],
84 | },
85 | payload: {
86 | payment_system: null,
87 | provider: [],
88 | },
89 | params: {},
90 | getSupportedMethods() {
91 | return getSupportedMethods().then((supported) => {
92 | this.setSupported(supported)
93 | return supported
94 | })
95 | },
96 | init(params) {
97 | this.params = params || {}
98 | },
99 | setSupported(supported) {
100 | this.supported = supported
101 | this.trigger('supported', supported)
102 | return this
103 | },
104 | setPayload(payload) {
105 | this.payload = payload
106 | this.trigger('payload', payload)
107 | return this
108 | },
109 | setMerchant(merchant) {
110 | this.merchant = merchant
111 | },
112 | setApi(api) {
113 | if (api instanceof Api) this.api = api
114 | return this
115 | },
116 | getProviderPayload(method) {
117 | return this.payload.provider[method] || {}
118 | },
119 | isMethodSupported(method) {
120 | return this.supported.provider.indexOf(method) !== -1
121 | },
122 | isFallbackMethod(method) {
123 | return method === 'google' && this.supported.fallback
124 | },
125 | })
126 | /**
127 | * @constructor
128 | */
129 | const PaymentRequestApi = PaymentRequestInterface.extend({
130 | request(method, params, success, failure) {
131 | if (this.api) {
132 | this.api.scope(
133 | this.proxy(function () {
134 | this.api
135 | .request('api.checkout.pay', method, params)
136 | .done(this.proxy(success))
137 | .fail(this.proxy(failure))
138 | })
139 | )
140 | }
141 | },
142 | update(data) {
143 | const defer = Deferred()
144 | this.request(
145 | 'methods',
146 | data,
147 | function (cx, model) {
148 | this.onUpdate(cx, model)
149 | defer.resolveWith(this, [model])
150 | },
151 | function (cx, model) {
152 | this.onError(cx, model)
153 | defer.rejectWith(this, [model])
154 | }
155 | )
156 | return defer
157 | },
158 | onUpdate(cx, model) {
159 | this.setPayload(model.serialize())
160 | },
161 | onError(cx, model) {
162 | this.trigger('error', model)
163 | },
164 | isPending() {
165 | return this.pendingState === true
166 | },
167 | setPending(state) {
168 | this.pendingState = state
169 | setTimeout(() => {
170 | this.trigger('pending', state)
171 | }, 100)
172 | },
173 | beforeCallback(defer) {
174 | defer.resolve()
175 | },
176 | setBeforeCallback(callback) {
177 | if (isFunction(callback)) {
178 | this.beforeCallback = callback
179 | }
180 | return this
181 | },
182 | before() {
183 | if (this.isPending()) return
184 | this.setPending(true)
185 | const defer = Deferred()
186 | defer.always(
187 | this.proxy(function () {
188 | this.setPending(false)
189 | })
190 | )
191 | this.beforeCallback(defer)
192 | return defer
193 | },
194 | pay(method) {
195 | if (this.isPending()) return
196 | this.setPending(true)
197 | const payload = this.getProviderPayload(method)
198 | const response = Deferred()
199 | response.always(function () {
200 | this.setPending(false)
201 | })
202 | if (this.isMethodSupported(method) === false) {
203 | return response.rejectWith(this, [{ test: true }])
204 | }
205 | if (this.isFallbackMethod(method)) {
206 | this.makePaymentFallback(response, payload.methods)
207 | } else {
208 | this.makeNativePayment(response, payload)
209 | }
210 | return response
211 | .done(function (details) {
212 | this.trigger('details', {
213 | payment_system: this.payload.payment_system,
214 | data: details,
215 | })
216 | })
217 | .fail(function (error) {
218 | this.trigger('error', error)
219 | if (this.params.embedded === true) {
220 | location.reload()
221 | this.trigger('reload', this.params)
222 | }
223 | })
224 | },
225 | makeNativePayment(defer, payload) {
226 | const self = this
227 | const request = getPaymentRequest(
228 | payload.methods,
229 | payload.details,
230 | payload.options
231 | )
232 | this.addEvent(request, 'merchantvalidation', 'merchantValidation')
233 | request
234 | .canMakePayment()
235 | .then(function () {
236 | request
237 | .show()
238 | .then(function (response) {
239 | response.complete('success').then(function () {
240 | defer.resolveWith(self, [response.details])
241 | })
242 | })
243 | .catch(function (e) {
244 | defer.rejectWith(self, [
245 | { code: e.code, message: e.message },
246 | ])
247 | })
248 | })
249 | .catch(function (e) {
250 | defer.rejectWith(self, [{ code: e.code, message: e.message }])
251 | })
252 | },
253 | makePaymentFallback(defer, methods) {
254 | const self = this
255 | GooglePay.load().then(() => {
256 | GooglePay.show(methods)
257 | .then((details) => {
258 | defer.resolveWith(self, [details])
259 | })
260 | .catch((e) => {
261 | defer.rejectWith(self, [
262 | { code: e.code, message: e.message },
263 | ])
264 | })
265 | })
266 | },
267 | appleSession(params) {
268 | const defer = Deferred()
269 | this.request(
270 | 'session',
271 | params,
272 | function (c, model) {
273 | defer.resolveWith(this, [model.serialize()])
274 | },
275 | function (c, model) {
276 | defer.rejectWith(this, [model])
277 | }
278 | )
279 | return defer
280 | },
281 | merchantValidation(cx, event) {
282 | const { validationURL } = event
283 | const { host } = location
284 | this.appleSession({
285 | url: validationURL,
286 | domain: host,
287 | merchant_id: this.merchant,
288 | })
289 | .done(function (session) {
290 | try {
291 | event.complete(session.data)
292 | } catch (error) {
293 | this.trigger('error', error)
294 | }
295 | })
296 | .fail(function (error) {
297 | this.trigger('error', error)
298 | })
299 | },
300 | })
301 |
302 | exports.PaymentRequestApi = PaymentRequestApi
303 |
--------------------------------------------------------------------------------
/src/core/response.js:
--------------------------------------------------------------------------------
1 | const { Model } = require('./model')
2 |
3 | const ProxyUrl = 'http://secure-redirect.cloudipsp.com/submit/'
4 |
5 | exports.Response = Model.extend({
6 | stringFormat(string) {
7 | const that = this
8 | return (string || '').replace(/{(.+?)}/g, function (match, prop) {
9 | return that.attr(['order.order_data', prop].join('.')) || match
10 | })
11 | },
12 | setConnector(connector) {
13 | this.connector = connector
14 | return this
15 | },
16 | setUID(uid) {
17 | this.uid = uid
18 | return this
19 | },
20 | getUID() {
21 | return this.uid
22 | },
23 | formDataProxy(url, data, target, method) {
24 | location.assign(
25 | [
26 | ProxyUrl,
27 | JSON.stringify({
28 | url: url,
29 | params: data,
30 | target: target,
31 | method: method,
32 | }),
33 | ].join('#')
34 | )
35 | },
36 | formDataSubmit(url, data, target, method) {
37 | if (url.match(/^http:/)) {
38 | return this.formDataProxy(url, data, target, method)
39 | }
40 | const action = this.stringFormat(url)
41 | const form = this.prepareForm(action, data, target, method)
42 | const body = this.utils.querySelector('body')
43 | body.appendChild(form)
44 | form.submit()
45 | form.parentNode.removeChild(form)
46 | },
47 | inProgress() {
48 | return this.attr('order.in_progress')
49 | },
50 | readyToSubmit() {
51 | return this.attr('order.ready_to_submit')
52 | },
53 | waitForResponse() {
54 | return this.attr('order.pending')
55 | },
56 | needVerifyCode() {
57 | return this.attr('order.need_verify_code')
58 | },
59 | redirectUrl() {
60 | if (this.attr('url')) {
61 | this.redirectToUrl(this.attr('url'))
62 | return true
63 | }
64 | return false
65 | },
66 | redirectToUrl(url) {
67 | location.assign(url)
68 | },
69 | submitToMerchant() {
70 | const ready = this.attr('order.ready_to_submit')
71 | const url = this.attr('model.url') || this.attr('order.response_url')
72 | const method = this.attr('order.method')
73 | const action = this.attr('order.action')
74 | const data =
75 | this.attr('model.send_data') || this.attr('order.order_data')
76 | if (ready && url && data) {
77 | if (
78 | action === 'redirect' ||
79 | data['get_without_parameters'] === true
80 | ) {
81 | this.redirectToUrl(url)
82 | } else {
83 | this.formDataSubmit(url, data, '_self', method)
84 | }
85 | return true
86 | }
87 | },
88 | submitForm() {
89 | const method = this.attr('method')
90 | const url = this.attr('url')
91 | const data = this.attr('send_data')
92 | if (url && data) {
93 | this.formDataSubmit(url, data, '_self', method)
94 | return true
95 | }
96 | return false
97 | },
98 | sendResponse() {
99 | const action = this.attr('action')
100 | if (action === 'submit') return this.submitForm()
101 | if (action === 'redirect') return this.redirectUrl()
102 | return false
103 | },
104 | prepare3dsData() {
105 | const params = {}
106 | const data = this.attr('submit3ds')
107 | if (data['3ds']) {
108 | params.token = this.attr('token')
109 | params.uid = this.getUID()
110 | params.frame = true
111 | if (data['send_data'].TermUrl) {
112 | data['send_data'].TermUrl = [
113 | data['send_data'].TermUrl,
114 | this.utils.param(params),
115 | ].join('#!!')
116 | }
117 | }
118 | return data
119 | },
120 | waitOn3dsDecline() {
121 | const data = this.alt('submit3ds.checkout_data', {
122 | js_wait_on_3ds_decline: false,
123 | js_wait_on_3ds_decline_duration: 0,
124 | })
125 | return data.js_wait_on_3ds_decline
126 | ? data.js_wait_on_3ds_decline_duration
127 | : 0
128 | },
129 | submit3dsForm() {
130 | if (this.attr('submit3ds.checkout_data')) {
131 | this.connector.trigger('modal', this.prepare3dsData())
132 | }
133 | },
134 | supportedMethod(method) {
135 | const item = this.find('methods', function (item) {
136 | return item.alt('supportedMethods', '').match(method)
137 | })
138 | if (item) {
139 | this.attr('methods', [item.serialize()])
140 | } else {
141 | this.attr('methods', [])
142 | }
143 | },
144 | })
145 |
--------------------------------------------------------------------------------
/src/core/template.js:
--------------------------------------------------------------------------------
1 | const { ClassObject } = require('./class')
2 | const Views = require('../views')
3 |
4 | function empty(name) {
5 | return function (data) {
6 | return ['template', name, 'not found'].join(' ')
7 | }
8 | }
9 |
10 | exports.Template = ClassObject.extend({
11 | init(name) {
12 | this.view = Views[name] || empty(name)
13 | },
14 | render(data) {
15 | return this.view(data)
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/src/core/utils.js:
--------------------------------------------------------------------------------
1 | const digitTest = /^\d+$/;
2 | const keyBreaker = /([^\[\]]+)|(\[\])/g;
3 | const paramTest = /([^?#]*)(#.*)?$/;
4 | const r20 = /%20/g;
5 | const rbracket = /\[\]$/;
6 | const matchSymbol = /\s([a-zA-Z]+)/
7 |
8 | const prep = (str) => {
9 | return decodeURIComponent(str.replace(/\+/g, ' '));
10 | }
11 | const flatten = (array) => {
12 | let result = []
13 | forEach(array, (item) => {
14 | result = result.concat(isArray(item) ? flatten(item) : item)
15 | })
16 | return result
17 | }
18 |
19 | const recursiveParams = (prefix, obj, next) => {
20 | if (isArray(obj)) {
21 | forEach(obj, (item, prop) => {
22 | if (rbracket.test(prefix)) {
23 | next(prefix, item);
24 | } else {
25 | recursiveParams(prefix + "[" + (isPlainObject(obj) ? prop : "") + "]", item, next);
26 | }
27 | });
28 | } else if (isPlainObject(obj)) {
29 | forEach(obj, (item, prop) => {
30 | recursiveParams(prefix + "[" + prop + "]", item, next);
31 | });
32 | } else {
33 | next(prefix, obj);
34 | }
35 | }
36 |
37 | const getType = exports.getType = (o) => {
38 | return ({}).toString.call(o).match(matchSymbol)[1].toLowerCase();
39 | }
40 | const isPlainObject = exports.isPlainObject = (o) => {
41 | return (!!o && typeof o === 'object' && o.constructor === Object);
42 | }
43 |
44 | const isFunction = exports.isFunction = (o) => {
45 | return getType(o) === 'function'
46 | }
47 |
48 | const isArray = exports.isArray = (o) => {
49 | return getType(o) === 'array';
50 | }
51 |
52 | const isElement = exports.isElement = (o) => {
53 | return o && o.nodeType === 1;
54 | }
55 |
56 | const toArray = exports.toArray = (o) => {
57 | return [].slice.call(o);
58 | }
59 |
60 | const hasProp = exports.hasProp = (o, v) => {
61 | return o && o.hasOwnProperty(v);
62 | }
63 |
64 | const forEach = exports.forEach = (ob, cb, cx) => {
65 | for (let p in ob)
66 | if (hasProp(ob, p))
67 | cb.call(cx || null, ob[p], p);
68 | }
69 |
70 | const cleanObject = exports.cleanObject = (ob) => {
71 | for (let p in ob) {
72 | if (hasProp(ob, p)) {
73 | if (ob[p].length === 0) {
74 | if (isArray(ob)) ob.splice(p, 1);
75 | if (isPlainObject(ob)) delete ob[p];
76 | } else if (isPlainObject(ob[p])) {
77 | cleanObject(ob[p]);
78 | }
79 | }
80 | }
81 | return ob;
82 | }
83 |
84 |
85 | const extend = exports.extend = (obj, ...args) => {
86 | forEach(args, function (o) {
87 | if (o !== null) {
88 | forEach(o, function (value, key) {
89 | if (isPlainObject(value)) {
90 | obj[key] = extend(obj[key] || {}, value);
91 | } else {
92 | obj[key] = value;
93 | }
94 | });
95 | }
96 | });
97 | return obj;
98 | }
99 |
100 | const uuid = exports.uuid = () => {
101 | let a = 0, b = '';
102 | while (a++ < 36) {
103 | if (a * 51 & 52) {
104 | b += (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16);
105 | } else {
106 | b += '-';
107 | }
108 | }
109 | return b;
110 | }
111 |
112 | exports.isObject = (o) => {
113 | return getType(o) === 'object';
114 | }
115 |
116 | exports.isRegexp = (o) => {
117 | return getType(o) === 'regexp';
118 | }
119 |
120 | exports.isArguments = (o) => {
121 | return getType(o) === 'arguments';
122 | }
123 |
124 | exports.isError = (o) => {
125 | return getType(o) === 'error';
126 | }
127 |
128 | exports.isDate = (o) => {
129 | return getType(o) === 'date';
130 | }
131 |
132 | exports.isString = (o) => {
133 | return getType(o) === 'string';
134 | }
135 |
136 | exports.isNumber = (o) => {
137 | return getType(o) === 'number';
138 | }
139 |
140 | exports.map = (ob, cb, cx) => {
141 | let p, t, r = [];
142 | for (p in ob)
143 | if (hasProp(ob, p))
144 | if ((t = cb.call(cx || null, ob[p], p)) !== undefined)
145 | r[p] = t;
146 | return r;
147 | }
148 |
149 | exports.querySelectorAll = (o, p) => {
150 | return toArray((p || document).querySelectorAll(o))
151 | }
152 |
153 | exports.querySelector = (o, p) => {
154 | return (p || document).querySelector(o)
155 | }
156 |
157 |
158 | exports.deparam = (params) => {
159 | const data = {}
160 | if (params.charAt(0) === '?') params = params.slice(1)
161 | if (params && paramTest.test(params)) {
162 | forEach(params.split('&'), (pair) => {
163 | let parts = pair.split('='),
164 | key = prep(parts.shift()),
165 | value = prep(parts.join('=')),
166 | current = data;
167 | if (key) {
168 | parts = key.match(keyBreaker);
169 | for (let j = 0, l = parts.length - 1; j < l; j++) {
170 | if (!current[parts[j]]) {
171 | current[parts[j]] = digitTest.test(parts[j + 1]) || parts[j + 1] === '[]' ? [] : {};
172 | }
173 | current = current[parts[j]];
174 | }
175 | const lastPart = parts.pop();
176 | if (lastPart === '[]') {
177 | current.push(value);
178 | } else {
179 | current[lastPart] = value;
180 | }
181 | }
182 | });
183 | }
184 | return data;
185 | }
186 |
187 | exports.param = (obj) => {
188 | const result = [];
189 | forEach(obj, (item, prop) => {
190 | recursiveParams(prop, item, (key, value) => {
191 | value = value == null ? "" : value;
192 | result.push([encodeURIComponent(key), encodeURIComponent(value)].join('='));
193 | });
194 | });
195 | return result.join("&").replace(r20, '+');
196 | }
197 |
198 | exports.removeElement = (el) => {
199 | el.parentNode.removeChild(el);
200 | }
201 |
202 | exports.createElement = (tag) => {
203 | return document.createElement(tag);
204 | }
205 |
206 | exports.addClass = (...args) => {
207 | const el = args.shift()
208 | if (isElement(el) === false) return;
209 | const classList = el.className.trim().split(/\s+/)
210 | const tokens = flatten(args.map(item => item.trim().split(/\s+/)));
211 | tokens.forEach(token => {
212 | if (token && !~classList.indexOf(token)) {
213 | classList.push(token)
214 | }
215 | })
216 | el.className = classList.join(' ').trim()
217 | }
218 |
219 | exports.removeClass = (...args) => {
220 | const el = args.shift()
221 | if (isElement(el) === false) return;
222 | const classList = el.className.trim().split(/\s+/)
223 | const tokens = flatten(args.map(item => item.trim().split(/\s+/)));
224 | tokens.forEach((token) => {
225 | if (token) {
226 | const index = classList.indexOf(token)
227 | if (!!~index) {
228 | classList.splice(index, 1)
229 | }
230 | }
231 | })
232 | el.className = classList.join(' ').trim()
233 | }
234 |
235 | exports.removeAttr = (el, attrs) => {
236 | if (isElement(el) === false) return false;
237 | if (isPlainObject(attrs)) {
238 | forEach(attrs, (value) => {
239 | el.removeAttribute(value)
240 | })
241 | }
242 | }
243 |
244 | exports.addAttr = (el, attrs) => {
245 | if (isElement(el) === false) return false;
246 | if (isPlainObject(attrs)) {
247 | forEach(attrs, (value, name) => {
248 | el.setAttribute(name, value)
249 | })
250 | }
251 | }
252 |
253 | exports.getStyle = (el, prop, getComputedStyle) => {
254 | getComputedStyle = window.getComputedStyle;
255 | return (getComputedStyle ? getComputedStyle(el) : el['currentStyle'])[prop.replace(/-(\w)/gi, function (word, letter) {
256 | return letter.toUpperCase()
257 | })];
258 | }
259 |
260 |
261 | exports.getPath = (path) => {
262 | const props = path.split('.');
263 | const first = props.shift();
264 | let value = null;
265 | if (hasProp(window, first)) {
266 | value = window[first];
267 | forEach(props, (name) => {
268 | value = hasProp(value, name) ? value[name] : null;
269 | })
270 | }
271 | return value;
272 | }
273 |
274 | exports.stringFormat = (format, params, expr) => {
275 | return (format || '').replace(expr || /{(.+?)}/g, (match, prop) => {
276 | return params[prop] || match;
277 | });
278 | }
279 |
280 | exports.cssUnit = (value, unit) => {
281 | return String(value || 0).concat(unit || '').concat(' !important')
282 | }
283 | /**
284 | * @function
285 | * @type {function(*, *, *)}
286 | */
287 | exports.getPaymentRequest = ((cx) => {
288 | let NativePaymentRequest;
289 | if (hasProp(cx, 'PaymentRequest') && isFunction(cx.PaymentRequest)) {
290 | NativePaymentRequest = cx.PaymentRequest
291 | }
292 | return (methods, details, options) => {
293 | let request = null;
294 | details = details || {};
295 | details.id = uuid();
296 | options = options || {};
297 | if (NativePaymentRequest) {
298 | try {
299 | request = new NativePaymentRequest(methods, details, options);
300 | } catch (e) {
301 | request = null;
302 | }
303 | }
304 | return request
305 | }
306 | })(window)
307 |
308 | exports.jsonParse = (value, defaults) => {
309 | try {
310 | return JSON.parse(value)
311 | } catch (e) {
312 | return defaults
313 | }
314 | }
315 |
316 |
--------------------------------------------------------------------------------
/src/core/widget/button.js:
--------------------------------------------------------------------------------
1 | const { Widget } = require('./index')
2 |
3 | exports.WidgetButton = Widget.extend({
4 | attributes: {},
5 | initElement(el) {
6 | if (this.utils.isPlainObject(this.params.attributes)) {
7 | this.utils.extend(this.attributes, this.params.attributes)
8 | }
9 | this.addSelectorEvent(el, 'click', 'sendRequest')
10 | },
11 | getRequestParams(el) {
12 | return this.utils.extend(
13 | {},
14 | this.params.options,
15 | this.getElementData(el)
16 | )
17 | },
18 | getElementData(el) {
19 | const result = {}
20 | this.utils.forEach(this.attributes, function (value, key) {
21 | if (el.hasAttribute(key)) {
22 | result[value] = el.getAttribute(key)
23 | }
24 | })
25 | return result
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/src/core/widget/form.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../module')
2 | const { Widget } = require('./index')
3 |
4 | const FormData = Module.extend({
5 | init(form) {
6 | this.setFormElement(form)
7 | },
8 | setFormElement(form) {
9 | if (this.utils.isElement(form)) {
10 | this.form = form
11 | }
12 | },
13 | getData(filter) {
14 | const params = this.deparam(this.serializeArray())
15 | return filter === true ? this.utils.cleanObject(params) : params
16 | },
17 | serializeArray() {
18 | const list = this.utils.toArray(this.form.elements)
19 | return this.utils.map(list, function (field) {
20 | if (field.disabled || field.name === '') return
21 | if (field.type.match('checkbox|radio') && !field.checked) return
22 | return {
23 | name: field.name,
24 | value: field.value,
25 | }
26 | })
27 | },
28 | serializeAndEncode() {
29 | return this.utils
30 | .map(this.serializeArray(), function (field) {
31 | return [field.name, encodeURIComponent(field.value)].join('=')
32 | })
33 | .join('&')
34 | },
35 | deparam(obj) {
36 | let prop,
37 | result = {}
38 | const breaker = /[^\[\]]+|\[\]$/g
39 | const attr = function (name, value) {
40 | let i,
41 | data = result,
42 | last = name.pop(),
43 | len = name.length
44 | for (i = 0; i < len; i++) {
45 | if (!data[name[i]])
46 | data[name[i]] = len === i + 1 && last === '[]' ? [] : {}
47 | data = data[name[i]]
48 | }
49 | if (last === '[]') {
50 | data.push(value)
51 | } else {
52 | data[last] = value
53 | }
54 | }
55 | for (prop in obj) {
56 | if (obj.hasOwnProperty(prop)) {
57 | attr(obj[prop].name.match(breaker), obj[prop].value)
58 | }
59 | }
60 | return result
61 | },
62 | })
63 |
64 | exports.WidgetForm = Widget.extend({
65 | initElement(el) {
66 | this.addSelectorEvent(el, 'submit', 'sendRequest')
67 | },
68 | getRequestParams(el) {
69 | return this.utils.extend(
70 | {},
71 | this.params.options,
72 | new FormData(el).getData()
73 | )
74 | },
75 | })
76 |
--------------------------------------------------------------------------------
/src/core/widget/index.js:
--------------------------------------------------------------------------------
1 | const { Api } = require('../api')
2 |
3 | exports.Widget = Api.extend({
4 | init(params) {
5 | this.initParams(params)
6 | this.initWidget()
7 | },
8 | initWidget() {
9 | this.initOptions(this.params.options)
10 | if (this.utils.isString(this.params.element)) {
11 | this.initElement(this.params.element)
12 | }
13 | },
14 | initOptions() {
15 | if (this.utils.isPlainObject(this.params.options)) {
16 | this.params.options = this.params.options || {}
17 | }
18 | },
19 | initElement(el) {},
20 | addSelectorEvent(el, ev, cb) {
21 | this.each(this.utils.querySelectorAll(el), function (cx, element) {
22 | this.addEvent(element, ev, cb)
23 | })
24 | return this
25 | },
26 | getRequestParams() {
27 | return {}
28 | },
29 | sendRequest(el, ev) {
30 | if (ev.defaultPrevented) return
31 | ev.preventDefault()
32 | this.trigger('request', this.getRequestParams(el))
33 | this.scope(function () {
34 | this.request(
35 | 'api.checkout.form',
36 | 'request',
37 | this.getRequestParams(el)
38 | )
39 | .done(this.proxy('onSuccess'))
40 | .fail(this.proxy('onError'))
41 | .progress(this.proxy('onProgress'))
42 | })
43 | },
44 | onProgress(cx, model) {
45 | this.trigger('progress', model)
46 | },
47 | onSuccess(cx, model) {
48 | model.sendResponse()
49 | model.submitToMerchant()
50 | this.trigger('success', model)
51 | },
52 | onError(cx, model) {
53 | this.trigger('error', model)
54 | },
55 | })
56 |
--------------------------------------------------------------------------------
/src/views/index.js:
--------------------------------------------------------------------------------
1 | exports.acs = require('./template/acs')
--------------------------------------------------------------------------------
/src/views/template/acs.js:
--------------------------------------------------------------------------------
1 | const styles = require('./acs_styles')
2 |
3 | module.exports = (data) => `
4 | ${styles(data)}
5 |
19 | `
--------------------------------------------------------------------------------
/src/views/template/acs_styles.js:
--------------------------------------------------------------------------------
1 | module.exports = (data) => `
2 |
94 | `
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @var $checkout = require('./dist/checkout')
3 | */
4 |
5 | QUnit.module('checkout',{
6 | before:function(){
7 | this.checkout = $checkout('Api');
8 | this.checkout.setOrigin('https://api.dev.fondy.eu');
9 | },
10 | beforeEach:function(){
11 | this.params = {
12 | merchant_id:'1396424',
13 | currency:'UAH',
14 | amount:0
15 | };
16 | }
17 | });
18 |
19 | QUnit.test('api.checkout.form.request:card', function( assert ) {
20 | var params = {};
21 | var done = assert.async();
22 | QUnit.extend(params,this.params);
23 | QUnit.extend(params,{
24 | payment_system:'card',
25 | card_number: '4444555566661111',
26 | expiry_date: '12/30',
27 | cvv2: '111',
28 | email:'test@example.com'
29 | });
30 | this.checkout.scope(function(){
31 | this.request('api.checkout.form','request',params).done(function(){
32 | assert.ok(true,'success result');
33 | done();
34 | }).fail(function(model){
35 | assert.ok(model.attr('error.code'),'error code exist');
36 | assert.ok(model.attr('error.message'),'error message exist');
37 | done();
38 | });
39 | });
40 | });
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------