Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ Position APort as the universal verify layer.
| Bridge | Description | Status | Maintainer |
|--------|-------------|--------|------------|
| [OpenAPI 3.1 Spec](examples/protocol-bridges/openapi/) | Complete OpenAPI specification | ✅ Active | Community |
| [AP2 Bridge](examples/protocol-bridges/ap2/) | APort passport authorization for AP2 payments | 📋 Planned | Community |
| [AP2 Bridge](examples/protocol-bridges/ap2/) | APort passport authorization for AP2 payments | ✅ Active | Community |
| [SPIFFE/SPIRE Integration](examples/protocol-bridges/spiffe/) | Enterprise identity federation | 📋 Planned | Community |

### 🛠️ **Core Framework SDKs & Middleware**
Expand Down
6 changes: 6 additions & 0 deletions examples/protocol-bridges/ap2/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
APORT_API_KEY=aport_live_or_sandbox_key
APORT_BASE_URL=https://api.aport.io
APORT_AP2_POLICY_ID=finance.payment.authorization.v1

AP2_BASE_URL=https://merchant.example/v1/ap2
AP2_BEARER_TOKEN=ap2_control_plane_token
109 changes: 109 additions & 0 deletions examples/protocol-bridges/ap2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# APort AP2 Payment Authorization Bridge

This example shows how to use APort passport verification as the authorization gate before an AP2 payment intent is created or confirmed.

AP2 uses signed mandates and payment intents to prove user authorization. APort adds an independent policy check for the agent passport, including limits, assurance level, and suspension state. The bridge writes the APort decision back into the AP2 `policyTrace` so the AP2 flow keeps a verifiable authorization record.

## What Is Included

- `APortAP2Bridge`: verifies an AP2 payment intent with `@aporthq/sdk-node`, appends an AP2 `policyTrace` entry, then creates or confirms the payment intent through an AP2 control plane.
- `AP2Client`: a small REST wrapper for AP2 `/v1/ap2/payment-intents` endpoints.
- Deny handling: denied APort decisions stop AP2 intent creation and can either return a structured result or throw.
- Tests for context mapping, policy trace generation, allow/deny behavior, confirmation payloads, and AP2 HTTP errors.
- A mocked payment-flow example that runs without external credentials.

## Install

```bash
cd examples/protocol-bridges/ap2
npm install
```

## Run The Local Demo

```bash
npm run example
```

The demo uses mocked APort and AP2 clients so you can inspect the authorization flow locally. For a live integration, set the variables from `.env.example` and instantiate the bridge without mock clients.

## Environment

```bash
APORT_API_KEY=aport_live_or_sandbox_key
APORT_BASE_URL=https://api.aport.io
APORT_AP2_POLICY_ID=finance.payment.authorization.v1

AP2_BASE_URL=https://merchant.example/v1/ap2
AP2_BEARER_TOKEN=ap2_control_plane_token
```

Use `APORT_AP2_POLICY_ID` for the APort policy pack that enforces your AP2 spending rules. The example defaults to `finance.payment.authorization.v1`; deployments can replace it with a policy pack that matches their APort tenant.

## Usage

```javascript
const { APortAP2Bridge } = require("./index");

const bridge = new APortAP2Bridge();

const result = await bridge.confirmAuthorizedPaymentIntent(
{
id: "ap2_pi_123",
amount: {
value: "149.99",
currency: "USDC"
},
participants: {
buyer: "did:ap2:buyer-bot-9f32",
seller: "did:ap2:merchant-agent"
},
terms: {
settlementRail: "x402",
captureType: "escrow_release",
releaseCondition: "shipment-confirmed",
disputeWindow: "P5D"
},
lineItems: [
{
sku: "RUN-SHOE-01",
quantity: 1,
unitPrice: "149.99"
}
],
mandates: {
intentMandateId: "mandate_intent_123",
cartMandateId: "mandate_cart_123",
paymentMandateId: "mandate_payment_123"
}
},
{
agentId: "agt_inst_buyer",
policyId: "finance.payment.authorization.v1"
}
);

if (!result.authorized) {
console.log("Payment blocked", result.decision.reasons);
}
```

## Authorization Flow

1. Validate the AP2 payment intent shape.
2. Resolve the APort agent ID from `options.agentId`, `paymentIntent.agentId`, or the AP2 buyer DID.
3. Build APort verification context from the AP2 amount, currency, buyer/seller DIDs, settlement rail, line items, evidence, and mandate references.
4. Call `APortClient.verifyPolicy(agentId, policyId, context, idempotencyKey)`.
5. Append the decision to AP2 `policyTrace`.
6. If allowed, create the AP2 payment intent and optionally call `/confirm`.
7. If denied, return a `denied` result or throw `APortAuthorizationError` when `throwOnDeny` is enabled.

## Test

```bash
npm test
```

## AP2 Notes

This bridge follows the AP2 payment-intent and policy-trace shape from the AP2 specification and keeps AP2 settlement separate from APort authorization. AP2 still owns mandate signature verification, payment-intent lifecycle, and settlement proof creation. APort only decides whether the agent passport is authorized to initiate or confirm the payment.
89 changes: 89 additions & 0 deletions examples/protocol-bridges/ap2/examples/mock-payment-flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const { APortAP2Bridge } = require("../index");

async function main() {
const aportClient = {
async verifyPolicy(agentId, policyId, context, idempotencyKey) {
console.log("APort verifyPolicy request:");
console.log(JSON.stringify({ agentId, policyId, context, idempotencyKey }, null, 2));

return {
allow: true,
decision_id: "dec_demo_ap2_001",
assurance_level: "github",
reasons: [],
};
},
};

const ap2Client = {
async createPaymentIntent(paymentIntent) {
return {
...paymentIntent,
status: "authorized",
};
},
async confirmPaymentIntent(intentId, confirmation) {
return {
id: intentId,
status: "confirmed",
confirmation,
};
},
};

const bridge = new APortAP2Bridge({
aportClient,
ap2Client,
now: () => new Date("2026-05-11T12:00:00.000Z"),
});

const result = await bridge.confirmAuthorizedPaymentIntent(
{
id: "ap2_pi_demo_001",
amount: {
value: "149.99",
currency: "USDC",
},
participants: {
buyer: "did:ap2:buyer-bot-9f32",
seller: "did:ap2:shoe-store-agent",
},
terms: {
settlementRail: "x402",
captureType: "escrow_release",
releaseCondition: "shipment-confirmed",
disputeWindow: "P5D",
},
lineItems: [
{
sku: "RUN-SHOE-01",
quantity: 1,
unitPrice: "149.99",
metadata: {
category: "running-shoes",
},
},
],
mandates: {
intentMandateId: "mandate_intent_demo_001",
cartMandateId: "mandate_cart_demo_001",
paymentMandateId: "mandate_payment_demo_001",
},
},
{
agentId: "agt_inst_demo_buyer",
policyId: "finance.payment.authorization.v1",
sellerAcceptance: {
acceptedBy: "did:ap2:shoe-store-agent",
},
}
);

console.log("\nAuthorized AP2 payment flow:");
console.log(JSON.stringify(result, null, 2));
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading