Hi AWS Community,
I'm encountering a persistent ResourceNotFoundException in my Amplify Gen 2 project when AppSync tries to resolve a GraphQL mutation by invoking a Lambda function. The error specifically states Function not found: arn:aws:lambda:us-east-2:ACCOUNT_ID:function:stripeCheckout, even though the Lambda exists, has an alias named "stripeCheckout", and I believe permissions are correctly set.
Project Setup:
Framework: AWS Amplify Gen 2 (using TypeScript with CDK for backend definitions)
Region: us-east-2
Goal: Integrate Stripe for subscriptions. The createStripeCheckoutSession mutation is failing.
Error Message from Client (via AppSync):
{
"data": {
"createStripeCheckoutSession": null
},
"errors": [
{
"path": [
"createStripeCheckoutSession"
],
"data": null,
"errorType": "Lambda:IllegalArgument",
"errorInfo": null,
"locations": [/* ... */],
"message": "software.amazon.awssdk.services.lambda.model.ResourceNotFoundException: Function not found: arn:aws:lambda:us-east-2:970547380883:function:stripeCheckout (Service: Lambda, Status Code: 404, Request ID: <some-uuid>) (SDK Attempt Count: 1)"
}
]
}
Relevant amplify/data/resource.ts (GraphQL Schema Definition):
import { a, defineData } from "@aws-amplify/backend";
const dataSchema = a.schema({
// ... other models ...
CreateStripeCheckoutSessionInput: a.customType({
subscriptionTierId: a.string().required(),
successUrl: a.string().required(),
cancelUrl: a.string().required(),
}),
StripeCheckoutSessionPayload: a.customType({
sessionId: a.string(),
sessionUrl: a.string(),
error: a.string(),
}),
// ... other types and models ...
createStripeCheckoutSession: a
.mutation()
.arguments({ input: a.ref("CreateStripeCheckoutSessionInput").required() })
.returns(a.ref("StripeCheckoutSessionPayload"))
.authorization((allow) => [allow.authenticated()])
.handler(a.handler.function("stripeCheckout")), // <--- Logical handler name
// Example of another (working) function handler:
getCurrentUserSubscription: a
.query()
.returns(a.ref("UserSubscription"))
.authorization((allow) => [allow.authenticated()])
.handler(a.handler.function("getUserSubFunc")),
// ... other mutations/queries ...
});
export const data = defineData({
schema: dataSchema,
authorizationModes: { /* ... */ }
});
Relevant amplify/backend.ts (CDK Backend Definition):
import { defineBackend } from "@aws-amplify/backend";
import { data } from "./data/resource"; // Imports the schema above
import { stripeCheckoutFunc } from "./functions/stripeCheckout/resource"; // Definition of the Lambda
import { getCurrentUserSubscriptionFunc } from "./functions/getCurrentUserSubscription/resource";
// ... other function imports ...
import { Alias, CfnPermission } from "aws-cdk-lib/aws-lambda";
import { Stack } from "aws-cdk-lib";
const backend = defineBackend({
data: data,
stripeCheckoutFunc: stripeCheckoutFunc,
getCurrentUserSubscriptionFunc: getCurrentUserSubscriptionFunc,
// ... other functions ...
});
const dataStack = Stack.of(backend.data as any);
// Alias for stripeCheckoutFunc
const stripeCheckoutFuncAlias = new Alias(dataStack, "stripeCheckoutFuncAlias", {
aliasName: "stripeCheckout", // Alias is NAMED "stripeCheckout"
version: backend.stripeCheckoutFunc.resources.lambda.latestVersion,
});
// Alias for a working function (example)
const getUserSubFuncAlias = new Alias(dataStack, "getUserSubFuncAlias", {
aliasName: "getUserSubFunc",
version: backend.getCurrentUserSubscriptionFunc.resources.lambda.latestVersion,
});
// Permission for AppSync to invoke the stripeCheckout ALIAS
new CfnPermission(dataStack, 'StripeCheckoutFuncAliasAppSyncInvoke', {
action: 'lambda:InvokeFunction',
principal: 'appsync.amazonaws.com',
functionName: stripeCheckoutFuncAlias.functionArn, // Using Alias ARN
sourceArn: backend.data.resources.cfnResources.cfnGraphqlApi.attrArn
});
// Permission for a working function (example)
new CfnPermission(dataStack, 'GetUserSubFuncAppSyncInvoke', {
action: 'lambda:InvokeFunction',
principal: 'appsync.amazonaws.com',
functionName: getUserSubFuncAlias.functionArn, // Using Alias ARN
sourceArn: backend.data.resources.cfnResources.cfnGraphqlApi.attrArn
});
// ... other permissions and configurations ...
Lambda Function Details:
The actual deployed Lambda for Stripe checkout has a physical name like amplify-greeetsamplify-gr-stripeCheckoutlambdaA793-mAz1cJj8xyzt.
It has an alias named stripeCheckout which points to $LATEST.
What I've Tried/Verified:
- Alias Exists: The "stripeCheckout" alias for the Lambda is correctly created and points to the latest version.
- CfnPermission Targets Alias ARN: The CfnPermission (StripeCheckoutFuncAliasAppSyncInvoke) uses stripeCheckoutFuncAlias.functionArn to grant AppSync permission to invoke the specific alias.
- Comparison with Working Function: Other functions (e.g., getCurrentUserSubscriptionFunc with alias getUserSubFunc) are defined with a similar pattern in data/resource.ts (using a logical name like "getUserSubFunc" in a.handler.function()) and backend.ts (alias creation and CfnPermission targeting the alias ARN), and they work correctly. AppSync seems to resolve their logical names to the correct alias ARNs.
- No Typos: The logical name "stripeCheckout" in a.handler.function("stripeCheckout") matches the aliasName: "stripeCheckout" for stripeCheckoutFuncAlias.
The Puzzle:
Why is AppSync attempting to invoke arn:aws:lambda:us-east-2:ACCOUNT_ID:function:stripeCheckout (using the short, logical name as the function's physical name) instead of the actual physical Lambda ARN or, preferably, the stripeCheckoutFuncAlias.functionArn for the createStripeCheckoutSession mutation? This behavior is inconsistent with other working Lambda resolvers in the same project.
Is there an edge case or a specific nuance in Amplify Gen 2's a.handler.function("logicalName") resolution when the logical name might be too simple, or perhaps when it exactly matches an alias name, that could cause it to form an incorrect ARN for the AppSync data source? How can I ensure Amplify correctly configures the AppSync data source for createStripeCheckoutSession to use the stripeCheckoutFuncAlias.functionArn?
Any insights or suggestions on how to debug or resolve this AppSync data source misconfiguration within the Amplify Gen 2 framework would be greatly appreciated!