<?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: Payneteasy</title>
    <description>The latest articles on DEV Community by Payneteasy (@payneteasy).</description>
    <link>https://dev.to/payneteasy</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%2F3902834%2Fd453c0d4-fa60-426a-8fad-988deb98e5d8.jpeg</url>
      <title>DEV Community: Payneteasy</title>
      <link>https://dev.to/payneteasy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/payneteasy"/>
    <language>en</language>
    <item>
      <title>Citi brings instant payments to Swift: what changes for PSPs, merchants and banks</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Tue, 06 Oct 2026 00:00:15 +0000</pubDate>
      <link>https://dev.to/payneteasy/citi-brings-instant-payments-to-swift-what-changes-for-psps-merchants-and-banks-1ehl</link>
      <guid>https://dev.to/payneteasy/citi-brings-instant-payments-to-swift-what-changes-for-psps-merchants-and-banks-1ehl</guid>
      <description>&lt;p&gt;Citi has launched a multi-market instant payments service built on Swift infrastructure. The key idea: corporate clients can reach several instant payment markets through a single account structure. That simplifies liquidity management and removes a lot of the operational overhead of cross-border flows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters.&lt;/strong&gt; Instant payments have mostly been a local story: SEPA Instant in Europe, UPI in India. Cross-border transfers stayed the place where speed lost to reliability and cost. Running instant payments over Swift takes the model international, on top of the global bank network. It is a signal that "instant" is becoming the default not only for consumer payments but for B2B too, where a delay of a few hours hurts supply chains and treasury.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is affected and how
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. PSPs and fintechs.&lt;/strong&gt; A large bank shipping this raises client expectations. A PSP that only offers standard Swift transfers (T+1/T+2) will need to look at similar instant rails or at partners with access to such schemes. In practice that means new transaction statuses and settlement speeds in the product and in the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Merchants and marketplaces.&lt;/strong&gt; International sellers can get paid faster by buyers and suppliers abroad. Faster cash turnover means less reliance on expensive credit lines and healthier cash flow. Marketplaces should look at these rails for faster payouts to sellers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Banks.&lt;/strong&gt; It is both an opportunity and a challenge. Banks can offer corporate clients a more modern product, but they also need technical compatibility with real-time data exchange standards, which may mean infrastructure investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Instant cross-border payments are moving faster than expected. For anyone building payment infrastructure, the practical question is which markets and schemes this launch actually covers, and whether your gateway or orchestration layer can route to instant rails when a client asks for it.&lt;/p&gt;

&lt;p&gt;Is instant cross-border settlement already on your roadmap, or still a "nice to have"?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://www.finextra.com/newsarticle/48508/citi-launches-multi-market-instant-payments-on-swift-scheme" rel="noopener noreferrer"&gt;Finextra, 1 Oct 2026&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>banking</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Hosted Fields: keep your own checkout and keep raw card data out of your stack</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Mon, 05 Oct 2026 16:18:35 +0000</pubDate>
      <link>https://dev.to/payneteasy/hosted-fields-keep-your-own-checkout-and-keep-raw-card-data-out-of-your-stack-1o8m</link>
      <guid>https://dev.to/payneteasy/hosted-fields-keep-your-own-checkout-and-keep-raw-card-data-out-of-your-stack-1o8m</guid>
      <description>&lt;p&gt;You spent real time on your checkout. Field order, copy, validation, light and dark themes, all tuned to how people actually pay. Then PCI DSS shows up: the moment your page touches a raw card number, that page, its JavaScript and your backend become part of the scope you have to manage and evidence.&lt;/p&gt;

&lt;p&gt;The usual way out is a hosted payment page. You redirect the payer to a page you do not control, and the checkout you designed disappears at the most sensitive step.&lt;/p&gt;

&lt;p&gt;Hosted Fields is the third option: you keep your checkout, and your page code and backend never receive the raw card data. Here is how it works under the hood, what the integration looks like, and where the limits are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a raw card input pulls so much into scope
&lt;/h2&gt;

