Comment j'ai créé un système d'acceptation de paiement Minecraft en utilisant Pure PowerShell


Dans cet article, nous allons visser le beignet impie au serveur vanille Minecraft en utilisant Powershell. L'avantage de la méthode est que minecraft n'est qu'un cas particulier de la mise en œuvre de paiements automatiques à l'aide de commandes de console. Nous écoutons simplement ce que le système de paiement nous envoie et nous l'enveloppons en équipe. Et surtout - pas de plugins.
Et nous accepterons les paiements via PayPal. Plus important encore, afin de commencer à accepter des paiements dont vous n'avez pas besoin de changer le code, PayPal nous enverra tout ce dont vous avez besoin. Nous utiliserons les boutons du site, pour que le site puisse faire du HTML pur. Nous nous abstenons des subtilités du système de paiement lui-même et nous nous concentrons uniquement sur les principaux points du code.

Soit dit en passant, l'auteur sera très heureux si vous parcourez tous ses modules et y trouvez les erreurs des enfants que vous pointez ou corrigez. Voici un lien vers le projet github .

Quelques mots sur IPN


IPN


Nous accepterons les paiements via les boutons. Les boutons ne nécessitent aucun backend de votre part, ils fonctionnent en HTML pur et ont également leurs propres champs.

Les boutons déclenchent IPN - Instant Payment Notification, dans lequel les données sont envoyées à notre WebListener. Nous considérerons la structure IPN ci-dessous.

De plus, toute personne possédant un compte PayPal peut créer son propre bouton.
IPN ne dispose pas de l'API REST PayPal complète, mais les fonctionnalités de base peuvent y être implémentées. En fait, l'IPN que nous envisageons n'est pas une API REST au sens plein du terme simplement parce que PayPal lui-même n'attend rien de nous sauf le code 200.

Raise WebListener


PayPal, pour des raisons de sécurité, n'envoie pas de demandes via HTTP, nous devons donc émettre un certificat pour commencer. 

L'auteur a utilisé WinAcme . Vous pouvez émettre un certificat vers n'importe quel domaine et vous devez placer le certificat dans un magasin de certificats local. Soit dit en passant, WinAcme est situé à la racine du disque dans l'image.

#     
Get-ChildItem -Path Cert:\LocalMachine\My 
 
#      443 .
netsh http add sslcert ipport=0.0.0.0:443 certhash=D106F5676534794B6767D1FB75B58D5E33906710 "appid={00112233-4455-6677-8899-AABBCCDDEEFF}"

Powershell peut utiliser des classes de .net, ce qui le rend presque égal à .net. Tout d'abord, à l'aide de la classe HttpListener, augmentez le serveur Web.

#   .net
$http = [System.Net.HttpListener]::new() 
 
#   
$http.Prefixes.Add("http://donate.to/")
$http.Prefixes.Add("https://donate.to/")
 
# 
$http.Start()

Pour vérifier que tout se passe bien, exécutez netstat.



Si notre script a commencé à écouter le port 443 dans la liste, cela signifie que vous avez tout fait correctement, et nous pouvons procéder à l'acceptation du traitement des demandes. N'oubliez pas le pare-feu.

Accepter la requête


En utilisant IPN Simulator, nous pouvons nous envoyer une demande de test POST pour voir de quoi il s'agit. Mais vous ne pouvez pas y inclure vos propres champs, donc l'auteur recommande de faire un bouton et d'acheter immédiatement quelque chose de vous-même. L'historique IPN affichera une demande normale à partir du bouton que vous utiliserez. C'est précisément ce que l'auteur a fait en achetant un charbon pour un rouble.

Nous accepterons d'utiliser la boucle While. Pendant que le serveur Web fonctionne, nous pouvons lire le flux de données entrant.

while ($http.IsListening) {
 
  $context = $http.GetContext()
 
  if ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/') {
 
    #  POST 
    $Reader = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
    
    #  .
    $DecodedContent = [System.Web.HttpUtility]::UrlDecode($Reader)
 
    #   .
    $Payment | Format-Table
 
    #  200 OK   .
    $context.Response.Headers.Add("Content-Type", "text/plain")
    $context.Response.StatusCode = 200
    $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
    $context.Response.ContentLength64 = $ResponseBuffer.Length
    $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
    $context.Response.Close()
  }
}

Si vous obtenez un vermicelle comme celui-ci, appliquez:

