← All provider guides

PayPal Server SDK migration: the 1.0.0 renames and the 2.0.0 options object

paypal sdk migration 6958 tracked change records

The PayPal Server SDK (@paypal/paypal-server-sdk) shipped two breaking majors back to back. The 1.0.0 release renamed every controller method: the ordersCreate and authorizationsCapture style names became createOrder and captureAuthorizedPayment, fifteen renames across the orders and payments controllers. Then 2.0.0 kept the new names but changed the calling convention itself: every method that took positional parameters now takes a single options object. Code migrated by hand for 1.0.0 broke again a release later. Both halves are mechanical, both touch every call site, and that is the profile of a codemod job.

The 1.0.0 controller renames

This list is read directly from the mendapi migration pack registry (paypal-server-sdk-v1-controller-renames) at page build time, so it matches what the codemod actually does:

  • ordersController: rename legacy method calls to their 1.0.0 names
  • paymentsController: rename legacy method calls to their 1.0.0 names

The full fifteen-pair rename map was verified token by token against the SDK source at tags 0.6.1 and 1.0.0: ordersCreate to createOrder, ordersCapture to captureOrder, authorizationsVoid to voidPayment, capturesRefund to refundCapturedPayment, and the rest of both controller families. Each rename applies from a whitelisted map and only on calls anchored to an ordersController. or paymentsController. member chain, so a same-named method on an unrelated object is never touched. One honest wrinkle from the release history: the rename actually landed in 0.7.0 and 1.0.0 kept it, so the pack mends pre-0.7.0 call sites no matter which release you jump to.

The 2.0.0 calling-convention change

The second pack (paypal-server-sdk-v2-options-object) handles the structural half:

  • Wrap positional controller-method arguments into the 2.0.0 options object (requestOptions stays positional)

In 1.0.0 a capture call was positional: paymentsController.captureAuthorizedPayment(authId, undefined, requestId). In 2.0.0 the same call wraps every former positional parameter into one object with named keys, while requestOptions stays behind as a second positional argument. A find-and-replace cannot do this rewrite, because the correct key for each position depends on which method you are calling. The pack carries the per-method parameter order extracted mechanically from the 1.0.0 signatures, so the position-to-key mapping is deterministic, and arguments are split with a string-aware bracket-depth scanner rather than a naive comma split, so nested calls and string literals containing commas are safe.

What the codemod refuses to touch

A migration tool earns trust by what it leaves alone. Multi-line call sites do not match the options-object rule at all: the rewrite simply skips them, which is the conservative direction. A call whose first argument is already an object literal is treated as migrated and left untouched, so running the pack twice is a no-op. Template literals and comments inside an argument list also stop the rewrite. Every rule in both packs is locked by golden-fixture regression tests with negative controls, so a rule that starts over-firing fails our build before it reaches your repo.

Preview each migration as one diff

Both packs run locally as a dry run by default. On the bundled fixture repos, each pack rewrites two files and writes a unified patch you can read before anything is applied:

npx mendapi sync
# -> one network call: pulls the change feed into a local database
npx mendapi scan
# -> which files touch the PayPal SDK, with line numbers
mendapi fix --migration paypal-server-sdk-v1-controller-renames
# -> dry run: changes.patch + fix-report.json, nothing applied
mendapi fix --migration paypal-server-sdk-v2-options-object
# -> the calling-convention half, same dry-run discipline
mendapi fix --migration paypal-server-sdk-v2-options-object --apply
# -> applies the reviewed diff

Nothing leaves your machine at any step; zero network calls is the default and only mode. The report records which rule fired where, and every rewritten file carries a syntax-check verdict, so review is a read of the patch, not an audit of the tool.

The diff itself, byte for byte

This is the actual changes.patch the 2.0.0 options-object pack's dry run produces on the bundled 1.0.0 fixture — embedded here from the golden regression evidence at page build time, not retyped. Two files change: positional calls like createOrder(orderRequest, undefined, requestId) collapse into a single named-key object with the paypalRequestId inferred from position, skipped undefined slots disappear instead of becoming junk keys, and body payloads like finalCapture and noteToPayer land under a body key. Note what the codemod leaves alone: the comment lines describing each case (Positional with skipped optionals, the bracket-depth splitter test) survive byte-for-byte, and the one call that already uses the 2.0.0 shape — getRefund with an object literal — is treated as migrated and not touched, which is why running the pack twice is a no-op.

