Cómo hice un sistema de aceptación de pagos de Minecraft usando PowerShell puro


En este artículo, atornillaremos el donut sin Dios al servidor de Minecraft Vanilla usando Powershell. La ventaja del método es que Minecraft es solo un caso especial de la implementación de pagos automáticos mediante comandos de consola. Simplemente escuchamos lo que nos envía el sistema de pago y lo envolvemos en un equipo. Y lo más importante: sin complementos.
Y aceptaremos pagos a través de PayPal. Lo más importante es que, para comenzar a aceptar pagos, no necesita cambiar el código, PayPal nos enviará todo lo que necesita. Usaremos los botones en el sitio, para que el sitio pueda hacerlo con HTML puro. Hacemos un resumen de las complejidades del sistema de pago en sí y nos concentramos solo en los puntos principales del código.

Por cierto, el autor estará muy contento si revisa todos sus módulos y encuentra errores infantiles en los que señala o corrige. Aquí hay un enlace al proyecto github .

Algunas palabras sobre IPN


IPN


Aceptaremos pagos a través de los botones. Los botones no requieren ningún back-end de su parte, funcionan en HTML puro y también tienen sus propios campos.

Los botones activan IPN: Notificación de pago instantánea, en la que los datos se envían a nuestro WebListener. Consideraremos la estructura de IPN a continuación.

Además, cualquiera que tenga una cuenta de PayPal puede crear su propio botón.
IPN no tiene la API REST completa de PayPal, pero la funcionalidad básica se puede implementar en ella. De hecho, el IPN que estamos considerando no es una API REST en el sentido completo de la palabra solo porque PayPal no espera nada de nosotros excepto el código 200.

Elevar WebListener


PayPal, por razones de seguridad, no envía solicitudes a través de HTTP, por lo que debemos emitir un certificado para comenzar. 

El autor usó WinAcme . Puede emitir un certificado a cualquier dominio y debe colocar el certificado en un almacén de certificados local. Por cierto, WinAcme se encuentra en la raíz del disco en la imagen.

#     
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 puede usar clases de .net, lo que lo hace casi igual a .net. Primero, usando la clase HttpListener, levante el servidor web.

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

Para verificar que todo esté bien, ejecute netstat.



Si nuestro script comenzó a escuchar el puerto 443 en la lista, significa que hizo todo correctamente, y podemos proceder a aceptar el procesamiento de la solicitud. Simplemente no te olvides del firewall.

Aceptar petición


Usando IPN Simulator, podemos enviarnos una solicitud POST de prueba para ver qué es. Pero no puede incluir sus propios campos, por lo que el autor recomienda hacer un botón e inmediatamente comprar algo de usted mismo. El historial de IPN mostrará una solicitud normal desde el botón que usará. El autor hizo exactamente eso comprando un carbón por un rublo.

Aceptaremos usar el ciclo While. Mientras se ejecuta el servidor web, podemos leer el flujo de datos entrantes.

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 obtienes un vermicelli como este, entonces aplica:

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



Después de eso, finalmente recibirá un objeto normal, donde todo el valor es una cadena.



Puedes dejar de leer aquí si no quieres profundizar en el código, pero solo quieres aceptar solicitudes de la API de alguien.

Aquí está el código que funciona de inmediato, copie y use:

# 
$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 Matices


Así que descubrimos cómo podemos recibir alertas sobre pagos, ahora podemos acreditarlos. Pero aquí tampoco es tan simple. El problema es que el juego no da elementos ni cambia el estado de los jugadores que no están en el servidor. Es decir, debemos esperar hasta que una persona ingrese al servidor para darle lo que pagó.

Por lo tanto, su atención se presenta al concepto general del fumador, para acreditar los pagos.



Los pagos se reciben a través del oyente anterior; solo se le agregó una línea para escribir el objeto en el archivo. Complete-Payment (Procesador) mira el apodo y lo compara con el nombre del archivo. Si encuentra un archivo, compila un comando para rcon y lo ejecuta.

Start-minecraftsobre el cual el autor escribió en un artículo anterior fue ligeramente modificado. Ahora escucha la conclusión, mira los apodos de los jugadores y los pasa al procesador de pagos.

Hacer devoluciones de llamada reales


Sin usar complementos, haremos verdaderas devoluciones de llamadas. Para esto, Start-Minecraft ha sido modificado. Ahora no solo sabe cómo agregar StdOut a un archivo, sino que también camina a lo largo de cada línea con un horario regular. Afortunadamente, Minecraft deja un mensaje muy específico cuando un jugador ingresa al servidor.

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

Es muy fácil elegir un apodo de esta línea. Aquí está todo el código que necesitamos para obtener datos de las cadenas 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) {
        #   
    }
}


Se introduce una nueva línea en la canalización de $ _, la escribimos en la ventana de la consola y la revisamos regularmente. El regular en sí mismo nos notifica cuando funciona, lo cual es muy conveniente.

Desde aquí podemos llamar a cualquier código. Por ejemplo, usando el mismo RCON, podemos saludar al jugador en el PM, usando el bot en la discordia para notificar que alguien ha iniciado sesión en el servidor, prohibir el jaque mate, etc.

