Build a Custom Checkout
This tutorial guides you through building a complete, custom e-commerce workflow using the Care Portals Patient and Public Cart APIs. You will learn how to create a shopping cart, manage its items, authenticate a patient, and finalize the purchase to make an order.
This guide is designed for developers building a bespoke checkout experience in a single-page application (SPA), mobile app, or server-rendered frontend.
Prerequisites
Before you begin, make sure you have the following:
- Configured Products: Ensure you have products available and configured for ordering within your organization's catalog.
- Authorization: Bearer
<YOUR_JWT>: You need to have a valid token to access the Care Portals API. See the Authentication guide for instructions on how to generate one.
Organization ID (
<ORG_ID>)
- For a CarePortals subdomain: You don’t need to send the Organization ID (
<ORG_ID>) in the request header since it’s retrieved from the domain.- For a custom domain configuration: If you are hosting the checkout on a custom domain, you must send the Organization ID (
<ORG_ID>) in the request header.
Flow Overview
Here's a high-level overview of the checkout flow we'll build:
- Create a Patient Account: Register a new patient.
- Log in the Patient: Authenticate the patient to obtain a JSON Web Token (JWT) for secure requests.
- Create a Cart: Initialize a new shopping cart and get a unique Cart ID.
- Start the Checkout: Fetch the complete checkout state, including the cart details and the patient's saved payment methods.
- Apply a Coupon (Optional): Apply a discount coupon to the cart.
- Finalize the Checkout: Submit the payment and shipping details to create the final order.
Step 1: Create a Patient Account
Before a user can check out, they need an account. Collect the user's information and send it to the Patient API to create a new patient record. Use the Create a Patient endpoint to send a POST request. A successful request creates the patient and returns the new user object.
curl -X POST '<https://patient-api.portals.care/patient/users>' \\
-H 'organization: <ORG_ID>' \\
-H 'Content-Type: application/json' \\
-d '{
"firstName": "John",
"lastName": "Doe",
"phone": "+11234567890",
"email": "[email protected]",
"password": "ValidPassword@123",
"dob": "2000-11-11",
"gender": "male",
"acceptMarketing": true,
"provinceCode": "AB"
}'
{
"_id": "a1b2c3d4e5f6a1b2c3d4e5f6",
"uname": "[email protected]",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"provinceCode": "AB",
"phone": "+11234567890",
"isInsuranceAvailable": false,
"organization": "<ORG_ID>",
"tags": "",
"currency": "CAD",
"notificationsCount": 0,
"contactPreference": {},
"watchlist": [],
"usedCoupons": [],
"meta": {},
"verifications": {}
}Step 2: Log In and Get JWT
Now that the patient has an account, you need to log them in to get an authentication token (JWT). This token is required for all subsequent checkout steps. To authenticate the user, you have to share their username and password using the Authenticate Patient with Password endpoint.
curl -X POST '<https://patient-api.portals.care/patient/auth/login>' \
-H 'Content-Type: application/json' \\
-H 'Authorization: Bearer <YOUR_JWT>'
-d '{
"username": "[email protected]",
"password": "ValidPassword@123"
}'
{
"_id": "a1b2c3d4e5f6a1b2c3d4e5f6",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"uname": "[email protected]",
"provinceCode": "AB",
"phone": "+11234567890",
"iat": "1672531200",
"exp": "1672617600",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"organization": "<ORG_ID>",
"referrer": ""
}Step 3: Create a Cart
After creating and logging the patient in, you need to create a shopping cart for the user. To create the cart, you need to use the Post Carts endpoint from the public API. While creating the cart, you can already add items to the cart, informing the productId and respective quantity. The response will contain the full cart object, including the _id, which we'll refer to as the <CART_ID>.
curl -X POST '<https://public-api.portals.care/public/v2/carts>' \\
-H 'Authorization: Bearer <YOUR_JWT>'
-H 'Content-Type: application/json' \\
-d '[
{
"productId": "67913a49df75bdf65ad795f3",
"quantity": 1
}
]'
{
"_id": "685c3ed23f131cba09b33247",
"organization": "<ORG_ID>",
"lineItems": [
{
"id": "<CART_ID>",
"name": "Finasteride 1mg",
"productId": "67913a49df75bdf65ad795f3",
"price": 90,
"listPrice": 111,
"quantity": 1,
"isSubscription": false
}
],
"baseAmount": 111,
"discountAmount": 21,
"totalAmount": 90
}The API will respond with the newly created cart object. You must save the _id from this response. You'll need it for all subsequent cart and checkout operations.
Anytime you modify the cart (add, update, or remove an item), the API returns the latest cart state. Always use the most recent response as your application's source of truth to avoid state drift.
Step 4: Start the Checkout
With an authenticated user and a cart created, you can now retrieve the complete checkout context. This includes the final state of the cart and a list of the patient's available payment methods. This data is everything you need to render your checkout UI. To retrieve this information, use the Start Checkout endpoint, ensuring that you include the patient's JWT.
curl -X GET '<https://patient-api.portals.care/patient/v2/checkout/><CART_ID>/start' \\
-H 'Authorization: Bearer <YOUR_JWT>'
{
"cart": {
"_id": "685c3ed23f131cba09b33247",
"organization": "<ORG_ID>",
"lineItems": [
{
"id": "UQ2XGj7d",
"name": "Finasteride 1mg",
"productId": "67913a49df75bdf65ad795f3",
"price": 90,
"listPrice": 111,
"quantity": 1,
"isSubscription": false
}
],
"baseAmount": 111,
"discountAmount": 21,
"totalAmount": 90
},
"paymentMethods": [
{
"id": "pm_1LzG9q2eZvKYlo2Cc0Q4f3gN",
"type": "card",
"last4": "4242",
"exp": "12/2025"
}
]
}The response contains the final cart details and an array of saved paymentMethods. If the array is empty, you'll need to prompt the user to add a new payment method.
Step 5 (Optional): Apply a Coupon
Here, if a coupon was applied, call the Apply Coupon to Cart endpoint with a POST request. Ensure you input the <CART_ID> and the <COUPON_CODE> in the request's path parameters.
curl -X POST 'https:https://patient-api.portals.care/v2/checkout/<CART_ID>/coupon/<COUPON_CODE>
-H 'Authorization: Bearer <YOUR_JWT>'Step 6: Process Payment and Create Order
This final step processes the payment and creates the corresponding order(s) for the items in the cart. To do this, call the Create Payment and Orders endpoint.
The server attempts to create orders based on the payment status. Orders are created only if the payment is successful. If the payment fails, the endpoint returns an error.
If the payment requires additional user verification (such as 3DS authentication), the endpoint updates the cart with the paymentIntentId and returns a paymentIntent object with a requires_action status, without creating any orders.
On the client side, if the response includes a paymentIntent with a requires_action status, use the Stripe SDK’s handleNextAction function to prompt the user to complete the required verification.
After the verification is complete, call the Create a Payment endpoint again. The server will retrieve the paymentIntent from the cart, verify its status, and, if the payment was successful, create and return the associated orders.
curl -X POST 'https://patient-api.portals.care/patient/v2/checkout/<CART_ID>/payments' \\
-H 'Authorization: Bearer <YOUR_JWT>' \\
-H 'Content-Type: application/json' \\
-H 'Organization: <ORG_ID>' \\
-d '{
"returnUrl": "https://your-domain.com/checkout/success",
"paymentMethodId": "pm_card_visa",
"shippingAddress": {
"address1": "456 Oak Avenue",
"city": "Toronto",
"provinceCode": "ON",
"postalCode": "M5H 2N2",
"countryCode": "CA"
},
"isNewShippingAddress": true,
"couponCode": "NEW20"
}'
{
"orders": [],
"intent": {
"id": "pi_3...",
"object": "payment_intent",
"status": "requires_action",
"client_secret": "pi_3..._secret_4...",
"next_action": {
"type": "use_stripe_sdk"
}
}
}If the payment requires further action from the user, the orders array will be empty and the intent object will have a status of requires_action.
{
"orders": [
{
"organization": "<ORG_ID>",
"_id": "f1e2d3c4b5a6f1e2d3c4b5a6",
"id": 12345,
"status": "Awaiting Fulfillment",
"lineItems": [
{
"name": "Product Name",
"price": 75,
"quantity": 1
}
],
"totalAmount": 75
}
],
"intent": {
"id": "pi_3...",
"object": "payment_intent",
"status": "succeeded"
}
}Upon successful payment (either immediately or after the second call), the response will contain the created order(s) and the completed payment intent.
Additionally, you can check the final status of a payment at any time by calling the Check Payment Status endpoint. This is a read-only request that returns the orders if the payment was successful or a failed status if it was not.
{
"orders": [
{
"organization": "<ORG_ID>",
"_id": "f1e2d3c4b5a6f1e2d3c4b5a6",
"id": 12345,
"status": "Awaiting Fulfillment"
}
],
"paymentStatus": "succeeded"
}{
"orders": [],
"paymentStatus": "failed"
}Updated about 2 months ago