--- a/index.js
+++ b/index.js
@@ -16,31 +16,31 @@
 
 async function createCheckoutOrder(orderRequest, requestId) {
   // Positional with skipped optionals: body, mockResponse, requestId.
-  const { result } = await ordersController.createOrder(orderRequest, undefined, requestId);
+  const { result } = await ordersController.createOrder({ body: orderRequest, paypalRequestId: requestId });
   return result;
 }
 
 async function captureWithPrefer(orderId) {
   // id, mock, requestId, prefer — a string literal containing a comma-free
   // value but exercising the arg-name inference (id !== orderId).
-  const { result } = await ordersController.captureOrder(orderId, undefined, undefined, 'return=representation');
+  const { result } = await ordersController.captureOrder({ id: orderId, prefer: 'return=representation' });
   return result;
 }
 
 async function inspectOrder(id) {
   // Single positional arg whose name matches the parameter: shorthand form.
-  const { result } = await ordersController.getOrder(id);
+  const { result } = await ordersController.getOrder({ id });
   return result.status;
 }
 
 async function amendOrder(id, patchBody, requestOptions) {
   // Full-arity call: trailing requestOptions must stay positional.
-  await ordersController.patchOrder(id, undefined, undefined, patchBody, requestOptions);
+  await ordersController.patchOrder({ id, body: patchBody }, requestOptions);
 }
 
 async function addTracking(id, tracker) {
   // Nested object literal argument with commas — bracket-depth splitter test.
-  await ordersController.createOrderTracking(id, { carrier: tracker.carrier, trackingNumber: tracker.number });
+  await ordersController.createOrderTracking({ id, body: { carrier: tracker.carrier, trackingNumber: tracker.number } });
 }
 
 module.exports = { createCheckoutOrder, captureWithPrefer, inspectOrder, amendOrder, addTracking };
--- a/lib/payments.js
+++ b/lib/payments.js
@@ -7,21 +7,21 @@
 const { paymentsController } = client;
 
 async function settle(authorizationId) {
-  const { result: auth } = await paymentsController.getAuthorizedPayment(authorizationId);
+  const { result: auth } = await paymentsController.getAuthorizedPayment({ authorizationId });
   if (auth.status === 'CREATED') {
-    await paymentsController.reauthorizePayment(authorizationId, undefined, 'return=minimal', undefined, { amount: auth.amount });
+    await paymentsController.reauthorizePayment({ authorizationId, prefer: 'return=minimal', body: { amount: auth.amount } });
   }
-  const { result } = await paymentsController.captureAuthorizedPayment(authorizationId, undefined, undefined, undefined, undefined, { finalCapture: true });
+  const { result } = await paymentsController.captureAuthorizedPayment({ authorizationId, body: { finalCapture: true } });
   return result;
 }
 
 async function cancel(authId) {
-  await paymentsController.voidPayment(authId);
+  await paymentsController.voidPayment({ authorizationId: authId });
 }
 
 async function refundFlow(captureId, note) {
-  const { result: capture } = await paymentsController.getCapturedPayment(captureId);
-  const { result: refund } = await paymentsController.refundCapturedPayment(captureId, undefined, undefined, undefined, undefined, { noteToPayer: note });
+  const { result: capture } = await paymentsController.getCapturedPayment({ captureId });
+  const { result: refund } = await paymentsController.refundCapturedPayment({ captureId, body: { noteToPayer: note } });
   // Already-migrated call site (2.0.0 shape): must stay untouched.
   const { result: check } = await paymentsController.getRefund({ refundId: refund.id });
   return { capture, refund, check };

The part no codemod fixes

Honest scope: the packs handle the mechanical rewrites. They do not decide how your code should adopt new SDK behavior: response typing differences, error-class changes, or code that picks a controller method dynamically at runtime. The tracked PayPal history counts 1198 breaking or deprecation entries, and the platform-behavior ones stay not code-fixable by verdict, because pretending a codemod covers them is how migrations go wrong.

Related

Every entry on this page is generated from the mendapi change database at build time and links its upstream source.