&lt;p&gt;A plain &lt;code&gt;&amp;lt;input name="card_number"&amp;gt;&lt;/code&gt; looks harmless, but think about everything that can read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your page JavaScript.&lt;/strong&gt; Any script on the page can read the value of an input in the same origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party scripts.&lt;/strong&gt; Analytics, tag managers and chat widgets run with the same access to the inputs as your own code. You do not control their release cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your backend.&lt;/strong&gt; The card number travels in a request to your server, so it passes through your web server, application code and anything that sits in between.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your logs.&lt;/strong&gt; Request logs, error trackers and APM tools tend to capture more than you expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a place where card data can be stored, leaked or misconfigured, and each one is part of what you have to defend.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Hosted Fields isolates the card
&lt;/h2&gt;

&lt;p&gt;Hosted Fields lets you build the payment form on your own site and drop the sensitive inputs into it as fields served by Payneteasy. Three fields, &lt;strong&gt;card number&lt;/strong&gt;, &lt;strong&gt;expiry date&lt;/strong&gt; and &lt;strong&gt;CVV&lt;/strong&gt;, are each loaded as a separate cross-origin iframe from the Payneteasy domain. Everything around them (name, email, cardholder name, headings, the "Pay" button) stays yours.&lt;/p&gt;

&lt;p&gt;To the payer it looks like one form. Technically, the card fields live in Payneteasy-origin iframes that your page code cannot read. The card number, expiry and CVV never enter your page's DOM or JavaScript, the requests to your server, or your logs. That isolation also keeps card data away from the third-party scripts on your page, which on a regular form have the same access to the inputs as your own code.&lt;/p&gt;

&lt;p&gt;You still control the look. Placement, spacing, borders, backgrounds and field containers are plain CSS on your side, and the inputs inside the iframes are styled through the SDK. Here is the same checkout in a light and a dark theme, with the card number, MM/YY and CVV fields restyled to match the rest of the form:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feu70r4qtlpt2ljiw376t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feu70r4qtlpt2ljiw376t.webp" alt="Hosted Fields checkout in light theme" width="715" height="1257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqvoxt1tlb2odfuk10pko.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqvoxt1tlb2odfuk10pko.webp" alt="Hosted Fields checkout in dark theme" width="715" height="1257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No redirect at the moment the card is entered, no unfamiliar page, no lost context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow: ephemeralTicket, hostedFieldsToken, Sale
&lt;/h2&gt;

&lt;p&gt;Two short-lived objects do the work, and they do different jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your server requests a single-use &lt;strong&gt;&lt;code&gt;ephemeralTicket&lt;/code&gt;&lt;/strong&gt; from Payneteasy and places it on the checkout page. The ticket lives for 15 minutes and authorises exactly one tokenization attempt. Your private RSA signing key stays on your server and never reaches the browser. Only the ticket does.&lt;/li&gt;
&lt;li&gt;The payer types the card details into the hosted fields on your page.&lt;/li&gt;
&lt;li&gt;On "Pay", the SDK exchanges those details for a &lt;strong&gt;&lt;code&gt;hostedFieldsToken&lt;/code&gt;&lt;/strong&gt;: a single-use token, valid for 5 minutes, that stands in for the card details in the next request. Your page code only ever sees this token.&lt;/li&gt;
&lt;li&gt;Your server sends the &lt;code&gt;hostedFieldsToken&lt;/code&gt; to the Sale (or Preauth) API in place of the card parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In short: the ticket authorises one tokenization attempt and is safe to place on the page. The token represents the card in the following Sale request, is accepted once, and the card cannot be reconstructed from it.&lt;/p&gt;

