Skip to content

1/n: Move mpp basics to link SDK - #379

Open
raubrey-stripe wants to merge 6 commits into
mainfrom
raubrey/mpp-pay-sdk
Open

raubrey-stripe wants to merge 6 commits into
mainfrom
raubrey/mpp-pay-sdk

Conversation

@raubrey-stripe

Copy link
Copy Markdown
Contributor

Today, the link-cli mpp package exposes two commands

  • decode: can be used to decode an MPP challenge
  • pay: can be used with a prepped SPT Spend Request complete an MPP challenge
    • Note: I'm just pulling in the command version that accepts an already approved spend request and can follow on with the more graceful version that creates the spend request on your behalf.

These commands were locked in the CLI and not available in the SDK. This work ensures these methods are available to agents integrating the SDK.

Demo snippet

Note again: I'll refactor a more graceful SDK abstraction to better bundle mpp.pay in a way that supports auto-creating the spend request.

import Link from '@stripe/link-sdk';

const link = new Link({
  accessToken: process.env.LINK_ACCESS_TOKEN!,
});

const url = 'https://merchant.example/api/purchase';
const request = {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ sku: 'sku_123' }),
};

// Probe the endpoint without payment credentials.
const response = await fetch(url, request);

if (response.status !== 402) {
  throw new Error(`Expected HTTP 402, received ${response.status}`);
}

const challengeHeader = response.headers.get('www-authenticate');
if (!challengeHeader) {
  throw new Error('HTTP 402 response omitted WWW-Authenticate');
}

await response.body?.cancel();

// A header can contain several payment challenges.
const challenges = link.mpp.decodeChallenge(challengeHeader);
const stripeChallenge = challenges.find(
  (challenge) => challenge.method === 'stripe',
);

if (!stripeChallenge) {
  throw new Error('No supported Stripe challenge found');
}

const amount = Number(stripeChallenge.request_json.amount);
const currency = stripeChallenge.request_json.currency;

if (!Number.isSafeInteger(amount) || amount <= 0) {
  throw new Error(`Invalid challenge amount: ${amount}`);
}

const paymentMethods = await link.paymentMethods.list();
const paymentMethod =
  paymentMethods.find((method) => method.is_default) ?? paymentMethods[0];

if (!paymentMethod) {
  throw new Error('No Link payment method is available');
}

// Create an SPT spend request matching the Stripe challenge.
let spendRequest = await link.spendRequests.create({
  payment_details: paymentMethod.id,
  credential_type: 'shared_payment_token',
  network_id: stripeChallenge.network_id,
  amount,
  currency,
  context:
    'The user asked the agent to purchase SKU 123 from Merchant using the amount and currency advertised by the merchant’s MPP challenge.',
  request_approval: true,
});

if (spendRequest.status === 'pending_approval') {
  // Show spendRequest.approval_url to the user and ask them to approve it.
  console.log(`Approve this purchase: ${spendRequest.approval_url}`);
}

// Wait for the user’s decision.
while (
  spendRequest.status === 'created' ||
  spendRequest.status === 'pending_approval'
) {
  await new Promise((resolve) => setTimeout(resolve, 2_000));

  const updated = await link.spendRequests.retrieve(spendRequest.id);
  if (!updated) throw new Error('Spend request no longer exists');

  spendRequest = updated;
}

if (spendRequest.status !== 'approved') {
  throw new Error(`Spend request was not approved: ${spendRequest.status}`);
}

// Pay the same request using the approved spend request.
const result = await link.mpp.pay({
  url,
  ...request,
  spendRequestId: spendRequest.id,
  challenge: challengeHeader,
});

console.log(result.status, result.body);

raubrey-stripe and others added 6 commits September 30, 2026 10:31
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Committed-By-Agent: codex
Co-authored-by: codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant