<?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: Wismook Teams</title>
    <description>The latest articles on DEV Community by Wismook Teams (@wismook_teams).</description>
    <link>https://dev.to/wismook_teams</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%2F4184647%2Fca00ab98-8599-4d14-ba3e-c0530fbe76f5.png</url>
      <title>DEV Community: Wismook Teams</title>
      <link>https://dev.to/wismook_teams</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wismook_teams"/>
    <language>en</language>
    <item>
      <title>How to Connect Cryptomus to Perfect Panel: API Keys, Domain Verification and Fees</title>
      <dc:creator>Wismook Teams</dc:creator>
      <pubDate>Sat, 17 Oct 2026 08:48:37 +0000</pubDate>
      <link>https://dev.to/wismook_teams/how-to-connect-cryptomus-to-perfect-panel-api-keys-domain-verification-and-fees-3blh</link>
      <guid>https://dev.to/wismook_teams/how-to-connect-cryptomus-to-perfect-panel-api-keys-domain-verification-and-fees-3blh</guid>
      <description>&lt;p&gt;The Cryptomus connection in Perfect Panel comes down to a few fields. Getting those fields ready is the part that takes more attention: your account needs verification, your website needs merchant approval, and the API key must be the one for receiving payments.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://zosmm.com/" rel="noopener noreferrer"&gt;ZOSMM Panel&lt;/a&gt;, and this guide follows the setup route used for a Perfect Panel website. It covers the existing Cryptomus payment integration, rather than building a separate checkout application from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referral disclosure:&lt;/strong&gt; the signup link below is my referral link. I may receive a commission if you register and use Cryptomus through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the account, then create the merchant
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://app.cryptomus.com/signup/?ref=79QW57" rel="noopener noreferrer"&gt;Create a Cryptomus account&lt;/a&gt; and complete the identity verification requested in your account before proceeding with business payment setup. Follow the current checks shown in the dashboard; creating a login is not the same as being approved to accept payments.&lt;/p&gt;

&lt;p&gt;Next, open &lt;strong&gt;Business → Merchants&lt;/strong&gt; and choose &lt;strong&gt;Create merchant&lt;/strong&gt;. Give the merchant a name you can recognize, then open its settings and enter your website URL and an accurate description of the business.&lt;/p&gt;

&lt;p&gt;Cryptomus describes that process in its &lt;a href="https://cryptomus.com/faq/how-to-add-a-project" rel="noopener noreferrer"&gt;merchant setup instructions&lt;/a&gt;. Domain verification and project moderation are part of it. Submit the actual website and business details, then wait for the result rather than treating account registration as approval.&lt;/p&gt;

&lt;p&gt;A merchant that is still under review is not ready just because the panel has somewhere to paste an API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the domain on a Perfect Panel website
&lt;/h2&gt;

&lt;p&gt;The domain verification step can be awkward when you use a hosted panel and do not have a conventional file manager. The route supplied for this setup uses Cryptomus's &lt;strong&gt;Using a meta tag on the site&lt;/strong&gt; option.&lt;/p&gt;

&lt;p&gt;Open your merchant settings in Cryptomus, find &lt;strong&gt;Confirm domain&lt;/strong&gt;, and choose that method. Copy the verification tag exactly as supplied.&lt;/p&gt;

&lt;p&gt;The Perfect Panel instructions for this route are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Admin area → Settings → Integrations&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open an integration that provides a &lt;strong&gt;Code snippet&lt;/strong&gt; field.&lt;/li&gt;
&lt;li&gt;Paste the Cryptomus verification tag into that field and save or activate it as required by the panel.&lt;/li&gt;
&lt;li&gt;Return to Cryptomus and request domain verification.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is a detail here worth checking before you move on: saving a snippet does not prove that it appears where the verifier expects it. The tag needs to be present on the public website in a location Cryptomus can recognize.&lt;/p&gt;

&lt;p&gt;Use a suitable custom-code option if your panel exposes one. If you are following the support workaround that uses another integration's snippet field, confirm with Perfect Panel support that the selected integration accepts the tag in the required location. Do not replace existing working integration code just to make room for it.&lt;/p&gt;

&lt;p&gt;Copy only the domain verification tag into public site code. Your Payment API Key belongs in the payment settings, never in this snippet.&lt;/p&gt;

&lt;p&gt;Once the domain check succeeds, finish the merchant settings and submit any remaining moderation information. Domain ownership verification and merchant approval are separate checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy the Payment API Key and Merchant ID
&lt;/h2&gt;

&lt;p&gt;After merchant approval, open the merchant settings and copy these two values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Payment API Key&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merchant ID&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cryptomus separates payment keys from payout keys. For this connection, you want the key used to accept payments, not the one used to make withdrawals.&lt;/p&gt;

&lt;p&gt;The current &lt;a href="https://doc.cryptomus.com/merchant-api/getting-api-keys" rel="noopener noreferrer"&gt;Getting API keys documentation&lt;/a&gt; shows the merchant creation, domain confirmation and moderation sequence. If an older instruction sends you to a documentation page that no longer opens, use that current page instead.&lt;/p&gt;

&lt;p&gt;Keep both values associated with the same merchant. Copying an ID from one project and a key from another is an avoidable source of configuration problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the credentials in Perfect Panel
&lt;/h2&gt;

&lt;p&gt;Open your panel's payment-method settings and add or edit the &lt;strong&gt;Cryptomus&lt;/strong&gt; method. Enter the Merchant ID and Payment API Key in their matching fields.&lt;/p&gt;

&lt;p&gt;Use the existing integration's setup instructions for any other fields it displays. This is configuration of a built-in connector; you do not need to publish your own API request script to complete these steps.&lt;/p&gt;

&lt;p&gt;The credentials should stay in the admin payment configuration. If you share screenshots while asking for help, hide the API key first.&lt;/p&gt;

&lt;p&gt;The remaining field that deserves a closer look is &lt;strong&gt;Subtract&lt;/strong&gt;. Its name is less clear than its purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subtract controls who pays the processing commission
&lt;/h2&gt;

&lt;p&gt;Subtract is the percentage of the payment commission passed to the customer. It is an integer from &lt;strong&gt;0 to 100&lt;/strong&gt;, with a documented default of &lt;strong&gt;0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is not the commission rate itself.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;Subtract = 0&lt;/strong&gt;, none of that processing commission is passed to the customer through this parameter. With &lt;strong&gt;Subtract = 100&lt;/strong&gt;, the customer covers all of it. A value between those endpoints shares the commission.&lt;/p&gt;

&lt;p&gt;Cryptomus gives a useful example in its &lt;a href="https://doc.cryptomus.com/merchant-api/payments/creating-invoice" rel="noopener noreferrer"&gt;invoice documentation&lt;/a&gt;: if your processing rate is &lt;strong&gt;1%&lt;/strong&gt; and the invoice is &lt;strong&gt;100 USDT&lt;/strong&gt;, setting &lt;strong&gt;Subtract = 100&lt;/strong&gt; makes the customer pay &lt;strong&gt;101 USDT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is an example, not a statement that every merchant has a 1% rate. Check your actual rate and the amount shown on the generated invoice. Do not confuse the processing commission with a wallet's separate network transaction fee.&lt;/p&gt;

&lt;p&gt;Also check whether you have configured an additional payment-method fee inside Perfect Panel. A separate panel surcharge and the Cryptomus commission allocation are different settings; make sure the checkout total reflects what you intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the full payment flow before relying on it
&lt;/h2&gt;

&lt;p&gt;A saved payment method proves that the configuration was saved. It does not prove that a customer deposit will be credited correctly.&lt;/p&gt;

&lt;p&gt;First check that the method appears on the customer-facing Add Funds page and that creating an invoice opens the expected Cryptomus payment page. Review the amount, selected currency and network, and any extra charge shown to the payer.&lt;/p&gt;

&lt;p&gt;When you perform an authorized test payment, compare the invoice result with the deposit record and customer balance inside the panel. An invoice being created, a payment being detected, and a balance being credited are different events.&lt;/p&gt;

