<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: pqa-labs</title>
    <description>The latest articles on DEV Community by pqa-labs (@pqalabs).</description>
    <link>https://dev.to/pqalabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4114764%2F1ff12cfc-291b-4d3f-bd49-42c1085798d3.png</url>
      <title>DEV Community: pqa-labs</title>
      <link>https://dev.to/pqalabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pqalabs"/>
    <language>en</language>
    <item>
      <title>PSP Change Drift Watch #1: PayPal IPN migration, Stripe API versions, Adyen v72</title>
      <dc:creator>pqa-labs</dc:creator>
      <pubDate>Tue, 08 Sep 2026 02:27:44 +0000</pubDate>
      <link>https://dev.to/pqalabs/psp-change-drift-watch-1-paypal-ipn-migration-stripe-api-versions-adyen-v72-5f8b</link>
      <guid>https://dev.to/pqalabs/psp-change-drift-watch-1-paypal-ipn-migration-stripe-api-versions-adyen-v72-5f8b</guid>
      <description>&lt;p&gt;What this series does: watch Stripe, PayPal and Adyen for API changes, SDK majors and ecosystem issues, and write up only the ones that can silently break a live integration. Issue 1 covers five.&lt;/p&gt;

&lt;p&gt;Bottom line&lt;br&gt;
Only one item really needs action now: if you still run PayPal IPN or Website Payments Standard, schedule a migration audit before January 2027. Migration is not the hard part; the hard part is that four layers change at once during the window, and each layer can "return 200" while the business logic never fires. The Stripe and Adyen items below are "run regression before you upgrade", not urgent like the PayPal one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzj54jo9zwigd9up00hr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzj54jo9zwigd9up00hr.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure 1 IPN to Webhooks four breakpoints&lt;/p&gt;

&lt;p&gt;Figure 1: moving from IPN to Webhooks changes four layers at once, and each one can silently drop a payment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PayPal IPN is in its migration window: the easiest way to lose money silently
Source: PayPal Orders v1-to-v2 upgrade guide + community analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Timeline: PayPal stopped issuing new IPN/WPS credentials at the end of 2025, marked Website Payments Standard deprecated in January 2026, and shuts it all down in January 2027. The replacement is REST Webhooks.&lt;/p&gt;

&lt;p&gt;The deadline is not the danger. The migration window is. Switching from IPN to Webhooks is not changing an endpoint URL; four layers change at the same time:&lt;/p&gt;

&lt;p&gt;Payload format: form-encoded becomes JSON. An old urlencoded parser receives JSON and every field comes back undefined; the handler still returns 200, PayPal treats it as delivered and stops retrying, and the order stays "unpaid".&lt;br&gt;
Field paths: mc_gross becomes resource.amount.value (still a string, not a number); custom becomes custom_id; payer_email is not even present on the capture resource and needs a second order lookup.&lt;br&gt;
Status words and events: Completed becomes COMPLETED, and a refund is a separate event type (PAYMENT.CAPTURE.REFUNDED). If you only subscribe to COMPLETED, refunds and disputes never arrive.&lt;br&gt;
Signature verification: the _notify-validate round-trip becomes offline RSA-SHA256 verification. Code that kept the old round-trip path fails verification on every webhook; a "pass if no signature" dev branch is worse, because it turns the webhook endpoint into a public interface.&lt;br&gt;
Quick self-check:&lt;/p&gt;

&lt;p&gt;grep -rn "payment_status|mc_gross|payer_email|_notify-validate" .&lt;br&gt;
Any hit means IPN-era code is still around. While you change it, also check: are refund/dispute events subscribed, is the amount explicitly converted to a number, and has the idempotency key moved from txn_id to the event id.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stripe API 2026-05-27.dahlia: billed_until is no longer returned by default
Source: Stripe changelog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On SubscriptionItem, billed_until changed from "included by default" to "only present if you expand it". Code that reads this field directly (reconciliation, renewal display, dunning) gets an empty value after the upgrade, often without an error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5n39oukf81i59dslrl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5n39oukf81i59dslrl2.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure 2 beforeafter comparison for billeduntil&lt;/p&gt;

&lt;p&gt;Figure 2: the same code read this field before the upgrade and reads null after it, with no error raised.&lt;/p&gt;

&lt;p&gt;Self-check before upgrading:&lt;/p&gt;

&lt;p&gt;Look at Dashboard &amp;gt; Developers &amp;gt; API version. Do not let the SDK silently move the pinned version.&lt;br&gt;
Search the codebase for billed_until and confirm every use expands it.&lt;br&gt;
Run the subscription cases: create, renew, cancel, plan change, and assert billed_until has a value.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stripe SDK majors: the wrong webhook parser now throws instead of failing silently
Source: stripe-node 21 release notes (pins 2026-03-25.dahlia)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;stripe-node 21 has an easy-to-miss breaking change: constructEvent and parseEventNotification now check the payload type, throw when you pass the wrong one, and point you to the other method. Before, using the wrong parser did not raise; it just returned an object with missing data, which is the "signature passes but no data comes out" class of silent failure. After upgrading to 21, the same code starts throwing. Also remember that v2 thin events do not carry full data; you still need to fetch the event with an API key.&lt;/p&gt;

