--- title: Identity verification (modal) route: /samples/identity/modal --- # Identity verification (modal) # Identity verification The modal integration allows you to collect documents for identity verification in your existing flow. You can customize the interface to match your branding and support users on a variety of platforms and in multiple programming languages, while limiting the amount of private information you handle on your site. For an integration solution that requires minimal coding, see the [redirect integration](https://docs.stripe.com/samples/identity/redirect.md). ```html Stripe Identity Sample

Verify your identity to book

Get ready to take a photo of your ID and a selfie

``` ```css /* Variables */ :root { --gray-offset: rgba(0, 0, 0, 0.03); --gray-border: rgba(0, 0, 0, 0.15); --gray-light: rgba(0, 0, 0, 0.4); --gray-mid: rgba(0, 0, 0, 0.7); --gray-dark: rgba(0, 0, 0, 0.9); --body-color: var(--gray-mid); --headline-color: var(--gray-dark); --accent-color: #0066f0; --body-font-family: -apple-system, BlinkMacSystemFont, sans-serif; --radius: 6px; --form-width: 343px; } /* Base */ * { box-sizing: border-box; } body { font-family: var(--body-font-family); font-size: 16px; color: var(--body-color); -webkit-font-smoothing: antialiased; } h1, h2, h3, h4, h5, h6 { color: var(--body-color); margin-top: 2px; margin-bottom: 4px; } h1 { font-size: 27px; color: var(--headline-color); } h4 { font-weight: 500; font-size: 14px; color: var(--gray-light); } /* Layout */ .sr-root { display: flex; flex-direction: row; width: 100%; max-width: 980px; padding: 48px; align-content: center; justify-content: center; height: auto; min-height: 100vh; margin: 0 auto; } .sr-header { margin-bottom: 32px; } .sr-payment-summary { margin-bottom: 20px; } .sr-main, .sr-content { display: flex; flex-direction: column; justify-content: center; height: 100%; align-self: center; } .sr-main { width: var(--form-width); } .sr-content { padding-left: 48px; } .sr-header__logo { background-image: var(--logo-image); height: 24px; background-size: contain; background-repeat: no-repeat; width: 100%; } .sr-legal-text { color: var(--gray-light); text-align: center; font-size: 13px; line-height: 17px; margin-top: 12px; } .sr-field-error { color: var(--accent-color); text-align: left; font-size: 13px; line-height: 17px; margin-top: 12px; } /* Form */ .sr-form-row { margin: 16px 0; } label { font-size: 13px; font-weight: 500; margin-bottom: 8px; display: inline-block; } /* Inputs */ .sr-input, .sr-select, input[type="text"] { border: 1px solid var(--gray-border); border-radius: var(--radius); padding: 5px 12px; height: 44px; width: 100%; transition: box-shadow 0.2s ease; background: white; -moz-appearance: none; -webkit-appearance: none; appearance: none; color: #32325d; } .sr-input:focus, input[type="text"]:focus, button:focus, .focused { box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 0 0 0 4px rgba(50, 151, 211, 0.3); outline: none; z-index: 9; } .sr-input::placeholder, input[type="text"]::placeholder { color: var(--gray-light); } /* Checkbox */ .sr-checkbox-label { position: relative; cursor: pointer; } .sr-checkbox-label input { opacity: 0; margin-right: 6px; } .sr-checkbox-label .sr-checkbox-check { position: absolute; left: 0; height: 16px; width: 16px; background-color: white; border: 1px solid var(--gray-border); border-radius: 4px; transition: all 0.2s ease; } .sr-checkbox-label input:focus ~ .sr-checkbox-check { box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 0 0 0 4px rgba(50, 151, 211, 0.3); outline: none; } .sr-checkbox-label input:checked ~ .sr-checkbox-check { background-color: var(--accent-color); background-repeat: no-repeat; background-size: 16px; background-position: -1px -1px; } /* Select */ .sr-select { display: block; height: 44px; margin: 0; background-repeat: no-repeat, repeat; background-position: right 12px top 50%, 0 0; background-size: 0.65em auto, 100%; } .sr-select::-ms-expand { display: none; } .sr-select:hover { cursor: pointer; } .sr-select:focus { box-shadow: 0 0 0 1px rgba(50, 151, 211, 0.3), 0 1px 1px 0 rgba(0, 0, 0, 0.07), 0 0 0 4px rgba(50, 151, 211, 0.3); outline: none; } .sr-select option { font-weight: 400; } .sr-select:invalid { color: var(--gray-light); } /* Combo inputs */ .sr-combo-inputs { display: flex; flex-direction: column; } .sr-combo-inputs input, .sr-combo-inputs .sr-select { border-radius: 0; border-bottom: 0; } .sr-combo-inputs > input:first-child, .sr-combo-inputs > .sr-select:first-child { border-radius: var(--radius) var(--radius) 0 0; } .sr-combo-inputs > input:last-child, .sr-combo-inputs > .sr-select:last-child { border-radius: 0 0 var(--radius) var(--radius); border-bottom: 1px solid var(--gray-border); } .sr-combo-inputs > .sr-combo-inputs-row:last-child input:first-child { border-radius: 0 0 0 var(--radius); border-bottom: 1px solid var(--gray-border); } .sr-combo-inputs > .sr-combo-inputs-row:last-child input:last-child { border-radius: 0 0 var(--radius) 0; border-bottom: 1px solid var(--gray-border); } .sr-combo-inputs > .sr-combo-inputs-row:first-child input:first-child { border-radius: var(--radius) 0 0 0; } .sr-combo-inputs > .sr-combo-inputs-row:first-child input:last-child { border-radius: 0 var(--radius) 0 0; } .sr-combo-inputs > .sr-combo-inputs-row:first-child input:only-child { border-radius: var(--radius) var(--radius) 0 0; } .sr-combo-inputs-row { width: 100%; display: flex; } .sr-combo-inputs-row > input { width: 100%; border-radius: 0; } .sr-combo-inputs-row > input:first-child:not(:only-child) { border-right: 0; } .sr-combo-inputs-row:not(:first-of-type) .sr-input { border-radius: 0 0 var(--radius) var(--radius); } .sr-result { height: 44px; -webkit-transition: height 1s ease; -moz-transition: height 1s ease; -o-transition: height 1s ease; transition: height 1s ease; color: var(--font-color); overflow: auto; } .sr-result code { overflow: scroll; } .sr-result.expand { height: 350px; } /* Buttons and links */ button { background: var(--accent-color); border-radius: var(--radius); color: white; border: 0; padding: 12px 16px; margin-top: 16px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: block; } button:hover { filter: contrast(115%); } button:active { transform: translateY(0px) scale(0.98); filter: brightness(0.9); } button:disabled { opacity: 0.5; cursor: not-allowed; } .sr-payment-form button, .fullwidth { width: 100%; } a { color: var(--accent-color); text-decoration: none; transition: all 0.2s ease; } a:hover { filter: brightness(0.8); } a:active { filter: brightness(0.5); } /* Code block */ .sr-callout { background: var(--gray-offset); padding: 12px; border-radius: var(--radius); max-height: 200px; overflow: auto; } code, pre { font-family: "SF Mono", "IBM Plex Mono", "Menlo", monospace; font-size: 12px; } /* Stripe Element placeholder */ .sr-card-element { padding-top: 12px; } /* Responsiveness */ @media (max-width: 720px) { .sr-root { flex-direction: column; justify-content: flex-start; padding: 48px 20px; min-width: 320px; } .sr-header__logo { background-position: center; } .sr-payment-summary { text-align: center; } .sr-content { display: none; } .sr-main { width: 100%; } } /* Pasha styles – Brand-overrides, can split these out */ :root { --accent-color: #ed5f74; --headline-color: var(--accent-color); } .pasha-image-stack { display: grid; grid-gap: 12px; grid-template-columns: auto auto; } .pasha-image-stack img { border-radius: var(--radius); background-color: var(--gray-border); box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.07); transition: all 0.8s ease; opacity: 0; } .pasha-image-stack img:nth-child(1) { transform: translate(12px, -12px); opacity: 1; } .pasha-image-stack img:nth-child(2) { transform: translate(-24px, 16px); opacity: 1; } .pasha-image-stack img:nth-child(3) { transform: translate(68px, -100px); opacity: 1; } /* todo: spinner/processing state, errors, animations */ .spinner, .spinner:before, .spinner:after { border-radius: 50%; } .spinner { color: #ffffff; font-size: 22px; text-indent: -99999px; margin: 0px auto; position: relative; width: 20px; height: 20px; box-shadow: inset 0 0 0 2px; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); } .spinner:before, .spinner:after { position: absolute; content: ""; } .spinner:before { width: 10.4px; height: 20.4px; background: var(--accent-color); border-radius: 20.4px 0 0 20.4px; top: -0.2px; left: -0.2px; -webkit-transform-origin: 10.4px 10.2px; transform-origin: 10.4px 10.2px; -webkit-animation: loading 2s infinite ease 1.5s; animation: loading 2s infinite ease 1.5s; } .spinner:after { width: 10.4px; height: 10.2px; background: var(--accent-color); border-radius: 0 10.2px 10.2px 0; top: -0.1px; left: 10.2px; -webkit-transform-origin: 0px 10.2px; transform-origin: 0px 10.2px; -webkit-animation: loading 2s infinite ease; animation: loading 2s infinite ease; } @-webkit-keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } /* Animated form */ .sr-root { animation: 0.4s form-in; animation-fill-mode: both; animation-timing-function: ease; } .sr-payment-form .sr-form-row { animation: 0.4s field-in; animation-fill-mode: both; animation-timing-function: ease; transform-origin: 50% 0%; } /* need saas for loop :D */ .sr-payment-form .sr-form-row:nth-child(1) { animation-delay: 0; } .sr-payment-form .sr-form-row:nth-child(2) { animation-delay: 60ms; } .sr-payment-form .sr-form-row:nth-child(3) { animation-delay: 120ms; } .sr-payment-form .sr-form-row:nth-child(4) { animation-delay: 180ms; } .sr-payment-form .sr-form-row:nth-child(5) { animation-delay: 240ms; } .sr-payment-form .sr-form-row:nth-child(6) { animation-delay: 300ms; } .hidden { display: none; } @keyframes field-in { 0% { opacity: 0; transform: translateY(8px) scale(0.95); } 100% { opacity: 1; transform: translateY(0px) scale(1); } } @keyframes form-in { 0% { opacity: 0; transform: scale(0.98); } 100% { opacity: 1; transform: scale(1); } } ``` ```css /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in iOS. */ html { line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Remove the margin in all browsers. */ body { margin: 0; } /** * Render the `main` element consistently in IE. */ main { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * Remove the gray background on active links in IE 10. */ a { background-color: transparent; } /** * 1. Remove the bottom border in Chrome 57- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Remove the border on images inside links in IE 10. */ img { border-style: none; } /* Forms ========================================================================== */ /** * 1. Change the font styles in all browsers. * 2. Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * Correct the inability to style clickable types in iOS and Safari. */ button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } /** * Correct the padding in Firefox. */ fieldset { padding: 0.35em 0.75em 0.625em; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { vertical-align: baseline; } /** * Remove the default vertical scrollbar in IE 10+. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10. * 2. Remove the padding in IE 10. */ [type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding in Chrome and Safari on macOS. */ [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in Edge, IE 10+, and Firefox. */ details { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Misc ========================================================================== */ /** * Add the correct display in IE 10+. */ template { display: none; } /** * Add the correct display in IE 10. */ [hidden] { display: none; } ``` ```html Identity verification submitted

Thanks for submitting your identity document.

We are processing your verification.

Restart demo
``` ````md # Identity verification This sample shows you how to integrate with Stripe [Identity](https://stripe.com/docs/identity). ### Integrations included: - [**Modal**] The modal integration allows you to collect identity documents as part of your existing flows. It also limits the amount of private information you handle on your site, allows you to support users in a variety of platforms and languages, and allows you to customize the style to match your branding. ## How to run locally This sample includes several server implementations. Follow the steps below to run locally. **1. Clone and configure the sample** The Stripe CLI is the fastest way to clone and configure a sample to run locally. **Using the Stripe CLI** 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. In your terminal shell, run the Stripe CLI command to clone the sample: ``` stripe samples create identity ``` 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. **Installing and cloning manually** If you do not want to use the Stripe CLI, you can manually clone and configure the sample yourself: ``` git clone https://github.com/stripe-samples/identity ``` Copy the `.env` file into a file named `.env` in the 'server' folder. For example: ``` cp .env server/.env ``` 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#api-keys) to find your API keys. ``` STRIPE_PUBLISHABLE_KEY= STRIPE_SECRET_KEY= ``` `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. **2. Follow the server instructions on how to run:** follow the instructions in the 'server' folder README on how to run. For example, if you are running the Node server: ``` cd server/ # there's a README in this folder with instructions npm install npm start ``` **3. [Optional] Run a webhook locally:** If you want to test webhook piece of the integration with a local webhook on your machine, you can use the Stripe CLI to easily spin one up. Make sure to [install the CLI](https://stripe.com/docs/stripe-cli) and [link your Stripe account](https://stripe.com/docs/stripe-cli#link-account). ``` stripe listen --forward-to localhost:4242/webhook ``` The CLI will print a webhook secret key to the console. Set `STRIPE_WEBHOOK_SECRET` to this value in your `.env` file. You should see events logged in the console where the CLI is running. When you are ready to create a live webhook endpoint, follow our guide in the docs on [configuring a webhook endpoint in the dashboard](https://stripe.com/docs/webhooks/setup#configure-webhook-settings). ## FAQ Q: Why did you pick these frameworks? 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. ## Get support If you found a bug or want to suggest a new [feature/use case/sample], please [file an issue](../../issues). If you have questions, comments, or need help with code, we're here to help: - on [Discord](https://stripe.com/go/developer-chat) - on Twitter at [@StripeDev](https://twitter.com/StripeDev) - on Stack Overflow at the [stripe-payments](https://stackoverflow.com/tags/stripe-payments/info) tag Sign up to [stay updated with developer news](https://go.stripe.global/dev-digest). ## Authors - [@bz-stripe](https://twitter.com/atav32) - [@cjavilla-stripe](https://twitter.com/cjav_dev) ```` ```bash {% step id="set-env-vars" %} # Stripe keys # https://dashboard.stripe.com/test/apikeys STRIPE_PUBLISHABLE_KEY={% key type="publishable" /%} STRIPE_SECRET_KEY={% key type="secret" /%} # https://stripe.com/docs/webhooks#verify-events STRIPE_WEBHOOK_SECRET=whsec_1234 # Environment variables STATIC_DIR=../client {% /step %} ``` ```txt MIT License Copyright (c) 2020 Stripe, Inc. (https://stripe.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ```bash .env .DS_Store .vscode # Dependencies node_modules package-lock.json yarn.lock !/yarn.lock composer.lock # Ruby files Gemfile.lock # Python files __pycache__ venv env # PHP files vendor logs # Java files .settings target/ .classpath .factorypath .project # Typescript dist # Dotnet files obj/ bin/ ``` ```go package main import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" "github.com/joho/godotenv" "github.com/stripe/stripe-go/v72" "github.com/stripe/stripe-go/v72/identity/verificationsession" "github.com/stripe/stripe-go/v72/webhook" ) func main() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } stripe.Key = os.Getenv("STRIPE_SECRET_KEY") // For sample support and debugging, not required for production: stripe.SetAppInfo(&stripe.AppInfo{ Name: "stripe-samples/identity/modal", Version: "0.0.1", URL: "https://github.com/stripe-samples", }) http.Handle("/", http.FileServer(http.Dir(os.Getenv("STATIC_DIR")))) http.HandleFunc("/config", handleConfig) http.HandleFunc("/create-verification-session", handleCreateVerificationSession) http.HandleFunc("/webhook", handleWebhook) log.Println("server running at 0.0.0.0:4242") http.ListenAndServe("0.0.0.0:4242", nil) } // ErrorResponseMessage represents the structure of the error // object sent in failed responses. type ErrorResponseMessage struct { Message string `json:"message"` } // ErrorResponse represents the structure of the error object sent // in failed responses. type ErrorResponse struct { Error *ErrorResponseMessage `json:"error"` } func handleConfig(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } writeJSON(w, struct { PublishableKey string `json:"publishableKey"` }{ PublishableKey: os.Getenv("STRIPE_PUBLISHABLE_KEY"), }) } func handleCreateVerificationSession(w http.ResponseWriter, r *http.Request) { params := &stripe.IdentityVerificationSessionParams{ Type: stripe.String("document"), // Additional options for configuring the verification session: // Options: &stripe.IdentityVerificationSessionOptionsParams{ // Document: &stripe.IdentityVerificationSessionOptionsDocumentParams{ // // Array of strings of allowed identity document types. // AllowedTypes: stripe.StringSlice([]string{"driving_license"}), // passport | id_card // // // Collect an ID number and perform an ID number check with the // // document’s extracted name and date of birth. // RequireIDNumber: stripe.Bool(true), // // // Disable image uploads, identity document images have to be captured // // using the device’s camera. // RequireLiveCapture: stripe.Bool(true), // // // Capture a face image and perform a selfie check comparing a photo // // ID and a picture of your user’s face. // RequireMatchingSelfie: stripe.Bool(true), // } // }, } params.AddMetadata("user_id", "{{USER_ID}}") vs, err := verificationsession.New(params) if err != nil { // Try to safely cast a generic error to a stripe.Error so that we can get at // some additional Stripe-specific information about what went wrong. if stripeErr, ok := err.(*stripe.Error); ok { fmt.Printf("Other Stripe error occurred: %v\n", stripeErr.Error()) writeJSONErrorMessage(w, stripeErr.Error(), 400) } else { fmt.Printf("Other error occurred: %v\n", err.Error()) writeJSONErrorMessage(w, "Unknown server error", 500) } return } writeJSON(w, struct { ClientSecret string `json:"client_secret"` }{ ClientSecret: vs.ClientSecret, }) } func handleWebhook(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } b, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("ioutil.ReadAll: %v", err) return } event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), os.Getenv("STRIPE_WEBHOOK_SECRET")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("webhook.ConstructEvent: %v", err) return } switch event.Type { case "identity.verification_session.verified": fmt.Fprintf(os.Stdout, "All the verification checks passed\n") var verificationSession stripe.IdentityVerificationSession err := json.Unmarshal(event.Data.Raw, &verificationSession) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } case "identity.verification_session.requires_input": fmt.Fprintf(os.Stdout, "At least one of the verification checks failed\n") var verificationSession stripe.IdentityVerificationSession err := json.Unmarshal(event.Data.Raw, &verificationSession) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } switch verificationSession.LastError.Code { case "document_unverified_other": fmt.Fprintf(os.Stdout, "The document was invalid") case "document_expired": fmt.Fprintf(os.Stdout, "The document was expired") case "document_type_not_supported": fmt.Fprintf(os.Stdout, "The document type was not supported") default: fmt.Fprintf(os.Stdout, "Other document error code") } default: fmt.Fprintf(os.Stdout, "Unhandled event type: %v", event.Type) } writeJSON(w, nil) } func writeJSON(w http.ResponseWriter, v interface{}) { var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(v); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewEncoder.Encode: %v", err) return } w.Header().Set("Content-Type", "application/json") if _, err := io.Copy(w, &buf); err != nil { log.Printf("io.Copy: %v", err) return } } func writeJSONError(w http.ResponseWriter, v interface{}, code int) { w.WriteHeader(code) writeJSON(w, v) return } func writeJSONErrorMessage(w http.ResponseWriter, message string, code int) { resp := &ErrorResponse{ Error: &ErrorResponseMessage{ Message: message, }, } writeJSONError(w, resp, code) } ``` ```go module stripe-sample go 1.15 require ( github.com/joho/godotenv v1.4.0 github.com/stripe/stripe-go/v72 v72.117.0 gopkg.in/yaml.v2 v2.2.8 // indirect ) ``` ```go github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stripe/stripe-go/v72 v72.117.0 h1:vXascEDirGvHST7FURr+TMlTIyBnrxY651IvIIOkOQI= github.com/stripe/stripe-go/v72 v72.117.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ``` ```java package com.stripe.sample; import java.util.HashMap; import java.nio.file.Paths; import static spark.Spark.get; import static spark.Spark.post; import static spark.Spark.staticFiles; import static spark.Spark.port; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.stripe.Stripe; import com.stripe.net.ApiResource; import com.stripe.model.Event; import com.stripe.model.StripeObject; import com.stripe.model.EventDataObjectDeserializer; import com.stripe.model.identity.VerificationSession; import com.stripe.exception.*; import com.stripe.net.Webhook; import com.stripe.param.identity.VerificationSessionCreateParams; import io.github.cdimascio.dotenv.Dotenv; public class Server { private static Gson gson = new Gson(); static class ConfigResponse { private String publishableKey; public ConfigResponse(String publishableKey) { this.publishableKey = publishableKey; } } static class FailureResponse { private HashMap error; public FailureResponse(String message) { this.error = new HashMap(); this.error.put("message", message); } } static class CreateVerificationSessionResponse { @SerializedName("client_secret") private String clientSecret; public CreateVerificationSessionResponse(String clientSecret) { this.clientSecret = clientSecret; } } public static void main(String[] args) { port(4242); Dotenv dotenv = Dotenv.load(); Stripe.apiKey = dotenv.get("STRIPE_SECRET_KEY"); // For sample support and debugging, not required for production: Stripe.setAppInfo( "stripe-samples/identity/modal", "0.0.1", "https://github.com/stripe-samples" ); staticFiles.externalLocation( Paths.get( Paths.get("").toAbsolutePath().toString(), dotenv.get("STATIC_DIR") ).normalize().toString()); get("/config", (request, response) -> { response.type("application/json"); return gson.toJson(new ConfigResponse(dotenv.get("STRIPE_PUBLISHABLE_KEY"))); }); post("/create-verification-session", (request, response) -> { response.type("application/json"); VerificationSessionCreateParams createParams = new VerificationSessionCreateParams .Builder() .setType(VerificationSessionCreateParams.Type.DOCUMENT) .build(); try { // Create a VerificationSession VerificationSession session = VerificationSession.create(createParams); // Send VerificationSession client_secret to client return gson.toJson(new CreateVerificationSessionResponse(session.getClientSecret())); } catch(StripeException e) { response.status(400); return gson.toJson(new FailureResponse(e.getMessage())); } catch(Exception e) { response.status(500); return gson.toJson(e); } }); post("/webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); String endpointSecret = dotenv.get("STRIPE_WEBHOOK_SECRET"); Event event = null; try { event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return ""; } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; switch(event.getType()) { case "identity.verification_session.verified": // All the verification checks passed if (dataObjectDeserializer.getObject().isPresent()) { VerificationSession verificationSession = (VerificationSession) dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } break; case "identity.verification_session.requires_input": // At least one of the verification checks failed if (dataObjectDeserializer.getObject().isPresent()) { VerificationSession verificationSession = (VerificationSession) dataObjectDeserializer.getObject().get(); switch(verificationSession.getLastError().getCode()) { case "document_unverified_other": // the document was invalid break; case "document_expired": // the document was expired break; case "document_type_not_supported": // document type not supported break; default: // ... } } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } break; default: // other event type } response.status(200); return ""; }); } } ``` ```xml 4.0.0 com.stripe.sample accept-a-payment 1.0.0-SNAPSHOT org.slf4j slf4j-simple 1.7.36 com.sparkjava spark-core 2.9.3 com.google.code.gson gson 2.9.0 com.stripe stripe-java 20.131.0 io.github.cdimascio java-dotenv 5.2.2 sample org.apache.maven.plugins maven-compiler-plugin 3.10.1 1.8 1.8 maven-assembly-plugin package single jar-with-dependencies Server ``` ````md \# Name of sample ## Requirements - Maven - Java - [Configured .env file](../README.md) 1. Build the jar ``` mvn package ``` 2. Run the packaged jar ``` java -cp target/sample-jar-with-dependencies.jar com.stripe.sample.Server ``` 3. Go to `localhost:4242` in your browser to see the demo ```` ```python #! /usr/bin/env python3.6 import stripe import json import os from flask import Flask, render_template, jsonify, request, send_from_directory from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) # For sample support and debugging, not required for production: stripe.set_app_info( 'stripe-samples/identity/modal', version='0.0.1', url='https://github.com/stripe-samples') stripe.api_version = '2020-08-27' stripe.api_key = os.getenv('STRIPE_SECRET_KEY') static_dir = str(os.path.abspath(os.path.join(__file__ , "..", os.getenv("STATIC_DIR")))) app = Flask(__name__, static_folder=static_dir, static_url_path="", template_folder=static_dir) @app.route('/', methods=['GET']) def get_root(): return render_template('index.html') @app.route('/config', methods=['GET']) def get_config(): return jsonify({'publishableKey': os.getenv('STRIPE_PUBLISHABLE_KEY')}) @app.route('/create-verification-session', methods=['POST']) def create_verification_session(): try: verification_session = stripe.identity.VerificationSession.create( type='document', metadata={ 'user_id': '{{USER_ID}}', } ) return jsonify({'client_secret': verification_session.client_secret}) except stripe.error.StripeError as e: return jsonify({'error': {'message': str(e)}}), 400 except Exception as e: return jsonify({'error': {'message': str(e)}}), 400 @app.route('/webhook', methods=['POST']) def webhook_received(): # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET') request_data = json.loads(request.data) if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] if event['type'] == 'identity.verification_session.verified': print("All the verification checks passed") verification_session = data_object elif event['type'] == 'identity.verification_session.requires_input': print("At least one verification check failed") verification_session = data_object if verification_session.last_error.code == 'document_unverified_other': print("The document was invalid") elif verification_session.last_error.code == 'document_expired': print("The document was expired") elif verification_session.last_error.code == 'document_type_not_suported': print("The document type was not supported") else: print("other error code") return jsonify({'status': 'success'}) if __name__ == '__main__': app.run(port=4242, debug=True) ``` ````md \# Name of sample ## Requirements## Requirements - Python 3 - [Configured .env file](../README.md) ## How to run 1. Create and activate a new virtual environment **MacOS / Unix** ``` python3 -m venv env source env/bin/activate ``` **Windows (PowerShell)** ``` python3 -m venv env .\env\Scripts\activate.bat ``` 2. Install dependencies ``` pip install -r requirements.txt ``` 3. Export and run the application **MacOS / Unix** ``` export FLASK_APP=server.py python3 -m flask run --port=4242 ``` **Windows (PowerShell)** ``` $env:FLASK_APP=“server.py" python3 -m flask run --port=4242 ``` 4. Go to `localhost:4242` in your browser to see the demo ```` ```txt Flask==2.1.2 python-dotenv==0.20.0 stripe==3.5.0 ``` ```ruby # frozen_string_literal: true require 'stripe' require 'sinatra' require 'dotenv' # Replace if using a different env file or config Dotenv.load # For sample support and debugging, not required for production: Stripe.set_app_info( 'stripe-samples/identity/modal', version: '0.0.1', url: 'https://github.com/stripe-samples' ) Stripe.api_version = '2020-08-27' Stripe.api_key = ENV['STRIPE_SECRET_KEY'] set :static, true set :public_folder, File.join(File.dirname(__FILE__), ENV['STATIC_DIR']) set :port, 4242 get '/' do content_type 'text/html' send_file File.join(settings.public_folder, 'index.html') end get '/config' do content_type 'application/json' { publishableKey: ENV['STRIPE_PUBLISHABLE_KEY'], }.to_json end post '/create-verification-session' do content_type 'application/json' # See https://stripe.com/docs/api/identity/verification_sessions/create # for the full list of accepted parameters. verification_session = Stripe::Identity::VerificationSession.create({ type: 'document', # 'id_number' | 'address' metadata: { user_id: '{{USER_ID}}', # Optionally pass the ID of the user in your system. }, # Additional options for configuring the verification session: # options: { # document: { # # Array of strings of allowed identity document types. # allowed_types: ['driving_license'], # passport | id_card # # # Collect an ID number and perform an ID number check with the # # document’s extracted name and date of birth. # require_id_number: true, # # # Disable image uploads, identity document images have to be captured # # using the device’s camera. # require_live_capture: true, # # # Capture a face image and perform a selfie check comparing a photo # # ID and a picture of your user’s face. # require_matching_selfie: true, # } # }, }) # Send the VerificationSession client_secret to the client. { client_secret: verification_session.client_secret }.to_json end post '/webhook' do # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = ENV['STRIPE_WEBHOOK_SECRET'] payload = request.body.read if !webhook_secret.empty? # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end case event.type when 'identity.verification_session.requires_input' verification_session = event.data.object puts " ❌ Identity requires input from user: #{verification_session.id}" # At least one of the verification checks failed case verification_session.last_error.code when 'document_unverified_other' # The document was invalid when 'document_expired' # The document was expired when 'document_type_not_suported' # The document type was not supported else # ... end when 'identity.verification_session.verified' verification_session = event.data.object puts " ✅ Identity verified: #{verification_session.id}" when 'identity.verification_session.canceled', 'identity.verification_session.created', 'identity.verification_session.processing' verification_session = event.data.object puts " 🟡 #{event.type}: #{verification_session.id}" end content_type 'application/json' { status: 'success' }.to_json end ``` ```ruby source 'https://rubygems.org/' gem 'dotenv' gem 'json' gem 'sinatra' gem 'stripe', '5.55.0' gem 'webrick' ``` ````md \# Name of sample A [Sinatra](http://sinatrarb.com/) implementation. ## Requirements - Ruby v2.4.5+ - [Configured .env file](../README.md) ## How to run 1. Install dependencies ``` bundle install ``` 2. Run the application ``` ruby server.rb ``` 3. Go to `http://localhost:4242` in your browser to see the demo ```` ```csharp using System.Collections.Generic; public class StripeOptions { public string PublishableKey { get; set; } public string SecretKey { get; set; } public string WebhookSecret { get; set; } } ``` ```csharp using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Stripe; using Stripe.Identity; namespace server.Controllers { public class IdentityController : Controller { public readonly IOptions options; private readonly IStripeClient client; public IdentityController(IOptions options) { this.options = options; this.client = new StripeClient(this.options.Value.SecretKey); } [HttpGet("config")] public ConfigResponse GetConfig() { // return json: publishableKey (.env) return new ConfigResponse { PublishableKey = this.options.Value.PublishableKey, }; } [HttpPost("create-verification-session")] public async Task CreateVerificationSession() { var options = new VerificationSessionCreateOptions { Type = "document", }; var service = new VerificationSessionService(this.client); try { var verificationSession = await service.CreateAsync(options); Response.Headers["Location"] = verificationSession.Url; return StatusCode(303); } catch (StripeException e) { return BadRequest(new { error = new { message = e.StripeError.Message}}); } catch (System.Exception) { return BadRequest(new { error = new { message = "unknown failure: 500"}}); } } [HttpPost("webhook")] public async Task Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try { stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], this.options.Value.WebhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } // If on SDK version < 46, use class Events instead of EventTypes if (stripeEvent.Type == EventTypes.IdentityVerificationSessionVerified) { var verificationSession = stripeEvent.Data.Object as VerificationSession; // All the verification checks passed } else if (stripeEvent.Type == EventTypes.IdentityVerificationSessionRequiresInput) { var verificationSession = stripeEvent.Data.Object as VerificationSession; if (verificationSession.LastError.Code == "document_unverified_other") { // The document was invalid } else if (verificationSession.LastError.Code == "document_expired") { // The document was expired } else if (verificationSession.LastError.Code == "document_type_not_supported") { // The document type was not supported } else { // ... } } return Ok(); } } } ``` ```json { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:4242" } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "server": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:4242", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ``` ```csharp using Newtonsoft.Json; public class ConfigResponse { [JsonProperty("publishableKey")] public string PublishableKey { get; set; } } ``` ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace server { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { DotNetEnv.Env.Load(); return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); webBuilder.UseWebRoot(Environment.GetEnvironmentVariable("STATIC_DIR")); }); } } } ``` ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json.Serialization; using Stripe; namespace server { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // For sample support and debugging, not required for production: StripeConfiguration.AppInfo = new AppInfo { Name = "stripe-samples/identity/redirect", Url = "https://github.com/stripe-samples", Version = "0.0.1", }; services.Configure(options => { options.PublishableKey = Environment.GetEnvironmentVariable("STRIPE_PUBLISHABLE_KEY"); options.SecretKey = Environment.GetEnvironmentVariable("STRIPE_SECRET_KEY"); options.WebhookSecret = Environment.GetEnvironmentVariable("STRIPE_WEBHOOK_SECRET"); }); services.AddControllersWithViews().AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy(), }; }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } //app.UseHttpsRedirection(); app.UseFileServer(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } } ``` ````md # Accept a payment An [.NET Core](https://dotnet.microsoft.com/download/dotnet-core) implementation You can [🎥 watch a video](https://www.youtube.com/watch?v=mqEjRgoZWdo) to see how this server was implemented and [read the transcripts](./TRANSCRIPTS.md). ## Requirements * .NET Core * [Configured .env file](../../README.md) ## How to run 1. Confirm `.env` configuration Ensure the API keys are configured in `.env` in this directory. It should include the following keys: ```yaml # Stripe API keys - see https://stripe.com/docs/get-started/development-environment#api-keys STRIPE_PUBLISHABLE_KEY=pk_test... STRIPE_SECRET_KEY=sk_test... # Required to verify signatures in the webhook handler. # See README on how to use the Stripe CLI to test webhooks STRIPE_WEBHOOK_SECRET=whsec_... # Path to front-end implementation. Note: PHP has it's own front end implementation. STATIC_DIR=../../client/html DOMAIN=http://localhost:4242 ``` 2. Run the application ``` dotnet run Program.cs ``` 4. If you're using the html client, go to `localhost:4242` to see the demo. For react, visit `localhost:3000`. ```` ```cs Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server", "server.csproj", "{A5BE002D-4BA4-4611-B86E-87602A060827}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A5BE002D-4BA4-4611-B86E-87602A060827}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A5BE002D-4BA4-4611-B86E-87602A060827}.Debug|Any CPU.Build.0 = Debug|Any CPU {A5BE002D-4BA4-4611-B86E-87602A060827}.Release|Any CPU.ActiveCfg = Release|Any CPU {A5BE002D-4BA4-4611-B86E-87602A060827}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal ``` ```xml net8.0 ``` ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" } ``` ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ``` ```csharp using Newtonsoft.Json; public class CreateVerificationSessionResponse { [JsonProperty("client_secret")] public string ClientSecret { get; set; } } ``` ```typescript import env from 'dotenv'; import path from 'path'; // Replace if using a different env file or config. env.config({ path: './.env' }); import bodyParser from 'body-parser'; import express from 'express'; import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2020-08-27', appInfo: { // For sample support and debugging, not required for production: name: 'stripe-samples/identity/modal', url: 'https://github.com/stripe-samples', version: '0.0.1', }, typescript: true, }); const app = express(); const resolve = path.resolve; app.use(express.static(process.env.STATIC_DIR)); app.use( ( req: express.Request, res: express.Response, next: express.NextFunction ): void => { if (req.originalUrl === '/webhook') { next(); } else { bodyParser.json()(req, res, next); } } ); app.get('/', (_: express.Request, res: express.Response): void => { // Serve checkout page. const indexPath = resolve(process.env.STATIC_DIR + '/index.html'); res.sendFile(indexPath); }); app.get('/config', (_: express.Request, res: express.Response): void => { // Serve checkout page. res.send({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY }); }); app.post( '/create-verification-session', async (req: express.Request, res: express.Response): Promise => { try { const verificationSession: Stripe.Identity.VerificationSession = await stripe.identity.verificationSessions.create({ type: 'document', metadata: { user_id: '{{USER_ID}}', } // Additional options for configuring the verification session: // options: { // document: { // # Array of strings of allowed identity document types. // allowed_types: ['driving_license'], # passport | id_card // // # Collect an ID number and perform an ID number check with the // # document’s extracted name and date of birth. // require_id_number: true, // // # Disable image uploads, identity document images have to be captured // # using the device’s camera. // require_live_capture: true, // // # Capture a face image and perform a selfie check comparing a photo // # ID and a picture of your user’s face. // require_matching_selfie: true, // } // }, }); // Send publishable key and PaymentIntent client_secret to client. res.send({ client_secret: verificationSession.client_secret, }); } catch (e) { console.log(e); res.status(400).send({ error: { message: e.message, } }); } } ); // Expose a endpoint as a webhook handler for asynchronous events. // Configure your webhook in the stripe developer dashboard: // https://dashboard.stripe.com/test/webhooks app.post( '/webhook', // Use body-parser to retrieve the raw body as a buffer. bodyParser.raw({ type: 'application/json' }), async (req: express.Request, res: express.Response): Promise => { // Retrieve the event by verifying the signature using the raw body and secret. let event: Stripe.Event; try { event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`); res.sendStatus(400); return; } // Extract the data from the event. const data: Stripe.Event.Data = event.data; const eventType: string = event.type; // Successfully constructed event switch (eventType) { case 'identity.verification_session.verified': { // All the verification checks passed const verificationSession: Stripe.Identity.VerificationSession = event.data.object as Stripe.Identity.VerificationSession; break; } case 'identity.verification_session.requires_input': { // At least one of the verification checks failed const verificationSession: Stripe.Identity.VerificationSession = event.data.object as Stripe.Identity.VerificationSession; console.log('Verification check failed: ' + verificationSession.last_error.reason); // Handle specific failure reasons switch (verificationSession.last_error.code) { case 'document_unverified_other': { // The document was invalid break; } case 'document_expired': { // The document was expired break; } case 'document_type_not_supported': { // document type not supported break; } default: { // ... } } } } res.sendStatus(200); } ); app.listen(4242, (): void => console.log(`Node server listening on port ${4242}!`) ); ``` ```json { "name": "stripe-sample-demo", "version": "1.0.0", "description": "A Stripe demo", "main": "dist/server.js", "scripts": { "prebuild": "tslint -c tslint.json -p tsconfig.json --fix", "build": "tsc", "prestart": "npm run build", "start": "node .", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "stripe-demos", "license": "ISC", "dependencies": { "body-parser": "^1.19.0", "dotenv": "latest", "express": "^4.17.1", "stripe": "9.13.0" }, "devDependencies": { "@types/express": "^4.17.2", "@types/node": "^17.0.1", "tslint": "^6.1.3", "typescript": "^4.3.5" } } ``` ````md # Name of sample A TypeScript [Express server](http://expressjs.com) implementation ## Requirements - Node v10+ - [Configured .env file](../README.md) ## How to run 1. Install dependencies ``` npm install ``` 2. Run the application ``` npm start ``` 3. Go to `localhost:4242` to see the demo ```` ```json { "compilerOptions": { "module": "commonjs", "esModuleInterop": true, "target": "es6", "noImplicitAny": true, "moduleResolution": "node", "sourceMap": true, "outDir": "dist", "baseUrl": ".", "paths": { "*": ["node_modules/*"] } }, "include": ["src/**/*"] } ``` ```json { "defaultSeverity": "error", "extends": ["tslint:recommended"], "jsRules": {}, "rules": { "trailing-comma": [false], "no-console": false, "quotemark": [true, "single"] }, "rulesDirectory": [] } ``` ```json { "name": "stripe-sample-demo", "version": "1.0.0", "description": "A Stripe demo", "main": "server.js", "scripts": { "start": "node server.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "stripe-demos", "license": "ISC", "dependencies": { "body-parser": "^1.19.0", "dotenv": "^16.0.0", "express": "^4.17.1", "stripe": "^8.48.0" } } ``` ```javascript const express = require('express'); const app = express(); const { resolve } = require('path'); // Replace if using a different env file or config const env = require('dotenv').config({ path: './.env' }); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, { apiVersion: '2020-08-27', appInfo: { // For sample support and debugging, not required for production: name: "stripe-samples/identity/modal", version: "0.0.1", url: "https://github.com/stripe-samples" } }); app.use(express.static(process.env.STATIC_DIR)); app.use( express.json({ // We need the raw body to verify webhook signatures. // Let's compute it only when hitting the Stripe webhook endpoint. verify: function(req, res, buf) { if (req.originalUrl.startsWith('/webhook')) { req.rawBody = buf.toString(); } } }) ); app.get('/', (req, res) => { const path = resolve(process.env.STATIC_DIR + '/index.html'); res.sendFile(path); }); app.get('/config', (req, res) => { res.send({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY, }); }); app.post('/create-verification-session', async (req, res) => { try { const verificationSession = await stripe.identity.verificationSessions.create({ type: 'document', metadata: { user_id: '{{USER_ID}}', } // Additional options for configuring the verification session: // options: { // document: { // # Array of strings of allowed identity document types. // allowed_types: ['driving_license'], # passport | id_card // // # Collect an ID number and perform an ID number check with the // # document’s extracted name and date of birth. // require_id_number: true, // // # Disable image uploads, identity document images have to be captured // # using the device’s camera. // require_live_capture: true, // // # Capture a face image and perform a selfie check comparing a photo // # ID and a picture of your user’s face. // require_matching_selfie: true, // } // }, }); // Send publishable key and PaymentIntent details to client res.send({ client_secret: verificationSession.client_secret }); } catch(e) { console.log(e) return res.status(400).send({ error: { message: e.message } }); } }); // Expose a endpoint as a webhook handler for asynchronous events. // Configure your webhook in the stripe developer dashboard // https://dashboard.stripe.com/test/webhooks app.post('/webhook', async (req, res) => { let data, eventType; // Check if webhook signing is configured. if (process.env.STRIPE_WEBHOOK_SECRET) { // Retrieve the event by verifying the signature using the raw body and secret. let event; let signature = req.headers['stripe-signature']; try { event = stripe.webhooks.constructEvent( req.rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`); return res.sendStatus(400); } data = event.data; eventType = event.type; } else { // Webhook signing is recommended, but if the secret is not configured in `config.js`, // we can retrieve the event data directly from the request body. data = req.body.data; eventType = req.body.type; } // Successfully constructed event switch (eventType) { case 'identity.verification_session.verified': { // All the verification checks passed const verificationSession = data.object; break; } case 'identity.verification_session.requires_input': { // At least one of the verification checks failed const verificationSession = data.object; console.log('Verification check failed: ' + verificationSession.last_error.reason); // Handle specific failure reasons switch (verificationSession.last_error.code) { case 'document_unverified_other': { // The document was invalid break; } case 'document_expired': { // The document was expired break; } case 'document_type_not_supported': { // document type not supported break; } default: { // ... } } } } res.sendStatus(200); }); app.listen(4242, () => console.log(`Node server listening at http://localhost:4242`)); ``` ````md \# Name of sample An [Express server](http://expressjs.com) implementation ## Requirements - Node v10+ - [Configured .env file](../README.md) ## How to run 1. Install dependencies ``` npm install ``` 2. Run the application ``` npm start ``` 3. Go to `localhost:4242` to see the demo ```` ```php identity->verificationSessions->create([ 'type' => 'document', 'metadata' => [ 'user_id' => '{{USER_ID}}', ] ]); echo json_encode(['client_secret' => $verification_session->client_secret]); ``` ```php $e->getMessage() ]); exit; } if ($event->type == 'identity.verification_session.verified') { // All the verification checks passed $verification_session = event->data->object; } elseif ($event->type == 'identity.verification_session.requires_input') { # At least one of the verification checks failed $verification_session = event->data->object; if ($verification_session->last_error->code == 'document_unverified_other') { # The document was invalid } elseif ($verification_session->last_error->code == 'document_expired') { # The document was expired } elseif $verification_session->last_error->code == 'document_type_not_suported') { # The document type was not supported } else { # ... } } echo json_encode(['status' => 'success']); ``` ```php

Missing .env

Make a copy of .env.example, place it in the same directory as composer.json, and name it .env, then populate the variables.

It should look something like the following, but contain your API keys:

STRIPE_PUBLISHABLE_KEY=pk_test...
STRIPE_SECRET_KEY=sk_test...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=http://localhost:4242

You can use this command to get started:

cp .env.example .env
load(); // Make sure the configuration file is good. if (!$_ENV['STRIPE_SECRET_KEY']) { ?>

Invalid .env

Make a copy of .env.example and name it .env, then populate the variables.

It should look something like the following, but contain your API keys:

STRIPE_PUBLISHABLE_KEY=pk_test...
STRIPE_SECRET_KEY=sk_test...
STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=http://localhost:4242

You can use this command to get started:

cp .env.example .env
$_ENV['STRIPE_SECRET_KEY'], 'stripe_version' => '2020-08-27', ]); ``` ```html Identity verification submitted