&lt;p&gt;On the payment API side the change is small. Four card parameters (&lt;code&gt;credit_card_number&lt;/code&gt;, &lt;code&gt;expire_month&lt;/code&gt;, &lt;code&gt;expire_year&lt;/code&gt;, &lt;code&gt;cvv2&lt;/code&gt;) are replaced by one &lt;code&gt;hosted_fields_token&lt;/code&gt;. The other Sale parameters stay the same. The token is accepted by Sale and Preauth and, for deposit-to-card transfers, for the receiving card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Front-end integration
&lt;/h2&gt;

&lt;p&gt;The basic setup is one script tag and one SDK initialization. Add the script (use the sandbox or production host from the integration guide):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://GATEWAY_HOST/assets/libs/hosted-fields/latest/index.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then initialize the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HostedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;endpointId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ENDPOINT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cardNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1234 1234 1234 1234&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;expiryDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;cvv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cvv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sendToYourServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hosted_fields_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payerMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;payButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EPHEMERAL_TICKET&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cardNumber&lt;/code&gt;, &lt;code&gt;expiryDate&lt;/code&gt; and &lt;code&gt;cvv&lt;/code&gt; are the ids of empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements in your markup. The SDK creates a Payneteasy-origin iframe inside each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reacting to what happens inside the iframe
&lt;/h3&gt;

&lt;p&gt;You cannot read the input, but you can still react to its state. The SDK toggles three classes on the field containers: &lt;code&gt;hf-field--focus&lt;/code&gt;, &lt;code&gt;hf-field--filled&lt;/code&gt; and &lt;code&gt;hf-field--error&lt;/code&gt;. Your CSS does the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card-field&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#c8ccd4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.card-field.hf-field--focus&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b6df0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.card-field.hf-field--filled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f6f8fb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.card-field.hf-field--error&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d64545&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;.card-field&lt;/code&gt; stands for whatever class you put on your own containers.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The server-side step
&lt;/h2&gt;

&lt;p&gt;This part is pseudocode. Request signing and exact parameters are in the docs and in the reference examples, so treat this as the shape of the flow rather than copy-paste code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. When rendering the checkout page
ticket = request_ephemeral_ticket(signed with your RSA key)   # 15 min, one tokenization
render_page(EPHEMERAL_TICKET = ticket)                        # key itself never leaves the server

# 2. When the browser posts the token back
token = request.body.hosted_fields_token                      # single use, 5 min
sale(order_params
     + hosted_fields_token = token)                           # instead of the 4 card params
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here. The signing key belongs to the server and only the ticket goes to the browser. And because the ticket authorises one attempt and the token is accepted once, nothing long-lived ever sits on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not self-host the SDK
&lt;/h2&gt;

&lt;p&gt;Load the SDK with a plain &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag straight from the Payneteasy domain. It is not an npm package, and it must not be self-hosted. A copy served from your site would create the fields on your own origin, which is the exact thing Hosted Fields exists to avoid, so &lt;code&gt;init&lt;/code&gt; refuses to start. If you were planning to bundle it with your build, this is the one place to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Security Policy
&lt;/h2&gt;

&lt;p&gt;If your page sets a CSP, allow the Payneteasy domain in two directives: &lt;code&gt;script-src&lt;/code&gt; for the SDK and &lt;code&gt;frame-src&lt;/code&gt; for the card field iframes. If the script loads but the fields do not appear, check the browser console for CSP violations first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about 3-D Secure?
&lt;/h2&gt;

&lt;p&gt;Hosted Fields changes how the card is collected, not how the payment is authenticated. When the issuer requires a 3-D Secure challenge, the order status response carries it as a URL or as ready-to-render HTML, and you decide how to show it: render it in an iframe inside your checkout so the payer never leaves your page, or redirect to it and return. Authentication itself runs through Payneteasy's 3DS Adapter, exactly as in a server-to-server integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does and does not do for PCI DSS
&lt;/h2&gt;

