diff --git a/packages/ack-pay/src/verify-payment-receipt.test.ts b/packages/ack-pay/src/verify-payment-receipt.test.ts index 1fe2b497..5072fbcc 100644 --- a/packages/ack-pay/src/verify-payment-receipt.test.ts +++ b/packages/ack-pay/src/verify-payment-receipt.test.ts @@ -56,6 +56,7 @@ describe("verifyPaymentReceipt()", () => { currency: "USD", network: "eip155:84532", recipient: "0x592D4858DE40BC81A77E5B373238B70D7C79D3C79", + receiptService: receiptIssuerDid, }, ], } @@ -240,4 +241,29 @@ describe("verifyPaymentReceipt()", () => { }), ).rejects.toThrow(UntrustedIssuerError) }) + + it("throws when the receipt issuer does not match the selected payment option receiptService", async () => { + const otherReceiptIssuerKeypair = await generateKeypair("secp256k1") + const otherReceiptIssuerDid = createDidKeyUri(otherReceiptIssuerKeypair) + const mismatchedReceipt = createPaymentReceipt({ + paymentRequestToken, + paymentOptionId: "test-payment-option-id", + issuer: otherReceiptIssuerDid, + payerDid: createDidPkhUri( + "eip155:84532", + "0x7B3D8F2E1C9A4B5D6E7F8A9B0C1D2E3F4A5B6C", + ), + }) + const signedMismatchedReceipt = await signCredential(mismatchedReceipt, { + did: otherReceiptIssuerDid, + signer: createJwtSigner(otherReceiptIssuerKeypair), + }) + + await expect( + verifyPaymentReceipt(signedMismatchedReceipt, { + resolver, + trustedReceiptIssuers: [receiptIssuerDid, otherReceiptIssuerDid], + }), + ).rejects.toThrow(UntrustedIssuerError) + }) }) diff --git a/packages/ack-pay/src/verify-payment-receipt.ts b/packages/ack-pay/src/verify-payment-receipt.ts index 9351fd10..dd5a4952 100644 --- a/packages/ack-pay/src/verify-payment-receipt.ts +++ b/packages/ack-pay/src/verify-payment-receipt.ts @@ -1,10 +1,11 @@ -import type { Resolvable } from "@agentcommercekit/did" +import { isDidUri, type Resolvable } from "@agentcommercekit/did" import { isJwtString, type JwtString } from "@agentcommercekit/jwt" import { InvalidCredentialError, InvalidCredentialSubjectError, isCredential, parseJwtCredential, + UntrustedIssuerError, verifyParsedCredential, type Verifiable, type W3CCredential, @@ -128,6 +129,26 @@ export async function verifyPaymentReceipt( }, ) + const selectedPaymentOption = paymentRequest.paymentOptions.find( + (paymentOption) => + paymentOption.id === verifiedReceipt.credentialSubject.paymentOptionId, + ) + + if (!selectedPaymentOption) { + throw new InvalidCredentialSubjectError( + "Payment option ID was not found in the Payment Request", + ) + } + + if ( + isDidUri(selectedPaymentOption.receiptService) && + selectedPaymentOption.receiptService !== verifiedReceipt.issuer.id + ) { + throw new UntrustedIssuerError( + "Receipt issuer does not match the selected payment option receiptService", + ) + } + return { receipt: verifiedReceipt, paymentRequestToken,