Step-by-Step Stripe Integration
Step 1: Request a Quote
This step has moved to its own consolidated page shared by both the Quote Engine and Stripe purchase flows.
➡️ Proceed to the new unified guide: Request a Quote
Once you have a quoteId, return here to continue with checkout.
With a successful quote response (see unified guide), a quoteId will be returned. You should persist this (e.g., URL param or session) for use during checkout.
Step 2: Make a Request to the Checkout endpoint
Once the customer has confirmed their quote details, and are ready to checkout, you will need to make a request to Embrace’s Checkout endpoint /quotes/{quoteId}/checkout.
More details on the checkout request, response, and the full endpoint can be found in our checkout endpoint page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
let publishableKey;
let clientSecret;
document.getElementById('quoteForm').addEventListener('submit', function(e) {
e.preventDefault();
// Create a URLSearchParams object from the current URL
const urlParams = new URLSearchParams(window.location.search);
// Get the value of 'quoteId' from the URL
const quoteId = urlParams.get('quoteId');
if(quoteId) {
// Send the quoteId via POST
fetch(`/checkout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(quoteId)
})
.then(response => response.json())
.then(data => {
if(data.stripePublishableKey && data.stripeSetupIntentClientSecret) {
// Set the stripe keys from the checkout response
publishableKey = data.stripePublishableKey;
clientSecret = data.stripeSetupIntentClientSecret;
} else {
console.error('Stripe keys not found in response:', data);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error submitting data.');
});
} else {
console.error('quoteId not found in URL:', quoteId);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// --------------------------------------
// ------- CONTINUED FROM STEP 1 --------
// --------------------------------------
// Add this BELOW the /submit-quote map and BEFORE app.Run()
app.MapPost("/checkout", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body, Encoding.UTF8);
var rawBody = await reader.ReadToEndAsync();
if (string.IsNullOrWhiteSpace(rawBody))
{
return Results.BadRequest(new { error = "Request body is empty" });
}
// Extract quoteId so we can build the Embrace URL while still forwarding unknown fields
string? quoteId = null;
try
{
using var doc = JsonDocument.Parse(rawBody);
if (doc.RootElement.TryGetProperty("quoteId", out var q))
{
quoteId = q.GetString();
}
}
catch { /* if parsing fails we'll let Embrace return an error */ }
if (string.IsNullOrWhiteSpace(quoteId))
{
return Results.BadRequest(new { error = "quoteId field is required in request body" });
}
var embraceRequest = new HttpRequestMessage(HttpMethod.Post, $"https://[embrace-test-endpoint]/v2/quotes/{quoteId}/checkout")
{
Content = new StringContent(rawBody, Encoding.UTF8, "application/json")
};
embraceRequest.Headers.Add("Cache-Control", "no-cache");
embraceRequest.Headers.Add("epi-apim-subscription-key", embraceApiKey);
var embraceResponse = await httpClient.SendAsync(embraceRequest);
var responseContent = await embraceResponse.Content.ReadAsStringAsync();
return Results.Content(responseContent, "application/json", embraceResponse.StatusCode);
});
// CONTINUED IN STEP 4
With a successful checkout response, you will receive the following:
stripePublishableKey- used to create a new instance of Stripe.stripeSetupIntentClientSecret- associated with this specific customer, and used to create a Stripe Element.
Example Response:
Step 3: Initialize Stripe and Display Checkout
Stripe offers front-end UI components called Stripe Elements. This is what we will use to display the checkout to the customer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<title>Accept a payment</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="checkout.css" />
<script src="https://js.stripe.com/v3/"></script>
<script src="checkout.js" defer></script>
</head>
<body>
<!-- Display a payment form -->
<form id="payment-form">
<div id="payment-element">
<!--Stripe.js injects the Payment Element-->
</div>
<button id="submit">
<div class="spinner hidden" id="spinner"></div>
<span id="button-text">Pay now</span>
</button>
<div id="payment-message" class="hidden"></div>
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Here is where you'll use the stripePublishableKey returned from checkout
const stripe = Stripe(publishableKey);
let paymentMethodId;
initialize();
document.querySelector("#payment-form").addEventListener("submit", handleSubmit);
async function initialize() {
const appearance = {
theme: 'stripe',
};
// Here is where you'll use the stripeSetupIntentClientSecret returned from checkout
elements = stripe.elements({ appearance, clientSecret });
const paymentElementOptions = {
layout: "tabs",
};
const paymentElement = elements.create("payment", paymentElementOptions);
paymentElement.mount("#payment-element");
}
// This is hit after the user clicks the button to pay
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { setupIntent, error } = await stripe.confirmSetup({
elements,
confirmParams: {},
redirect: "if_required"
});
if(setupIntent) {
// Get the payment_method ID, to send to the Purchase endpoint
paymentMethodId = setupIntent.payment_method;
// -----------------------------------------------------------------------------------
// ------- This is where you'll call the Purchase endpoint as seen in Step 4 -------
// -----------------------------------------------------------------------------------
} else {
console.error("Something went wrong");
showMessage("An unexpected error occurred.");
}
setLoading(false);
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageContainer.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/* Variables */
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 16px;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
height: 100vh;
width: 100vw;
}
form {
width: 30vw;
min-width: 500px;
align-self: center;
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
border-radius: 7px;
padding: 40px;
margin-top: auto;
margin-bottom: auto;
}
.hidden {
display: none;
}
#payment-message {
color: rgb(105, 115, 134);
font-size: 16px;
line-height: 20px;
padding-top: 12px;
text-align: center;
}
#payment-element {
margin-bottom: 24px;
}
/* Buttons and links */
button {
background: #0055DE;
font-family: Arial, sans-serif;
color: #ffffff;
border-radius: 4px;
border: 0;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: block;
transition: all 0.2s ease;
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
width: 100%;
}
button:hover {
filter: contrast(115%);
}
button:disabled {
opacity: 0.5;
cursor: default;
}
/* spinner/processing state, errors */
.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: #0055DE;
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: #0055DE;
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;
}
/* Payment status page */
#payment-status {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
row-gap: 30px;
width: 30vw;
min-width: 500px;
min-height: 380px;
align-self: center;
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
border-radius: 7px;
padding: 40px;
opacity: 0;
animation: fadeInAnimation 1s ease forwards;
}
#status-icon {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
width: 40px;
border-radius: 50%;
}
h2 {
margin: 0;
color: #30313D;
text-align: center;
}
a {
text-decoration: none;
font-size: 16px;
font-weight: 600;
font-family: Arial, sans-serif;
display: block;
}
a:hover {
filter: contrast(120%);
}
#details-table {
overflow-x: auto;
width: 100%;
}
table {
width: 100%;
font-size: 14px;
border-collapse: collapse;
}
table tbody tr:first-child td {
border-top: 1px solid #E6E6E6; /* Top border */
padding-top: 10px;
}
table tbody tr:last-child td {
border-bottom: 1px solid #E6E6E6; /* Bottom border */
}
td {
padding-bottom: 10px;
}
.TableContent {
text-align: right;
color: #6D6E78;
}
.TableLabel {
font-weight: 600;
color: #30313D;
}
#view-details {
color: #0055DE;
}
#retry-button {
text-align: center;
background: #0055DE;
color: #ffffff;
border-radius: 4px;
border: 0;
padding: 12px 16px;
transition: all 0.2s ease;
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
width: 100%;
}
@-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);
}
}
@keyframes fadeInAnimation {
to {
opacity: 1;
}
}
@media only screen and (max-width: 600px) {
form, #dpm-annotation, #payment-status{
width: 80vw;
min-width: initial;
}
}
After the customer submits their payment information, you should call Stripe’s confirmSetup function, as seen on line 30 in the checkout.js example above.
In our example, we added redirect: "if_required" so the page doesn’t automatically redirect. This is so we can retreive the payment_method ID from the setupIntent. This ID will need to be added to the purchase request, in Step 4, to complete the policy purchase.
To test this checkout, you can use Stripe’s test cards. Check the Testing page for more information.
Step 4: Call the Purchase Endpoint
To finalize the policy purchase, you will need to call the purchase-stripe endpoint, and pass in the payment_method ID that was returned from Stripe.
Make sure to view our purchase-stripe endpoint page to see the full endpoint and request schema.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
async function completePurchase() {
let data = {
// The payment_method ID that was returned from Stripe
paymentMethodToken: paymentMethodId,
analytics: analytics,
quoteIdToPurchase: quoteId,
allPetsVisitedVet: allPetsVisitedVet,
mailingAddress: mailingAddress,
billingAddress: billingAddress,
agreeToTermsOfService: agreeToTermsOfService,
firstName: firstName,
lastName: lastName
};
// Send the data via POST
fetch('/purchase-stripe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if(data.policyPurchaseSucceeded) {
console.log(data.policyNumber);
} else {
console.error('error when purchasing:', data);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error submitting data.');
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ---------------------------------------
// -------- CONTINUED FROM STEP 2 --------
// ---------------------------------------
// Add this BELOW the /checkout map and BEFORE app.Run()
app.MapPost("/purchase-stripe", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body, Encoding.UTF8);
var rawBody = await reader.ReadToEndAsync();
if (string.IsNullOrWhiteSpace(rawBody))
{
return Results.BadRequest(new { error = "Request body is empty" });
}
string? quoteId = null;
try
{
using var doc = JsonDocument.Parse(rawBody);
if (doc.RootElement.TryGetProperty("quoteIdToPurchase", out var q))
{
quoteId = q.GetString();
}
}
catch { }
if (string.IsNullOrWhiteSpace(quoteId))
{
return Results.BadRequest(new { error = "quoteIdToPurchase field is required in request body" });
}
var embraceRequest = new HttpRequestMessage(HttpMethod.Post, $"https://[embrace-test-endpoint]/v2/quotes/fullquote/{quoteId}/purchase-stripe")
{
Content = new StringContent(rawBody, Encoding.UTF8, "application/json")
};
embraceRequest.Headers.Add("Cache-Control", "no-cache");
embraceRequest.Headers.Add("epi-apim-subscription-key", embraceApiKey);
var embraceResponse = await httpClient.SendAsync(embraceRequest);
var responseContent = await embraceResponse.Content.ReadAsStringAsync();
return Results.Content(responseContent, "application/json", embraceResponse.StatusCode);
});
// NOTE: app.Run() is already present after the first snippet.
Example Response:
{
"purchaseSucceeded": true,
"policyNumber": "INS-987654321"
}Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.