&lt;p&gt;For a built-in integration, follow its documented notification setup. Do not invent a callback URL or paste an unrelated endpoint into merchant settings.&lt;/p&gt;

&lt;p&gt;If the invoice is paid but the panel balance does not update, collect the invoice reference and the matching panel payment record for support. Avoid immediately adding a manual credit without checking whether an automatic update is still pending; otherwise the same deposit could be credited twice.&lt;/p&gt;

&lt;p&gt;The final check is practical: the invoice shows the amount you intended, its payment result reaches the panel, and the customer receives the corresponding balance credit once.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>crypto</category>
    </item>
    <item>
      <title>SMM Panel API Integration: Connecting Is Easy, Keeping Orders in Sync Is Harder</title>
      <dc:creator>Wismook Teams</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:42:09 +0000</pubDate>
      <link>https://dev.to/wismook_teams/smm-panel-api-integration-connecting-is-easy-keeping-orders-in-sync-is-harder-1eng</link>
      <guid>https://dev.to/wismook_teams/smm-panel-api-integration-connecting-is-easy-keeping-orders-in-sync-is-harder-1eng</guid>
      <description>&lt;p&gt;At &lt;a href="https://zosmm.com/" rel="noopener noreferrer"&gt;ZO Panel&lt;/a&gt;, we used to handle a situation that made this difference obvious. An order would be marked completed. The customer would dispute it, and after checking, we would find that it should have been canceled instead.&lt;br&gt;
At ZO Panel, we used to handle a situation that made this difference obvious. An order would be marked completed. The customer would dispute it, and after checking, we would find that it should have been canceled instead.&lt;/p&gt;

&lt;p&gt;The provider had to correct the order manually. Then we had to make the matching correction on our panel.&lt;/p&gt;

&lt;p&gt;One customer order. Two separate updates.&lt;/p&gt;

&lt;p&gt;Our current Perfect Panel connections handle that correction differently: when the order status changes on the connected side, the change can also reach our panel after the order has already been marked completed. We see this from both directions, whether we are buying from a provider or supplying services to a reseller.&lt;/p&gt;

&lt;p&gt;That experience changed what I pay attention to when looking at an integration. A successful connection is a starting point. It does not tell you how the system will behave when an earlier status turns out to be wrong.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I run ZO Panel. The operational example above comes from our own workflow. The PHP example below is illustrative and has not been tested against a live account.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  When both panels use Perfect Panel
&lt;/h2&gt;

&lt;p&gt;In our workflow, a reseller using Perfect Panel can take the API key from their account profile on our panel and enter it in their own panel’s provider settings.&lt;/p&gt;

&lt;p&gt;Perfect Panel’s &lt;a href="https://perfectpanel.com/blog/how-to-connect-smm-services-providers" rel="noopener noreferrer"&gt;provider connection guide&lt;/a&gt; explains why this is straightforward: providers using the same platform are already integrated, so the connection does not require writing a new API client.&lt;/p&gt;

&lt;p&gt;There is still setup work. Connecting a provider and deciding which services to offer are separate decisions. Service selection, pricing, and synchronization settings deserve a check before customers start placing orders.&lt;/p&gt;

&lt;p&gt;I would also avoid describing this as “everything syncs.” That phrase hides the details a reseller actually needs to understand.&lt;/p&gt;

&lt;p&gt;Which service fields update? Which order changes reach the receiving panel? What happens to an order that was completed yesterday and corrected today?&lt;/p&gt;

&lt;p&gt;Our experience with corrections after completion is useful, but it should not be treated as a promise about every installation. Perfect Panel’s public &lt;a href="https://perfectpanel.com/features" rel="noopener noreferrer"&gt;feature list&lt;/a&gt; also describes a manual option for requesting updated statuses of mistakenly completed orders. Verify the behavior of your own connection.&lt;/p&gt;
&lt;h2&gt;
  
  
  When the other system does not use Perfect Panel
&lt;/h2&gt;

&lt;p&gt;For a custom application or another panel platform, start with the provider’s documented request format.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://zosmm.com/api" rel="noopener noreferrer"&gt;ZO Panel API documentation&lt;/a&gt; specifies a POST endpoint at &lt;code&gt;https://zosmm.com/api/v2&lt;/code&gt;, JSON responses, and actions for retrieving services, submitting orders, checking statuses, and checking balance.&lt;/p&gt;