&lt;p&gt;Raw card details are captured by Payneteasy, not by your page, your server calls or your logs. Payneteasy handles them inside its PCI DSS Level 1 Service Provider environment, while your systems work only with the token. That isolation reduces the PCI DSS scope you have to defend and evidence.&lt;/p&gt;

&lt;p&gt;It is worth being precise about the claim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hosted Fields reduces PCI DSS scope; it does not remove PCI DSS obligations altogether. The SAQ and controls applicable to a particular merchant depend on the overall integration and should be confirmed with the merchant's acquirer or QSA.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do not tell your auditor "we are out of scope" because you added an iframe. Ask your acquirer or QSA which SAQ applies to your whole integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes for PSPs and white-label partners
&lt;/h2&gt;

&lt;p&gt;If you run a white-label installation of the Payneteasy platform, Hosted Fields is available to your merchants on the same terms. The SDK and the card fields are served under your installation's own domain, and every ticket and token is bound to that installation: a ticket issued on one installation is rejected on another. Your merchants get the same integration guide and reference examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary of the trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout stays yours.&lt;/strong&gt; Payers stay on your page, in your design, at the moment they enter the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Card data stays out of your systems.&lt;/strong&gt; Number, expiry and CVV never reach your page, servers or logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small API change.&lt;/strong&gt; Four card parameters become one &lt;code&gt;hosted_fields_token&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing long-lived on the page.&lt;/strong&gt; A ticket lives 15 minutes and admits one tokenization. A token lives 5 minutes and is accepted once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Hosted Fields is available in both sandbox and production. Ask your account manager to enable it for your endpoints, then start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration guide and SDK reference: &lt;a href="https://doc.payneteasy.com/integration/api_use_cases/hosted_fields.html" rel="noopener noreferrer"&gt;Hosted Fields documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reference integrations in PHP, Node.js, Python, .NET, Go, Ruby, Java, Kotlin and Rust, plus React on Next.js, OAuth signature included: &lt;a href="https://github.com/payneteasy/hosted-fields-examples" rel="noopener noreferrer"&gt;hosted-fields-examples on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full write-up, including the payment flow diagram, is on the Payneteasy blog: &lt;a href="https://payneteasy.com/blog/hosted-fields?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=hosted-fields&amp;amp;utm_content=cta" rel="noopener noreferrer"&gt;Hosted Fields: keep your checkout, keep card data out of your systems&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have already put card fields in iframes on your own checkout, what was the part that took you longest to get right?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>api</category>
      <category>paymentgateway</category>
    </item>
    <item>
      <title>Retry logic can trip the card network's own decline-rate monitor</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Mon, 05 Oct 2026 00:45:14 +0000</pubDate>
      <link>https://dev.to/payneteasy/retry-logic-can-trip-the-card-networks-own-decline-rate-monitor-kf0</link>
      <guid>https://dev.to/payneteasy/retry-logic-can-trip-the-card-networks-own-decline-rate-monitor-kf0</guid>
      <description>&lt;p&gt;Found this after a client asked why their merchant account got flagged for "excessive decline rate" despite every individual transaction eventually succeeding.&lt;/p&gt;

&lt;p&gt;Their retry logic resubmitted a declined authorization up to 4 times with short backoff, same card, same amount, hoping a soft decline would clear. It usually did, on attempt 2 or 3. From the app's point of view, that's a success story.&lt;/p&gt;

&lt;p&gt;From the network's point of view, that's 4 authorization attempts, 3 of them declined. Visa and Mastercard both run decline-rate monitoring at the merchant level, counting declines over total auth attempts in a rolling window, not unique transactions. Retries inflate the denominator and the decline count together. Cross a threshold (ballpark 15% triggers a warning tier on some programs) and the merchant account itself gets reviewed, sometimes rate-limited by the acquirer regardless of how clean the actual approval-after-retry outcome looks.&lt;/p&gt;

&lt;p&gt;Fix was capping retries to 1 for hard declines, routing only specific soft-decline codes (51, 65) into a delayed retry queue, and treating anything else as terminal on first attempt.&lt;/p&gt;

