├── .cli.json ├── .env.example ├── .github └── dependabot.yml ├── .gitignore ├── .readme └── screenshot.png ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── client ├── css │ ├── global.css │ └── normalize.css ├── index.html └── script.js └── server ├── README.md ├── java ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── stripe │ └── sample │ └── Server.java ├── node ├── README.md ├── package-lock.json ├── package.json └── server.js ├── php ├── .htaccess ├── README.md ├── composer.json ├── composer.lock ├── config.php └── index.php ├── python ├── README.md ├── requirements.txt └── server.py └── ruby ├── Gemfile ├── README.md └── server.rb /.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-direct-charge", 3 | "configureDotEnv": true, 4 | "integrations": [ 5 | { 6 | "name": "main", 7 | "clients": ["web"], 8 | "servers": ["java", "node", "php", "python", "ruby"] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Stripe keys 2 | STRIPE_PUBLISHABLE_KEY=pk_12345 3 | STRIPE_SECRET_KEY=sk_12345 4 | STRIPE_WEBHOOK_SECRET=whsec_1234 5 | 6 | # Environment variables 7 | STATIC_DIR=../../client 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # ruby dependencies 9 | - package-ecosystem: "bundler" 10 | directory: "/server/ruby/" 11 | schedule: 12 | interval: "weekly" 13 | day: "thursday" 14 | 15 | # python dependencies 16 | - package-ecosystem: "pip" 17 | directory: "/server/python/" 18 | schedule: 19 | interval: "weekly" 20 | day: "thursday" 21 | 22 | # php dependencies 23 | - package-ecosystem: "composer" 24 | directory: "/server/php/" 25 | schedule: 26 | interval: "weekly" 27 | day: "thursday" 28 | 29 | # node dependencies 30 | - package-ecosystem: "npm" 31 | directory: "/server/node/" 32 | schedule: 33 | interval: "weekly" 34 | day: "thursday" 35 | 36 | # java dependencies 37 | - package-ecosystem: "maven" 38 | directory: "/server/java/" 39 | schedule: 40 | interval: "weekly" 41 | day: "thursday" 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | .vscode/* 4 | !.vscode/extensions.json 5 | 6 | # Node files 7 | node_modules/ 8 | 9 | # Ruby files 10 | Gemfile.lock 11 | 12 | # Python files 13 | __pycache__ 14 | venv 15 | 16 | # PHP files 17 | vendor 18 | logs 19 | 20 | # Java files 21 | .settings 22 | target/ 23 | .classpath 24 | .factorypath 25 | .project 26 | 27 | # Typescript 28 | dist 29 | 30 | **/virtual -------------------------------------------------------------------------------- /.readme/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-samples/connect-direct-charge/e7520892a2d0241b9471285a5bd2d2307df70e0d/.readme/screenshot.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["stripe.vscode-stripe"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019- Stripe, Inc. (https://stripe.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 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 | # Connect direct charges 2 | 3 | This Stripe sample shows you how to process a Connect [direct charge](https://stripe.com/docs/connect/direct-charges) using Stripe elements. 4 | Before using this sample, you should have onboarded at least one Connect account. For more about onboarding accounts and making Connect charges, read our [step-by-step Connect guide](https://stripe.com/docs/connect/enable-payment-acceptance-guide#accept-payment). 5 | 6 | ![demo](.readme/screenshot.png) 7 | 8 | ## How to run locally 9 | 10 | This sample includes 5 server implementations in Node, Ruby, Python, Java, and PHP. 11 | 12 | Follow the steps below to run locally. 13 | 14 | **1. Clone and configure the sample** 15 | 16 | **Using the Stripe CLI** 17 | 18 | If you haven't already installed the CLI, follow the [installation steps](https://github.com/stripe/stripe-cli#installation) in the project README. The CLI is useful for cloning samples and locally testing webhooks and Stripe integrations. 19 | 20 | In your terminal shell, run the Stripe CLI command to clone the sample: 21 | 22 | ``` 23 | stripe samples create connect-direct-charge 24 | ``` 25 | 26 | The CLI will walk you through picking your integration type, server and client languages. Make sure to configure your .env file as shown below. 27 | 28 | **Installing and cloning manually** 29 | 30 | If you do not want to use the Stripe CLI, you can manually clone the sample yourself: 31 | 32 | ``` 33 | git clone https://github.com/stripe-samples/connect-direct-charge 34 | ``` 35 | 36 | Make sure to configure your .env file as shown below. 37 | 38 | **Configuring your .env file** 39 | 40 | Copy the .env.example file into a file named .env in the folder of the server you want to use. For example: 41 | 42 | ``` 43 | cp .env.example server/node/.env 44 | ``` 45 | 46 | You will need a Stripe account in order to run the demo. Once you set up your account, go to the Stripe [developer dashboard](https://stripe.com/docs/development/quickstart#api-keys) to find your API keys. 47 | 48 | ``` 49 | STRIPE_PUBLISHABLE_KEY= 50 | STRIPE_SECRET_KEY= 51 | ``` 52 | 53 | `STATIC_DIR` tells the server where to the client files are located and does not need to be modified unless you move the server files. 54 | 55 | **2. Follow the server instructions on how to run:** 56 | 57 | Pick the server language you want and follow the instructions in the server folder README on how to run. 58 | 59 | For example, if you want to run the Node server: 60 | 61 | ``` 62 | cd server/node # there's a README in this folder with instructions 63 | npm install 64 | npm start 65 | ``` 66 | 67 | ## FAQ 68 | 69 | Q: Why did you pick these frameworks? 70 | 71 | A: We chose the most minimal framework to convey the key Stripe calls and concepts you need to understand. These demos are meant as an educational tool that helps you roadmap how to integrate Stripe within your own system independent of the framework. 72 | 73 | ## Get support 74 | If you found a bug or want to suggest a new [feature/use case/sample], please [file an issue](../../issues). 75 | 76 | If you have questions, comments, or need help with code, we're here to help: 77 | - on [Discord](https://stripe.com/go/developer-chat) 78 | - on Twitter at [@StripeDev](https://twitter.com/StripeDev) 79 | - on Stack Overflow at the [stripe-payments](https://stackoverflow.com/tags/stripe-payments/info) tag 80 | - by [email](mailto:support+github@stripe.com) 81 | 82 | Sign up to [stay updated with developer news](https://go.stripe.global/dev-digest). 83 | 84 | ## Author(s) 85 | 86 | @emorphis 87 | -------------------------------------------------------------------------------- /client/css/global.css: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | :root { 3 | --gray-offset: rgba(0, 0, 0, 0.03); 4 | --gray-border: rgba(0, 0, 0, 0.15); 5 | --gray-light: rgba(0, 0, 0, 0.4); 6 | --gray-mid: rgba(0, 0, 0, 0.7); 7 | --gray-dark: rgba(0, 0, 0, 0.9); 8 | --body-color: var(--gray-mid); 9 | --headline-color: var(--gray-dark); 10 | --accent-color: #0066f0; 11 | --error-color: red; 12 | --body-font-family: -apple-system, BlinkMacSystemFont, sans-serif; 13 | --radius: 6px; 14 | --form-width: 343px; 15 | } 16 | 17 | /* Base */ 18 | * { 19 | box-sizing: border-box; 20 | } 21 | body { 22 | font-family: var(--body-font-family); 23 | font-size: 16px; 24 | color: var(--body-color); 25 | -webkit-font-smoothing: antialiased; 26 | } 27 | h1, 28 | h2, 29 | h3, 30 | h4, 31 | h5, 32 | h6 { 33 | color: var(--body-color); 34 | margin-top: 2px; 35 | margin-bottom: 4px; 36 | } 37 | h1 { 38 | font-size: 27px; 39 | color: var(--headline-color); 40 | } 41 | h4 { 42 | font-weight: 500; 43 | font-size: 14px; 44 | color: var(--gray-light); 45 | } 46 | 47 | /* Layout */ 48 | .sr-root { 49 | display: flex; 50 | flex-direction: row; 51 | width: 100%; 52 | max-width: 980px; 53 | padding: 48px; 54 | align-content: center; 55 | justify-content: center; 56 | height: auto; 57 | min-height: 100vh; 58 | margin: 0 auto; 59 | } 60 | .sr-header { 61 | margin-bottom: 32px; 62 | } 63 | .sr-payment-summary { 64 | margin-bottom: 20px; 65 | } 66 | .sr-main, 67 | .sr-content { 68 | display: flex; 69 | flex-direction: column; 70 | justify-content: center; 71 | height: 100%; 72 | align-self: center; 73 | } 74 | .sr-main { 75 | width: var(--form-width); 76 | } 77 | .sr-content { 78 | padding-left: 48px; 79 | } 80 | .sr-header__logo { 81 | background-image: var(--logo-image); 82 | height: 24px; 83 | background-size: contain; 84 | background-repeat: no-repeat; 85 | width: 100%; 86 | } 87 | .sr-legal-text { 88 | color: var(--gray-light); 89 | text-align: center; 90 | font-size: 13px; 91 | line-height: 17px; 92 | margin-top: 12px; 93 | } 94 | .sr-field-error { 95 | color: var(--error-color); 96 | text-align: left; 97 | font-size: 13px; 98 | line-height: 17px; 99 | margin-top: 12px; 100 | } 101 | 102 | /* Form */ 103 | .sr-form-row { 104 | margin: 16px 0; 105 | } 106 | label { 107 | font-size: 13px; 108 | font-weight: 500; 109 | margin-bottom: 8px; 110 | display: inline-block; 111 | } 112 | 113 | /* Inputs */ 114 | .sr-input, 115 | .sr-select, 116 | input[type="text"] { 117 | border: 1px solid var(--gray-border); 118 | border-radius: var(--radius); 119 | padding: 5px 12px; 120 | height: 44px; 121 | width: 100%; 122 | transition: box-shadow 0.2s ease; 123 | background: white; 124 | -moz-appearance: none; 125 | -webkit-appearance: none; 126 | appearance: none; 127 | color: #32325d; 128 | } 129 | .sr-input:focus, 130 | input[type="text"]:focus, 131 | button:focus, 132 | .focused { 133 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 134 | 0 0 0 4px rgba(50, 151, 211, 0.3); 135 | outline: none; 136 | z-index: 9; 137 | } 138 | .sr-input::placeholder, 139 | input[type="text"]::placeholder { 140 | color: var(--gray-light); 141 | } 142 | 143 | /* Checkbox */ 144 | .sr-checkbox-label { 145 | position: relative; 146 | cursor: pointer; 147 | } 148 | 149 | .sr-checkbox-label input { 150 | opacity: 0; 151 | margin-right: 6px; 152 | } 153 | 154 | .sr-checkbox-label .sr-checkbox-check { 155 | position: absolute; 156 | left: 0; 157 | height: 16px; 158 | width: 16px; 159 | background-color: white; 160 | border: 1px solid var(--gray-border); 161 | border-radius: 4px; 162 | transition: all 0.2s ease; 163 | } 164 | 165 | .sr-checkbox-label input:focus ~ .sr-checkbox-check { 166 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 167 | 0 0 0 4px rgba(50, 151, 211, 0.3); 168 | outline: none; 169 | } 170 | 171 | .sr-checkbox-label input:checked ~ .sr-checkbox-check { 172 | background-color: var(--accent-color); 173 | background-repeat: no-repeat; 174 | background-size: 16px; 175 | background-position: -1px -1px; 176 | } 177 | 178 | /* Select */ 179 | .sr-select { 180 | display: block; 181 | height: 44px; 182 | margin: 0; 183 | background-repeat: no-repeat, repeat; 184 | background-position: right 12px top 50%, 0 0; 185 | background-size: 0.65em auto, 100%; 186 | } 187 | .sr-select:after { 188 | } 189 | .sr-select::-ms-expand { 190 | display: none; 191 | } 192 | .sr-select:hover { 193 | cursor: pointer; 194 | } 195 | .sr-select:focus { 196 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 197 | 0 0 0 4px rgba(50, 151, 211, 0.3); 198 | outline: none; 199 | } 200 | .sr-select option { 201 | font-weight: 400; 202 | } 203 | .sr-select:invalid { 204 | color: var(--gray-light); 205 | background-opacity: 0.4; 206 | } 207 | 208 | /* Combo inputs */ 209 | .sr-combo-inputs { 210 | display: flex; 211 | flex-direction: column; 212 | } 213 | .sr-combo-inputs input, 214 | .sr-combo-inputs .sr-select { 215 | border-radius: 0; 216 | border-bottom: 0; 217 | } 218 | .sr-combo-inputs > input:first-child, 219 | .sr-combo-inputs > .sr-select:first-child { 220 | border-radius: var(--radius) var(--radius) 0 0; 221 | } 222 | .sr-combo-inputs > input:last-child, 223 | .sr-combo-inputs > .sr-select:last-child { 224 | border-radius: 0 0 var(--radius) var(--radius); 225 | border-bottom: 1px solid var(--gray-border); 226 | } 227 | .sr-combo-inputs > .sr-combo-inputs-row:last-child input:first-child { 228 | border-radius: 0 0 0 var(--radius); 229 | border-bottom: 1px solid var(--gray-border); 230 | } 231 | .sr-combo-inputs > .sr-combo-inputs-row:last-child input:last-child { 232 | border-radius: 0 0 var(--radius) 0; 233 | border-bottom: 1px solid var(--gray-border); 234 | } 235 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:first-child { 236 | border-radius: var(--radius) 0 0 0; 237 | } 238 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:last-child { 239 | border-radius: 0 var(--radius) 0 0; 240 | } 241 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:only-child { 242 | border-radius: var(--radius) var(--radius) 0 0; 243 | } 244 | .sr-combo-inputs-row { 245 | width: 100%; 246 | display: flex; 247 | } 248 | 249 | .sr-combo-inputs-row > input { 250 | width: 100%; 251 | border-radius: 0; 252 | } 253 | 254 | .sr-combo-inputs-row > input:first-child:not(:only-child) { 255 | border-right: 0; 256 | } 257 | 258 | .sr-combo-inputs-row:not(:first-of-type) .sr-input { 259 | border-radius: 0 0 var(--radius) var(--radius); 260 | } 261 | 262 | /* Buttons and links */ 263 | button { 264 | background: var(--accent-color); 265 | border-radius: var(--radius); 266 | color: white; 267 | border: 0; 268 | padding: 12px 16px; 269 | margin-top: 16px; 270 | font-weight: 600; 271 | cursor: pointer; 272 | transition: all 0.2s ease; 273 | display: block; 274 | } 275 | button:hover { 276 | filter: contrast(115%); 277 | } 278 | button:active { 279 | transform: translateY(0px) scale(0.98); 280 | filter: brightness(0.9); 281 | } 282 | button:disabled { 283 | opacity: 0.5; 284 | cursor: none; 285 | } 286 | 287 | button, 288 | .fullwidth { 289 | width: 100%; 290 | } 291 | 292 | a { 293 | color: var(--accent-color); 294 | text-decoration: none; 295 | transition: all 0.2s ease; 296 | } 297 | 298 | a:hover { 299 | filter: brightness(0.8); 300 | } 301 | 302 | a:active { 303 | filter: brightness(0.5); 304 | } 305 | 306 | /* Code block */ 307 | .sr-callout { 308 | background: var(--gray-offset); 309 | padding: 12px; 310 | border-radius: var(--radius); 311 | max-height: 200px; 312 | overflow: auto; 313 | } 314 | code, 315 | pre { 316 | font-family: "SF Mono", "IBM Plex Mono", "Menlo", monospace; 317 | font-size: 12px; 318 | } 319 | 320 | /* Stripe Element placeholder */ 321 | .sr-card-element { 322 | padding-top: 12px; 323 | } 324 | 325 | /* Responsiveness */ 326 | @media (max-width: 720px) { 327 | .sr-root { 328 | flex-direction: column; 329 | justify-content: flex-start; 330 | padding: 48px 20px; 331 | min-width: 320px; 332 | } 333 | 334 | .sr-header__logo { 335 | background-position: center; 336 | } 337 | 338 | .sr-payment-summary { 339 | text-align: center; 340 | } 341 | 342 | .sr-content { 343 | display: none; 344 | } 345 | 346 | .sr-main { 347 | width: 100%; 348 | } 349 | } 350 | 351 | /* todo: spinner/processing state, errors, animations */ 352 | 353 | .spinner, 354 | .spinner:before, 355 | .spinner:after { 356 | border-radius: 50%; 357 | } 358 | .spinner { 359 | color: #ffffff; 360 | font-size: 22px; 361 | text-indent: -99999px; 362 | margin: 0px auto; 363 | position: relative; 364 | width: 20px; 365 | height: 20px; 366 | box-shadow: inset 0 0 0 2px; 367 | -webkit-transform: translateZ(0); 368 | -ms-transform: translateZ(0); 369 | transform: translateZ(0); 370 | } 371 | .spinner:before, 372 | .spinner:after { 373 | position: absolute; 374 | content: ""; 375 | } 376 | .spinner:before { 377 | width: 10.4px; 378 | height: 20.4px; 379 | background: var(--accent-color); 380 | border-radius: 20.4px 0 0 20.4px; 381 | top: -0.2px; 382 | left: -0.2px; 383 | -webkit-transform-origin: 10.4px 10.2px; 384 | transform-origin: 10.4px 10.2px; 385 | -webkit-animation: loading 2s infinite ease 1.5s; 386 | animation: loading 2s infinite ease 1.5s; 387 | } 388 | .spinner:after { 389 | width: 10.4px; 390 | height: 10.2px; 391 | background: var(--accent-color); 392 | border-radius: 0 10.2px 10.2px 0; 393 | top: -0.1px; 394 | left: 10.2px; 395 | -webkit-transform-origin: 0px 10.2px; 396 | transform-origin: 0px 10.2px; 397 | -webkit-animation: loading 2s infinite ease; 398 | animation: loading 2s infinite ease; 399 | } 400 | @-webkit-keyframes loading { 401 | 0% { 402 | -webkit-transform: rotate(0deg); 403 | transform: rotate(0deg); 404 | } 405 | 100% { 406 | -webkit-transform: rotate(360deg); 407 | transform: rotate(360deg); 408 | } 409 | } 410 | @keyframes loading { 411 | 0% { 412 | -webkit-transform: rotate(0deg); 413 | transform: rotate(0deg); 414 | } 415 | 100% { 416 | -webkit-transform: rotate(360deg); 417 | transform: rotate(360deg); 418 | } 419 | } 420 | 421 | /* Animated form */ 422 | 423 | .sr-root { 424 | animation: 0.4s form-in; 425 | animation-fill-mode: both; 426 | animation-timing-function: ease; 427 | } 428 | 429 | .sr-payment-form .sr-form-row { 430 | animation: 0.4s field-in; 431 | animation-fill-mode: both; 432 | animation-timing-function: ease; 433 | transform-origin: 50% 0%; 434 | } 435 | 436 | /* need saas for loop :D */ 437 | .sr-payment-form .sr-form-row:nth-child(1) { 438 | animation-delay: 0; 439 | } 440 | .sr-payment-form .sr-form-row:nth-child(2) { 441 | animation-delay: 60ms; 442 | } 443 | .sr-payment-form .sr-form-row:nth-child(3) { 444 | animation-delay: 120ms; 445 | } 446 | .sr-payment-form .sr-form-row:nth-child(4) { 447 | animation-delay: 180ms; 448 | } 449 | .sr-payment-form .sr-form-row:nth-child(5) { 450 | animation-delay: 240ms; 451 | } 452 | .sr-payment-form .sr-form-row:nth-child(6) { 453 | animation-delay: 300ms; 454 | } 455 | .hidden { 456 | display: none; 457 | } 458 | 459 | @keyframes field-in { 460 | 0% { 461 | opacity: 0; 462 | transform: translateY(8px) scale(0.95); 463 | } 464 | 100% { 465 | opacity: 1; 466 | transform: translateY(0px) scale(1); 467 | } 468 | } 469 | 470 | @keyframes form-in { 471 | 0% { 472 | opacity: 0; 473 | transform: scale(0.98); 474 | } 475 | 100% { 476 | opacity: 1; 477 | transform: scale(1); 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /client/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stripe Sample Direct charge 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Accept a payment with direct charges