&lt;p&gt;An API key alone does not make an unrelated application compatible. The receiving software needs to support those requests and interpret the responses, either through an existing connector or code you write.&lt;/p&gt;

&lt;p&gt;For a first connection check, retrieve the service list. There is no need to create a paid order just to check whether your application can communicate with the endpoint.&lt;/p&gt;
&lt;h3&gt;
  
  
  A small PHP request example
&lt;/h3&gt;

&lt;p&gt;This example requires PHP 8 or later with cURL enabled. Store the key in the server’s environment as &lt;code&gt;ZOSMM_API_KEY&lt;/code&gt;, rather than putting it in a public file or browser script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;panelRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ZOSMM_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Missing API key.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://zosmm.com/api/v2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;curl_setopt_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_POST&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;http_build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'action'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_CONNECTTIMEOUT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;CURLOPT_TIMEOUT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$httpCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$body&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="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Transport failure; outcome unknown.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$httpCode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$httpCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Unexpected HTTP status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$httpCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Unexpected response structure.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_key_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'API returned an error; review securely.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;panelRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Received %d services.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$services&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timeouts are example client settings, not documented provider limits. This function handles a single request; it does not implement storage, scheduling, or order reconciliation.&lt;/p&gt;

&lt;p&gt;Notice that it checks both the HTTP response and the decoded body. Those are different layers. Your application should not proceed simply because it received a response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep your order ID and the provider’s order ID separate
&lt;/h2&gt;

&lt;p&gt;When submitting an order, your application already has a local record. The provider returns its own order identifier.&lt;/p&gt;

&lt;p&gt;Keep both.&lt;/p&gt;

&lt;p&gt;Use the local identifier for your customer-facing order and the provider identifier for later status requests. Store the relationship before sending the order into any background processing workflow.&lt;/p&gt;

&lt;p&gt;There is another detail worth handling before you automate submissions: a timeout leaves uncertainty.&lt;/p&gt;

&lt;p&gt;Your application may stop waiting after the provider has accepted the request. Automatically submitting the same order again could create a duplicate. A timeout should therefore move the order into a review or reconciliation path, rather than trigger an unconditional retry.&lt;/p&gt;

&lt;p&gt;The documentation reviewed for this example does not establish an idempotency mechanism. Do not assume repeated submissions are harmless.&lt;/p&gt;

&lt;h2&gt;
  
  
  A completed order may still need reconciliation
&lt;/h2&gt;

&lt;p&gt;The correction we used to make manually exposes a weakness in a common design: stop checking an order forever as soon as it becomes completed.&lt;/p&gt;

&lt;p&gt;That design is simple. It also means the application has no way to discover a later correction unless another process brings it back into view.&lt;/p&gt;

&lt;p&gt;For a custom integration, distinguish the current order status from your policy for checking it again.&lt;/p&gt;

&lt;p&gt;Active orders need routine updates. Disputed orders need a way to request a fresh status. Recently closed orders may need a separate reconciliation policy, depending on what the provider supports.&lt;/p&gt;

&lt;p&gt;That does not mean polling every historical order indefinitely. It means deciding how corrections will reach your system instead of assuming they never happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep a history of corrections
&lt;/h3&gt;

&lt;p&gt;Overwriting a status tells you what the order says now. Keeping a status history tells you what changed.&lt;/p&gt;

&lt;p&gt;A useful record includes the previous status, the new status, the provider order ID, and when your application observed the change. Keep credentials out of that record.&lt;/p&gt;

&lt;p&gt;Financial handling needs its own care. A status correction should not cause the same customer balance adjustment to run twice. Updating the displayed status and recording a refund are related operations, but they are not interchangeable.&lt;/p&gt;

&lt;p&gt;Before relying on the connection, ask one concrete question: if the provider corrects an order after your system has closed it, how will that correction reach the customer’s order record?&lt;/p&gt;

</description>
      <category>api</category>
      <category>php</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