&lt;p&gt;Anyone tracking their decline ratio as "attempts" vs "unique transactions" separately, or is this usually invisible until the acquirer calls?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>backend</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The BIN table said debit. The issuer said credit.</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:15:14 +0000</pubDate>
      <link>https://dev.to/payneteasy/the-bin-table-said-debit-the-issuer-said-credit-57i5</link>
      <guid>https://dev.to/payneteasy/the-bin-table-said-debit-the-issuer-said-credit-57i5</guid>
      <description>&lt;p&gt;Added card-surcharging logic for a US merchant: debit cards skip the 3% fee, credit cards get it, per the usual state rules. Classification ran off a static BIN range table refreshed quarterly from our processor.&lt;/p&gt;

&lt;p&gt;Surcharge complaints started trickling in — cardholders insisting their debit card got charged a credit surcharge. Pulled the flagged transactions: roughly 1 in 180 had a BIN that our table listed as credit, but the issuer's own authorization response carried a debit indicator in the response fields.&lt;/p&gt;

&lt;p&gt;Turns out prepaid and co-branded cards get reissued under ranges that shift between debit and credit designation as issuers rebalance portfolios, and BIN tables lag actual issuer records by weeks to months. The authorization response usually carries the real account type in the response (card type indicator, or equivalent field depending on network) — more current than any static table, because it comes straight from the issuer at auth time.&lt;/p&gt;

&lt;p&gt;Switched the surcharge decision to read off the authorization response instead of the pre-auth BIN lookup. Still keep the BIN table for pre-auth estimates shown at checkout, but settle the actual charge based on what the issuer says in the response.&lt;/p&gt;

&lt;p&gt;Anyone else running surcharge or interchange logic purely off BIN lookups? Curious how often that assumption breaks for you in practice.&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>Settlement batches group by capture date, not auth date, and reconciliation breaks on day boundaries</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Sat, 03 Oct 2026 00:00:14 +0000</pubDate>
      <link>https://dev.to/payneteasy/settlement-batches-group-by-capture-date-not-auth-date-and-reconciliation-breaks-on-day-boundaries-c85</link>
      <guid>https://dev.to/payneteasy/settlement-batches-group-by-capture-date-not-auth-date-and-reconciliation-breaks-on-day-boundaries-c85</guid>
      <description>&lt;p&gt;Spent a morning chasing a reconciliation gap on a B2B invoicing flow: authorize at 23:40 local time, capture triggered by a backend job that runs at 00:10 once the invoice is finalized.&lt;/p&gt;

&lt;p&gt;The authorization and capture land in two different settlement batches, because the acquirer groups transactions by capture timestamp, not authorization timestamp. Our internal ledger was matching on order_id plus auth_date, assuming both events would settle together. They didn't. One order showed up as "authorized, no matching settlement" for 24 hours, then resolved itself the next cutoff cycle.&lt;/p&gt;

&lt;p&gt;Nothing was actually wrong. The acquirer's settlement report was doing exactly what its spec said. Our matching logic just assumed same-batch settlement because that's what happens 95% of the time when auth and capture are seconds apart.&lt;/p&gt;

&lt;p&gt;Fixed it by matching on a settlement_batch_id pulled from the acquirer's report instead of inferring it from timestamps, and added a 48-hour grace window before flagging an authorization as unsettled.&lt;/p&gt;