17 |
18 | 19 | 22 | 23 | 26 | 27 | 40 | 41 | 66 |
67 |
68 | 69 | -------------------------------------------------------------------------------- /client/script.js: -------------------------------------------------------------------------------- 1 | var paymentForm = document.getElementById("payment-form"); 2 | var paymentIntentData = { 3 | // You might send a list of items the customer is purchasing so that you can compute 4 | // the price on the server. 5 | items: [{ id: "photo-subscription" }], 6 | currency: "usd" 7 | }; 8 | 9 | // Secret from the server, which we'll overwrite each time we create a new payment intent. 10 | var paymentIntentClientSecret = null; 11 | 12 | // Set up Stripe.js and Elements to use in checkout form 13 | var setupElements = function(data) { 14 | // Initialize Stripe with the same account that we created the PaymentIntent with. 15 | stripe = Stripe(data.publishableKey, { 16 | stripeAccount: paymentIntentData.account 17 | }); 18 | var elements = stripe.elements(); 19 | var style = { 20 | base: { 21 | color: "#32325d", 22 | fontFamily: '"Helvetica Neue", Helvetica, sans-serif', 23 | fontSmoothing: "antialiased", 24 | fontSize: "16px", 25 | "::placeholder": { 26 | color: "#aab7c4" 27 | } 28 | }, 29 | invalid: { 30 | color: "#fa755a", 31 | iconColor: "#fa755a" 32 | } 33 | }; 34 | 35 | var card = elements.create("card", { style: style }); 36 | card.mount("#card-element"); 37 | 38 | // Handle form submission. 39 | paymentForm.addEventListener("submit", function(event) { 40 | event.preventDefault(); 41 | // Initiate payment when the submit button is clicked 42 | pay(stripe, card, paymentIntentClientSecret); 43 | }); 44 | }; 45 | 46 | /* 47 | * Calls stripe.confirmCardPayment which creates a pop-up modal to 48 | * prompt the user to enter extra authentication details without leaving your page 49 | */ 50 | var pay = function(stripe, card, clientSecret) { 51 | changeLoadingState(true); 52 | 53 | // Initiate the payment. 54 | // If authentication is required, confirmCardPayment will automatically display a modal 55 | stripe 56 | .confirmCardPayment(clientSecret, { 57 | payment_method: { 58 | card: card 59 | } 60 | }) 61 | .then(function(result) { 62 | if (result.error) { 63 | // Show error to your customer 64 | showError(result.error.message); 65 | } else { 66 | // The payment has been processed! 67 | orderComplete(clientSecret); 68 | } 69 | }); 70 | }; 71 | 72 | const updatePaymentIntent = (account, shouldSetupElements = false) => { 73 | // Disable the button while we fetch the payment intent. 74 | paymentForm.querySelector("button").disabled = true; 75 | 76 | // The account will be used as the Stripe-Account header when creating the 77 | // PaymentIntent on the server side. 78 | paymentIntentData.account = account; 79 | 80 | fetch("/create-payment-intent", { 81 | method: "POST", 82 | headers: { 83 | "Content-Type": "application/json" 84 | }, 85 | body: JSON.stringify(paymentIntentData) 86 | }) 87 | .then(function(result) { 88 | return result.json(); 89 | }) 90 | .then(function(data) { 91 | paymentIntentClientSecret = data.clientSecret 92 | if (shouldSetupElements) { 93 | setupElements(data); 94 | } 95 | 96 | paymentForm.querySelector("button").disabled = false; 97 | }); 98 | } 99 | 100 | // When the selected account changes, create a new PaymentIntent on the server 101 | // side and update the front-end accordingly. 102 | var enabledAccounts = document.querySelector("#enabled-accounts-select") 103 | enabledAccounts.addEventListener("change", function(event) { 104 | updatePaymentIntent(enabledAccounts.value); 105 | }); 106 | 107 | /* ------- Post-payment helpers ------- */ 108 | 109 | /* Shows a success / error message when the payment is complete */ 110 | var orderComplete = function(clientSecret) { 111 | // Just for the purpose of the sample, show the PaymentIntent response object 112 | stripe.retrievePaymentIntent(clientSecret).then(function(result) { 113 | var paymentIntent = result.paymentIntent; 114 | var paymentIntentJson = JSON.stringify(paymentIntent, null, 2); 115 | 116 | document.querySelector(".sr-payment-form").classList.add("hidden"); 117 | document.querySelector("pre").textContent = paymentIntentJson; 118 | 119 | document.querySelector(".sr-result").classList.remove("hidden"); 120 | setTimeout(function() { 121 | document.querySelector(".sr-result").classList.add("expand"); 122 | }, 200); 123 | 124 | changeLoadingState(false); 125 | }); 126 | }; 127 | 128 | var showError = function(errorMsgText) { 129 | changeLoadingState(false); 130 | var errorMsg = document.querySelector(".sr-field-error"); 131 | errorMsg.textContent = errorMsgText; 132 | setTimeout(function() { 133 | errorMsg.textContent = ""; 134 | }, 4000); 135 | }; 136 | 137 | // Show a spinner on payment submission 138 | var changeLoadingState = function(isLoading) { 139 | if (isLoading) { 140 | document.querySelector("button").disabled = true; 141 | document.querySelector("#spinner").classList.remove("hidden"); 142 | document.querySelector("#button-text").classList.add("hidden"); 143 | } else { 144 | document.querySelector("button").disabled = false; 145 | document.querySelector("#spinner").classList.add("hidden"); 146 | document.querySelector("#button-text").classList.remove("hidden"); 147 | } 148 | }; 149 | 150 | /* ------- Account list ------- */ 151 | 152 | // Fetch 10 most recent accounts from the server. We'll display one of three states in the UI, depending on the 153 | // accounts list; (1) if you haven't created any accounts, we'll re-direct you to the onboarding guide, (2) if none of 154 | // of your accounts have charges enabled, we'll display instructions on how to finish the onboarding process, (3) 155 | // otherwise, we'll display a payment form, as a customer might see it. 156 | fetch("/recent-accounts", { 157 | method: "GET", 158 | headers: { 159 | "Content-Type": "application/json" 160 | } 161 | }) 162 | .then(response => response.json()) 163 | .then(data => { 164 | document.querySelector(".spinner").classList.add("hidden"); 165 | 166 | var accounts = data.accounts.data; 167 | 168 | // If there are no accounts, display a message pointing to an onboarding guide. 169 | if (!accounts.length) { 170 | document.querySelector("#no-accounts-section").classList.remove("hidden"); 171 | return; 172 | } 173 | 174 | var cardPaymentsAccounts = accounts.filter((acct) => !!acct?.capabilities?.card_payments); 175 | if (!cardPaymentsAccounts.length) { 176 | document.querySelector("#no-card-payments-section").classList.remove("hidden"); 177 | return; 178 | } 179 | 180 | var enabledAccounts = accounts.filter((acct) => acct?.capabilities?.card_payments === 'active'); 181 | 182 | // If no accounts are enabled, display instructions on how to enable an account. In an actual 183 | // application, you should only surface Express dashboard links to your connected account owners, 184 | // not to their customers. 185 | if (!enabledAccounts.length) { 186 | var expressAccounts = accounts.filter((acct) => acct.type == 'express'); 187 | var hasCustom = !!accounts.filter((acct) => acct.type == 'custom'); 188 | var hasStandard = !!accounts.filter((acct) => acct.type == 'standard'); 189 | 190 | var wrapper = document.querySelector("#disabled-accounts-section"); 191 | var input = document.querySelector("#disabled-accounts-select"); 192 | expressAccounts.forEach((acct) => { 193 | var element = document.createElement("option"); 194 | element.setAttribute("value", acct.id); 195 | element.innerHTML = acct.email || acct.id; 196 | input.appendChild(element) 197 | }); 198 | // Remove the hidden CSS class on one of the sections with instruction on how to finish onboarding 199 | // for a given account type. 200 | if (hasStandard) { 201 | wrapper.querySelector('.standard').classList.remove("hidden"); 202 | } 203 | else if (expressAccounts.length) { 204 | document.querySelector('#disabled-accounts-form').classList.remove("hidden"); 205 | wrapper.querySelector('.express').classList.remove("hidden"); 206 | } 207 | else if (hasCustom) { 208 | wrapper.querySelector('.custom').classList.remove("hidden"); 209 | } 210 | wrapper.classList.remove("hidden"); 211 | return; 212 | } 213 | 214 | // If at least one account is enabled, show the account selector and payment form. 215 | var wrapper = document.querySelector("#enabled-accounts-section"); 216 | var input = document.querySelector("#enabled-accounts-select"); 217 | enabledAccounts.forEach((acct) => { 218 | var element = document.createElement("option"); 219 | element.setAttribute("value", acct.id); 220 | element.innerHTML = acct.email || acct.id; 221 | input.appendChild(element) 222 | }); 223 | wrapper.classList.remove("hidden"); 224 | updatePaymentIntent(input.value, true /* shouldSetupElement */); 225 | return; 226 | }); 227 | 228 | 229 | /* ------- Express dashboard ------- */ 230 | 231 | // When no accounts are enabled, this sample provides a way to log in as 232 | // an Express account to finish the onboarding process. Here, we set up 233 | // the event handler to construct the Express dashboard link. 234 | expressDashboardForm = document.querySelector('#disabled-accounts-form'); 235 | expressDashboardForm.addEventListener( 236 | "submit", 237 | event => { 238 | event.preventDefault(); 239 | button = expressDashboardForm.querySelector('button'); 240 | button.setAttribute("disabled", "disabled"); 241 | button.textContent = "Opening..."; 242 | 243 | var url = new URL("/express-dashboard-link", document.baseURI); 244 | params = {account_id: document.querySelector("#disabled-accounts-select").value}; 245 | url.search = new URLSearchParams(params).toString(); 246 | 247 | fetch(url, { 248 | method: "GET", 249 | headers: { 250 | "Content-Type": "application/json" 251 | } 252 | }) 253 | .then(response => response.json()) 254 | .then(data => { 255 | if (data.url) { 256 | window.location = data.url; 257 | } else { 258 | elmButton.removeAttribute("disabled"); 259 | elmButton.textContent = ""; 260 | console.log("data", data); 261 | } 262 | }); 263 | }, 264 | false 265 | ); -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Running the server 2 | 3 | We included several RESTful server that each implement the same endpoints and logic. 4 | Pick the language you are most comfortable in and follow the instructions in the directory on how to run. 5 | 6 | # Supported languages 7 | 8 | * [JavaScript (Node)](node/README.md) 9 | * [Python (Flask)](python/README.md) 10 | * [Ruby (Sinatra)](ruby/README.md) 11 | * [PHP (Slim)](php/README.md) 12 | * [Java (Spark)](java/README.md) -------------------------------------------------------------------------------- /server/java/README.md: -------------------------------------------------------------------------------- 1 | # Name of sample 2 | 3 | ## Requirements 4 | 5 | - Maven 6 | - Java 7 | - [Configured .env file](../../README.md) 8 | 9 | 1. Build the jar 10 | 11 | ``` 12 | mvn package 13 | ``` 14 | 15 | 2. Run the packaged jar 16 | 17 | ``` 18 | java -cp target/connect-direct-charge-1.0.0-SNAPSHOT-jar-with-dependencies.jar com.stripe.sample.Server 19 | ``` 20 | 21 | 3. Go to `localhost:4242` in your browser to see the demo 22 | -------------------------------------------------------------------------------- /server/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.stripe.sample 7 | connect-direct-charge 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 12 | 13 | org.slf4j 14 | slf4j-simple 15 | 1.7.21 16 | 17 | 18 | com.sparkjava 19 | spark-core 20 | 2.8.0 21 | 22 | 23 | com.google.code.gson 24 | gson 25 | 2.9.1 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | 1.18.8 31 | provided 32 | 33 | 34 | com.stripe 35 | stripe-java 36 | 21.12.0 37 | 38 | 39 | io.github.cdimascio 40 | java-dotenv 41 | 5.1.1 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 2.3.2 50 | 51 | 1.8 52 | 1.8 53 | 54 | 55 | 56 | maven-assembly-plugin 57 | 58 | 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | 68 | jar-with-dependencies 69 | 70 | 71 | 72 | Server 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /server/java/src/main/java/com/stripe/sample/Server.java: -------------------------------------------------------------------------------- 1 | package com.stripe.sample; 2 | 3 | import java.nio.file.Paths; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import static spark.Spark.get; 9 | import static spark.Spark.post; 10 | import static spark.Spark.port; 11 | import static spark.Spark.staticFiles; 12 | 13 | import com.google.gson.Gson; 14 | import com.google.gson.JsonSyntaxException; 15 | import com.stripe.Stripe; 16 | import com.stripe.exception.*; 17 | 18 | import io.github.cdimascio.dotenv.Dotenv; 19 | 20 | import com.stripe.model.PaymentIntent; 21 | import com.stripe.model.Account; 22 | import com.stripe.model.AccountCollection; 23 | import com.stripe.model.LoginLink; 24 | import com.stripe.model.Event; 25 | import com.stripe.model.EventDataObjectDeserializer; 26 | import com.stripe.exception.SignatureVerificationException; 27 | import com.stripe.net.Webhook; 28 | import com.stripe.net.RequestOptions; 29 | 30 | public class Server { 31 | private static Gson gson = new Gson(); 32 | 33 | static class CreateResponse { 34 | private String publishableKey; 35 | private String clientSecret; 36 | 37 | public CreateResponse(String publishableKey, String clientSecret) { 38 | this.publishableKey = publishableKey; 39 | this.clientSecret = clientSecret; 40 | } 41 | } 42 | 43 | static class CreateParams { 44 | private Object[] items; 45 | private String account; 46 | private String currency; 47 | } 48 | 49 | static class AccountsResponse { 50 | private AccountCollection accounts; 51 | 52 | public AccountsResponse(AccountCollection accounts) { 53 | this.accounts = accounts; 54 | } 55 | } 56 | 57 | static class LoginLinkResponse { 58 | private String url; 59 | 60 | public LoginLinkResponse(String url) { 61 | this.url = url; 62 | } 63 | } 64 | 65 | private static int calculateOrderAmount(Object[] items) { 66 | // Replace this constant with a calculation of the order's amount 67 | // Calculate the order total on the server to prevent 68 | // people from directly manipulating the amount on the client 69 | return 1400; 70 | } 71 | 72 | private static int calculateApplicationFeeAmount(int amount) { 73 | // Take a 10% cut. 74 | return (int) (0.1 * amount); 75 | } 76 | 77 | public static void main(String[] args) { 78 | port(4242); 79 | Dotenv dotenv = Dotenv.load(); 80 | Stripe.apiKey = dotenv.get("STRIPE_SECRET_KEY"); 81 | staticFiles.externalLocation( 82 | Paths.get(Paths.get("").toAbsolutePath().toString(), dotenv.get("STATIC_DIR")).normalize().toString()); 83 | 84 | get("/", (request, response) -> { 85 | response.type("application/json"); 86 | 87 | Map responseData = new HashMap<>(); 88 | responseData.put("some_key", "some_value"); 89 | return gson.toJson(responseData); 90 | }); 91 | 92 | post("/create-payment-intent", (request, response) -> { 93 | response.type("application/json"); 94 | 95 | CreateParams data = gson.fromJson(request.body(), CreateParams.class); 96 | int amount = calculateOrderAmount(data.items); 97 | 98 | Map params = new HashMap<>(); 99 | params.put("amount", amount); 100 | params.put("currency", data.currency); 101 | params.put("application_fee_amount", calculateApplicationFeeAmount(amount)); 102 | 103 | RequestOptions requestOptions = RequestOptions.builder().setStripeAccount(data.account).build(); 104 | PaymentIntent paymentIntent = PaymentIntent.create(params, requestOptions); 105 | 106 | return gson.toJson(new CreateResponse( 107 | dotenv.get("STRIPE_PUBLISHABLE_KEY"), 108 | paymentIntent.getClientSecret() 109 | )); 110 | }); 111 | 112 | get("/recent-accounts", (request, response) -> { 113 | Map params = new HashMap<>(); 114 | params.put("limit", 10); 115 | AccountCollection accounts = Account.list(params); 116 | return gson.toJson(new AccountsResponse(accounts)); 117 | }); 118 | 119 | get("/express-dashboard-link", (request, response) -> { 120 | String accountId = request.queryParams("account_id"); 121 | Map params = new HashMap<>(); 122 | params.put("redirect_url", request.scheme() + "://" + request.host()); 123 | LoginLink link = LoginLink.createOnAccount(accountId, params, null); 124 | return gson.toJson(new LoginLinkResponse(link.getUrl())); 125 | }); 126 | 127 | post("/webhook", (request, response) -> { 128 | String payload = request.body(); 129 | String sigHeader = request.headers("Stripe-Signature"); 130 | 131 | // Uncomment and replace with a real secret. You can find your endpoint's 132 | // secret in your webhook settings. 133 | // String webhookSecret = "whsec_..." 134 | 135 | Event event = null; 136 | 137 | // Verify webhook signature and extract the event. 138 | // See https://stripe.com/docs/webhooks/signatures for more information. 139 | try { 140 | event = Webhook.constructEvent( 141 | payload, sigHeader, dotenv.get("STRIPE_WEBHOOK_SECRET") 142 | ); 143 | } catch (JsonSyntaxException e) { 144 | // Invalid payload. 145 | response.status(400); 146 | return ""; 147 | } catch (SignatureVerificationException e) { 148 | // Invalid Signature. 149 | response.status(400); 150 | return ""; 151 | } 152 | 153 | if ("payment_intent.succeeded".equals(event.getType())) { 154 | // Deserialize the nested object inside the event 155 | EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); 156 | PaymentIntent paymentIntent = null; 157 | if (dataObjectDeserializer.getObject().isPresent()) { 158 | paymentIntent = (PaymentIntent) dataObjectDeserializer.getObject().get(); 159 | String connectedAccountId = event.getAccount(); 160 | handleSuccessfulPaymentIntent(connectedAccountId, paymentIntent); 161 | } else { 162 | // Deserialization failed, probably due to an API version mismatch. 163 | // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for 164 | // instructions on how to handle this case, or return an error here. 165 | } 166 | } 167 | 168 | response.status(200); 169 | return ""; 170 | }); 171 | } 172 | 173 | private static void handleSuccessfulPaymentIntent(String connectedAccountId, PaymentIntent paymentIntent) { 174 | // Fulfill the purchase. 175 | System.out.println("Connected account ID: " + connectedAccountId); 176 | System.out.println("PaymentIntent ID: " + paymentIntent.getId()); 177 | } 178 | } -------------------------------------------------------------------------------- /server/node/README.md: -------------------------------------------------------------------------------- 1 | # Name of sample 2 | An [Express server](http://expressjs.com) implementation 3 | 4 | ## Requirements 5 | * Node v10+ 6 | * [Configured .env file](../../README.md) 7 | 8 | ## How to run 9 | 10 | 1. Install dependencies 11 | 12 | ``` 13 | npm install 14 | ``` 15 | 16 | 2. Run the application 17 | 18 | ``` 19 | npm start 20 | ``` 21 | 22 | 3. Go to `localhost:4242` to see the demo -------------------------------------------------------------------------------- /server/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-sample-connect-direct-charge", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "18.15.11", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", 10 | "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" 11 | }, 12 | "accepts": { 13 | "version": "1.3.8", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 15 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 16 | "requires": { 17 | "mime-types": "~2.1.34", 18 | "negotiator": "0.6.3" 19 | }, 20 | "dependencies": { 21 | "mime-db": { 22 | "version": "1.52.0", 23 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 24 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 25 | }, 26 | "mime-types": { 27 | "version": "2.1.35", 28 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 29 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 30 | "requires": { 31 | "mime-db": "1.52.0" 32 | } 33 | } 34 | } 35 | }, 36 | "array-flatten": { 37 | "version": "1.1.1", 38 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 39 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 40 | }, 41 | "body-parser": { 42 | "version": "1.20.0", 43 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 44 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 45 | "requires": { 46 | "bytes": "3.1.2", 47 | "content-type": "~1.0.4", 48 | "debug": "2.6.9", 49 | "depd": "2.0.0", 50 | "destroy": "1.2.0", 51 | "http-errors": "2.0.0", 52 | "iconv-lite": "0.4.24", 53 | "on-finished": "2.4.1", 54 | "qs": "6.10.3", 55 | "raw-body": "2.5.1", 56 | "type-is": "~1.6.18", 57 | "unpipe": "1.0.0" 58 | }, 59 | "dependencies": { 60 | "depd": { 61 | "version": "2.0.0", 62 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 63 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 64 | }, 65 | "destroy": { 66 | "version": "1.2.0", 67 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 68 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 69 | }, 70 | "http-errors": { 71 | "version": "2.0.0", 72 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 73 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 74 | "requires": { 75 | "depd": "2.0.0", 76 | "inherits": "2.0.4", 77 | "setprototypeof": "1.2.0", 78 | "statuses": "2.0.1", 79 | "toidentifier": "1.0.1" 80 | } 81 | }, 82 | "inherits": { 83 | "version": "2.0.4", 84 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 85 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 86 | }, 87 | "on-finished": { 88 | "version": "2.4.1", 89 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 90 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 91 | "requires": { 92 | "ee-first": "1.1.1" 93 | } 94 | }, 95 | "qs": { 96 | "version": "6.10.3", 97 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 98 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 99 | "requires": { 100 | "side-channel": "^1.0.4" 101 | } 102 | }, 103 | "setprototypeof": { 104 | "version": "1.2.0", 105 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 106 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 107 | }, 108 | "statuses": { 109 | "version": "2.0.1", 110 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 111 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 112 | }, 113 | "toidentifier": { 114 | "version": "1.0.1", 115 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 116 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 117 | } 118 | } 119 | }, 120 | "bytes": { 121 | "version": "3.1.2", 122 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 123 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 124 | }, 125 | "call-bind": { 126 | "version": "1.0.2", 127 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 128 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 129 | "requires": { 130 | "function-bind": "^1.1.1", 131 | "get-intrinsic": "^1.0.2" 132 | } 133 | }, 134 | "content-disposition": { 135 | "version": "0.5.4", 136 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 137 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 138 | "requires": { 139 | "safe-buffer": "5.2.1" 140 | } 141 | }, 142 | "content-type": { 143 | "version": "1.0.4", 144 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 145 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 146 | }, 147 | "cookie": { 148 | "version": "0.4.0", 149 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 150 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 151 | }, 152 | "cookie-signature": { 153 | "version": "1.0.6", 154 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 155 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 156 | }, 157 | "debug": { 158 | "version": "2.6.9", 159 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 160 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 161 | "requires": { 162 | "ms": "2.0.0" 163 | } 164 | }, 165 | "depd": { 166 | "version": "2.0.0", 167 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 168 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 169 | }, 170 | "destroy": { 171 | "version": "1.2.0", 172 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 173 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 174 | }, 175 | "dotenv": { 176 | "version": "16.0.2", 177 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", 178 | "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==" 179 | }, 180 | "ee-first": { 181 | "version": "1.1.1", 182 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 183 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 184 | }, 185 | "encodeurl": { 186 | "version": "1.0.2", 187 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 188 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 189 | }, 190 | "escape-html": { 191 | "version": "1.0.3", 192 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 193 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 194 | }, 195 | "etag": { 196 | "version": "1.8.1", 197 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 198 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 199 | }, 200 | "express": { 201 | "version": "4.18.1", 202 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", 203 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", 204 | "requires": { 205 | "accepts": "~1.3.8", 206 | "array-flatten": "1.1.1", 207 | "body-parser": "1.20.0", 208 | "content-disposition": "0.5.4", 209 | "content-type": "~1.0.4", 210 | "cookie": "0.5.0", 211 | "cookie-signature": "1.0.6", 212 | "debug": "2.6.9", 213 | "depd": "2.0.0", 214 | "encodeurl": "~1.0.2", 215 | "escape-html": "~1.0.3", 216 | "etag": "~1.8.1", 217 | "finalhandler": "1.2.0", 218 | "fresh": "0.5.2", 219 | "http-errors": "2.0.0", 220 | "merge-descriptors": "1.0.1", 221 | "methods": "~1.1.2", 222 | "on-finished": "2.4.1", 223 | "parseurl": "~1.3.3", 224 | "path-to-regexp": "0.1.7", 225 | "proxy-addr": "~2.0.7", 226 | "qs": "6.10.3", 227 | "range-parser": "~1.2.1", 228 | "safe-buffer": "5.2.1", 229 | "send": "0.18.0", 230 | "serve-static": "1.15.0", 231 | "setprototypeof": "1.2.0", 232 | "statuses": "2.0.1", 233 | "type-is": "~1.6.18", 234 | "utils-merge": "1.0.1", 235 | "vary": "~1.1.2" 236 | }, 237 | "dependencies": { 238 | "cookie": { 239 | "version": "0.5.0", 240 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 241 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 242 | } 243 | } 244 | }, 245 | "express-session": { 246 | "version": "1.17.0", 247 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", 248 | "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", 249 | "requires": { 250 | "cookie": "0.4.0", 251 | "cookie-signature": "1.0.6", 252 | "debug": "2.6.9", 253 | "depd": "~2.0.0", 254 | "on-headers": "~1.0.2", 255 | "parseurl": "~1.3.3", 256 | "safe-buffer": "5.2.0", 257 | "uid-safe": "~2.1.5" 258 | }, 259 | "dependencies": { 260 | "depd": { 261 | "version": "2.0.0", 262 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 263 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 264 | }, 265 | "safe-buffer": { 266 | "version": "5.2.0", 267 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 268 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 269 | } 270 | } 271 | }, 272 | "finalhandler": { 273 | "version": "1.2.0", 274 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 275 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 276 | "requires": { 277 | "debug": "2.6.9", 278 | "encodeurl": "~1.0.2", 279 | "escape-html": "~1.0.3", 280 | "on-finished": "2.4.1", 281 | "parseurl": "~1.3.3", 282 | "statuses": "2.0.1", 283 | "unpipe": "~1.0.0" 284 | } 285 | }, 286 | "forwarded": { 287 | "version": "0.2.0", 288 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 289 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 290 | }, 291 | "fresh": { 292 | "version": "0.5.2", 293 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 294 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 295 | }, 296 | "function-bind": { 297 | "version": "1.1.1", 298 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 299 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 300 | }, 301 | "get-intrinsic": { 302 | "version": "1.1.2", 303 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", 304 | "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", 305 | "requires": { 306 | "function-bind": "^1.1.1", 307 | "has": "^1.0.3", 308 | "has-symbols": "^1.0.3" 309 | } 310 | }, 311 | "has": { 312 | "version": "1.0.3", 313 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 314 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 315 | "requires": { 316 | "function-bind": "^1.1.1" 317 | } 318 | }, 319 | "has-symbols": { 320 | "version": "1.0.3", 321 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 322 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 323 | }, 324 | "http-errors": { 325 | "version": "2.0.0", 326 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 327 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 328 | "requires": { 329 | "depd": "2.0.0", 330 | "inherits": "2.0.4", 331 | "setprototypeof": "1.2.0", 332 | "statuses": "2.0.1", 333 | "toidentifier": "1.0.1" 334 | } 335 | }, 336 | "iconv-lite": { 337 | "version": "0.4.24", 338 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 339 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 340 | "requires": { 341 | "safer-buffer": ">= 2.1.2 < 3" 342 | } 343 | }, 344 | "inherits": { 345 | "version": "2.0.4", 346 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 347 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 348 | }, 349 | "ipaddr.js": { 350 | "version": "1.9.1", 351 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 352 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 353 | }, 354 | "media-typer": { 355 | "version": "0.3.0", 356 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 357 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 358 | }, 359 | "merge-descriptors": { 360 | "version": "1.0.1", 361 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 362 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 363 | }, 364 | "methods": { 365 | "version": "1.1.2", 366 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 367 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 368 | }, 369 | "mime": { 370 | "version": "1.6.0", 371 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 372 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 373 | }, 374 | "mime-db": { 375 | "version": "1.40.0", 376 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 377 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 378 | }, 379 | "mime-types": { 380 | "version": "2.1.24", 381 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 382 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 383 | "requires": { 384 | "mime-db": "1.40.0" 385 | } 386 | }, 387 | "ms": { 388 | "version": "2.0.0", 389 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 390 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 391 | }, 392 | "negotiator": { 393 | "version": "0.6.3", 394 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 395 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 396 | }, 397 | "object-inspect": { 398 | "version": "1.12.2", 399 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 400 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" 401 | }, 402 | "on-finished": { 403 | "version": "2.4.1", 404 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 405 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 406 | "requires": { 407 | "ee-first": "1.1.1" 408 | } 409 | }, 410 | "on-headers": { 411 | "version": "1.0.2", 412 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 413 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 414 | }, 415 | "parseurl": { 416 | "version": "1.3.3", 417 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 418 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 419 | }, 420 | "path-to-regexp": { 421 | "version": "0.1.7", 422 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 423 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 424 | }, 425 | "proxy-addr": { 426 | "version": "2.0.7", 427 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 428 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 429 | "requires": { 430 | "forwarded": "0.2.0", 431 | "ipaddr.js": "1.9.1" 432 | } 433 | }, 434 | "qs": { 435 | "version": "6.10.3", 436 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 437 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 438 | "requires": { 439 | "side-channel": "^1.0.4" 440 | } 441 | }, 442 | "random-bytes": { 443 | "version": "1.0.0", 444 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 445 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 446 | }, 447 | "range-parser": { 448 | "version": "1.2.1", 449 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 450 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 451 | }, 452 | "raw-body": { 453 | "version": "2.5.1", 454 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 455 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 456 | "requires": { 457 | "bytes": "3.1.2", 458 | "http-errors": "2.0.0", 459 | "iconv-lite": "0.4.24", 460 | "unpipe": "1.0.0" 461 | }, 462 | "dependencies": { 463 | "depd": { 464 | "version": "2.0.0", 465 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 466 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 467 | }, 468 | "http-errors": { 469 | "version": "2.0.0", 470 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 471 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 472 | "requires": { 473 | "depd": "2.0.0", 474 | "inherits": "2.0.4", 475 | "setprototypeof": "1.2.0", 476 | "statuses": "2.0.1", 477 | "toidentifier": "1.0.1" 478 | } 479 | }, 480 | "inherits": { 481 | "version": "2.0.4", 482 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 483 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 484 | }, 485 | "setprototypeof": { 486 | "version": "1.2.0", 487 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 488 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 489 | }, 490 | "statuses": { 491 | "version": "2.0.1", 492 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 493 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 494 | }, 495 | "toidentifier": { 496 | "version": "1.0.1", 497 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 498 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 499 | } 500 | } 501 | }, 502 | "safe-buffer": { 503 | "version": "5.2.1", 504 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 505 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 506 | }, 507 | "safer-buffer": { 508 | "version": "2.1.2", 509 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 510 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 511 | }, 512 | "send": { 513 | "version": "0.18.0", 514 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 515 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 516 | "requires": { 517 | "debug": "2.6.9", 518 | "depd": "2.0.0", 519 | "destroy": "1.2.0", 520 | "encodeurl": "~1.0.2", 521 | "escape-html": "~1.0.3", 522 | "etag": "~1.8.1", 523 | "fresh": "0.5.2", 524 | "http-errors": "2.0.0", 525 | "mime": "1.6.0", 526 | "ms": "2.1.3", 527 | "on-finished": "2.4.1", 528 | "range-parser": "~1.2.1", 529 | "statuses": "2.0.1" 530 | }, 531 | "dependencies": { 532 | "ms": { 533 | "version": "2.1.3", 534 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 535 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 536 | } 537 | } 538 | }, 539 | "serve-static": { 540 | "version": "1.15.0", 541 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 542 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 543 | "requires": { 544 | "encodeurl": "~1.0.2", 545 | "escape-html": "~1.0.3", 546 | "parseurl": "~1.3.3", 547 | "send": "0.18.0" 548 | } 549 | }, 550 | "setprototypeof": { 551 | "version": "1.2.0", 552 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 553 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 554 | }, 555 | "side-channel": { 556 | "version": "1.0.4", 557 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 558 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 559 | "requires": { 560 | "call-bind": "^1.0.0", 561 | "get-intrinsic": "^1.0.2", 562 | "object-inspect": "^1.9.0" 563 | } 564 | }, 565 | "statuses": { 566 | "version": "2.0.1", 567 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 568 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 569 | }, 570 | "stripe": { 571 | "version": "11.17.0", 572 | "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.17.0.tgz", 573 | "integrity": "sha512-nDH5afq2J5x8cp0b3Uf0h4U7mLLntDLXA91/4cg+LRhMOrDc2BI6xOGjYpI2uwRG80PU16pqhZh9oVupvCG/pQ==", 574 | "requires": { 575 | "@types/node": ">=8.1.0", 576 | "qs": "^6.11.0" 577 | }, 578 | "dependencies": { 579 | "qs": { 580 | "version": "6.11.1", 581 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", 582 | "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", 583 | "requires": { 584 | "side-channel": "^1.0.4" 585 | } 586 | } 587 | } 588 | }, 589 | "toidentifier": { 590 | "version": "1.0.1", 591 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 592 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 593 | }, 594 | "type-is": { 595 | "version": "1.6.18", 596 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 597 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 598 | "requires": { 599 | "media-typer": "0.3.0", 600 | "mime-types": "~2.1.24" 601 | } 602 | }, 603 | "uid-safe": { 604 | "version": "2.1.5", 605 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 606 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 607 | "requires": { 608 | "random-bytes": "~1.0.0" 609 | } 610 | }, 611 | "unpipe": { 612 | "version": "1.0.0", 613 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 614 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 615 | }, 616 | "utils-merge": { 617 | "version": "1.0.1", 618 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 619 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 620 | }, 621 | "vary": { 622 | "version": "1.1.2", 623 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 624 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 625 | } 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /server/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-sample-connect-direct-charge", 3 | "version": "1.0.0", 4 | "description": "Accept a payment with direct charges.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "stripe-samples", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.20.0", 14 | "dotenv": "^16.0.2", 15 | "express": "^4.18.1", 16 | "express-session": "^1.17.0", 17 | "stripe": "^11.17.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/node/server.js: -------------------------------------------------------------------------------- 1 | // Replace if using a different env file or config 2 | require("dotenv").config(); 3 | const bodyParser = require("body-parser"); 4 | const express = require("express"); 5 | const { resolve } = require("path"); 6 | const session = require("express-session"); 7 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); 8 | 9 | const app = express(); 10 | const port = process.env.PORT || 4242; 11 | 12 | app.use(express.static(process.env.STATIC_DIR)); 13 | app.use(session({ 14 | secret: "Set this to a random string that is kept secure", 15 | resave: false, 16 | saveUninitialized: true, 17 | })) 18 | 19 | // Use JSON parser for all non-webhook routes 20 | app.use((req, res, next) => { 21 | if (req.originalUrl === "/webhook") { 22 | next(); 23 | } else { 24 | bodyParser.json()(req, res, next); 25 | } 26 | }); 27 | 28 | app.get("/", (req, res) => { 29 | const path = resolve(process.env.STATIC_DIR + "/index.html"); 30 | res.sendFile(path); 31 | }); 32 | 33 | const calculateOrderAmount = (items) => { 34 | // Replace this constant with a calculation of the order's amount 35 | // Calculate the order total on the server to prevent 36 | // people from directly manipulating the amount on the client 37 | return 1400 38 | } 39 | 40 | // Take a 10% cut. 41 | const calculateApplicationFeeAmount = (amount) => .1 * amount; 42 | 43 | app.post('/create-payment-intent', async (req, res) => { 44 | 45 | const data = req.body; 46 | const amount = calculateOrderAmount(data.items) 47 | 48 | await stripe.paymentIntents.create({ 49 | amount: amount, 50 | currency: data.currency, 51 | application_fee_amount: calculateApplicationFeeAmount(amount), 52 | }, { 53 | stripe_account: data.account, 54 | }).then(function(paymentIntent) { 55 | try { 56 | return res.send({ 57 | publishableKey: process.env.STRIPE_PUBLISHABLE_KEY, 58 | clientSecret: paymentIntent.client_secret 59 | }); 60 | } catch (err) { 61 | return res.status(500).send({ 62 | error: err.message 63 | }); 64 | } 65 | }); 66 | }); 67 | 68 | app.get("/recent-accounts", async (_, res) => { 69 | stripe.accounts.list( 70 | {limit: 10}, 71 | function(err, accounts) { 72 | if (err) { 73 | return res.status(500).send({ 74 | error: err.message 75 | }); 76 | } 77 | return res.send({accounts}); 78 | } 79 | ); 80 | }); 81 | 82 | app.get("/express-dashboard-link", async (req, res) => { 83 | stripe.accounts.createLoginLink( 84 | req.query.account_id, 85 | {redirect_url: req.headers.referer}, 86 | function(err, loginLink) { 87 | if (err) { 88 | return res.status(500).send({ 89 | error: err.message 90 | }); 91 | } 92 | return res.send({url: loginLink.url}); 93 | } 94 | ); 95 | }); 96 | 97 | app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => { 98 | const sig = req.headers['stripe-signature']; 99 | 100 | let event; 101 | 102 | // Verify webhook signature and extract the event. 103 | // See https://stripe.com/docs/webhooks/signatures for more information. 104 | try { 105 | event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); 106 | } catch (err) { 107 | return response.status(400).send(`Webhook Error: ${err.message}`); 108 | } 109 | 110 | if (event.type === 'payment_intent.succeeded') { 111 | const paymentIntent = event.data.object; 112 | const connectedAccountId = event.account; 113 | handleSuccessfulPaymentIntent(connectedAccountId, paymentIntent); 114 | } 115 | 116 | res.json({received: true}); 117 | }); 118 | 119 | const handleSuccessfulPaymentIntent = (connectedAccountId, paymentIntent) => { 120 | // Fulfill the purchase. 121 | console.log('Connected account ID: ' + connectedAccountId); 122 | console.log('PaymentIntent: ' + JSON.stringify(paymentIntent)); 123 | } 124 | 125 | app.listen(port, () => console.log(`Node server listening on port ${port}!`)); 126 | -------------------------------------------------------------------------------- /server/php/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteRule ^ index.php [QSA,L] 6 | -------------------------------------------------------------------------------- /server/php/README.md: -------------------------------------------------------------------------------- 1 | # Connect direct charges 2 | 3 | ## Requirements 4 | * PHP >= 7.1.3 5 | * Composer 6 | * [Slim](http://www.slimframework.com/) 7 | * [Configured .env file](../../README.md) 8 | 9 | ## How to run 10 | 11 | 1. Install dependencies 12 | 13 | ``` 14 | composer install 15 | ``` 16 | 17 | 2. Run the application 18 | 19 | ``` 20 | composer start 21 | ``` 22 | 23 | 3. Go to `localhost:4242` in your browser to see the demo -------------------------------------------------------------------------------- /server/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "slim/slim": "^3.12", 4 | "vlucas/phpdotenv": "^3.4", 5 | "stripe/stripe-php": "^10.0", 6 | "monolog/monolog": "^1.17" 7 | }, 8 | "scripts": { 9 | "start": "php -S localhost:4242 index.php" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/php/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "bfa2002614d13d24890d3c1363bf4f5d", 8 | "packages": [ 9 | { 10 | "name": "container-interop/container-interop", 11 | "version": "1.2.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/container-interop/container-interop.git", 15 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", 20 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "psr/container": "^1.0" 25 | }, 26 | "type": "library", 27 | "autoload": { 28 | "psr-4": { 29 | "Interop\\Container\\": "src/Interop/Container/" 30 | } 31 | }, 32 | "notification-url": "https://packagist.org/downloads/", 33 | "license": [ 34 | "MIT" 35 | ], 36 | "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", 37 | "homepage": "https://github.com/container-interop/container-interop", 38 | "time": "2017-02-14T19:40:03+00:00" 39 | }, 40 | { 41 | "name": "monolog/monolog", 42 | "version": "1.24.0", 43 | "source": { 44 | "type": "git", 45 | "url": "https://github.com/Seldaek/monolog.git", 46 | "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" 47 | }, 48 | "dist": { 49 | "type": "zip", 50 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", 51 | "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", 52 | "shasum": "" 53 | }, 54 | "require": { 55 | "php": ">=5.3.0", 56 | "psr/log": "~1.0" 57 | }, 58 | "provide": { 59 | "psr/log-implementation": "1.0.0" 60 | }, 61 | "require-dev": { 62 | "aws/aws-sdk-php": "^2.4.9 || ^3.0", 63 | "doctrine/couchdb": "~1.0@dev", 64 | "graylog2/gelf-php": "~1.0", 65 | "jakub-onderka/php-parallel-lint": "0.9", 66 | "php-amqplib/php-amqplib": "~2.4", 67 | "php-console/php-console": "^3.1.3", 68 | "phpunit/phpunit": "~4.5", 69 | "phpunit/phpunit-mock-objects": "2.3.0", 70 | "ruflin/elastica": ">=0.90 <3.0", 71 | "sentry/sentry": "^0.13", 72 | "swiftmailer/swiftmailer": "^5.3|^6.0" 73 | }, 74 | "suggest": { 75 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 76 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server", 77 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 78 | "ext-mongo": "Allow sending log messages to a MongoDB server", 79 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 80 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", 81 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", 82 | "php-console/php-console": "Allow sending log messages to Google Chrome", 83 | "rollbar/rollbar": "Allow sending log messages to Rollbar", 84 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 85 | "sentry/sentry": "Allow sending log messages to a Sentry server" 86 | }, 87 | "type": "library", 88 | "extra": { 89 | "branch-alias": { 90 | "dev-master": "2.0.x-dev" 91 | } 92 | }, 93 | "autoload": { 94 | "psr-4": { 95 | "Monolog\\": "src/Monolog" 96 | } 97 | }, 98 | "notification-url": "https://packagist.org/downloads/", 99 | "license": [ 100 | "MIT" 101 | ], 102 | "authors": [ 103 | { 104 | "name": "Jordi Boggiano", 105 | "email": "j.boggiano@seld.be", 106 | "homepage": "http://seld.be" 107 | } 108 | ], 109 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services", 110 | "homepage": "http://github.com/Seldaek/monolog", 111 | "keywords": [ 112 | "log", 113 | "logging", 114 | "psr-3" 115 | ], 116 | "time": "2018-11-05T09:00:11+00:00" 117 | }, 118 | { 119 | "name": "nikic/fast-route", 120 | "version": "v1.3.0", 121 | "source": { 122 | "type": "git", 123 | "url": "https://github.com/nikic/FastRoute.git", 124 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812" 125 | }, 126 | "dist": { 127 | "type": "zip", 128 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", 129 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812", 130 | "shasum": "" 131 | }, 132 | "require": { 133 | "php": ">=5.4.0" 134 | }, 135 | "require-dev": { 136 | "phpunit/phpunit": "^4.8.35|~5.7" 137 | }, 138 | "type": "library", 139 | "autoload": { 140 | "psr-4": { 141 | "FastRoute\\": "src/" 142 | }, 143 | "files": [ 144 | "src/functions.php" 145 | ] 146 | }, 147 | "notification-url": "https://packagist.org/downloads/", 148 | "license": [ 149 | "BSD-3-Clause" 150 | ], 151 | "authors": [ 152 | { 153 | "name": "Nikita Popov", 154 | "email": "nikic@php.net" 155 | } 156 | ], 157 | "description": "Fast request router for PHP", 158 | "keywords": [ 159 | "router", 160 | "routing" 161 | ], 162 | "time": "2018-02-13T20:26:39+00:00" 163 | }, 164 | { 165 | "name": "phpoption/phpoption", 166 | "version": "1.5.0", 167 | "source": { 168 | "type": "git", 169 | "url": "https://github.com/schmittjoh/php-option.git", 170 | "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" 171 | }, 172 | "dist": { 173 | "type": "zip", 174 | "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", 175 | "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", 176 | "shasum": "" 177 | }, 178 | "require": { 179 | "php": ">=5.3.0" 180 | }, 181 | "require-dev": { 182 | "phpunit/phpunit": "4.7.*" 183 | }, 184 | "type": "library", 185 | "extra": { 186 | "branch-alias": { 187 | "dev-master": "1.3-dev" 188 | } 189 | }, 190 | "autoload": { 191 | "psr-0": { 192 | "PhpOption\\": "src/" 193 | } 194 | }, 195 | "notification-url": "https://packagist.org/downloads/", 196 | "license": [ 197 | "Apache2" 198 | ], 199 | "authors": [ 200 | { 201 | "name": "Johannes M. Schmitt", 202 | "email": "schmittjoh@gmail.com" 203 | } 204 | ], 205 | "description": "Option Type for PHP", 206 | "keywords": [ 207 | "language", 208 | "option", 209 | "php", 210 | "type" 211 | ], 212 | "time": "2015-07-25T16:39:46+00:00" 213 | }, 214 | { 215 | "name": "pimple/pimple", 216 | "version": "v3.2.3", 217 | "source": { 218 | "type": "git", 219 | "url": "https://github.com/silexphp/Pimple.git", 220 | "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" 221 | }, 222 | "dist": { 223 | "type": "zip", 224 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", 225 | "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", 226 | "shasum": "" 227 | }, 228 | "require": { 229 | "php": ">=5.3.0", 230 | "psr/container": "^1.0" 231 | }, 232 | "require-dev": { 233 | "symfony/phpunit-bridge": "^3.2" 234 | }, 235 | "type": "library", 236 | "extra": { 237 | "branch-alias": { 238 | "dev-master": "3.2.x-dev" 239 | } 240 | }, 241 | "autoload": { 242 | "psr-0": { 243 | "Pimple": "src/" 244 | } 245 | }, 246 | "notification-url": "https://packagist.org/downloads/", 247 | "license": [ 248 | "MIT" 249 | ], 250 | "authors": [ 251 | { 252 | "name": "Fabien Potencier", 253 | "email": "fabien@symfony.com" 254 | } 255 | ], 256 | "description": "Pimple, a simple Dependency Injection Container", 257 | "homepage": "http://pimple.sensiolabs.org", 258 | "keywords": [ 259 | "container", 260 | "dependency injection" 261 | ], 262 | "time": "2018-01-21T07:42:36+00:00" 263 | }, 264 | { 265 | "name": "psr/container", 266 | "version": "1.0.0", 267 | "source": { 268 | "type": "git", 269 | "url": "https://github.com/php-fig/container.git", 270 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 271 | }, 272 | "dist": { 273 | "type": "zip", 274 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 275 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 276 | "shasum": "" 277 | }, 278 | "require": { 279 | "php": ">=5.3.0" 280 | }, 281 | "type": "library", 282 | "extra": { 283 | "branch-alias": { 284 | "dev-master": "1.0.x-dev" 285 | } 286 | }, 287 | "autoload": { 288 | "psr-4": { 289 | "Psr\\Container\\": "src/" 290 | } 291 | }, 292 | "notification-url": "https://packagist.org/downloads/", 293 | "license": [ 294 | "MIT" 295 | ], 296 | "authors": [ 297 | { 298 | "name": "PHP-FIG", 299 | "homepage": "http://www.php-fig.org/" 300 | } 301 | ], 302 | "description": "Common Container Interface (PHP FIG PSR-11)", 303 | "homepage": "https://github.com/php-fig/container", 304 | "keywords": [ 305 | "PSR-11", 306 | "container", 307 | "container-interface", 308 | "container-interop", 309 | "psr" 310 | ], 311 | "time": "2017-02-14T16:28:37+00:00" 312 | }, 313 | { 314 | "name": "psr/http-message", 315 | "version": "1.0.1", 316 | "source": { 317 | "type": "git", 318 | "url": "https://github.com/php-fig/http-message.git", 319 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 320 | }, 321 | "dist": { 322 | "type": "zip", 323 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 324 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 325 | "shasum": "" 326 | }, 327 | "require": { 328 | "php": ">=5.3.0" 329 | }, 330 | "type": "library", 331 | "extra": { 332 | "branch-alias": { 333 | "dev-master": "1.0.x-dev" 334 | } 335 | }, 336 | "autoload": { 337 | "psr-4": { 338 | "Psr\\Http\\Message\\": "src/" 339 | } 340 | }, 341 | "notification-url": "https://packagist.org/downloads/", 342 | "license": [ 343 | "MIT" 344 | ], 345 | "authors": [ 346 | { 347 | "name": "PHP-FIG", 348 | "homepage": "http://www.php-fig.org/" 349 | } 350 | ], 351 | "description": "Common interface for HTTP messages", 352 | "homepage": "https://github.com/php-fig/http-message", 353 | "keywords": [ 354 | "http", 355 | "http-message", 356 | "psr", 357 | "psr-7", 358 | "request", 359 | "response" 360 | ], 361 | "time": "2016-08-06T14:39:51+00:00" 362 | }, 363 | { 364 | "name": "psr/log", 365 | "version": "1.1.0", 366 | "source": { 367 | "type": "git", 368 | "url": "https://github.com/php-fig/log.git", 369 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" 370 | }, 371 | "dist": { 372 | "type": "zip", 373 | "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 374 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 375 | "shasum": "" 376 | }, 377 | "require": { 378 | "php": ">=5.3.0" 379 | }, 380 | "type": "library", 381 | "extra": { 382 | "branch-alias": { 383 | "dev-master": "1.0.x-dev" 384 | } 385 | }, 386 | "autoload": { 387 | "psr-4": { 388 | "Psr\\Log\\": "Psr/Log/" 389 | } 390 | }, 391 | "notification-url": "https://packagist.org/downloads/", 392 | "license": [ 393 | "MIT" 394 | ], 395 | "authors": [ 396 | { 397 | "name": "PHP-FIG", 398 | "homepage": "http://www.php-fig.org/" 399 | } 400 | ], 401 | "description": "Common interface for logging libraries", 402 | "homepage": "https://github.com/php-fig/log", 403 | "keywords": [ 404 | "log", 405 | "psr", 406 | "psr-3" 407 | ], 408 | "time": "2018-11-20T15:27:04+00:00" 409 | }, 410 | { 411 | "name": "slim/slim", 412 | "version": "3.12.1", 413 | "source": { 414 | "type": "git", 415 | "url": "https://github.com/slimphp/Slim.git", 416 | "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b" 417 | }, 418 | "dist": { 419 | "type": "zip", 420 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/eaee12ef8d0750db62b8c548016d82fb33addb6b", 421 | "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b", 422 | "shasum": "" 423 | }, 424 | "require": { 425 | "container-interop/container-interop": "^1.2", 426 | "nikic/fast-route": "^1.0", 427 | "php": ">=5.5.0", 428 | "pimple/pimple": "^3.0", 429 | "psr/container": "^1.0", 430 | "psr/http-message": "^1.0" 431 | }, 432 | "provide": { 433 | "psr/http-message-implementation": "1.0" 434 | }, 435 | "require-dev": { 436 | "phpunit/phpunit": "^4.0", 437 | "squizlabs/php_codesniffer": "^2.5" 438 | }, 439 | "type": "library", 440 | "autoload": { 441 | "psr-4": { 442 | "Slim\\": "Slim" 443 | } 444 | }, 445 | "notification-url": "https://packagist.org/downloads/", 446 | "license": [ 447 | "MIT" 448 | ], 449 | "authors": [ 450 | { 451 | "name": "Rob Allen", 452 | "email": "rob@akrabat.com", 453 | "homepage": "http://akrabat.com" 454 | }, 455 | { 456 | "name": "Josh Lockhart", 457 | "email": "hello@joshlockhart.com", 458 | "homepage": "https://joshlockhart.com" 459 | }, 460 | { 461 | "name": "Gabriel Manricks", 462 | "email": "gmanricks@me.com", 463 | "homepage": "http://gabrielmanricks.com" 464 | }, 465 | { 466 | "name": "Andrew Smith", 467 | "email": "a.smith@silentworks.co.uk", 468 | "homepage": "http://silentworks.co.uk" 469 | } 470 | ], 471 | "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", 472 | "homepage": "https://slimframework.com", 473 | "keywords": [ 474 | "api", 475 | "framework", 476 | "micro", 477 | "router" 478 | ], 479 | "time": "2019-04-16T16:47:29+00:00" 480 | }, 481 | { 482 | "name": "stripe/stripe-php", 483 | "version": "v10.20.0", 484 | "source": { 485 | "type": "git", 486 | "url": "https://github.com/stripe/stripe-php.git", 487 | "reference": "6be8827ee0d6466bf48e2b6997a8feac596405f9" 488 | }, 489 | "dist": { 490 | "type": "zip", 491 | "url": "https://api.github.com/repos/stripe/stripe-php/zipball/6be8827ee0d6466bf48e2b6997a8feac596405f9", 492 | "reference": "6be8827ee0d6466bf48e2b6997a8feac596405f9", 493 | "shasum": "" 494 | }, 495 | "require": { 496 | "ext-curl": "*", 497 | "ext-json": "*", 498 | "ext-mbstring": "*", 499 | "php": ">=5.6.0" 500 | }, 501 | "require-dev": { 502 | "friendsofphp/php-cs-fixer": "3.5.0", 503 | "php-coveralls/php-coveralls": "^2.5", 504 | "phpstan/phpstan": "^1.2", 505 | "phpunit/phpunit": "^5.7 || ^9.0", 506 | "squizlabs/php_codesniffer": "^3.3" 507 | }, 508 | "type": "library", 509 | "extra": { 510 | "branch-alias": { 511 | "dev-master": "2.0-dev" 512 | } 513 | }, 514 | "autoload": { 515 | "psr-4": { 516 | "Stripe\\": "lib/" 517 | } 518 | }, 519 | "notification-url": "https://packagist.org/downloads/", 520 | "license": [ 521 | "MIT" 522 | ], 523 | "authors": [ 524 | { 525 | "name": "Stripe and contributors", 526 | "homepage": "https://github.com/stripe/stripe-php/contributors" 527 | } 528 | ], 529 | "description": "Stripe PHP Library", 530 | "homepage": "https://stripe.com/", 531 | "keywords": [ 532 | "api", 533 | "payment processing", 534 | "stripe" 535 | ], 536 | "support": { 537 | "issues": "https://github.com/stripe/stripe-php/issues", 538 | "source": "https://github.com/stripe/stripe-php/tree/v10.20.0" 539 | }, 540 | "time": "2023-08-03T20:01:03+00:00" 541 | }, 542 | { 543 | "name": "symfony/polyfill-ctype", 544 | "version": "v1.11.0", 545 | "source": { 546 | "type": "git", 547 | "url": "https://github.com/symfony/polyfill-ctype.git", 548 | "reference": "82ebae02209c21113908c229e9883c419720738a" 549 | }, 550 | "dist": { 551 | "type": "zip", 552 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", 553 | "reference": "82ebae02209c21113908c229e9883c419720738a", 554 | "shasum": "" 555 | }, 556 | "require": { 557 | "php": ">=5.3.3" 558 | }, 559 | "suggest": { 560 | "ext-ctype": "For best performance" 561 | }, 562 | "type": "library", 563 | "extra": { 564 | "branch-alias": { 565 | "dev-master": "1.11-dev" 566 | } 567 | }, 568 | "autoload": { 569 | "psr-4": { 570 | "Symfony\\Polyfill\\Ctype\\": "" 571 | }, 572 | "files": [ 573 | "bootstrap.php" 574 | ] 575 | }, 576 | "notification-url": "https://packagist.org/downloads/", 577 | "license": [ 578 | "MIT" 579 | ], 580 | "authors": [ 581 | { 582 | "name": "Symfony Community", 583 | "homepage": "https://symfony.com/contributors" 584 | }, 585 | { 586 | "name": "Gert de Pagter", 587 | "email": "BackEndTea@gmail.com" 588 | } 589 | ], 590 | "description": "Symfony polyfill for ctype functions", 591 | "homepage": "https://symfony.com", 592 | "keywords": [ 593 | "compatibility", 594 | "ctype", 595 | "polyfill", 596 | "portable" 597 | ], 598 | "time": "2019-02-06T07:57:58+00:00" 599 | }, 600 | { 601 | "name": "vlucas/phpdotenv", 602 | "version": "v3.4.0", 603 | "source": { 604 | "type": "git", 605 | "url": "https://github.com/vlucas/phpdotenv.git", 606 | "reference": "5084b23845c24dbff8ac6c204290c341e4776c92" 607 | }, 608 | "dist": { 609 | "type": "zip", 610 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/5084b23845c24dbff8ac6c204290c341e4776c92", 611 | "reference": "5084b23845c24dbff8ac6c204290c341e4776c92", 612 | "shasum": "" 613 | }, 614 | "require": { 615 | "php": "^5.4 || ^7.0", 616 | "phpoption/phpoption": "^1.5", 617 | "symfony/polyfill-ctype": "^1.9" 618 | }, 619 | "require-dev": { 620 | "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0" 621 | }, 622 | "type": "library", 623 | "extra": { 624 | "branch-alias": { 625 | "dev-master": "3.4-dev" 626 | } 627 | }, 628 | "autoload": { 629 | "psr-4": { 630 | "Dotenv\\": "src/" 631 | } 632 | }, 633 | "notification-url": "https://packagist.org/downloads/", 634 | "license": [ 635 | "BSD-3-Clause" 636 | ], 637 | "authors": [ 638 | { 639 | "name": "Vance Lucas", 640 | "email": "vance@vancelucas.com", 641 | "homepage": "http://www.vancelucas.com" 642 | } 643 | ], 644 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 645 | "keywords": [ 646 | "dotenv", 647 | "env", 648 | "environment" 649 | ], 650 | "time": "2019-06-15T22:40:20+00:00" 651 | } 652 | ], 653 | "packages-dev": [], 654 | "aliases": [], 655 | "minimum-stability": "stable", 656 | "stability-flags": [], 657 | "prefer-stable": false, 658 | "prefer-lowest": false, 659 | "platform": [], 660 | "platform-dev": [], 661 | "plugin-api-version": "2.3.0" 662 | } 663 | -------------------------------------------------------------------------------- /server/php/config.php: -------------------------------------------------------------------------------- 1 | load(); 10 | 11 | require './config.php'; 12 | 13 | $app = new \Slim\App; 14 | 15 | // Instantiate the logger as a dependency 16 | $container = $app->getContainer(); 17 | $container['logger'] = function ($c) { 18 | $settings = $c->get('settings')['logger']; 19 | $logger = new Monolog\Logger($settings['name']); 20 | $logger->pushProcessor(new Monolog\Processor\UidProcessor()); 21 | $logger->pushHandler(new Monolog\Handler\StreamHandler(__DIR__ . '/logs/app.log', \Monolog\Logger::DEBUG)); 22 | return $logger; 23 | }; 24 | 25 | $app->add(function ($request, $response, $next) { 26 | Stripe::setApiKey(getenv('STRIPE_SECRET_KEY')); 27 | return $next($request, $response); 28 | }); 29 | 30 | function calculateOrderAmount($items) { 31 | // Replace this constant with a calculation of the order's amount 32 | // Calculate the order total on the server to prevent 33 | // people from directly manipulating the amount on the client 34 | return 1400; 35 | } 36 | 37 | function calculateApplicationFeeAmount($amount) { 38 | // Take a 10% cut. 39 | return 0.1 * $amount; 40 | } 41 | 42 | $app->post('/create-payment-intent', function (Request $request, Response $response, array $args) { 43 | $data = json_decode($request->getBody(), true); 44 | $amount = calculateOrderAmount($data['items']); 45 | 46 | $paymentIntent = \Stripe\PaymentIntent::create([ 47 | 'amount' => $amount, 48 | 'currency' => $data['currency'], 49 | 'application_fee_amount' => calculateApplicationFeeAmount($amount), 50 | ], ['stripe_account' => $data['account']]); 51 | 52 | return $response->withJson(array( 53 | 'publishableKey' => getenv('STRIPE_PUBLISHABLE_KEY'), 54 | 'clientSecret' => $paymentIntent->client_secret, 55 | )); 56 | }); 57 | 58 | $app->get('/recent-accounts', function (Request $request, Response $response, array $args) { 59 | $accounts = \Stripe\Account::all(['limit' => 10]); 60 | return $response->withJson(array('accounts' => $accounts)); 61 | }); 62 | 63 | $app->get('/express-dashboard-link', function (Request $request, Response $response, array $args) { 64 | $account_id = $request->getQueryParam('account_id'); 65 | $link = \Stripe\Account::createLoginLink( 66 | $account_id, 67 | ['redirect_url' => $request->getUri()->getBaseUrl()] 68 | ); 69 | return $response->withJson(array('url' => $link->url)); 70 | }); 71 | 72 | $app->post('/webhook', function ($request, $response, $next) { 73 | $payload = $request->getBody(); 74 | $sig_header = $request->getHeaderLine('stripe-signature'); 75 | 76 | $event = null; 77 | 78 | // Verify webhook signature and extract the event. 79 | // See https://stripe.com/docs/webhooks/signatures for more information. 80 | try { 81 | $event = \Stripe\Webhook::constructEvent( 82 | $payload, $sig_header, getenv('STRIPE_WEBHOOK_SECRET') 83 | ); 84 | } catch(\UnexpectedValueException $e) { 85 | // Invalid payload. 86 | return $response->withStatus(400); 87 | } catch(\Stripe\Exception\SignatureVerificationException $e) { 88 | // Invalid Signature. 89 | return $response->withStatus(400); 90 | } 91 | 92 | if ($event->type == 'payment_intent.succeeded') { 93 | $paymentIntent = $event->data->object; 94 | $connectedAccountId = $event->account; 95 | handleSuccessfulPaymentIntent($connectedAccountId, $paymentIntent); 96 | } 97 | 98 | return $response->withStatus(200); 99 | }); 100 | 101 | function handleSuccessfulPaymentIntent($connectedAccountId, $paymentIntent) { 102 | // Fulfill the purchase. 103 | echo 'Connected account ID: ' . $connectedAccountId; 104 | echo 'PaymentIntent: ' . $paymentIntent; 105 | }; 106 | 107 | $app->get('/', function (Request $request, Response $response, array $args) { 108 | return $response->write(file_get_contents(getenv('STATIC_DIR') . '/index.html')); 109 | }); 110 | 111 | $app->run(); 112 | -------------------------------------------------------------------------------- /server/python/README.md: -------------------------------------------------------------------------------- 1 | # Connect direct charges 2 | 3 | ## Requirements 4 | 5 | - Python 3 6 | - [Configured .env file](../../README.md) 7 | 8 | ## How to run 9 | 10 | 1. Create and activate a new virtual environment 11 | 12 | **MacOS / Unix** 13 | 14 | ``` 15 | python3 -m venv env 16 | source env/bin/activate 17 | ``` 18 | 19 | **Windows (PowerShell)** 20 | 21 | ``` 22 | python3 -m venv env 23 | .\env\Scripts\activate.bat 24 | ``` 25 | 26 | 2. Install dependencies 27 | 28 | ``` 29 | pip install -r requirements.txt 30 | ``` 31 | 32 | 3. Export and run the application 33 | 34 | **MacOS / Unix** 35 | 36 | ``` 37 | export FLASK_APP=server.py 38 | python3 -m flask run --port=4242 39 | ``` 40 | 41 | **Windows (PowerShell)** 42 | 43 | ``` 44 | $env:FLASK_APP=“server.py" 45 | python3 -m flask run --port=4242 46 | ``` 47 | 48 | 4. Go to `localhost:4242` in your browser to see the demo -------------------------------------------------------------------------------- /server/python/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.3.9 2 | chardet==3.0.4 3 | Click==7.0 4 | Flask==1.0.3 5 | idna==3.3 6 | itsdangerous==1.1.0 7 | Jinja2==2.11.3 8 | MarkupSafe==1.1.1 9 | python-dotenv==0.10.3 10 | requests==2.22.0 11 | stripe==2.29.3 12 | toml==0.9.6 13 | urllib3==1.26.5 14 | Werkzeug==1.0.1 15 | -------------------------------------------------------------------------------- /server/python/server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3.6 2 | 3 | """ 4 | server.py 5 | Stripe Sample. 6 | Python 3.6 or newer required. 7 | """ 8 | 9 | import json 10 | import os 11 | import random 12 | import string 13 | 14 | import stripe 15 | from dotenv import load_dotenv, find_dotenv 16 | from flask import Flask, jsonify, render_template, redirect, request, session, send_from_directory, Response 17 | import urllib 18 | 19 | # Setup Stripe python client library 20 | load_dotenv(find_dotenv()) 21 | stripe.api_key = os.getenv('STRIPE_SECRET_KEY') 22 | stripe.api_version = os.getenv('STRIPE_API_VERSION', '2019-12-03') 23 | 24 | static_dir = str(os.path.abspath(os.path.join(__file__ , "..", os.getenv("STATIC_DIR")))) 25 | app = Flask(__name__, static_folder=static_dir, 26 | static_url_path="", template_folder=static_dir) 27 | 28 | # Set the secret key to some random bytes. Keep this really secret! 29 | # This enables Flask sessions. 30 | app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' 31 | 32 | @app.route('/', methods=['GET']) 33 | def get_example(): 34 | return render_template('index.html') 35 | 36 | 37 | def calculate_order_amount(items): 38 | # Replace this constant with a calculation of the order's amount 39 | # Calculate the order total on the server to prevent 40 | # people from directly manipulating the amount on the client 41 | return 1400 42 | 43 | 44 | def calculate_application_fee_amount(amount): 45 | # Take a 10% cut. 46 | return int(.1 * amount) 47 | 48 | 49 | @app.route('/create-payment-intent', methods=['POST']) 50 | def create_payment(): 51 | data = json.loads(request.data) 52 | amount = calculate_order_amount(data['items']) 53 | 54 | # Create a PaymentIntent with the order amount, currency, and Stripe account. 55 | intent = stripe.PaymentIntent.create( 56 | amount=amount, 57 | currency=data['currency'], 58 | application_fee_amount=calculate_application_fee_amount(amount), 59 | stripe_account=data['account'], 60 | ) 61 | 62 | try: 63 | # Send publishable key and PaymentIntent details to client 64 | return jsonify({'publishableKey': os.getenv('STRIPE_PUBLISHABLE_KEY'), 'clientSecret': intent.client_secret}) 65 | except Exception as e: 66 | return jsonify(error=str(e)), 403 67 | 68 | 69 | @app.route("/recent-accounts", methods=["GET"]) 70 | def get_accounts(): 71 | accounts = stripe.Account.list(limit=10) 72 | return jsonify({'accounts': accounts}) 73 | 74 | 75 | @app.route("/express-dashboard-link", methods=["GET"]) 76 | def get_express_dashboard_link(): 77 | account_id = request.args.get('account_id') 78 | link = stripe.Account.create_login_link(account_id, redirect_url=(request.url_root)) 79 | return jsonify({'url': link.url}) 80 | 81 | 82 | @app.route("/webhook", methods=["POST"]) 83 | def webhook_received(): 84 | payload = request.get_data() 85 | signature = request.headers.get("stripe-signature") 86 | 87 | # Verify webhook signature and extract the event. 88 | # See https://stripe.com/docs/webhooks/signatures for more information. 89 | try: 90 | event = stripe.Webhook.construct_event( 91 | payload=payload, sig_header=signature, secret=os.getenv('STRIPE_WEBHOOK_SECRET') 92 | ) 93 | except ValueError as e: 94 | # Invalid payload. 95 | print(e) 96 | return Response(status=400) 97 | except stripe.error.SignatureVerificationError as e: 98 | # Invalid Signature. 99 | print(e, signature, os.getenv('STRIPE_WEBHOOK_SECRET'), payload) 100 | return Response(status=400) 101 | 102 | if event["type"] == "payment_intent.succeeded": 103 | payment_intent = event["data"]["object"] 104 | connected_account_id = event["account"] 105 | handle_successful_payment_intent(connected_account_id, payment_intent) 106 | 107 | return json.dumps({"success": True}), 200 108 | 109 | 110 | def handle_successful_payment_intent(connected_account_id, payment_intent): 111 | # Fulfill the purchase. 112 | print('Connected account ID: ' + connected_account_id) 113 | print('PaymentIntent: ' + str(payment_intent)) 114 | 115 | 116 | if __name__== '__main__': 117 | app.run(port=4242) 118 | -------------------------------------------------------------------------------- /server/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gem 'dotenv' 4 | gem 'json' 5 | gem 'sinatra' 6 | gem 'stripe' 7 | -------------------------------------------------------------------------------- /server/ruby/README.md: -------------------------------------------------------------------------------- 1 | # Connect direct charges 2 | 3 | A [Sinatra](http://sinatrarb.com/) implementation. 4 | 5 | ## Requirements 6 | * Ruby v2.4.5+ 7 | * [Configured .env file](../../README.md) 8 | 9 | ## How to run 10 | 11 | 1. Install dependencies 12 | ``` 13 | bundle install 14 | ``` 15 | 16 | 2. Run the application 17 | ``` 18 | ruby server.rb 19 | ``` 20 | 21 | 3. Go to `localhost:4242` in your browser to see the demo -------------------------------------------------------------------------------- /server/ruby/server.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'stripe' 4 | require 'sinatra' 5 | require 'dotenv' 6 | 7 | # Replace if using a different env file or config 8 | Dotenv.load 9 | Stripe.api_key = ENV['STRIPE_SECRET_KEY'] 10 | 11 | enable :sessions 12 | set :static, true 13 | set :public_folder, File.join(File.dirname(__FILE__), ENV['STATIC_DIR']) 14 | set :port, 4242 15 | 16 | helpers do 17 | def request_headers 18 | env.each_with_object({}) { |(k, v), acc| acc[Regexp.last_match(1).downcase] = v if k =~ /^http_(.*)/i; } 19 | end 20 | end 21 | 22 | get '/' do 23 | content_type 'text/html' 24 | send_file File.join(settings.public_folder, 'index.html') 25 | end 26 | 27 | def calculate_order_amount(items) 28 | # Replace this constant with a calculation of the order's amount 29 | # Calculate the order total on the server to prevent 30 | # people from directly manipulating the amount on the client 31 | 1400 32 | end 33 | 34 | def calculate_application_fee_amount(amount) 35 | # Take a 10% cut. 36 | (0.1 * amount).to_i 37 | end 38 | 39 | post '/create-payment-intent' do 40 | data = JSON.parse(request.body.read) 41 | amount = calculate_order_amount(data['items']) 42 | 43 | payment_intent = Stripe::PaymentIntent.create({ 44 | amount: amount, 45 | currency: data['currency'], 46 | application_fee_amount: calculate_application_fee_amount(amount), 47 | }, stripe_account: data['account']) 48 | 49 | { 50 | 'publishableKey': ENV['STRIPE_PUBLISHABLE_KEY'], 51 | 'clientSecret': payment_intent.client_secret 52 | }.to_json 53 | end 54 | 55 | get '/recent-accounts' do 56 | accounts = Stripe::Account.list({limit: 10}) 57 | {'accounts': accounts}.to_json 58 | end 59 | 60 | get '/express-dashboard-link' do 61 | account_id = params[:account_id] 62 | link = Stripe::Account.create_login_link(account_id, redirect_url: (request.base_url)) 63 | {'url': link.url}.to_json 64 | end 65 | 66 | post '/webhook' do 67 | payload = request.body.read 68 | sig_header = request.env['HTTP_STRIPE_SIGNATURE'] 69 | 70 | event = nil 71 | 72 | # Verify webhook signature and extract the event. 73 | # See https://stripe.com/docs/webhooks/signatures for more information. 74 | begin 75 | event = Stripe::Webhook.construct_event( 76 | payload, sig_header, ENV['STRIPE_WEBHOOK_SECRET'] 77 | ) 78 | rescue JSON::ParserError => e 79 | # Invalid payload. 80 | puts e 81 | status 400 82 | return 83 | rescue Stripe::SignatureVerificationError => e 84 | # Invalid Signature. 85 | puts payload 86 | puts sig_header 87 | puts ENV['STRIPE_WEBHOOK_SECRET'] 88 | status 400 89 | return 90 | end 91 | 92 | if event['type'] == 'payment_intent.succeeded' 93 | payment_intent = event['data']['object'] 94 | connected_account_id = event['account'] 95 | handle_successful_payment_intent(connected_account_id, payment_intent) 96 | end 97 | 98 | status 200 99 | end 100 | 101 | def handle_successful_payment_intent(connected_account_id, payment_intent) 102 | # Fulfill the purchase. 103 | puts 'Connected account ID: ' + connected_account_id 104 | puts 'PaymentIntent: ' + payment_intent.to_s 105 | end --------------------------------------------------------------------------------