Skip to content

Commit

Permalink
(feat): add webhook verfication support for hyperswitch webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
AkshayaFoiger committed Jun 25, 2024
1 parent 2d0f332 commit f2cc63b
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 86 deletions.
9 changes: 4 additions & 5 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ export const JsonSchemaError = BaseError.subclass("JsonSchemaError");
export const MissingSaleorApiUrlError = BaseError.subclass("MissingSaleorApiUrlError");
export const MissingAuthDataError = BaseError.subclass("MissingAuthDataError");
export const ChannelNotConfigured = BaseError.subclass("ChannelNotConfiguredError");
export const UnsupportedEvent = BaseError.subclass("UnsupportedEventError")
export const HyperswitchHttpClientError = BaseError.subclass("HyperswitchHttpClientError"
);
export const UnsupportedEvent = BaseError.subclass("UnsupportedEventError");
export const HyperswitchHttpClientError = BaseError.subclass("HyperswitchHttpClientError");
export const HttpRequestError = BaseError.subclass("HttpRequestError", {
props: {} as { statusCode: number; body: string; headers: IncomingHttpHeaders },
});
Expand Down Expand Up @@ -91,5 +90,5 @@ export declare class ApiError extends Error {
readonly status: number;
readonly statusText: string;
readonly data: any;
constructor(response: Omit<ApiResponse, 'ok'>);
}
constructor(response: Omit<ApiResponse, "ok">);
}
14 changes: 10 additions & 4 deletions src/modules/payment-app-configuration/config-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ export const paymentAppConfigEntryEncryptedSchema = z.object({
apiKey: z
.string({ required_error: "Secret Key is required" })
.min(1, { message: "Secret Key is required" }),
paymentResponseHashKey: z
.string({ required_error: "Payment Response Hash Key is required" })
.min(1, { message: "Payment Response Hash Key is required" }),
});

export const paymentAppConfigEntryPublicSchema = z.object({
publishableKey: z
.string({ required_error: "Publishable Key is required" })
.min(1, { message: "Publishable Key is required" }),
profileId: z
.string({ required_error: "Profile ID is required" })
.min(1, { message: "Profile ID is required" }),
.string({ required_error: "Profile ID is required" })
.min(1, { message: "Profile ID is required" }),
});

export const paymentAppConfigEntrySchema = paymentAppConfigEntryEncryptedSchema
Expand All @@ -43,6 +46,7 @@ export const paymentAppFullyConfiguredEntrySchema = z
configurationName: paymentAppConfigEntryInternalSchema.shape.configurationName,
configurationId: paymentAppConfigEntryInternalSchema.shape.configurationId,
apiKey: paymentAppConfigEntryEncryptedSchema.shape.apiKey,
paymentResponseHashKey: paymentAppConfigEntryEncryptedSchema.shape.paymentResponseHashKey,
publishableKey: paymentAppConfigEntryPublicSchema.shape.publishableKey,
profileId: paymentAppConfigEntryPublicSchema.shape.profileId,
// webhookSecret: DANGEROUS_paymentAppConfigHiddenSchema.shape.webhookSecret,
Expand All @@ -57,6 +61,7 @@ export const paymentAppFormConfigEntrySchema = z
"snd_",
"This isn't Hyperwitch api key, it must start with snd_",
),
paymentResponseHashKey: paymentAppConfigEntryEncryptedSchema.shape.paymentResponseHashKey,
publishableKey: paymentAppConfigEntryPublicSchema.shape.publishableKey.startsWith(
"pk_",
"This isn't publishable key, it must start with pk_",
Expand All @@ -65,17 +70,18 @@ export const paymentAppFormConfigEntrySchema = z
"pro_",
"This isn't publishable key, it must start with pro_",
),
configurationName: paymentAppConfigEntryPublicSchema.shape.profileId.startsWith(
configurationName: paymentAppConfigEntryInternalSchema.shape.configurationName.startsWith(
"con_",
"This isn't publishable key, it must start with con_",
),
})
.strict()
.default({
apiKey: "",
paymentResponseHashKey: "",
publishableKey: "",
profileId: "",
configurationName: ""
configurationName: "",
});

/** Schema used in front-end forms
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@ export const paymentAppConfigurationRouter = router({
add: protectedClientProcedure
.input(paymentAppFormConfigEntrySchema)
.mutation(async ({ input, ctx }) => {
const {configurationName, apiKey, publishableKey, profileId} = input;
const { configurationName, apiKey, paymentResponseHashKey, publishableKey, profileId } =
input;
ctx.logger.info("appConfigurationRouter.paymentConfig.add called");
ctx.logger.debug(
{ configurationName, apiKey: redactLogValue(apiKey), publishableKey: redactLogValue(publishableKey), profileId:(profileId)},
{
configurationName,
apiKey: redactLogValue(apiKey),
paymentResponseHashKey: redactLogValue(paymentResponseHashKey),
publishableKey: redactLogValue(publishableKey),
profileId: profileId,
},
"appConfigurationRouter.paymentConfig.add input",
);
invariant(ctx.appUrl, "Missing app url");
Expand All @@ -77,13 +84,15 @@ export const paymentAppConfigurationRouter = router({
.output(paymentAppUserVisibleConfigEntrySchema)
.mutation(async ({ input, ctx }) => {
const { configurationId, entry } = input;
const {apiKey, publishableKey, profileId, configurationName} = entry;
const { apiKey, paymentResponseHashKey, publishableKey, profileId, configurationName } =
entry;
ctx.logger.info("appConfigurationRouter.paymentConfig.update called");
ctx.logger.debug(
{
configurationId,
entry: {
apiKey,
paymentResponseHashKey,
publishableKey,
profileId,
configurationName,
Expand Down
3 changes: 2 additions & 1 deletion src/modules/payment-app-configuration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
export const obfuscateConfigEntry = (
entry: PaymentAppConfigEntry | PaymentAppUserVisibleConfigEntry,
): PaymentAppUserVisibleConfigEntry => {
const { apiKey, publishableKey, profileId, configurationName, configurationId} = entry;
const { apiKey, paymentResponseHashKey, publishableKey, profileId, configurationName, configurationId} = entry;

const configValuesToObfuscate = {
apiKey,
paymentResponseHashKey
} satisfies PaymentAppEncryptedConfig;

return paymentAppUserVisibleConfigEntrySchema.parse({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const HyperswitchConfigurationForm = ({
defaultValues: {
publishableKey: "",
apiKey: "",
paymentResponseHashKey: "",
profileId: "",
configurationName: "",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ export const AddHyperswitchCredentialsForm = ({
name="profileId"
size="medium"
/>
<FormInput
control={control}
type="password"
autoComplete="off"
label="Payment Response Hash Key"
name="paymentResponseHashKey"
size="medium"
/>
</Box>
</RoundedBoxWithFooter>
);
Expand Down
1 change: 1 addition & 0 deletions src/modules/webhooks/transaction-cancelation-requested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const TransactionCancelationRequestedWebhookHandler = async (
const HyperswitchConfig = paymentAppFullyConfiguredEntrySchema.parse(appChannelConfig);
const hyperswitchClient = createHyperswitchClient({
apiKey: HyperswitchConfig.apiKey,

});

const cancelHyperswitchPayment = hyperswitchClient
Expand Down
Loading

0 comments on commit f2cc63b

Please sign in to comment.