&lt;p&gt;The part that still bugs me: this only shows up for merchants with delayed capture crossing midnight in the acquirer's settlement timezone, which is rarely the same timezone as the merchant's. Anyone built reconciliation that explicitly tracks the acquirer's cutoff timezone separately from the merchant's business timezone, or did you also find out the hard way?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>api</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The FX rate nobody locked between auth and capture</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Fri, 02 Oct 2026 00:00:15 +0000</pubDate>
      <link>https://dev.to/payneteasy/the-fx-rate-nobody-locked-between-auth-and-capture-3n5d</link>
      <guid>https://dev.to/payneteasy/the-fx-rate-nobody-locked-between-auth-and-capture-3n5d</guid>
      <description>&lt;p&gt;Ran into this on a cross-border order: card authorized in EUR, capture fires four days later once the item ships, settlement currency is USD. Between those two events the EUR/USD rate moved about 1.1%. The captured amount came back higher than the number shown at checkout, and the customer's statement didn't match the order confirmation.&lt;/p&gt;

&lt;p&gt;Turns out card networks only guarantee the authorization hold in the cardholder's currency for a window that varies by processor, usually somewhere between 24 and 72 hours. Past that window, some acquirers recalculate the conversion at capture time using the current rate instead of the one quoted at authorization. Nothing in the API response flags this. You just get a captured amount that's a few cents or a few dollars off from what you authorized, and your reconciliation job either silently absorbs it or throws a false mismatch alert depending on your tolerance threshold.&lt;/p&gt;

&lt;p&gt;We ended up capturing the FX rate at auth time ourselves and comparing it against the settlement report line by line, then eating small drifts under a fixed cap and flagging anything above it for manual review.&lt;/p&gt;

&lt;p&gt;For anyone running delayed capture across currencies: do you reprice at capture, refund the delta, or just absorb it below a threshold?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The tip adjustment nobody codes for until it declines</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Thu, 01 Oct 2026 00:00:15 +0000</pubDate>
      <link>https://dev.to/payneteasy/the-tip-adjustment-nobody-codes-for-until-it-declines-5d72</link>
      <guid>https://dev.to/payneteasy/the-tip-adjustment-nobody-codes-for-until-it-declines-5d72</guid>
      <description>&lt;p&gt;Bar tab system: authorize $40 when the card is opened, capture the final total with tip when the tab closes. Worked fine until a night with heavy tipping pushed captures 25-30% over the original authorization.&lt;/p&gt;

&lt;p&gt;Turns out card networks cap how much a capture can exceed the initial auth without a step-up. Visa's tolerance for restaurant MCC 5812 is 20% over the authorized amount. Go past that and the issuer can decline the capture outright, or your gateway silently splits it into a new incremental authorization request that gets evaluated independently, with its own risk scoring, its own chance of a soft decline unrelated to the original approval.&lt;/p&gt;

&lt;p&gt;The threshold isn't universal either. Lodging and car rental MCCs get different tolerance rules because no-shows and extended stays are expected to run over. Retail MCCs generally get none, an exact-match capture or nothing.&lt;/p&gt;

&lt;p&gt;Our capture code just sent whatever the final total was and let the processor sort it out. It sorted it out by declining roughly 1 in 30 tabs over $100, all clustered on nights with above-average tip percentages. Fix was capping tip-adjusted captures at 18% over auth and forcing a fresh authorization for anything beyond that.&lt;/p&gt;

&lt;p&gt;Anyone tracking these tolerance percentages per network/MCC combo, or is everyone just hardcoding one number and hoping?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>api</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Settlement batches don't know about daylight saving time</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Wed, 30 Sep 2026 00:15:15 +0000</pubDate>
      <link>https://dev.to/payneteasy/settlement-batches-dont-know-about-daylight-saving-time-48ib</link>
      <guid>https://dev.to/payneteasy/settlement-batches-dont-know-about-daylight-saving-time-48ib</guid>
      <description>&lt;p&gt;Reconciliation broke twice a year for months before we found the pattern. Our settlement cutoff was defined as 23:00 local time, converted to UTC at deploy time and hardcoded. Worked fine for six months, then the clocks changed and the cutoff silently shifted by an hour relative to the acquirer's actual batch close.&lt;/p&gt;

