EBAYCCLONE
FRONTEND GUIDE FOR AI CODING AGENTS - PART 14 - OrderManagement Service OrderManagementOrder Payment Flow
This document is a part of a REST API guide for the ebaycclone project. It is designed for AI agents that will generate frontend code to consume the project’s backend.
Stripe Payment Flow For OrderManagementOrder
OrderManagementOrder
is a data object that stores order information used for Stripe
payments. The payment flow can only start after an instance of
this data object is created in the database.
The ID of this data object—referenced as
orderManagementOrderId
in the general business logic—will be used as the
orderId
in the payment flow.
Accessing the service API for the payment flow API
The Ebaycclone application doesn’t have a separate payment
service; the payment flow is handled within the same service that
manages orders. To access the related APIs, use the base URL of
the
orderManagement
service. Note that the application may be deployed to Preview,
Staging, or Production. As with all API access, you should call
the API using the base URL for the selected deployment.
For the
orderManagement
service, the base URLs are:
-
Preview:
https://ebaycclone.prw.mindbricks.com/ordermanagement-api -
Staging:
https://ebaycclone-stage.mindbricks.co/ordermanagement-api -
Production:
https://ebaycclone.mindbricks.co/ordermanagement-api
Creating the OrderManagementOrder
While creating the
orderManagementOrder
instance is part of the business logic and can be implemented
according to your architecture, this instance acts as the central
hub for the payment flow and its related data objects. The order
object is typically created via its own API (see the Business API
for the create route of
orderManagementOrder). The payment flow begins after the object is
created.
Because of the data object’s Stripe order settings, the payment flow is aware of the following fields, references, and their purposes:
-
id(used asorderIdor${dataObject.objectName}Id): The unique identifier of the data object instance at the center of the payment flow. -
orderId: The order identifier is resolved fromthis.orderManagementOrder.id. -
amount: The payment amount is resolved fromthis.orderManagementOrder.summary.total. -
currency: The payment currency is resolved fromthis.orderManagementOrder.summary.currency. -
description: The payment description is resolved fromOrder #${this.orderManagementOrder.orderNumber} for buyerId ${this.orderManagementOrder.buyerId}. -
orderStatusProperty:statusis updated automatically by the payment flow using a mapped status value. -
orderStatusUpdateDateProperty:updatedAtstores the timestamp of the latest payment status update. -
orderOwnerIdProperty:buyerIdis used by the payment flow to verify the order owner and match it with the current user’s ID. -
mapPaymentResultToOrderStatus: The order status is written to the data object instance using the following mapping.
paymentResultStarted:"PENDING_PAYMENT"
paymentResultCanceled:"CANCELLED"
paymentResultFailed:"CANCELLED"
paymentResultSuccess:"PAID"
Before Payment Flow Starts
It is assumed that the frontend provides a
“Pay” or “Checkout” button that
initiates the payment flow. The following steps occur after the
user clicks this button.
Note that an
orderManagementOrder
instance must already exist to represent the order being paid,
with its initial status set.
A Stripe payment flow can be implemented in several ways, but the
best practice is to use a PaymentIntent and
manage it jointly from the backend and frontend.
A PaymentIntent represents the intent to collect payment
for a given order (or any payable entity).
In the Ebaycclone application, the
PaymentIntent is created in the backend, while
the PaymentMethod (the user’s stored card
information) is created in the frontend.
Only the PaymentMethod ID and minimal metadata are stored in the
backend for later reference.
The frontend first requests the current user’s saved payment
methods from the backend, displays them in a list, and provides UI
options to add or remove payment methods.
The user must select a Payment Method before starting the payment
flow.
Listing the Payment Methods for the User
To list the payment methods of the currently logged-in user, call the following system API (unversioned):
GET /payment-methods/list
This endpoint requires no parameters and returns an array of payment methods belonging to the user — without any envelope.
const response = await fetch("$serviceUrl/payment-methods/list", {
method: "GET",
headers: { "Content-Type": "application/json" },
});
Example response:
[
{
"id": "19a5fbfd-3c25-405b-a7f7-06f023f2ca01",
"paymentMethodId": "pm_1SQv9CP5uUv56Cse5BQ3nGW8",
"userId": "f7103b85-fcda-4dec-92c6-c336f71fd3a2",
"customerId": "cus_TNgWUw5QkmUPLa",
"cardHolderName": "John Doe",
"cardHolderZip": "34662",
"platform": "stripe",
"cardInfo": {
"brand": "visa",
"last4": "4242",
"checks": {
"cvc_check": "pass",
"address_postal_code_check": "pass"
},
"funding": "credit",
"exp_month": 11,
"exp_year": 2033
},
"isActive": true,
"createdAt": "2025-11-07T19:16:38.469Z",
"updatedAt": "2025-11-07T19:16:38.469Z",
"_owner": "f7103b85-fcda-4dec-92c6-c336f71fd3a2"
}
]
In each payment method object, the following fields are useful for displaying to the user:
for (const method of paymentMethods) {
const brand = method.cardInfo.brand; // use brand for displaying VISA/MASTERCARD icons
const paymentMethodId = method.paymentMethodId; // send this when initiating the payment flow
const cardHolderName = method.cardHolderName; // show in list
const number = `**** **** **** ${method.cardInfo.last4}`; // masked card number
const expDate = `${method.cardInfo.exp_month}/${method.cardInfo.exp_year}`; // expiry date
const id = method.id; // internal DB record ID, used for deletion
const customerId = method.customerId; // Stripe customer reference
}
If the list is empty, prompt the user to add a new payment method.
Creating a Payment Method
The payment page (or user profile page) should allow users to add a new payment method (credit card). Creating a Payment Method is a secure operation handled entirely through Stripe.js on the frontend — the backend never handles sensitive card data. After a card is successfully created, the backend only stores its reference (PaymentMethod ID) for reuse.
Stripe provides multiple ways to collect card information, all through secure UI elements. Below is an example setup — refer to the latest Stripe documentation for alternative patterns.
To initialize Stripe on the frontend, include your public key:
<script src="https://js.stripe.com/v3/?advancedFraudSignals=false"></script>
const stripe = Stripe("pk_test_51POkqt4..................");
const elements = stripe.elements();
const cardNumberElement = elements.create("cardNumber", {
style: { base: { color: "#545454", fontSize: "16px" } },
});
cardNumberElement.mount("#card-number-element");
const cardExpiryElement = elements.create("cardExpiry", {
style: { base: { color: "#545454", fontSize: "16px" } },
});
cardExpiryElement.mount("#card-expiry-element");
const cardCvcElement = elements.create("cardCvc", {
style: { base: { color: "#545454", fontSize: "16px" } },
});
cardCvcElement.mount("#card-cvc-element");
// Note: cardholder name and ZIP code are collected via non-Stripe inputs (not secure).
You can dynamically show the card brand while typing:
cardNumberElement.on("change", (event) => {
const cardBrand = event.brand;
const cardNumberDiv = document.getElementById("card-number-element");
cardNumberDiv.style.backgroundImage = getBrandImageUrl(cardBrand);
});
Once the user completes the card form, create the Payment Method on Stripe. Note that the expiry and CVC fields are securely handled by Stripe.js and are never readable from your code.
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: "card",
card: cardNumberElement,
billing_details: {
name: cardholderName.value,
address: { postal_code: cardholderZip.value },
},
});
When a
paymentMethod
is successfully created, send its ID to your backend to attach it
to the logged-in user’s account.
Use the system API (unversioned):
POST /payment-methods/add
Example:
const response = await fetch("$serviceUrl/payment-methods/add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ paymentMethodId: paymentMethod.id }),
});
When
addPaymentMethod
is called, the backend retrieves or creates the user’s Stripe
Customer ID, attaches the Payment Method to that customer, and
stores the reference in the local database for future use.
Example response:
{
"isActive": true,
"cardHolderName": "John Doe",
"userId": "f7103b85-fcda-4dec-92c6-c336f71fd3a2",
"customerId": "cus_TNgWUw5QkmUPLa",
"paymentMethodId": "pm_1SQw5aP5uUv56CseDGzT1dzP",
"platform": "stripe",
"cardHolderZip": "34662",
"cardInfo": {
"brand": "visa",
"last4": "4242",
"funding": "credit",
"exp_month": 11,
"exp_year": 2033
},
"id": "19a5ff70-4986-4760-8fc4-6b591bd6bbbf",
"createdAt": "2025-11-07T20:16:55.451Z",
"updatedAt": "2025-11-07T20:16:55.451Z"
}
You can append this new entry directly to the UI list or refresh
the list using the
listPaymentMethods
API.
Deleting a Payment Method
To remove a saved payment method from the current user’s account, call the system API (unversioned):
DELETE /payment-methods/delete/:paymentMethodId
Example:
await fetch(
`$serviceUrl/payment-methods/delete/${paymentMethodId}`,
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
}
);
Starting the Payment Flow in Backend — Creation and Confirmation of the PaymentIntent Object
The payment flow is initiated in the backend through the
startOrderManagementOrderPayment
API.
This API must be called with one of the user’s existing payment
methods. Therefore, ensure that the frontend
forces the user to select a payment method before
initiating the payment.
The
startOrderManagementOrderPayment
API is a versioned Business Logic API and follows
the same structure as other business APIs.
In the Ebaycclone application, the payment flow starts by creating
a Stripe PaymentIntent and confirming it in a
single step within the backend.
In a typical (“happy”) path, when the
startOrderManagementOrderPayment
API is called, the response will include a successful or failed
PaymentIntent result inside the
paymentResult
object, along with the
orderManagementOrder
object.
However, in certain edge cases—such as when 3D Secure (3DS) or
other bank-level authentication is required—the confirmation step
cannot complete immediately.
In such cases, control should return to a frontend page to allow
the user to finish the process.
To enable this, a
return_url
must be provided during the PaymentIntent creation step.
Although technically optional, it is
strongly recommended to include a
return_url.
This ensures that the frontend payment result page can display
both successful and failed payments and complete flows that
require user interaction.
The
return_url
must be a frontend URL.
The
paymentUserParams
parameter of the
startOrderManagementOrderPayment
API contains the data necessary to create the Stripe
PaymentIntent.
Call the API as follows:
const response = await fetch(
`$serviceUrl/v1/startorderManagementOrderpayment/${orderId}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
paymentUserParams: {
paymentMethodId,
return_url: `${yourFrontendReturnUrl}`,
},
}),
}
);
The API response will contain a
paymentResult
object. If an error occurs, it will begin with
{ "result": "ERR" }. Otherwise, it will include the PaymentIntent information:
{
"paymentResult": {
"success": true,
"paymentTicketId": "19a60f8f-eeff-43a2-9954-58b18839e1da",
"orderId": "19a60f84-56ee-40c4-b9c1-392f83877838",
"paymentId": "pi_3SR0UHP5uUv56Cse1kwQWCK8",
"paymentStatus": "succeeded",
"paymentIntentInfo": {
"paymentIntentId": "pi_3SR0UHP5uUv56Cse1kwQWCK8",
"clientSecret": "pi_3SR0UHP5uUv56Cse1kwQWCK8_secret_PTc3DriD0YU5Th4isBepvDWdg",
"publicKey": "pk_test_51POkqWP5uU",
"status": "succeeded"
},
"statusLiteral": "success",
"amount": 10,
"currency": "USD",
"description": "Your credit card is charged for babilOrder for 10",
"metadata": {
"order": "Purchase-Purchase-order",
"orderId": "19a60f84-56ee-40c4-b9c1-392f83877838",
"checkoutName": "babilOrder"
},
"paymentUserParams": {
"paymentMethodId": "pm_1SQw5aP5uUv56CseDGzT1dzP",
"return_url": "${yourFrontendReturnUrl}"
}
}
}
Start Ordermanagementorderpayment
API
Start payment for orderManagementOrder
Rest Route
The
startOrderManagementOrderPayment
API REST controller can be triggered via the following route:
/v1/startordermanagementorderpayment/:orderManagementOrderId
Rest Request Parameters
The
startOrderManagementOrderPayment
api has got 2 request parameters
| Parameter | Type | Required | Population |
|---|---|---|---|
| orderManagementOrderId | ID | true | request.params?.orderManagementOrderId |
| paymentUserParams | Object | false | request.body?.paymentUserParams |
| orderManagementOrderId : This id paremeter is used to select the required data object that will be updated | |||
| paymentUserParams : The user parameters that should be defined to start a stripe payment process |
REST Request To access the api you can use the REST controller with the path PATCH /v1/startordermanagementorderpayment/:orderManagementOrderId
axios({
method: 'PATCH',
url: `/v1/startordermanagementorderpayment/${orderManagementOrderId}`,
data: {
paymentUserParams:"Object",
},
params: {
}
});
REST Response
{
"status": "OK",
"statusCode": "200",
"elapsedMs": 126,
"ssoTime": 120,
"source": "db",
"cacheKey": "hexCode",
"userId": "ID",
"sessionId": "ID",
"requestId": "ID",
"dataName": "orderManagementOrder",
"method": "PATCH",
"action": "update",
"appVersion": "Version",
"rowCount": 1,
"orderManagementOrder": {
"id": "ID",
"orderNumber": "String",
"items": "ID",
"buyerId": "ID",
"status": "Enum",
"status_idx": "Integer",
"paymentMethodId": "String",
"stripeCustomerId": "String",
"paymentIntentId": "String",
"shippingAddress": "Object",
"summary": "Object",
"trackingNumber": "String",
"estimatedDelivery": "Date",
"cancelledAt": "Date",
"paidAt": "Date",
"deliveredAt": "Date",
"shippedAt": "Date",
"carrier": "String",
"_paymentConfirmation": "Enum",
"_paymentConfirmation_idx": "Integer",
"isActive": true,
"recordVersion": "Integer",
"createdAt": "Date",
"updatedAt": "Date",
"_owner": "ID"
},
"paymentResult": {
"paymentTicketId": "ID",
"orderId": "ID",
"paymentId": "String",
"paymentStatus": "Enum",
"paymentIntentInfo": "Object",
"statusLiteral": "String",
"amount": "Double",
"currency": "String",
"success": true,
"description": "String",
"metadata": "Object",
"paymentUserParams": "Object"
}
}
Analyzing the API Response
After calling the
startOrderManagementOrderPayment
API, the most common expected outcome is a confirmed and completed
payment. However, several alternate cases should be handled on the
frontend.
System Error Case
The API may return a classic service-level error (unrelated to
payment). Check the HTTP status code of the response. It should be
200
or
201. Any
400,
401,
403, or
404
indicates a system error.
{
"result": "ERR",
"status": 404,
"message": "Record not found",
"date": "2025-11-08T00:57:54.820Z"
}
Handle system errors on the payment page (show a retry option). Do not navigate to the result page.
Payment Error Case
The API performs both database operations and the Stripe payment
operation. If the payment fails but the service logic succeeds,
the API may still return a
200 OK
status, with the failure recorded in the
paymentResult.
In this case, show an error message and allow the user to retry.
{
"status": "OK",
"statusCode": "200",
"orderManagementOrder": {
"id": "19a60f8f-eeff-43a2-9954-58b18839e1da",
"status": "failed"
},
"paymentResult": {
"result": "ERR",
"status": 500,
"message": "Stripe error message: Your card number is incorrect.",
"errCode": "invalid_number",
"date": "2025-11-08T00:57:54.820Z"
}
}
Payment errors should be handled on the payment page (retry option). Do not go to the result page.
Happy Case
When both the service and payment result succeed, this is
considered the happy path. In this case, use the
orderManagementOrder
and
paymentResult
objects in the response to display a success message to the user.
amount
and
description
values are included to help you show payment details on the result
page.
{
"status": "OK",
"statusCode": "200",
"order": {
"id": "19a60f8f-eeff-43a2-9954-58b18839e1da",
"status": "paid"
},
"paymentResult": {
"success": true,
"paymentStatus": "succeeded",
"paymentIntentInfo": {
"status": "succeeded"
},
"amount": 10,
"currency": "USD",
"description": "Your credit card is charged for babilOrder for 10"
}
}
To verify success:
if (paymentResult.paymentIntentInfo.status === "succeeded") {
// Redirect to result page
}
Note: A successful result does not trigger fulfillment immediately. Fulfillment begins only after the Stripe webhook updates the database. It’s recommended to show a short “success” toast, wait a few milliseconds, and then navigate to the result page.
Handle the happy case in the result page by
sending the
orderManagementOrderId
and the payment intent secret.
const orderId = new URLSearchParams(window.location.search).get("orderId");
const url = new URL(`$yourResultPageUrl`, location.origin);
url.searchParams.set("orderId", orderId);
url.searchParams.set("payment_intent_client_secret", currentPaymentIntent.clientSecret);
setTimeout(() => { window.location.href = url.toString(); }, 600);
Edge Cases
Although
startOrderManagementOrderPayment
is designed to handle both creation and confirmation in one step,
Stripe may return an incomplete result if third-party
authentication or redirect steps are required.
You must handle these cases in both the payment page and the result page, because some next actions are available immediately, while others occur only after a redirect.
If the
paymentIntentInfo.status
equals
"requires_action", handle it using Stripe.js as shown below:
if (paymentResult.paymentIntentInfo.status === "requires_action") {
await runNextAction(
paymentResult.paymentIntentInfo.clientSecret,
paymentResult.paymentIntentInfo.publicKey
);
}
Helper function:
async function runNextAction(clientSecret, publicKey) {
const stripe = Stripe(publicKey);
const { error } = await stripe.handleNextAction({ clientSecret });
if (error) {
console.log("next_action error:", error);
showToast(error.code + ": " + error.message, "fa-circle-xmark text-red-500");
throw new Error(error.message);
}
}
After handling the next action, re-fetch the PaymentIntent from Stripe, evaluate its status, show appropriate feedback, and navigate to the result page.
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
if (paymentIntent.status === "succeeded") {
showToast("Payment successful!", "fa-circle-check text-green-500");
} else if (paymentIntent.status === "processing") {
showToast("Payment is processing…", "fa-circle-info text-blue-500");
} else if (paymentIntent.status === "requires_payment_method") {
showToast("Payment failed. Try another card.", "fa-circle-xmark text-red-500");
}
const orderId = new URLSearchParams(window.location.search).get("orderId");
const url = new URL(`$yourResultPageUrl`, location.origin);
url.searchParams.set("orderId", orderId);
url.searchParams.set("payment_intent_client_secret", currentPaymentIntent.clientSecret);
setTimeout(() => { window.location.href = url.toString(); }, 600);
The Result Page
The payment result page should handle the following steps:
-
Read
orderIdandpayment_intent_client_secretfrom the query parameters. - Retrieve the PaymentIntent from Stripe and check its status.
-
If required, handle any
next_actionand re-fetch the PaymentIntent. -
If the status is
"succeeded", display a clear visual confirmation. -
Fetch the
orderManagementOrderinstance from the backend to display any additional order or fulfillment details.
Note that paymentIntent status only gives information about the
Stripe side. The
orderManagementOrder
instance in the service should also ve updated to start the
fulfillment. In most cases, the
startorderManagementOrderPayment
api updates the status of the order using the response of the
paymentIntent confirmation, but as stated above in some cases this
update can be done only when the webhook executes. So in teh
result page always get the final payment status in the
`orderManagementOrder.
To ensure that service i To fetch the
orderManagementOrder
instance, you van use the related api which is given before, and
to ensure that the service is updated with the latest status read
the _paymentConfirmation field of the
orderManagementOrder
instance.
if (orderManagementOrder._paymentConfirmation == "canceled") {
// the payment is canceled, user can be informed that they should try again
} if (orderManagementOrder._paymentConfirmation == "paid") {
// service knows that payment is done, user can be informed that fullfillment started
} else {
// it may be pending, processing
// Fetch the object again until a canceled or paid status
}