$Payment = $DecodedContent -split "&" | ConvertFrom-StringData



Après cela, vous recevrez enfin un objet normal, où toute la valeur est String.



Vous pouvez arrêter de lire ici si vous ne voulez pas approfondir le code, mais simplement accepter les demandes de l'API de quelqu'un.

Voici le code qui fonctionne dès la sortie de la boîte, copiez et utilisez:

# 
$http = [System.Net.HttpListener]::new() 
 
# ,   
$http.Prefixes.Add("http://localhost/")
$http.Prefixes.Add("https://localhost/")
 
$http.Start()
 
while ($http.IsListening) {
 
  $context = $http.GetContext()
 
  if ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/') {
 
    #  POST 
    $Reader = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
    
    #  .
    $DecodedContent = [System.Web.HttpUtility]::UrlDecode($Reader)
          
    #  IPN   
    $Payment = $DecodedContent -split "&" | ConvertFrom-StringData
 
    #   .
    $Payment | Format-Table
 
    #  200 OK   .
    $context.Response.Headers.Add("Content-Type", "text/plain")
    $context.Response.StatusCode = 200
    $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
    $context.Response.ContentLength64 = $ResponseBuffer.Length
    $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
    $context.Response.Close()
  }
}

Minecraft Nuances


Nous avons donc compris comment recevoir des alertes sur les paiements, maintenant nous pouvons les créditer. Mais ici aussi, ce n'est pas si simple. Le problème est que le jeu ne donne pas d'éléments ou ne modifie pas le statut des joueurs qui ne sont pas sur le serveur. Autrement dit, nous devons attendre qu'une personne entre sur le serveur pour lui donner ce pour quoi elle a payé.

Par conséquent, votre attention est portée sur le concept général du fumeur, pour créditer les paiements.



Les paiements sont reçus via l'écouteur ci-dessus; une seule ligne y a été ajoutée pour écrire l'objet dans le fichier. Complete-Payment (Processor) examine le surnom et le fait correspondre avec le nom du fichier. S'il trouve un fichier, compile une commande pour rcon et l'exécute.

Start-minecraftdont l'auteur a écrit dans un article précédent a été légèrement modifié. Maintenant, il écoute la conclusion, regarde les surnoms des joueurs et les transmet au processeur de paiement.

Faire de vrais rappels


Sans utiliser de plugins, nous ferons de vrais rappels. Pour cela, Start-Minecraft a été modifié. Maintenant, il sait non seulement comment ajouter StdOut à un fichier, mais parcourt également chaque ligne avec un horaire régulier. Heureusement, Minecraft laisse un message très spécifique lorsqu'un joueur entre sur le serveur.

[04:20:00 INFO]: UUID of player XXPROHUNTERXX is 23e93d2e-r34d-7h15 -5h17-a9192cd70b48

Il est très facile de choisir un surnom sur cette ligne. Voici tout le code dont nous avons besoin pour obtenir des données à partir des chaînes Stdout.

$Regex = [Regex]::new("of player ([^ ]+)")
 
powershell.exe -file ".\Start-MinecraftHandler.ps1" -type $type -MinecraftPath $MinecraftPath | Tee-Object $LogFile -Append | ForEach-Object {
 
     Write-host $_
        
    $Player = $Regex.Matches($_).value -replace "of player "
        
    if ($true -eq $Regex.Matches($_).Success) {
        #   
    }
}


Une nouvelle ligne est introduite dans le pipeline $ _, nous l'écrivons dans la fenêtre de la console et la parcourons régulièrement. Le régulier lui-même nous avertit quand cela fonctionne, ce qui est très pratique.

De là, nous pouvons appeler n'importe quel code. Par exemple, en utilisant le même RCON, nous pouvons saluer le joueur dans le PM, en utilisant le bot dans la discorde pour notifier qu'une personne s'est connectée au serveur, interdire le mat et ainsi de suite.

Effectuer des paiements


Depuis que nous avons commencé à traiter les paiements, nous aimerions avoir au moins des données assez complètes sur l'opération et l'historique des opérations effectuées, car nous parlons pour ainsi dire de nombres avec deux zéros.