&lt;p&gt;Self-check: confirm whether your events are v1 or v2. v1 goes through constructEvent; v2 goes through parseEventNotification plus a data fetch. After the upgrade, trigger the same event in sandbox and in production, and make that a required test rather than only running the happy path.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adyen Checkout v72: stricter validation and clearer error codes
Source: Adyen upgrade notes (April 2026)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;v72 is not a silent breaker; it turns vague failures into explicit errors, which is a good direction, but it is still a behavior change for older integrations. Malformed requests that used to be tolerated now fail outright. The default Pix session validity was also extended to 24 hours, which interacts with timeout logic.&lt;/p&gt;

&lt;p&gt;Self-check: before upgrading, run the failure paths once each (decline, expiry, retry, partial payment) and confirm the new error codes line up with your retry branches. Do not test only the success path.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Low priority, worth a note
PayPal order receiptJson structure changed in March 2026; merchants using the Shopify PayPal channel should check where they parse it.
PayPal Enterprise Payouts now exposes direct-debit return reasons through webhooks and a GET endpoint; reconciliation folks can wire it up.
Stripe 2026-07-29.preview surfaces some requirements errors earlier. Do not run preview versions on production.
How to decide whether a change is worth covering
Watch for three signals:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EOL or migration: there is a deadline, but the real risk lives in the migration window, where silent failures are most likely (PayPal IPN here).&lt;br&gt;
Field default changes: no error, the value is just suddenly missing (billed_until here).&lt;br&gt;
SDK major with a pinned API version: the upgrade itself hides the risk (Stripe SDK here).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63n8ya28nsf2q6yava1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63n8ya28nsf2q6yava1v.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;br&gt;
Figure 3 the three signals&lt;/p&gt;

&lt;p&gt;Figure 3: a change that can "return 200 while the business logic never runs" is worth an issue entry with a self-check.&lt;/p&gt;

&lt;p&gt;Watch these sources: official platform changelogs, SDK release notes, issues in open-source integration libraries, and payment developer communities. If a change could "return 200" without the business logic running, it belongs in the next issue.&lt;/p&gt;

&lt;p&gt;General regression checklist before any upgrade&lt;br&gt;
Pin the API version; upgrading is an explicit step, not a side effect of a dependency update&lt;br&gt;
Run success, decline, expiry, refund, dispute and duplicate-callback cases&lt;br&gt;
Assert in tests whether a missing field raises or silently continues&lt;br&gt;
Cover subscription changes separately: create, renew, cancel, plan change&lt;br&gt;
Trigger the same event once in sandbox and once in production and confirm both behave the same&lt;br&gt;
Over the years I have done both development and testing for cross-border payment systems: payment integrations and SDK upgrades, test automation frameworks, payment flow testing, dropped-order triage, and reconciliation fallbacks. I now focus on payment verification for products going global, and I also take on integration and automation work. Happy to compare notes.&lt;/p&gt;

&lt;p&gt;Sources (Issue 1)&lt;br&gt;
PayPal Orders v1-to-v2 migration guide: &lt;a href="https://developer.paypal.com/api/rest/integration/orders-api/v1-v2-migration" rel="noopener noreferrer"&gt;https://developer.paypal.com/api/rest/integration/orders-api/v1-v2-migration&lt;/a&gt;&lt;br&gt;
PayPal IPN deprecation analysis: &lt;a href="https://dev.to/flarecanary/paypal-ipn-is-deprecated-the-ipn-webhooks-migration-drops-payments-in-four-silent-ways-1afp"&gt;https://dev.to/flarecanary/paypal-ipn-is-deprecated-the-ipn-webhooks-migration-drops-payments-in-four-silent-ways-1afp&lt;/a&gt;&lt;br&gt;
Stripe billed_until change: &lt;a href="https://docs.stripe.com/changelog/dahlia/2026-05-27/subscription-item-billed-until-includable" rel="noopener noreferrer"&gt;https://docs.stripe.com/changelog/dahlia/2026-05-27/subscription-item-billed-until-includable&lt;/a&gt;&lt;br&gt;
stripe-node 21 release notes (webhook parser throws): &lt;a href="https://newreleases.io/project/npm/stripe/release/21.0.0" rel="noopener noreferrer"&gt;https://newreleases.io/project/npm/stripe/release/21.0.0&lt;/a&gt;&lt;br&gt;
stripe-node PR #2616 (wrong parser throws): &lt;a href="https://github.com/stripe/stripe-node/pull/2616" rel="noopener noreferrer"&gt;https://github.com/stripe/stripe-node/pull/2616&lt;/a&gt;&lt;br&gt;
Adyen Checkout v72: &lt;a href="https://www.adyen.com/the-latest/upgrade-to-checkout-v72-for-more-reliable-payment-flows" rel="noopener noreferrer"&gt;https://www.adyen.com/the-latest/upgrade-to-checkout-v72-for-more-reliable-payment-flows&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