&lt;p&gt;The result: on the Sunday DST kicked in, roughly 40 minutes of transactions that should have landed in Monday's settlement file landed in Sunday's instead. Nothing errored. Both files were valid, both totals reconciled internally, but our ledger attributed a chunk of Monday's revenue to Sunday. Finance caught it three weeks later during month-end close, not us.&lt;/p&gt;

&lt;p&gt;Fix was boring: store cutoff as a timezone-aware rule (America/New_York, 23:00, DST-adjusted), not a fixed UTC offset baked in at deploy. Also added a daily check comparing our computed batch boundary against the acquirer's actual file timestamp, alerting on drift over 5 minutes.&lt;/p&gt;

&lt;p&gt;What surprised me is how long a boundary bug like this can survive. It doesn't fail loudly, it just quietly reassigns transactions across a date line twice a year, and every individual number still looks correct in isolation.&lt;/p&gt;

&lt;p&gt;Anyone else hardcode a UTC offset somewhere they later regretted?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Idempotency keys stored after the charge, not before</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Tue, 29 Sep 2026 00:00:12 +0000</pubDate>
      <link>https://dev.to/payneteasy/idempotency-keys-stored-after-the-charge-not-before-17fg</link>
      <guid>https://dev.to/payneteasy/idempotency-keys-stored-after-the-charge-not-before-17fg</guid>
      <description>&lt;p&gt;Traced a double-charge bug that had nothing to do with retry timing or refunds. It was write order.&lt;/p&gt;

&lt;p&gt;Our idempotency middleware called the processor first, then wrote the idempotency key to the database once the charge succeeded. Made sense on paper: don't mark a key as "used" until you know the outcome.&lt;/p&gt;

&lt;p&gt;Problem shows up when the process dies between those two steps. Processor accepts the charge, returns a 201, and the pod gets OOM-killed before the key write commits. Client sees a timeout, retries with the same key. Middleware checks the table, finds nothing, treats it as a fresh request, charges again.&lt;/p&gt;

&lt;p&gt;Fixed it by flipping the order: write the key with status "pending" before calling the processor, then update to "complete" after. A retry that lands mid-flight now sees "pending" and can poll or reject instead of reprocessing. Slightly more writes per request, but the failure window shrinks from "anytime during the whole request" to a single row update.&lt;/p&gt;

&lt;p&gt;Most idempotency writeups show the happy path — client retries, server checks key, returns cached response. Fewer show what your key-write timing actually protects against, which turns out to matter more than the retry logic itself.&lt;/p&gt;

&lt;p&gt;Where do you write your idempotency key — before the call to the processor, or after?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>Requesting a 3DS exemption doesn't mean you get one</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Mon, 28 Sep 2026 00:00:14 +0000</pubDate>
      <link>https://dev.to/payneteasy/requesting-a-3ds-exemption-doesnt-mean-you-get-one-17hh</link>
      <guid>https://dev.to/payneteasy/requesting-a-3ds-exemption-doesnt-mean-you-get-one-17hh</guid>
      <description>&lt;p&gt;Set up TRA (transaction risk analysis) exemption flags for low-risk, low-value card transactions under our PSD2 flow. Sent the exemption indicator on every eligible auth, assumed the issuer would honor it since we were under their fraud rate threshold for the exemption bucket.&lt;/p&gt;

&lt;p&gt;About 18% of flagged transactions still came back with a challenge requested anyway.&lt;/p&gt;

&lt;p&gt;Turns out the exemption flag is a request, not an instruction. The issuer's ACS makes the final call regardless of what indicator you send, and it can override based on its own risk engine, a mismatch between billing and shipping country, or just a stale risk model that hasn't been retrained since a fraud spike last quarter. Nothing in the response tells you why the exemption was ignored, you just get a challenge flow you didn't build UX for because you assumed frictionless.&lt;/p&gt;

&lt;p&gt;Fix was ugly but simple: always render the challenge-capable flow client-side even when requesting an exemption, and log the exemption request vs actual outcome per issuer BIN range so we could see which issuers reliably honor it and which don't bother.&lt;/p&gt;

