Refactor OrderFulfillmentController for improved readability and consistency

- Consolidated import statements for better clarity.
- Streamlined API operation and parameter definitions for enhanced readability.
- Cleaned up response schemas and error handling for consistency across API responses.
- Improved logging messages for better context during order fulfillment requests.
This commit is contained in:
T. Narantuya 2025-09-02 16:09:54 +09:00
parent 98f998db51
commit bb6593c9a3

View File

@ -1,13 +1,4 @@
import { import { Controller, Post, Param, Body, Headers, HttpCode, HttpStatus, UseGuards } from "@nestjs/common";
Controller,
Post,
Param,
Body,
Headers,
HttpCode,
HttpStatus,
UseGuards,
} from "@nestjs/common";
import { ApiTags, ApiOperation, ApiParam, ApiResponse, ApiHeader } from "@nestjs/swagger"; import { ApiTags, ApiOperation, ApiParam, ApiResponse, ApiHeader } from "@nestjs/swagger";
import { ThrottlerGuard } from "@nestjs/throttler"; import { ThrottlerGuard } from "@nestjs/throttler";
import { Logger } from "nestjs-pino"; import { Logger } from "nestjs-pino";
@ -30,38 +21,37 @@ export class OrderFulfillmentController {
@UseGuards(ThrottlerGuard, EnhancedWebhookSignatureGuard) @UseGuards(ThrottlerGuard, EnhancedWebhookSignatureGuard)
@ApiOperation({ @ApiOperation({
summary: "Fulfill order from Salesforce", summary: "Fulfill order from Salesforce",
description: description: "Secure endpoint called by Salesforce Quick Action to fulfill orders in WHMCS. Handles complete flow: SF Order → WHMCS AddOrder/AcceptOrder → SF Status Update"
"Secure endpoint called by Salesforce Quick Action to fulfill orders in WHMCS. Handles complete flow: SF Order → WHMCS AddOrder/AcceptOrder → SF Status Update",
}) })
@ApiParam({ @ApiParam({
name: "sfOrderId", name: "sfOrderId",
type: String, type: String,
description: "Salesforce Order ID to provision", description: "Salesforce Order ID to provision",
example: "8014x000000ABCDXYZ", example: "8014x000000ABCDXYZ"
}) })
@ApiHeader({ @ApiHeader({
name: "X-SF-Signature", name: "X-SF-Signature",
description: "HMAC-SHA256 signature of request body using shared secret", description: "HMAC-SHA256 signature of request body using shared secret",
required: true, required: true,
example: "a1b2c3d4e5f6...", example: "a1b2c3d4e5f6..."
}) })
@ApiHeader({ @ApiHeader({
name: "X-SF-Timestamp", name: "X-SF-Timestamp",
description: "ISO timestamp of request (max 5 minutes old)", description: "ISO timestamp of request (max 5 minutes old)",
required: true, required: true,
example: "2024-01-15T10:30:00Z", example: "2024-01-15T10:30:00Z"
}) })
@ApiHeader({ @ApiHeader({
name: "X-SF-Nonce", name: "X-SF-Nonce",
description: "Unique nonce to prevent replay attacks", description: "Unique nonce to prevent replay attacks",
required: true, required: true,
example: "abc123def456", example: "abc123def456"
}) })
@ApiHeader({ @ApiHeader({
name: "Idempotency-Key", name: "Idempotency-Key",
description: "Unique key for safe retries", description: "Unique key for safe retries",
required: true, required: true,
example: "provision_8014x000000ABCDXYZ_1705312200000", example: "provision_8014x000000ABCDXYZ_1705312200000"
}) })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
@ -70,16 +60,12 @@ export class OrderFulfillmentController {
type: "object", type: "object",
properties: { properties: {
success: { type: "boolean", example: true }, success: { type: "boolean", example: true },
status: { status: { type: "string", enum: ["Provisioned", "Already Provisioned"], example: "Provisioned" },
type: "string",
enum: ["Provisioned", "Already Provisioned"],
example: "Provisioned",
},
whmcsOrderId: { type: "string", example: "12345" }, whmcsOrderId: { type: "string", example: "12345" },
whmcsServiceIds: { type: "array", items: { type: "number" }, example: [67890, 67891] }, whmcsServiceIds: { type: "array", items: { type: "number" }, example: [67890, 67891] },
message: { type: "string", example: "Order provisioned successfully in WHMCS" }, message: { type: "string", example: "Order provisioned successfully in WHMCS" }
}, }
}, }
}) })
@ApiResponse({ @ApiResponse({
status: 400, status: 400,
@ -90,13 +76,13 @@ export class OrderFulfillmentController {
success: { type: "boolean", example: false }, success: { type: "boolean", example: false },
status: { type: "string", example: "Failed" }, status: { type: "string", example: "Failed" },
message: { type: "string", example: "Salesforce order not found" }, message: { type: "string", example: "Salesforce order not found" },
errorCode: { type: "string", example: "ORDER_NOT_FOUND" }, errorCode: { type: "string", example: "ORDER_NOT_FOUND" }
}, }
}, }
}) })
@ApiResponse({ @ApiResponse({
status: 401, status: 401,
description: "Invalid signature or authentication", description: "Invalid signature or authentication"
}) })
@ApiResponse({ @ApiResponse({
status: 409, status: 409,
@ -106,20 +92,17 @@ export class OrderFulfillmentController {
properties: { properties: {
success: { type: "boolean", example: false }, success: { type: "boolean", example: false },
status: { type: "string", example: "Failed" }, status: { type: "string", example: "Failed" },
message: { message: { type: "string", example: "Payment method missing - client must add payment method before provisioning" },
type: "string", errorCode: { type: "string", example: "PAYMENT_METHOD_MISSING" }
example: "Payment method missing - client must add payment method before provisioning", }
}, }
errorCode: { type: "string", example: "PAYMENT_METHOD_MISSING" },
},
},
}) })
async fulfillOrder( async fulfillOrder(
@Param("sfOrderId") sfOrderId: string, @Param("sfOrderId") sfOrderId: string,
@Body() payload: OrderFulfillmentRequest, @Body() payload: OrderFulfillmentRequest,
@Headers("idempotency-key") idempotencyKey: string @Headers("idempotency-key") idempotencyKey: string
) { ) {
this.logger.log("Salesforce fulfillment request received", { this.logger.log("Salesforce order fulfillment request received", {
sfOrderId, sfOrderId,
idempotencyKey, idempotencyKey,
timestamp: payload.timestamp, timestamp: payload.timestamp,
@ -150,6 +133,7 @@ export class OrderFulfillmentController {
...(result.errorCode && { errorCode: result.errorCode }), ...(result.errorCode && { errorCode: result.errorCode }),
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
} catch (error) { } catch (error) {
this.logger.error("Salesforce provisioning failed", { this.logger.error("Salesforce provisioning failed", {
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),