Haciendo pagos


Desde que comenzamos a procesar los pagos, nos gustaría tener al menos datos bastante completos sobre la operación y el historial de las operaciones realizadas, porque estamos hablando de números con dos ceros, por así decirlo.

El autor quiere dejar todo extremadamente simple y no simular una base todavía. Veamos el enfoque NoSQL. Creemos nuestra propia clase, que importará todos los pagos aceptados a la carpeta / pagos / en los archivos 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

Eso es todo lo que se requería de nuestro oyente. Reciba datos de PayPal y escriba en un archivo.

Hacemos procesamiento de pagos


El controlador se llamará el habitual sobre el que se escribió anteriormente. Transferimos el apodo del jugador al módulo y eso es todo. A continuación, se inicia un nuevo script, que busca el archivo, y si hay un archivo, le da al jugador el elemento que está escrito en el archivo.

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
        }
    }

Cuando se activa la regularidad, se inicia un módulo que completa el pago, es decir, le da al jugador un artículo. Para hacer esto, en la carpeta / Payments / Pending /, el script busca archivos que contengan el apodo del jugador que ingresó al juego y lee su contenido.

Ahora necesita recopilar el comando para el servidor y enviarlo allí. Se recopilará de un archivo. Sabemos que se registró el apodo del jugador, el nombre del elemento y su ID, cuántas piezas también se registraron, solo queda enviar un comando al servidor del juego. Para esto usaremos 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
    }

Lo hacemos todo en un módulo conveniente


El proceso Java y el proceso WebListener requieren diferentes subprocesos, pero el autor no está satisfecho con la necesidad de ejecutar WebListener por separado y el servidor por separado. El autor quiere todo a la vez con un equipo.

Por lo tanto, usando Powershell 7, lanzaremos tanto esto como aquello. Y nos ayudará:

ForEach-Object -Parallel {}

El cmdlet funciona con inputObject, por lo que lo alimentamos con una matriz sin complicaciones y compartimos las transmisiones con un interruptor.

"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\"
        }
        
    }
}

Entonces, de manera muleta, comenzamos dos procesos diferentes desde un terminal y ni siquiera perdimos entrada. Pero había otro problema. WebListener bloquea la consola después de un apagado regular del servidor y no quiere ir a ningún lado.

Para no reiniciar el terminal cada vez, se agregó una clave aleatoria a Start-MinecraftHandler.ps1 y Start-WebListener.ps1, que detendrá el servidor a través de POST en WebListener.

Start-MinecraftHandler.ps1, cuando registra una finalización exitosa, ejecuta el comando:

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

$ StopToken contiene un valor numérico aleatorio que el script de inicio pasa previamente a Listener y Handler. El oyente mira lo que recibió en la solicitud y se apaga si el cuerpo de la solicitud coincide con $ 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
      }

Es lo suficientemente seguro, solo RAM conoce el token y nadie más. Todos los módulos se inician desde PowerShell 7, y la ruta a los módulos para PowerShell 7 es diferente de la ruta en Windows Powershell. Todo estaba apilado aquí. Tenga en cuenta al escribir el suyo.

C:\Program Files\PowerShell\7\Modules

Hacemos un archivo de configuración


Para poder utilizar toda esta desgracia sin un fuerte dolor de cabeza, debe crear un archivo de configuración normal. El archivo contendrá variables y nada más. La configuración se aferra usando el estándar:

Import-Module $MinecraftPath\config.ps1 -Force

Necesitamos señalar lo más importante. El dominio que se está aprovechando es el habitual que busca el apodo del jugador, ya que la salida puede variar de una versión a otra y la contraseña es de rcon.

Se parece a esto:

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

Es deseable colocar la configuración en la carpeta del servidor, porque el script la está buscando en la raíz-MinecraftPath

¿Cómo usar todo esto?


En primer lugar, estos scripts están instalados y listos para usar en el mercado de Ruvds , pero si aún no tiene un cliente o no ha probado la imagen, aquí hay un enlace a todos los archivos en el repositorio , no dude en comprometerse. 

  1. Descargue e instale PowerShell 7
  2. Descargue y descomprima el archivo de módulos


Ahora han aparecido todos los módulos y comandos necesarios. ¿Qué están haciendo?

Start-minecraft


Opciones:

-Tipo
Forja o Vainilla. Inicia el servidor desde Server.Jar o Forge, eligiendo la última versión que está en la carpeta.

-MinecraftPath señala
a la carpeta desde la cual se iniciará el servidor.

-LogFile
Una forma alternativa de recopilar registros. Indica un archivo en el que se escribirá todo lo que aparece en la consola.

-StartPaymentListener
Junto con el servidor, inicia y acepta pagos. La aceptación del pago en sí está disponible como un módulo separado. Reemplaza el cmdlet Start-Weblistener

Start-weblistener


Inicia el módulo de aceptación de pago.

-MinecraftPath señala
a la carpeta con el archivo de configuración.

-StopToken
Especifica -Body HTTP POST solicitud para detener WebListener'a.

Conclusión:


Bueno, los milagros suceden.


All Articles