L'auteur veut tout laisser extrêmement simple et ne pas encore simuler une base. Regardons l'approche NoSQL. Créons notre propre classe, qui importera tous les paiements acceptés dans le dossier / paiements / des fichiers Json.

    class Payment {
        #  .
        [datetime]$Date = [datetime]::ParseExact($i.payment_date, "HH:mm:ss MMM dd, yyyy PDT", [System.Globalization.CultureInfo]::InvariantCulture)
        # 
        [string]$Item = $i.item_name
        # 
        [UInt16]$Quantity = $i.Quantity
        #    
        [UInt16]$AmountPaid = $AmountPaid -as [UInt16]
        #     
        [string]$Currency = $i.mc_currency
        # ,   
        [string]$Player = $i.option_selection1
    
        [bool]$Completed = $false
        [UInt16]$ItemId = $i.item_number
    }
/source>

    , , ,          .

 ,    <b>option_selection1</b> –   .      input,   ,     .
     <b>option_selection1</b>,<b>option_selection2</b>   .

      ,     ,      .

<source lang="powershell"> #     Payment,        .
    $Payment = [Payment]::new()
    $Payment | Format-Table
    #  ,   ----
    $FileName = $Payment.Player + "-" + $Payment.date.Hour + "-" + $Payment.date.Minute + "-" + $Payment.date.Day + "-" + $Payment.date.Month + "-" + $Payment.date.Year + ".json"
 
# ,      
    $JsonPath = Join-Path $MinecraftPath \payments\Pending $FileName
    
    #   
    $Payment | ConvertTo-Json | Out-File $JsonPath

C'est tout ce qui était exigé de notre auditeur. Recevez des données de PayPal et écrivez dans un fichier.

Nous effectuons le traitement des paiements


Le gestionnaire sera appelé celui qui a été écrit plus tôt. Nous transférons le surnom du joueur au module et c'est tout. Ensuite, un nouveau script est lancé, qui recherche le fichier, et s'il y a un fichier, il donne au lecteur l'élément qui est écrit dans le fichier.

powershell.exe -file "C:\mc.fern\Start-MinecraftHandler.ps1" -type $type -MinecraftPath $MinecraftPath | Tee-Object $LogFile -Append | ForEach-Object {
       
        #     ,      .
        Write-host $_   
 
        # Regex     
        if ($true -eq $Regex.Matches($_).Success) {
            
            #       
            $Player = $Regex.Matches($_).value -replace "of player "
            
            #  ,       
            Complete-Payment -Player $Player
        }
    }

Lorsque la régularité est déclenchée, un module est lancé qui termine le paiement, c'est-à-dire qu'il donne au joueur un objet. Pour ce faire, dans le dossier / Payments / Pending /, le script recherche les fichiers contenant le pseudo du joueur qui est entré dans le jeu et lit son contenu.

Vous devez maintenant collecter la commande pour le serveur et l'envoyer là-bas. Il sera collecté à partir d'un fichier. Nous connaissons le surnom du joueur, le nom de l'objet et son ID ont été enregistrés, combien de pièces ont également été enregistrées, il ne reste plus qu'à envoyer une commande au serveur de jeu. Pour cela, nous utiliserons mcrcon .

#    
    $JsonPath = Join-Path $MinecraftPath\payments\Pending -ChildPath $Player*
    $i = $JsonPath | Get-Item | Where-Object { !$_.PSIsContainer } | Get-Content | ConvertFrom-Json -ErrorVariable Errored
 
    #      
    if ($null -ne $i) {
 
        #  
        $Command = '"' + "give " + $i.Player + " " + $i.Item + " " + $i.Quantity + '"'
        Write-host $Command -ForegroundColor Green
    
        #   
        Start-Process -FilePath mcrcon.exe -ArgumentList "-H localhost -p 123 -w 5 $Command"
    
        # ,      
        $JsonPath = Join-Path $MinecraftPath\payments\Pending -ChildPath $FileName
        
        #   
        $i | ConvertTo-Json | Out-File $JsonPath
    
        #     
        Move-Item  -Path $JsonPath -Destination $MinecraftPath\payments\Completed
    }

Nous faisons tout dans un module pratique


Le processus Java et le processus WebListener nécessitent des threads différents, mais l'auteur n'est pas satisfait de la nécessité d'exécuter WebListener séparément et le serveur séparément. L'auteur veut tout à la fois avec une seule équipe.

Par conséquent, en utilisant Powershell 7, nous lancerons ceci et cela. Et cela nous aidera:

ForEach-Object -Parallel {}

L'applet de commande fonctionne avec inputObject, nous lui fournissons donc un tableau simple et partageons les flux à l'aide d'un commutateur.

