├── .cli.json ├── .env.example ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── client ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── api.js │ ├── components │ ├── CheckoutForm.css │ ├── CheckoutForm.js │ ├── DemoText.css │ └── DemoText.js │ ├── index.css │ └── index.js ├── demo.png ├── package.json └── server ├── README.md ├── java ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── stripe │ └── sample │ └── Server.java ├── node ├── README.md ├── package.json └── server.js ├── php ├── .htaccess ├── README.md ├── composer.json ├── composer.lock └── index.php ├── python ├── README.md ├── requirements.txt └── server.py └── ruby ├── Gemfile ├── README.md └── server.rb /.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-elements-card-payment", 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_PUBLISHABLE_KEY=pk_12345 2 | STRIPE_SECRET_KEY=sk_12345 3 | 4 | STRIPE_WEBHOOK_SECRET=whsec_1234 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please only file issues here that you believe represent actual bugs or feature requests for this sample. 2 | 3 | If you're having general trouble with your Stripe integration, please reach out to support using the form at https://support.stripe.com/ (preferred) or via email to support@stripe.com. 4 | 5 | If you are reporting a bug, please include the server language you're using, as well as any other details that may be helpful in reproducing the problem. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | .vscode/* 4 | !.vscode/extensions.json 5 | 6 | # Dependencies 7 | node_modules 8 | package-lock.json 9 | yarn.lock 10 | composer.lock 11 | 12 | # Ruby files 13 | Gemfile.lock 14 | 15 | # Python files 16 | __pycache__ 17 | venv 18 | 19 | # PHP files 20 | vendor 21 | logs 22 | 23 | # Java files 24 | .settings 25 | target/ 26 | .classpath 27 | .factorypath 28 | .project -------------------------------------------------------------------------------- /.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 | > 2 | > 3 | > This project is deprecated and is no longer being actively maintained. 4 | > 5 | > Please see the [accept a payment](https://github.com/stripe-samples/accept-a-payment) sample showing how to accept Card payments and more payment method types with React Stripe.js. 6 | 7 | # Card Payment using React 8 | 9 | This sample shows how to build a card form to take a payment using the [Payment Intents API](https://stripe.com/docs/payments/payment-intents), [Stripe Elements](https://stripe.com/payments/elements) and [React](https://reactjs.org/). 10 | 11 | See a hosted version of the [demo](https://rl6zb.sse.codesandbox.io/) in test mode or [fork on codesandbox.io](https://codesandbox.io/s/github/stripe-samples/react-elements-card-payment/tree/codesandbox) 12 | 13 | Preview of recipe 14 | 15 | ## Features 16 | 17 | This sample consists of a `client` in React and a `server` piece available in 5 common languages. 18 | 19 | The client is implemented using `create-react-app` to provide the boilerplate for React. Stripe Elements is integrated using [`react-stripe-js`](https://github.com/stripe/react-stripe-js), which is the official React library provided by Stripe. 20 | 21 | The server includes [5 server implementations](server/README.md) in Node, Ruby, Python, Java, and PHP in the [/server](/server) directory. We included several RESTful server implementations, each with the same endpoints and logic. 22 | 23 | ## How to run locally 24 | 25 | To run this sample locally you need to start both a local dev server for the `front-end` and another server for the `back-end`. 26 | 27 | You will need a Stripe account with its own set of [API keys](https://stripe.com/docs/development#api-keys). 28 | 29 | Follow the steps below to run locally. 30 | 31 | **1. Clone and configure the sample** 32 | 33 | The Stripe CLI is the fastest way to clone and configure a sample to run locally. 34 | 35 | **Using the Stripe CLI** 36 | 37 | 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. 38 | 39 | In your terminal shell, run the Stripe CLI command to clone the sample: 40 | 41 | ``` 42 | stripe samples create react-elements-card-payment 43 | ``` 44 | 45 | The CLI will walk you through picking your integration type, server and client languages, and configuring your .env config file with your Stripe API keys. 46 | 47 | **Installing and cloning manually** 48 | 49 | If you do not want to use the Stripe CLI, you can manually clone and configure the sample yourself: 50 | 51 | ``` 52 | git clone https://github.com/stripe-samples/react-elements-card-payment 53 | ``` 54 | 55 | Copy the .env.example file into a file named .env in the folder of the server you want to use. For example: 56 | 57 | ``` 58 | cp .env.example server/node/.env 59 | ``` 60 | 61 | 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. 62 | 63 | ``` 64 | STRIPE_PUBLISHABLE_KEY= 65 | STRIPE_SECRET_KEY= 66 | ``` 67 | 68 | ### Running the API server 69 | 70 | 1. Go to `/server` 71 | 1. Pick the language you are most comfortable in and follow the instructions in the directory on how to run. 72 | 73 | ### Running the React client 74 | 75 | 1. Go to `/client` 76 | 1. Run `yarn` 77 | 1. Run `yarn start` and your default browser should now open with the front-end being served from `http://localhost:3000/`. 78 | 79 | ### Using the sample app 80 | 81 | When running both servers, you are now ready to use the app running in [http://localhost:3000](http://localhost:3000). 82 | 83 | 1. Enter your name and card details 84 | 1. Hit "Pay" 85 | 1. 🎉 86 | 87 | ## FAQ 88 | 89 | Q: Why did you pick these frameworks? 90 | 91 | 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. 92 | 93 | ## Get support 94 | If you found a bug or want to suggest a new [feature/use case/sample], please [file an issue](../../issues). 95 | 96 | If you have questions, comments, or need help with code, we're here to help: 97 | - on [IRC via freenode](https://webchat.freenode.net/?channel=#stripe) 98 | - on Twitter at [@StripeDev](https://twitter.com/StripeDev) 99 | - on Stack Overflow at the [stripe-payments](https://stackoverflow.com/tags/stripe-payments/info) tag 100 | - by [email](mailto:support+github@stripe.com) 101 | 102 | Sign up to [stay updated with developer news](https://go.stripe.global/dev-digest). 103 | 104 | ## Author(s) 105 | 106 | [@auchenberg-stripe](https://twitter.com/auchenberg) 107 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Getting started 4 | 5 | 1. Run `yarn` 6 | 1. Run `yarn start` 7 | 1. Your default browser should now open with the front-end being served from `http://localhost:3000/`. 8 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@stripe/react-stripe-js": "^1.0.0", 7 | "@stripe/stripe-js": "^1.0.0", 8 | "react": "^16.9.0", 9 | "react-dom": "^16.9.0", 10 | "react-scripts": "^3.4.0" 11 | }, 12 | "proxy": "http://localhost:4242", 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/react-elements-card-payment/7692c34b08ed4f93b422d22eabcf2e40e7da39bc/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Stripe Card Payment using React 11 | 12 | 13 | 14 |
15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | /* Pasha styles – Brand-overrides, can split these out */ 2 | :root { 3 | --accent-color: #ed5f74; 4 | --success-color: #5fed82; 5 | --headline-color: var(--accent-color); 6 | --logo-image: url("https://storage.googleapis.com/stripe-sample-images/logo-pasha.svg"); 7 | } 8 | 9 | .pasha-image-stack { 10 | display: grid; 11 | grid-gap: 12px; 12 | grid-template-columns: auto auto; 13 | } 14 | 15 | .pasha-image-stack img { 16 | border-radius: var(--radius); 17 | background-color: var(--gray-border); 18 | box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1), 19 | 0 3px 6px 0 rgba(0, 0, 0, 0.07); 20 | transition: all 0.8s ease; 21 | opacity: 0; 22 | } 23 | 24 | .pasha-image-stack img:nth-child(1) { 25 | transform: translate(12px, -12px); 26 | opacity: 1; 27 | } 28 | .pasha-image-stack img:nth-child(2) { 29 | transform: translate(-24px, 16px); 30 | opacity: 1; 31 | } 32 | .pasha-image-stack img:nth-child(3) { 33 | transform: translate(68px, -100px); 34 | opacity: 1; 35 | } 36 | 37 | .sample-info { 38 | padding: 20px 30px; 39 | border: 1px solid var(--gray-border); 40 | border-radius: var(--radius); 41 | 42 | position: fixed; 43 | top: 10px; 44 | left: 50%; 45 | margin-left: -250px; 46 | max-width: 500px; 47 | } 48 | 49 | @media (max-width: 720px) { 50 | .sample-info { 51 | top: 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { loadStripe } from "@stripe/stripe-js"; 3 | import { Elements } from "@stripe/react-stripe-js"; 4 | import DemoText from "./components/DemoText"; 5 | import CheckoutForm from "./components/CheckoutForm"; 6 | 7 | import api from "./api"; 8 | 9 | import "./App.css"; 10 | 11 | const stripePromise = api.getPublicStripeKey().then(key => loadStripe(key)); 12 | 13 | export default function App() { 14 | return ( 15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 34 | 40 | 46 | 52 |
53 |
54 |
55 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /client/src/api.js: -------------------------------------------------------------------------------- 1 | const createPaymentIntent = options => { 2 | return window 3 | .fetch(`/create-payment-intent`, { 4 | method: "POST", 5 | headers: { 6 | "Content-Type": "application/json" 7 | }, 8 | body: JSON.stringify(options) 9 | }) 10 | .then(res => { 11 | if (res.status === 200) { 12 | return res.json(); 13 | } else { 14 | return null; 15 | } 16 | }) 17 | .then(data => { 18 | if (!data || data.error) { 19 | console.log("API error:", { data }); 20 | throw new Error("PaymentIntent API Error"); 21 | } else { 22 | return data.client_secret; 23 | } 24 | }); 25 | }; 26 | 27 | const getProductDetails = options => { 28 | return window 29 | .fetch(`/product-details`, { 30 | method: "GET", 31 | headers: { 32 | "Content-Type": "application/json" 33 | } 34 | }) 35 | .then(res => { 36 | if (res.status === 200) { 37 | return res.json(); 38 | } else { 39 | return null; 40 | } 41 | }) 42 | .then(data => { 43 | if (!data || data.error) { 44 | console.log("API error:", { data }); 45 | throw Error("API Error"); 46 | } else { 47 | return data; 48 | } 49 | }); 50 | }; 51 | 52 | const getPublicStripeKey = options => { 53 | return window 54 | .fetch(`/public-key`, { 55 | method: "GET", 56 | headers: { 57 | "Content-Type": "application/json" 58 | } 59 | }) 60 | .then(res => { 61 | if (res.status === 200) { 62 | return res.json(); 63 | } else { 64 | return null; 65 | } 66 | }) 67 | .then(data => { 68 | if (!data || data.error) { 69 | console.log("API error:", { data }); 70 | throw Error("API Error"); 71 | } else { 72 | return data.publicKey; 73 | } 74 | }); 75 | }; 76 | 77 | const api = { 78 | createPaymentIntent, 79 | getPublicStripeKey: getPublicStripeKey, 80 | getProductDetails: getProductDetails 81 | }; 82 | 83 | export default api; 84 | -------------------------------------------------------------------------------- /client/src/components/CheckoutForm.css: -------------------------------------------------------------------------------- 1 | .sr-combo-inputs { 2 | margin: 20px 0; 3 | } 4 | 5 | .sr-input { 6 | font-size: 16px; 7 | } 8 | 9 | .sr-card-element { 10 | padding-top: 12px; 11 | } 12 | 13 | .btn { 14 | font-size: 16px; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/components/CheckoutForm.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js"; 3 | import "./CheckoutForm.css"; 4 | import api from "../api"; 5 | 6 | export default function CheckoutForm() { 7 | const [amount, setAmount] = useState(0); 8 | const [currency, setCurrency] = useState(""); 9 | const [clientSecret, setClientSecret] = useState(null); 10 | const [error, setError] = useState(null); 11 | const [metadata, setMetadata] = useState(null); 12 | const [succeeded, setSucceeded] = useState(false); 13 | const [processing, setProcessing] = useState(false); 14 | const stripe = useStripe(); 15 | const elements = useElements(); 16 | 17 | useEffect(() => { 18 | // Step 1: Fetch product details such as amount and currency from 19 | // API to make sure it can't be tampered with in the client. 20 | api.getProductDetails().then((productDetails) => { 21 | setAmount(productDetails.amount / 100); 22 | setCurrency(productDetails.currency); 23 | }); 24 | 25 | // Step 2: Create PaymentIntent over Stripe API 26 | api 27 | .createPaymentIntent() 28 | .then((clientSecret) => { 29 | setClientSecret(clientSecret); 30 | }) 31 | .catch((err) => { 32 | setError(err.message); 33 | }); 34 | }, []); 35 | 36 | const handleSubmit = async (ev) => { 37 | ev.preventDefault(); 38 | setProcessing(true); 39 | 40 | // Step 3: Use clientSecret from PaymentIntent and the CardElement 41 | // to confirm payment with stripe.confirmCardPayment() 42 | const payload = await stripe.confirmCardPayment(clientSecret, { 43 | payment_method: { 44 | card: elements.getElement(CardElement), 45 | billing_details: { 46 | name: ev.target.name.value, 47 | }, 48 | }, 49 | }); 50 | 51 | if (payload.error) { 52 | setError(`Payment failed: ${payload.error.message}`); 53 | setProcessing(false); 54 | console.log("[error]", payload.error); 55 | } else { 56 | setError(null); 57 | setSucceeded(true); 58 | setProcessing(false); 59 | setMetadata(payload.paymentIntent); 60 | console.log("[PaymentIntent]", payload.paymentIntent); 61 | } 62 | }; 63 | 64 | const renderSuccess = () => { 65 | return ( 66 |
67 |

Your test payment succeeded

68 |

View PaymentIntent response:

69 |
 70 |           {JSON.stringify(metadata, null, 2)}
 71 |         
72 |
73 | ); 74 | }; 75 | 76 | const renderForm = () => { 77 | const options = { 78 | style: { 79 | base: { 80 | color: "#32325d", 81 | fontFamily: '"Helvetica Neue", Helvetica, sans-serif', 82 | fontSmoothing: "antialiased", 83 | fontSize: "16px", 84 | "::placeholder": { 85 | color: "#aab7c4", 86 | }, 87 | }, 88 | invalid: { 89 | color: "#fa755a", 90 | iconColor: "#fa755a", 91 | }, 92 | }, 93 | }; 94 | 95 | return ( 96 |
97 |

98 | {currency.toLocaleUpperCase()}{" "} 99 | {amount.toLocaleString(navigator.language, { 100 | minimumFractionDigits: 2, 101 | })}{" "} 102 |

103 |

Pre-order the Pasha package

104 | 105 |
106 |
107 | 115 |
116 | 117 |
118 | 122 |
123 |
124 | 125 | {error &&
{error}
} 126 | 127 | 133 |
134 | ); 135 | }; 136 | 137 | return ( 138 |
139 |
140 |
141 | {succeeded ? renderSuccess() : renderForm()} 142 |
143 |
144 | ); 145 | } 146 | -------------------------------------------------------------------------------- /client/src/components/DemoText.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | max-width: 825px; 3 | margin: 0 auto; 4 | padding: 0 22px; 5 | font-size: 14px; 6 | background: white; 7 | color: #6a7c94; 8 | border-radius: 22px; 9 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15); 10 | display: flex; 11 | align-items: center; 12 | box-sizing: border-box; 13 | padding: 10px; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/components/DemoText.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./DemoText.css"; 3 | 4 | export default function DemoText() { 5 | return ( 6 |
7 | 8 | This is a{" "} 9 | Stripe Sample on how 10 | to build a payment form in React to accept card payments.{" "} 11 | 12 | View code on GitHub. 13 | 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/src/index.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 | --body-font-family: -apple-system, BlinkMacSystemFont, sans-serif; 12 | --radius: 6px; 13 | --logo-image: url("https://storage.googleapis.com/stripe-sample-images/KAVHOLM.svg"); 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 | line-height: 30px; 41 | } 42 | h4 { 43 | font-weight: 500; 44 | font-size: 14px; 45 | color: var(--gray-light); 46 | } 47 | 48 | /* Layout */ 49 | .App { 50 | min-height: 100vh; 51 | display: flex; 52 | flex-direction: column; 53 | justify-content: center; 54 | } 55 | .sr-root { 56 | display: flex; 57 | flex-direction: row; 58 | width: 100%; 59 | max-width: 980px; 60 | padding: 48px; 61 | align-content: center; 62 | justify-content: center; 63 | height: auto; 64 | margin: 0 auto; 65 | } 66 | .sr-header { 67 | margin-bottom: 32px; 68 | } 69 | .sr-payment-summary { 70 | margin-bottom: 20px; 71 | } 72 | .sr-main, 73 | .sr-content { 74 | display: flex; 75 | flex-direction: column; 76 | justify-content: center; 77 | height: 100%; 78 | align-self: center; 79 | } 80 | .sr-main { 81 | width: var(--form-width); 82 | } 83 | .sr-content { 84 | padding-left: 48px; 85 | } 86 | .sr-header__logo { 87 | background-image: var(--logo-image); 88 | height: 24px; 89 | background-size: contain; 90 | background-repeat: no-repeat; 91 | width: 100%; 92 | } 93 | .sr-legal-text { 94 | color: var(--gray-light); 95 | text-align: center; 96 | font-size: 13px; 97 | line-height: 17px; 98 | margin-top: 12px; 99 | } 100 | .sr-field-error { 101 | color: var(--accent-color); 102 | text-align: left; 103 | font-size: 13px; 104 | line-height: 17px; 105 | margin-top: 12px; 106 | } 107 | 108 | .sr-field-success { 109 | text-align: left; 110 | font-size: 13px; 111 | line-height: 17px; 112 | margin-top: 12px; 113 | } 114 | 115 | /* Form */ 116 | .sr-form-row { 117 | margin: 16px 0; 118 | } 119 | label { 120 | font-size: 13px; 121 | font-weight: 500; 122 | margin-bottom: 8px; 123 | display: inline-block; 124 | } 125 | 126 | /* Inputs */ 127 | .sr-input, 128 | .sr-select, 129 | input[type="text"] { 130 | border: 1px solid var(--gray-border); 131 | border-radius: var(--radius); 132 | padding: 5px 12px; 133 | height: 44px; 134 | width: 100%; 135 | transition: box-shadow 0.2s ease; 136 | background: white; 137 | -moz-appearance: none; 138 | -webkit-appearance: none; 139 | appearance: none; 140 | color: #32325d; 141 | } 142 | .sr-input:focus, 143 | .StripeElement--focus, 144 | input[type="text"]:focus, 145 | button:focus, 146 | .focused { 147 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 148 | 0 0 0 4px rgba(50, 151, 211, 0.3); 149 | outline: none; 150 | z-index: 9; 151 | } 152 | .sr-input::placeholder, 153 | input[type="text"]::placeholder { 154 | color: var(--gray-light); 155 | } 156 | 157 | /* Checkbox */ 158 | .sr-checkbox-label { 159 | position: relative; 160 | cursor: pointer; 161 | } 162 | 163 | .sr-checkbox-label input { 164 | opacity: 0; 165 | margin-right: 6px; 166 | } 167 | 168 | .sr-checkbox-label .sr-checkbox-check { 169 | position: absolute; 170 | left: 0; 171 | height: 16px; 172 | width: 16px; 173 | background-color: white; 174 | border: 1px solid var(--gray-border); 175 | border-radius: 4px; 176 | transition: all 0.2s ease; 177 | } 178 | 179 | .sr-checkbox-label input:focus ~ .sr-checkbox-check { 180 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 181 | 0 0 0 4px rgba(50, 151, 211, 0.3); 182 | outline: none; 183 | } 184 | 185 | .sr-checkbox-label input:checked ~ .sr-checkbox-check { 186 | background-color: var(--accent-color); 187 | background-image: url("https://storage.googleapis.com/stripe-sample-images/icon-checkmark.svg"); 188 | background-repeat: no-repeat; 189 | background-size: 16px; 190 | background-position: -1px -1px; 191 | } 192 | 193 | /* Select */ 194 | .sr-select { 195 | display: block; 196 | height: 44px; 197 | margin: 0; 198 | background-image: url("https://storage.googleapis.com/stripe-sample-images/icon-chevron-down.svg"); 199 | background-repeat: no-repeat, repeat; 200 | background-position: right 12px top 50%, 0 0; 201 | background-size: 0.65em auto, 100%; 202 | } 203 | .sr-select:after { 204 | } 205 | .sr-select::-ms-expand { 206 | display: none; 207 | } 208 | .sr-select:hover { 209 | cursor: pointer; 210 | } 211 | .sr-select:focus { 212 | box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 213 | 0 0 0 4px rgba(50, 151, 211, 0.3); 214 | outline: none; 215 | } 216 | .sr-select option { 217 | font-weight: 400; 218 | } 219 | .sr-select:invalid { 220 | color: var(--gray-light); 221 | background-opacity: 0.4; 222 | } 223 | 224 | /* Combo inputs */ 225 | .sr-combo-inputs { 226 | display: flex; 227 | flex-direction: column; 228 | } 229 | .sr-combo-inputs input, 230 | .sr-combo-inputs .sr-select { 231 | border-radius: 0; 232 | border-bottom: 0; 233 | } 234 | .sr-combo-inputs > input:first-child, 235 | .sr-combo-inputs > .sr-select:first-child { 236 | border-radius: var(--radius) var(--radius) 0 0; 237 | } 238 | .sr-combo-inputs > input:last-child, 239 | .sr-combo-inputs > .sr-select:last-child { 240 | border-radius: 0 0 var(--radius) var(--radius); 241 | border-bottom: 1px solid var(--gray-border); 242 | } 243 | .sr-combo-inputs > .sr-combo-inputs-row:last-child input:first-child { 244 | border-radius: 0 0 0 var(--radius); 245 | border-bottom: 1px solid var(--gray-border); 246 | } 247 | .sr-combo-inputs > .sr-combo-inputs-row:last-child input:last-child { 248 | border-radius: 0 0 var(--radius) 0; 249 | border-bottom: 1px solid var(--gray-border); 250 | } 251 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:first-child { 252 | border-radius: var(--radius) 0 0 0; 253 | } 254 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:last-child { 255 | border-radius: 0 var(--radius) 0 0; 256 | } 257 | .sr-combo-inputs > .sr-combo-inputs-row:first-child input:only-child { 258 | border-radius: var(--radius) var(--radius) 0 0; 259 | } 260 | .sr-combo-inputs-row { 261 | width: 100%; 262 | display: flex; 263 | } 264 | 265 | .sr-combo-inputs-row > input { 266 | width: 100%; 267 | border-radius: 0; 268 | } 269 | 270 | .sr-combo-inputs-row > input:first-child:not(:only-child) { 271 | border-right: 0; 272 | } 273 | 274 | .sr-combo-inputs-row:not(:first-of-type) .sr-input { 275 | border-radius: 0 0 var(--radius) var(--radius); 276 | } 277 | 278 | /* Buttons and links */ 279 | button { 280 | background: var(--accent-color); 281 | border-radius: var(--radius); 282 | color: white; 283 | border: 0; 284 | padding: 12px 16px; 285 | margin-top: 16px; 286 | font-weight: 600; 287 | cursor: pointer; 288 | transition: all 0.2s ease; 289 | display: block; 290 | } 291 | button:hover { 292 | filter: contrast(115%); 293 | } 294 | button:active { 295 | transform: translateY(0px) scale(0.98); 296 | filter: brightness(0.9); 297 | } 298 | button:disabled { 299 | opacity: 0.5; 300 | cursor: none; 301 | } 302 | 303 | .sr-payment-form button, 304 | .fullwidth { 305 | width: 100%; 306 | } 307 | 308 | a { 309 | color: var(--accent-color); 310 | text-decoration: none; 311 | transition: all 0.2s ease; 312 | } 313 | 314 | a:hover { 315 | filter: brightness(0.8); 316 | } 317 | 318 | a:active { 319 | filter: brightness(0.5); 320 | } 321 | 322 | /* Code block */ 323 | .sr-callout { 324 | background: var(--gray-offset); 325 | padding: 12px; 326 | border-radius: var(--radius); 327 | max-height: 200px; 328 | overflow: auto; 329 | } 330 | code, 331 | pre { 332 | font-family: "SF Mono", "IBM Plex Mono", "Menlo", monospace; 333 | font-size: 12px; 334 | } 335 | 336 | /* todo: spinner/processing state, errors, animations */ 337 | 338 | .spinner, 339 | .spinner:before, 340 | .spinner:after { 341 | border-radius: 50%; 342 | } 343 | .spinner { 344 | color: #ffffff; 345 | font-size: 22px; 346 | text-indent: -99999px; 347 | margin: 0px auto; 348 | position: relative; 349 | width: 20px; 350 | height: 20px; 351 | box-shadow: inset 0 0 0 2px; 352 | -webkit-transform: translateZ(0); 353 | -ms-transform: translateZ(0); 354 | transform: translateZ(0); 355 | } 356 | .spinner:before, 357 | .spinner:after { 358 | position: absolute; 359 | content: ""; 360 | } 361 | .spinner:before { 362 | width: 10.4px; 363 | height: 20.4px; 364 | background: var(--accent-color); 365 | border-radius: 20.4px 0 0 20.4px; 366 | top: -0.2px; 367 | left: -0.2px; 368 | -webkit-transform-origin: 10.4px 10.2px; 369 | transform-origin: 10.4px 10.2px; 370 | -webkit-animation: loading 2s infinite ease 1.5s; 371 | animation: loading 2s infinite ease 1.5s; 372 | } 373 | .spinner:after { 374 | width: 10.4px; 375 | height: 10.2px; 376 | background: var(--accent-color); 377 | border-radius: 0 10.2px 10.2px 0; 378 | top: -0.1px; 379 | left: 10.2px; 380 | -webkit-transform-origin: 0px 10.2px; 381 | transform-origin: 0px 10.2px; 382 | -webkit-animation: loading 2s infinite ease; 383 | animation: loading 2s infinite ease; 384 | } 385 | 386 | @-webkit-keyframes loading { 387 | 0% { 388 | -webkit-transform: rotate(0deg); 389 | transform: rotate(0deg); 390 | } 391 | 100% { 392 | -webkit-transform: rotate(360deg); 393 | transform: rotate(360deg); 394 | } 395 | } 396 | 397 | @keyframes loading { 398 | 0% { 399 | -webkit-transform: rotate(0deg); 400 | transform: rotate(0deg); 401 | } 402 | 100% { 403 | -webkit-transform: rotate(360deg); 404 | transform: rotate(360deg); 405 | } 406 | } 407 | 408 | /* Animated form */ 409 | 410 | .sr-root { 411 | animation: 0.4s form-in; 412 | animation-fill-mode: both; 413 | animation-timing-function: ease; 414 | } 415 | 416 | .sr-payment-form .sr-form-row { 417 | animation: 0.4s field-in; 418 | animation-fill-mode: both; 419 | animation-timing-function: ease; 420 | transform-origin: 50% 0%; 421 | } 422 | 423 | /* need saas for loop :D */ 424 | .sr-payment-form .sr-form-row:nth-child(1) { 425 | animation-delay: 0; 426 | } 427 | .sr-payment-form .sr-form-row:nth-child(2) { 428 | animation-delay: 60ms; 429 | } 430 | .sr-payment-form .sr-form-row:nth-child(3) { 431 | animation-delay: 120ms; 432 | } 433 | .sr-payment-form .sr-form-row:nth-child(4) { 434 | animation-delay: 180ms; 435 | } 436 | .sr-payment-form .sr-form-row:nth-child(5) { 437 | animation-delay: 240ms; 438 | } 439 | .sr-payment-form .sr-form-row:nth-child(6) { 440 | animation-delay: 300ms; 441 | } 442 | 443 | .hidden { 444 | display: none; 445 | } 446 | 447 | @keyframes field-in { 448 | 0% { 449 | opacity: 0; 450 | transform: translateY(8px) scale(0.95); 451 | } 452 | 100% { 453 | opacity: 1; 454 | transform: translateY(0px) scale(1); 455 | } 456 | } 457 | 458 | @keyframes form-in { 459 | 0% { 460 | opacity: 0; 461 | transform: scale(0.98); 462 | } 463 | 100% { 464 | opacity: 1; 465 | transform: scale(1); 466 | } 467 | } 468 | 469 | /* Responsiveness */ 470 | @media (max-width: 720px) { 471 | .sr-root { 472 | flex-direction: column; 473 | justify-content: flex-start; 474 | padding: 48px 20px; 475 | min-width: 320px; 476 | } 477 | 478 | .sr-header__logo { 479 | background-position: center; 480 | } 481 | 482 | .sr-payment-summary { 483 | text-align: center; 484 | } 485 | 486 | .sr-content { 487 | display: none; 488 | } 489 | 490 | .sr-main { 491 | margin-top: 120px; 492 | width: 100%; 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/react-elements-card-payment/7692c34b08ed4f93b422d22eabcf2e40e7da39bc/demo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-react-card-payment", 3 | "version": "1.0.0", 4 | "description": "How to build a card form to take a payment using React", 5 | "scripts": { 6 | "client": "cd client && yarn start", 7 | "server": "cd server/node && yarn start", 8 | "start": "concurrently \"yarn client\" \"yarn server\"" 9 | }, 10 | "author": "stripe-demos", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.19.0", 14 | "concurrently": "4.1.2", 15 | "dotenv": "^8.0.0", 16 | "express": "^4.17.1", 17 | "stripe": "^7.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | 8 | 1. Build the jar 9 | 10 | ``` 11 | mvn package 12 | ``` 13 | 14 | 2. Run the packaged jar 15 | 16 | ``` 17 | SPARK_LOCAL_IP=0.0.0.0 java -cp target/react-1.0.0-SNAPSHOT-jar-with-dependencies.jar com.stripe.sample.Server 18 | ``` 19 | 20 | 3. Go to `localhost:4242` in your browser to see the demo 21 | -------------------------------------------------------------------------------- /server/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.stripe.sample 7 | react 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.3.1 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | 1.18.8 31 | provided 32 | 33 | 34 | com.stripe 35 | stripe-java 36 | 10.0.2 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.util.HashMap; 4 | import java.util.Map; 5 | 6 | import static spark.Spark.get; 7 | import static spark.Spark.post; 8 | import static spark.Spark.port; 9 | 10 | import com.google.gson.Gson; 11 | import com.google.gson.annotations.SerializedName; 12 | 13 | import com.stripe.Stripe; 14 | import com.stripe.model.Event; 15 | import com.stripe.model.PaymentIntent; 16 | import com.stripe.exception.*; 17 | import com.stripe.net.Webhook; 18 | import com.stripe.param.PaymentIntentCreateParams; 19 | 20 | import io.github.cdimascio.dotenv.Dotenv; 21 | 22 | public class Server { 23 | private static Gson gson = new Gson(); 24 | 25 | static class ProductDetails { 26 | @SerializedName("amount") 27 | Long amount; 28 | 29 | @SerializedName("currency") 30 | String currency; 31 | 32 | public String getCurrency() { 33 | return currency; 34 | } 35 | 36 | public Long getAmount() { 37 | return amount; 38 | } 39 | } 40 | 41 | private static ProductDetails getProductDetails() { 42 | ProductDetails details = new ProductDetails(); 43 | details.amount = (long) 9900; 44 | details.currency = "EUR"; 45 | 46 | return details; 47 | } 48 | 49 | public static void main(String[] args) { 50 | port(4242); 51 | 52 | Dotenv dotenv = Dotenv.load(); 53 | Stripe.apiKey = dotenv.get("STRIPE_SECRET_KEY"); 54 | 55 | get("/", (request, response) -> { 56 | response.type("application/json"); 57 | return "Hello from API"; 58 | }); 59 | 60 | get("/public-key", (request, response) -> { 61 | response.type("application/json"); 62 | 63 | Map responseData = new HashMap<>(); 64 | responseData.put("publicKey", dotenv.get("STRIPE_PUBLISHABLE_KEY")); 65 | return gson.toJson(responseData); 66 | }); 67 | 68 | get("/product-details", (request, response) -> { 69 | response.type("application/json"); 70 | 71 | ProductDetails productDetails = getProductDetails(); 72 | return gson.toJson(productDetails); 73 | }); 74 | 75 | post("/create-payment-intent", (request, response) -> { 76 | response.type("application/json"); 77 | 78 | ProductDetails productDetails = getProductDetails(); 79 | 80 | PaymentIntentCreateParams createParams = new PaymentIntentCreateParams.Builder() 81 | .setCurrency(productDetails.getCurrency()).setAmount(productDetails.getAmount()).build(); 82 | 83 | PaymentIntent intent = PaymentIntent.create(createParams); 84 | 85 | return gson.toJson(intent); 86 | }); 87 | 88 | post("/webhook", (request, response) -> { 89 | System.out.println("Webhook"); 90 | String payload = request.body(); 91 | String sigHeader = request.headers("Stripe-Signature"); 92 | String endpointSecret = System.getenv("STRIPE_WEBHOOK_SECRET"); 93 | 94 | Event event = null; 95 | 96 | try { 97 | event = Webhook.constructEvent(payload, sigHeader, endpointSecret); 98 | } catch (SignatureVerificationException e) { 99 | // Invalid signature 100 | response.status(400); 101 | return ""; 102 | } 103 | 104 | switch (event.getType()) { 105 | case "payment_intent.succeeded": 106 | // Fulfill any orders, e-mail receipts, etc 107 | System.out.println("💰 Payment received!"); 108 | break; 109 | case "payment_intent.payment_failed": 110 | // Notify the customer that their order was not fulfilled 111 | System.out.println("❌ Payment failed."); 112 | break; 113 | default: 114 | // Unexpected event type 115 | response.status(400); 116 | return ""; 117 | } 118 | 119 | response.status(200); 120 | return ""; 121 | }); 122 | } 123 | } -------------------------------------------------------------------------------- /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.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-recipe-demo", 3 | "version": "1.0.0", 4 | "description": "A Stripe demo", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "engines": { 11 | "node": ">=10.13.0" 12 | }, 13 | "author": "stripe-demos", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.19.0", 17 | "dotenv": "^8.0.0", 18 | "express": "^4.17.1", 19 | "stripe": "^7.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/node/server.js: -------------------------------------------------------------------------------- 1 | const env = require("dotenv").config({ path: "./.env" }); 2 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); 3 | const express = require("express"); 4 | const bodyParser = require("body-parser"); 5 | const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; 6 | const app = express(); 7 | const { resolve } = require("path"); 8 | 9 | 10 | // Use JSON parser for all non-webhook routes 11 | app.use((req, res, next) => { 12 | if (req.originalUrl === '/webhook') { 13 | next(); 14 | } else { 15 | bodyParser.json()(req, res, next); 16 | } 17 | }); 18 | 19 | app.get("/", (req, res) => { 20 | res.send("Hello from API"); 21 | }); 22 | 23 | app.get("/public-key", (req, res) => { 24 | res.send({ publicKey: process.env.STRIPE_PUBLISHABLE_KEY }); 25 | }); 26 | 27 | app.get("/product-details", (req, res) => { 28 | let data = getProductDetails(); 29 | res.send(data); 30 | }); 31 | 32 | app.post("/create-payment-intent", async (req, res) => { 33 | const body = req.body; 34 | const productDetails = getProductDetails(); 35 | 36 | const options = { 37 | ...body, 38 | amount: productDetails.amount, 39 | currency: productDetails.currency 40 | }; 41 | 42 | try { 43 | const paymentIntent = await stripe.paymentIntents.create(options); 44 | res.json(paymentIntent); 45 | } catch (err) { 46 | res.json(err); 47 | } 48 | }); 49 | 50 | let getProductDetails = () => { 51 | return { currency: "EUR", amount: 9900 }; 52 | }; 53 | 54 | // Webhook handler for asynchronous events. 55 | app.post('/webhook', bodyParser.raw({type: 'application/json'}), async (req, res) => { 56 | let data; 57 | let eventType; 58 | // Check if webhook signing is configured. 59 | if (webhookSecret) { 60 | // Retrieve the event by verifying the signature using the raw body and secret. 61 | let event; 62 | let signature = req.headers["stripe-signature"]; 63 | 64 | try { 65 | event = stripe.webhooks.constructEvent( 66 | req.body, 67 | signature, 68 | webhookSecret 69 | ); 70 | } catch (err) { 71 | console.log(`⚠️ Webhook signature verification failed.`); 72 | return res.sendStatus(400); 73 | } 74 | // Extract the object from the event. 75 | data = event.data; 76 | eventType = event.type; 77 | } else { 78 | // Webhook signing is recommended, but if the secret is not configured in `config.js`, 79 | // retrieve the event data directly from the request body. 80 | data = req.body.data; 81 | eventType = req.body.type; 82 | } 83 | 84 | if (eventType === "payment_intent.succeeded") { 85 | // Fulfill any orders, e-mail receipts, etc 86 | console.log("💰 Payment received!"); 87 | } 88 | 89 | if (eventType === "payment_intent.payment_failed") { 90 | // Notify the customer that their order was not fulfilled 91 | console.log("❌ Payment failed."); 92 | } 93 | 94 | res.sendStatus(200); 95 | }); 96 | 97 | app.listen(4242, () => console.log(`Node server listening on port ${4242}!`)); 98 | -------------------------------------------------------------------------------- /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 | # Name of sample 2 | 3 | ## Requirements 4 | * PHP >= 7.1.3 5 | * Composer 6 | * [Slim](http://www.slimframework.com/) 7 | 8 | ## How to run 9 | 10 | 1. Install dependencies 11 | 12 | ``` 13 | composer install 14 | ``` 15 | 16 | 2. Run the application 17 | 18 | ``` 19 | composer start 20 | ``` 21 | 22 | 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": "^6.31", 6 | "monolog/monolog": "^1.17" 7 | }, 8 | "scripts": { 9 | "start": "php -S 0.0.0.0: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": "c2cd44e5029aab5ea306cc312f6ebfb9", 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": "v6.39.1", 484 | "source": { 485 | "type": "git", 486 | "url": "https://github.com/stripe/stripe-php.git", 487 | "reference": "51b7153bb443fcb48d892547ea9b78fc2de9e6d9" 488 | }, 489 | "dist": { 490 | "type": "zip", 491 | "url": "https://api.github.com/repos/stripe/stripe-php/zipball/51b7153bb443fcb48d892547ea9b78fc2de9e6d9", 492 | "reference": "51b7153bb443fcb48d892547ea9b78fc2de9e6d9", 493 | "shasum": "" 494 | }, 495 | "require": { 496 | "ext-curl": "*", 497 | "ext-json": "*", 498 | "ext-mbstring": "*", 499 | "php": ">=5.4.0" 500 | }, 501 | "require-dev": { 502 | "php-coveralls/php-coveralls": "1.*", 503 | "phpunit/phpunit": "~4.0", 504 | "squizlabs/php_codesniffer": "~2.0", 505 | "symfony/process": "~2.8" 506 | }, 507 | "type": "library", 508 | "extra": { 509 | "branch-alias": { 510 | "dev-master": "2.0-dev" 511 | } 512 | }, 513 | "autoload": { 514 | "psr-4": { 515 | "Stripe\\": "lib/" 516 | } 517 | }, 518 | "notification-url": "https://packagist.org/downloads/", 519 | "license": [ 520 | "MIT" 521 | ], 522 | "authors": [ 523 | { 524 | "name": "Stripe and contributors", 525 | "homepage": "https://github.com/stripe/stripe-php/contributors" 526 | } 527 | ], 528 | "description": "Stripe PHP Library", 529 | "homepage": "https://stripe.com/", 530 | "keywords": [ 531 | "api", 532 | "payment processing", 533 | "stripe" 534 | ], 535 | "time": "2019-06-25T20:23:43+00:00" 536 | }, 537 | { 538 | "name": "symfony/polyfill-ctype", 539 | "version": "v1.11.0", 540 | "source": { 541 | "type": "git", 542 | "url": "https://github.com/symfony/polyfill-ctype.git", 543 | "reference": "82ebae02209c21113908c229e9883c419720738a" 544 | }, 545 | "dist": { 546 | "type": "zip", 547 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", 548 | "reference": "82ebae02209c21113908c229e9883c419720738a", 549 | "shasum": "" 550 | }, 551 | "require": { 552 | "php": ">=5.3.3" 553 | }, 554 | "suggest": { 555 | "ext-ctype": "For best performance" 556 | }, 557 | "type": "library", 558 | "extra": { 559 | "branch-alias": { 560 | "dev-master": "1.11-dev" 561 | } 562 | }, 563 | "autoload": { 564 | "psr-4": { 565 | "Symfony\\Polyfill\\Ctype\\": "" 566 | }, 567 | "files": [ 568 | "bootstrap.php" 569 | ] 570 | }, 571 | "notification-url": "https://packagist.org/downloads/", 572 | "license": [ 573 | "MIT" 574 | ], 575 | "authors": [ 576 | { 577 | "name": "Symfony Community", 578 | "homepage": "https://symfony.com/contributors" 579 | }, 580 | { 581 | "name": "Gert de Pagter", 582 | "email": "BackEndTea@gmail.com" 583 | } 584 | ], 585 | "description": "Symfony polyfill for ctype functions", 586 | "homepage": "https://symfony.com", 587 | "keywords": [ 588 | "compatibility", 589 | "ctype", 590 | "polyfill", 591 | "portable" 592 | ], 593 | "time": "2019-02-06T07:57:58+00:00" 594 | }, 595 | { 596 | "name": "vlucas/phpdotenv", 597 | "version": "v3.4.0", 598 | "source": { 599 | "type": "git", 600 | "url": "https://github.com/vlucas/phpdotenv.git", 601 | "reference": "5084b23845c24dbff8ac6c204290c341e4776c92" 602 | }, 603 | "dist": { 604 | "type": "zip", 605 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/5084b23845c24dbff8ac6c204290c341e4776c92", 606 | "reference": "5084b23845c24dbff8ac6c204290c341e4776c92", 607 | "shasum": "" 608 | }, 609 | "require": { 610 | "php": "^5.4 || ^7.0", 611 | "phpoption/phpoption": "^1.5", 612 | "symfony/polyfill-ctype": "^1.9" 613 | }, 614 | "require-dev": { 615 | "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0" 616 | }, 617 | "type": "library", 618 | "extra": { 619 | "branch-alias": { 620 | "dev-master": "3.4-dev" 621 | } 622 | }, 623 | "autoload": { 624 | "psr-4": { 625 | "Dotenv\\": "src/" 626 | } 627 | }, 628 | "notification-url": "https://packagist.org/downloads/", 629 | "license": [ 630 | "BSD-3-Clause" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Vance Lucas", 635 | "email": "vance@vancelucas.com", 636 | "homepage": "http://www.vancelucas.com" 637 | } 638 | ], 639 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 640 | "keywords": [ 641 | "dotenv", 642 | "env", 643 | "environment" 644 | ], 645 | "time": "2019-06-15T22:40:20+00:00" 646 | } 647 | ], 648 | "packages-dev": [], 649 | "aliases": [], 650 | "minimum-stability": "stable", 651 | "stability-flags": [], 652 | "prefer-stable": false, 653 | "prefer-lowest": false, 654 | "platform": [], 655 | "platform-dev": [] 656 | } 657 | -------------------------------------------------------------------------------- /server/php/index.php: -------------------------------------------------------------------------------- 1 | load(); 10 | 11 | $config = [ 12 | 'settings' => [ 13 | 'displayErrorDetails' => true, 14 | ] 15 | ]; 16 | 17 | $app = new \Slim\App($config); 18 | 19 | // Instantiate the logger as a dependency 20 | $container = $app->getContainer(); 21 | $container['logger'] = function ($c) { 22 | $settings = $c->get('settings')['logger']; 23 | $logger = new Monolog\Logger($settings['name']); 24 | $logger->pushProcessor(new Monolog\Processor\UidProcessor()); 25 | $logger->pushHandler(new Monolog\Handler\StreamHandler(__DIR__ . '/logs/app.log', \Monolog\Logger::DEBUG)); 26 | return $logger; 27 | }; 28 | 29 | $app->add(function ($request, $response, $next) { 30 | Stripe::setApiKey(getenv('STRIPE_SECRET_KEY')); 31 | return $next($request, $response); 32 | }); 33 | 34 | $app->get('/', function (Request $request, Response $response, array $args) { 35 | return $response->write("Hello from API"); 36 | }); 37 | 38 | $app->get('/public-key', function (Request $request, Response $response, array $args) { 39 | $pub_key = getenv('STRIPE_PUBLISHABLE_KEY'); 40 | $data = array('publicKey' => $pub_key); 41 | return $response->withJson($data); 42 | }); 43 | 44 | $app->get('/product-details', function (Request $request, Response $response, array $args) { 45 | $data = product_details(); 46 | return $response->withJson($data); 47 | }); 48 | 49 | 50 | $app->post('/create-payment-intent', function(Request $request, Response $response) { 51 | $body = $request->getParsedBody(); 52 | $product = product_details(); 53 | $options = array_merge($body, $product); 54 | 55 | $payment_intent = \Stripe\PaymentIntent::create($options); 56 | 57 | return $response->withJson($payment_intent); 58 | }); 59 | 60 | $app->post('/webhook', function(Request $request, Response $response) { 61 | $logger = $this->get('logger'); 62 | $event = $request->getParsedBody(); 63 | // Parse the message body (and check the signature if possible) 64 | $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); 65 | if ($webhookSecret) { 66 | try { 67 | $event = \Stripe\Webhook::constructEvent( 68 | $request->getBody(), 69 | $request->getHeaderLine('stripe-signature'), 70 | $webhookSecret 71 | ); 72 | } catch (\Exception $e) { 73 | return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); 74 | } 75 | } else { 76 | $event = $request->getParsedBody(); 77 | } 78 | $type = $event['type']; 79 | $object = $event['data']['object']; 80 | 81 | $logger->info('🔔 Webhook received! ' . $type); 82 | 83 | if($type == 'payment_intent.succeeded') { 84 | # Fulfill any orders, e-mail receipts, etc 85 | $logger->info("💰 Payment received!"); 86 | } 87 | 88 | if($type == 'payment_intent.payment_failed') { 89 | #Notify the customer that their order was not fulfilled 90 | $logger->info("❌ Payment failed."); 91 | } 92 | 93 | return $response->withJson([ 'status' => 'success' ])->withStatus(200); 94 | }); 95 | 96 | $app->run(); 97 | 98 | 99 | function product_details() { 100 | return array('currency' => 'EUR', 'amount' => 9900 ); 101 | }; 102 | -------------------------------------------------------------------------------- /server/python/README.md: -------------------------------------------------------------------------------- 1 | # Name of sample 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 | ``` 13 | python3 -m venv /path/to/new/virtual/environment 14 | source /path/to/new/virtual/environment/venv/bin/activate 15 | ``` 16 | 17 | 2. Install dependencies 18 | 19 | ``` 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | 3. Export and run the application 24 | 25 | ``` 26 | python3 server.py 27 | ``` 28 | 29 | 4. Go to `localhost:4242` in your browser to see the demo 30 | -------------------------------------------------------------------------------- /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==2.8 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.25.8 14 | Werkzeug==1.0.1 15 | -------------------------------------------------------------------------------- /server/python/server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3.6 2 | 3 | """ 4 | server.py 5 | Stripe Recipe. 6 | Python 3.6 or newer required. 7 | """ 8 | 9 | import stripe 10 | import json 11 | import os 12 | 13 | from flask import Flask, render_template, jsonify, request, send_from_directory 14 | from dotenv import load_dotenv, find_dotenv 15 | 16 | app = Flask(__name__, static_url_path="") 17 | 18 | # Setup Stripe python client library 19 | load_dotenv(find_dotenv()) 20 | stripe.api_key = os.getenv('STRIPE_SECRET_KEY') 21 | stripe.api_version = os.getenv('STRIPE_API_VERSION') 22 | 23 | def product_details(): 24 | return { 25 | 'currency': 'EUR', 26 | 'amount': 9900 27 | } 28 | 29 | @app.route('/', methods=['GET']) 30 | def home(): 31 | return "Hello from API!" 32 | 33 | @app.route('/public-key', methods=['GET']) 34 | def PUBLISHABLE_KEY(): 35 | return jsonify({ 36 | 'publicKey': os.getenv('STRIPE_PUBLISHABLE_KEY') 37 | }) 38 | 39 | @app.route('/product-details', methods=['GET']) 40 | def get_product_details(): 41 | product = product_details() 42 | return jsonify(product) 43 | 44 | @app.route('/create-payment-intent', methods=['POST']) 45 | def post_payment_intent(): 46 | # Reads application/json and returns a response 47 | data = json.loads(request.data or '{}') 48 | product = product_details() 49 | 50 | options = dict() 51 | options.update(data) 52 | options.update(product) 53 | 54 | # Create a PaymentIntent with the order amount and currency 55 | payment_intent = stripe.PaymentIntent.create(**options) 56 | 57 | try: 58 | return jsonify(payment_intent) 59 | except Exception as e: 60 | return jsonify(error=str(e)), 403 61 | 62 | @app.route('/webhook', methods=['POST']) 63 | def webhook_received(): 64 | # You can use webhooks to receive information about asynchronous payment events. 65 | # For more about our webhook events check out https://stripe.com/docs/webhooks. 66 | webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET') 67 | request_data = json.loads(request.data) 68 | 69 | if webhook_secret: 70 | # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. 71 | signature = request.headers.get('stripe-signature') 72 | try: 73 | event = stripe.Webhook.construct_event( 74 | payload=request.data, sig_header=signature, secret=webhook_secret) 75 | data = event['data'] 76 | except Exception as e: 77 | return e 78 | # Get the type of webhook event sent - used to check the status of PaymentIntents. 79 | event_type = event['type'] 80 | else: 81 | data = request_data['data'] 82 | event_type = request_data['type'] 83 | data_object = data['object'] 84 | 85 | print('event ' + event_type) 86 | 87 | if event_type == 'payment_intent.succeeded': 88 | # Fulfill any orders, e-mail receipts, etc 89 | print("💰 Payment received!") 90 | 91 | if event_type == 'payment_intent.payment_failed': 92 | #Notify the customer that their order was not fulfilled 93 | print("❌ Payment failed.") 94 | 95 | return jsonify({'status': 'success'}) 96 | 97 | 98 | if __name__== '__main__': 99 | app.run(host='0.0.0.0', port=4242) 100 | -------------------------------------------------------------------------------- /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 | # Name of sample 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 | require 'stripe' 2 | require 'sinatra' 3 | require 'dotenv' 4 | 5 | Dotenv.load 6 | 7 | Stripe.api_key = ENV['STRIPE_SECRET_KEY'] 8 | 9 | set :port, 4242 10 | set :bind, '0.0.0.0' 11 | 12 | get '/' do 13 | 'Hello from API' 14 | end 15 | 16 | get '/public-key' do 17 | content_type 'application/json' 18 | 19 | response = { 20 | 'publicKey': ENV['STRIPE_PUBLISHABLE_KEY'] 21 | } 22 | response.to_json 23 | end 24 | 25 | get '/product-details' do 26 | content_type 'application/json' 27 | data = product_details 28 | data.to_json 29 | end 30 | 31 | post '/create-payment-intent' do 32 | content_type 'application/json' 33 | 34 | data = JSON.parse(request.body.read) 35 | product = product_details 36 | 37 | options = product.merge(data) 38 | 39 | payment_intent = Stripe::PaymentIntent.create(options) 40 | payment_intent.to_json 41 | end 42 | 43 | post '/webhook' do 44 | # You can use webhooks to receive information about asynchronous payment events. 45 | # For more about our webhook events check out https://stripe.com/docs/webhooks. 46 | webhook_secret = ENV['STRIPE_WEBHOOK_SECRET'] 47 | payload = request.body.read 48 | if !webhook_secret.empty? 49 | # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. 50 | sig_header = request.env['HTTP_STRIPE_SIGNATURE'] 51 | event = nil 52 | 53 | begin 54 | event = Stripe::Webhook.construct_event( 55 | payload, sig_header, webhook_secret 56 | ) 57 | rescue JSON::ParserError => e 58 | # Invalid payload 59 | status 400 60 | return 61 | rescue Stripe::SignatureVerificationError => e 62 | # Invalid signature 63 | puts "⚠️ Webhook signature verification failed." 64 | status 400 65 | return 66 | end 67 | else 68 | data = JSON.parse(payload, symbolize_names: true) 69 | event = Stripe::Event.construct_from(data) 70 | end 71 | 72 | # Get the type of webhook event sent - used to check the status of PaymentIntents. 73 | event_type = event['type'] 74 | data = event['data'] 75 | data_object = data['object'] 76 | 77 | if event_type == 'payment_intent.succeeded' 78 | # Fulfill any orders, e-mail receipts, etc 79 | puts "💰 Payment received!" 80 | end 81 | 82 | if event_type == 'payment_intent.payment_failed' 83 | #Notify the customer that their order was not fulfilled 84 | puts "❌ Payment failed." 85 | end 86 | 87 | 88 | content_type 'application/json' 89 | { 90 | status: 'success' 91 | }.to_json 92 | 93 | end 94 | 95 | 96 | def product_details 97 | { 98 | 'currency': 'EUR', 99 | 'amount': 9900 100 | } 101 | end --------------------------------------------------------------------------------