Thanks for submitting your identity document.

We are processing your verification.

Restart demo
``` ```json { "require": { "stripe/stripe-php": "^7.75", "vlucas/phpdotenv": "^5.3" }, "scripts": { "start": "cd public && php -S 0.0.0.0:4242" } } ``` ````md \# Your sample name An implementation in PHP ## Requirements - PHP ## How to run 1. Confirm `.env` configuration Ensure the API keys are configured in `.env` in this directory. It should include the following keys: ```yaml # Stripe API keys - see https://stripe.com/docs/get-started/development-environment#api-keys STRIPE_PUBLISHABLE_KEY=pk_test... STRIPE_SECRET_KEY=sk_test... # Required to verify signatures in the webhook handler. # See README on how to use the Stripe CLI to test webhooks STRIPE_WEBHOOK_SECRET=whsec_... # Path to front-end implementation. Note: PHP has it's own front end implementation. STATIC_DIR=../../client/html ``` 2. Run composer to set up dependencies ``` composer install ``` 3. Copy .env.example to .env and replace with your Stripe API keys ``` cp ../../.env.example .env ``` 4. Run the server locally ``` cd public php -S 127.0.0.1:4242 ``` 4. Go to [localhost:4242](http://localhost:4242) ```` ```php RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L] ``` ```php Stripe Identity Sample

Verify your identity to book

Get ready to take a photo of your ID and a selfie

``` ```php // Helper for displaying status messages. const addMessage = (message) => { const messagesDiv = document.querySelector('#messages'); messagesDiv.style.display = 'block'; const messageWithLinks = addDashboardLinks(message); messagesDiv.innerHTML += `> ${messageWithLinks}
`; console.log(`Debug: ${message}`); }; // Adds links for known Stripe objects to the Stripe dashboard. const addDashboardLinks = (message) => { const piDashboardBase = 'https://dashboard.stripe.com/test/payments'; return message.replace( /(pi_(\S*)\b)/g, `$1` ); }; ```