"A", "B" | ForEach-Object -Parallel {
 
    Import-Module ".\Start-Minecraft.ps1"
 
    Import-Module ".\Start-WebListener.ps1"
 
    switch ($_) {
        "A" {
            Start-WebListener -Path "C:\mc\"
        }
        "B" {
            Start-Minecraft -Type Vanilla -LogFile ".\stdout.txt" -MinecraftPath "C:\mc\"
        }
        
    }
}

Donc, d'une manière béquille, nous avons commencé deux processus différents à partir d'un terminal et nous n'avons même pas perdu d'entrée. Mais il y avait un autre problème. WebListener verrouille la console après un arrêt normal du serveur et ne veut aller nulle part.

Afin de ne pas redémarrer le terminal à chaque fois, une clé aléatoire a été ajoutée à Start-MinecraftHandler.ps1 et à Start-WebListener.ps1, ce qui arrêtera le serveur via POST sur WebListener.

Start-MinecraftHandler.ps1, lorsqu'il enregistre une réussite, exécute la commande:

Invoke-WebRequest -Method Post -Uri localhost -Body $StopToken | Out-Null

$ StopToken contient une valeur numérique aléatoire qui est pré-transmise par le script de démarrage à l'écouteur et au gestionnaire. Le Listener examine ce qu'il a reçu dans la demande et se désactive si le corps de la demande correspond à $ StopToken.

if ($DecodedContent -eq $StopToken) {
        Write-Host "Stopping WebListener"
        $context.Response.Headers.Add("Content-Type", "text/plain")
        $context.Response.StatusCode = 200
        $ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
        $context.Response.ContentLength64 = $ResponseBuffer.Length
        $context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
        $context.Response.Close()
        $http.Close()
        break
      }

Il est suffisamment sûr, seule la RAM connaît le jeton et personne d'autre. Tous les modules sont lancés sous PowerShell 7, et le chemin d'accès aux modules pour PowerShell 7 est différent du chemin d'accès dans Windows Powershell. Tout était empilé ici. Gardez à l'esprit lorsque vous écrivez le vôtre.

C:\Program Files\PowerShell\7\Modules

Nous faisons un fichier de configuration


Pour que toute cette honte puisse être utilisée sans mal de tête sévère, vous devez créer un fichier de configuration normal. Le fichier contiendra des variables et rien de plus. La configuration s'accroche en utilisant la norme:

Import-Module $MinecraftPath\config.ps1 -Force

Nous devons souligner la chose la plus importante. Le domaine qui est exploité est celui qui recherche le surnom du joueur, car la sortie peut varier d'une version à l'autre et le mot de passe provient de rcon.

Cela ressemble à ceci:

#,    
$DomainName = "localhost"
 
# ,      
# ,  
$RegExp = "of player ([^ ]+)"
#    ,   ,  .
$RegExpCut = "of player "
 
#  rcon,     server.properties
$rconPassword = "123"

Il est souhaitable de placer la configuration dans le dossier du serveur, car le script la recherche dans le root-MinecraftPath

Comment utiliser tout ça?


Tout d'abord, ces scripts sont installés et prêts à l'emploi sur la marketplace Ruvds , mais si vous n'avez pas encore de client ou n'avez pas essayé l'image, voici un lien vers tous les fichiers du référentiel , n'hésitez pas à vous engager. 

  1. Téléchargez et installez PowerShell 7
  2. Téléchargez et décompressez l'archive des modules


Maintenant, tous les modules et commandes nécessaires sont apparus. Que font-ils?

Start-minecraft


Options:

-Type
Forge ou Vanilla. Il démarre le serveur à partir de Server.Jar ou Forge, en choisissant la dernière version qui se trouve dans le dossier.

-MinecraftPath Pointe
vers le dossier à partir duquel le serveur sera lancé.

-LogFile
Une autre façon de collecter les journaux. Indique un fichier dans lequel tout ce qui apparaît dans la console sera écrit.

-StartPaymentListener
Avec le serveur, il démarre et accepte les paiements. L'acceptation du paiement elle-même est disponible dans un module séparé. Remplace l'applet de commande Start-Weblistener

Start-weblistener


Démarre le module d'acceptation de paiement.

-MinecraftPath pointe
vers le dossier contenant le fichier de configuration.

-StopToken
Spécifie une demande HTTP POST -Body pour arrêter WebListener'a.

Conclusion:


Eh bien, des miracles se produisent.


All Articles