&lt;p&gt;Anyone tracking exemption honor rates by issuer? Curious if this is consistent across regions or if it's mostly an EU-issuer quirk.&lt;/p&gt;

</description>
      <category>payments</category>
      <category>security</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Missing Level 3 data added 180bps to a B2B card transaction</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Sun, 27 Sep 2026 00:00:14 +0000</pubDate>
      <link>https://dev.to/payneteasy/missing-level-3-data-added-180bps-to-a-b2b-card-transaction-1e54</link>
      <guid>https://dev.to/payneteasy/missing-level-3-data-added-180bps-to-a-b2b-card-transaction-1e54</guid>
      <description>&lt;p&gt;Reconciling a corporate card processing report for a B2B marketplace and noticed the effective rate on a chunk of transactions was way above what the acquirer contract promised. Same card type, same MCC, same ticket size range, but a visible split in the fee line.&lt;/p&gt;

&lt;p&gt;Traced it to Level 2/3 data. Commercial and purchasing cards qualify for lower interchange only if the merchant submits the extra fields: tax amount, PO number, customer code, and for Level 3 the line-item detail (SKU, quantity, unit price, commodity code). Our integration was sending Level 1 data only — card number, amount, date — because that's all the checkout form collected. Every one of those transactions got auto-downgraded to standard commercial rate.&lt;/p&gt;

&lt;p&gt;Ran the math on a month of volume: transactions with full Level 3 data settled around 1.8% effective. The ones missing it settled closer to 3.6%. On six-figure monthly volume that gap is not rounding error, it's a line item finance will ask about.&lt;/p&gt;

&lt;p&gt;Fixed it by making tax amount and PO number required fields before authorization, plus pulling line-item detail from the order object we already had in the database. Rate on new transactions dropped within one billing cycle.&lt;/p&gt;

&lt;p&gt;How are you validating L2/L3 fields are actually populated before the auth request goes out, not just present in the schema?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>api</category>
      <category>backend</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Statement descriptors get cut to 22 characters and nobody warns you</title>
      <dc:creator>Payneteasy</dc:creator>
      <pubDate>Sat, 26 Sep 2026 00:00:15 +0000</pubDate>
      <link>https://dev.to/payneteasy/statement-descriptors-get-cut-to-22-characters-and-nobody-warns-you-13</link>
      <guid>https://dev.to/payneteasy/statement-descriptors-get-cut-to-22-characters-and-nobody-warns-you-13</guid>
      <description>&lt;p&gt;Spent a week chasing a spike in 'unrecognized charge' disputes for a subscription product. Turned out the statement descriptor we sent — company name plus order reference — was 31 characters. Fine on our side, fine in the gateway's API response, fine in our own dashboard. But Visa truncates descriptors to 22 characters at the issuer level, and every bank does the truncation differently. Some cut from the end, some cut from the middle and keep a suffix, one issuer we tested just dropped vowels.&lt;/p&gt;

&lt;p&gt;So a descriptor like "COMPANYNAME ORDER-88213" became "COMPANYNAME ORD" on one card, "COMPANYNAM-88213" on another. Customers didn't recognize either one, called their bank, bank filed it as fraud. Chargeback rate on that SKU was 3x the account average, and the reason code was almost always "cardholder does not recognize transaction," not actual fraud.&lt;/p&gt;

&lt;p&gt;Fix ended up being: put the recognizable brand name in the first 12 characters, since that part survives truncation on basically every issuer we tested, and drop the order reference entirely (support already looks up orders by amount + timestamp anyway).&lt;/p&gt;

&lt;p&gt;No gateway doc I've read mentions the 22-char limit or truncation behavior — you find out from dispute reason codes months later. Anyone tracking descriptor-related dispute rates as a metric on its own, separate from general chargeback rate?&lt;/p&gt;

</description>
      <category>payments</category>
      <category>fintech</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
