feat(convex): add implementation rules and configuration for backend functions
- Introduced a comprehensive markdown document outlining implementation rules for Convex functions, including syntax, registration, HTTP endpoints, and TypeScript usage. - Created a new configuration file for the Convex app, integrating the Resend service. - Added a new HTTP route for handling Shippo webhooks to ensure proper response handling. - Implemented integration tests for order timeline events, covering various scenarios including order fulfillment and status changes. - Enhanced existing functions with type safety improvements and additional validation logic. This commit establishes clear guidelines for backend development and improves the overall structure and reliability of the Convex application.
This commit is contained in:
@@ -343,6 +343,68 @@ export async function getShippingRatesFromShippo(input: {
|
||||
return { shipmentObjectId: body.object_id, rates };
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Shippo shipment by ID and returns the `object_id` of the rate
|
||||
* matching `serviceCode` (servicelevel.token) and `carrier` (provider).
|
||||
* Throws a ConvexError if the shipment is not found, the rate is missing,
|
||||
* or the API is unreachable.
|
||||
*/
|
||||
export async function getShipmentRateObjectId(
|
||||
shipmentId: string,
|
||||
serviceCode: string,
|
||||
carrier: string,
|
||||
): Promise<string> {
|
||||
const apiKey = process.env.SHIPPO_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new ConvexError("Shipping service unavailable (missing API key).");
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${SHIPPO_SHIPMENTS_URL}${shipmentId}`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: `ShippoToken ${apiKey}` },
|
||||
});
|
||||
} catch {
|
||||
throw new ConvexError("Shippo service is unreachable. Please try again.");
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new ConvexError(`Shipment "${shipmentId}" not found in Shippo.`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new ConvexError(`Shippo service error (status ${response.status}).`);
|
||||
}
|
||||
|
||||
let body: {
|
||||
object_id: string;
|
||||
rates: Array<{
|
||||
object_id: string;
|
||||
provider: string;
|
||||
servicelevel: { token: string; name: string };
|
||||
}>;
|
||||
};
|
||||
try {
|
||||
body = await response.json();
|
||||
} catch {
|
||||
throw new ConvexError("Shippo returned an unexpected response.");
|
||||
}
|
||||
|
||||
const matching = body.rates.filter(
|
||||
(r) =>
|
||||
r.servicelevel.token === serviceCode &&
|
||||
r.provider.toLowerCase() === carrier.toLowerCase(),
|
||||
);
|
||||
|
||||
if (matching.length === 0) {
|
||||
throw new ConvexError(
|
||||
`No rate found for service "${serviceCode}" and carrier "${carrier}". The rate may have expired.`,
|
||||
);
|
||||
}
|
||||
|
||||
return matching[0].object_id;
|
||||
}
|
||||
|
||||
export function selectBestRate(rates: ShippoRate[]): {
|
||||
selected: ShippoRate;
|
||||
alternatives: ShippoRate[];
|
||||
|
||||
Reference in New Issue
Block a user