njoguamos/laravel-pesapal

一个用于与 https://www.pesapal.com api 交互的 Laravel 扩展包


README

Laravel 10+ 扩展包,用于与 Pesapal API 交互

Latest Version on Packagist GitHub Actions Test Status GitHub Actions Workflow Status GitHub Actions PHPStan Status Total Downloads

此扩展包提供了一种与 Pesapal API 交互的方式。它提供了生成 access_token 和将即时支付通知(IPN)存储在数据库中的方法。它还提供了一种提交订单请求和检查交易状态的方法。

为什么使用这个扩展包

  • 提供一种生成 Pesapal api access_token 的方法,该 token 通常在 5 分钟后过期
  • 提供与 Pesapal v3 API 交互的网关
  • 提供一种将即时支付通知(IPN)存储在数据库中的方法
  • 节省您重复编写相同代码的时间

游乐场

如果您想测试此扩展包,我创建了一个 游乐场,您可以在其中测试扩展包,而无需创建新的 Laravel 项目。

安装

您可以通过 composer 安装此扩展包

composer require njoguamos/laravel-pesapal

此扩展包包含以下表

  • pesapal_tokens - 用于存储 Pesapal API 的 access_tokenexpires_at
  • pesapal_ipns - 用于存储即时支付通知

发布并运行迁移

php artisan vendor:publish --tag="pesapal-migrations"
php artisan migrate

您可以选择发布配置文件

php artisan vendor:publish --tag="pesapal-config"

更新您的应用程序中的环境变量。

PESAPAL_LIVE=
PESAPAL_CONSUMER_KEY=
PESAPAL_CONSUMER_SECRET=

使用方法

1. 生成 access_token

要生成 access_token,您可以运行以下命令

php artisan pesapal:auth

该命令将使用 CONSUMER_KEYCONSUMER_SECRET 从 Pesapal API 获取新的 access_token 并将其保存到数据库中。该 access_token 有效期为 5 分钟,因此建议每 4 分钟运行一次命令。此外,当您设置了 model:prune 命令时,所有过期的 access_token 将从数据库中删除,因为它们不再有用。

 # Laravel 10 -> app/Console/Kernel.php

use NjoguAmos\Pesapal\Models\PesapalToken;

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule): void
    {
        # Other scheduled commands
        $schedule->command('pesapal:auth')->everyFourMinutes();
        Schedule::command('model:prune', ['--model' => [PesapalToken::class]])->everyFiveMinutes();
    }
}
 # Laravel 11 -> routes/console.php
use Illuminate\Support\Facades\Schedule;
use NjoguAmos\Pesapal\Models\PesapalToken;

Schedule::command('pesapal:auth')->everyFourMinutes();
Schedule::command('model:prune', ['--model' => [PesapalToken::class]])->everyFiveMinutes();

您还可以直接在 Pesapal 类中调用 createToken 方法来生成 access_token。该方法将返回 null 或新的 PesapalToken 实例。

use NjoguAmos\Pesapal\Pesapal;

$token = Pesapal::createToken();

createToken 的结果是 PesapalToken Eloquent 模型的一个实例。这意味着您可以调用 Eloquent 方法,例如。

$data = $token->toArray();
[
 'access_token' => "eyJhbGciOiJIUzI1NiIs...6pVj1_DS37ghMGQ",
 'expires_at' => Carbon\Carbon instance
]

2. 创建即时支付通知

要创建即时支付通知,您可以使用 Pesapal 类中的 createIpn 方法。该方法将返回 PesapalIpn 实例或 null(如果请求失败)。

info 确保您的 pesapal_tokens 表中有一个未过期的 access_token

use NjoguAmos\Pesapal\Pesapal;

$ipn = Pesapal::createIpn(
    url: 'https://www.yourapp.com/ipn',
    ipnType: IpnType::GET,
);

createIpn 的结果是 PesapalIpn Eloquent 模型的一个实例。这意味着您可以调用 Eloquent 方法,例如。

$data = $ipn->toArray();
[
    'url' => 'https://www.yourapp.com/ipn'
    'ipn_id' => 'e32182ca-0983-4fa0-91bc-c3bb813ba750'
    'type' => 'GET'
    'status' => 'Active'
]

info 该 URL 应该是一个公开 URL,可以由 pesapal.com 访问。该 ipnType 可以是 IpnType::GETIpnType::POST

您可以使用 ipn_id 提交订单请求。

info 确保您的 pesapal_tokens 表中有一个未过期的 access_token。当然,如果您已安排了 pesapal:auth 命令,您不必担心 access_token 会过期。

3. 获取已注册IPN端点

获取已注册IPN有两种方式。

  1. 您可以使用Pesapal类中的getIpns方法从Pesapal API获取IPN。该方法在成功响应时返回一个数组,在失败响应时返回一个Saloon Response实例。
use NjoguAmos\Pesapal\Pesapal;
use NjoguAmos\Pesapal\Pesapal;

$response = Pesapal::getIpns();

示例成功响应

[
    [
        "url" => "https://www.myapplication.com/ipn",
        "created_date" => "2022-03-03T17:29:03.7208266Z",
        "ipn_id" => "e32182ca-0983-4fa0-91bc-c3bb813ba750",
        "error" => null,
        "status" => "200"
    ],
    [
        "url"=> "https://ipn.myapplication.com/application2",
        "created_date"=> "2021-12-05T04:23:45.5509243Z",
        "ipn_id"=> "c3bb813ba750-0983-4fa0-91bc-e32182ca",
        "error"=> null,
        "status"=> "200"
    ]
]
  1. 或者从数据库中获取IPNs。
use NjoguAmos\Pesapal\Models\PesapalIpn;

$ips = PesapalIpn::all();
[
    [
    "id" => 1
    "url" => "http://kautzer.com/omnis-ut-qui-illo-id-laborum-numquam"
    "ipn_id" => "767e3275-d504-41a0-920a-dd752aafb5ac"
    "type" => 0
    "status" => 1
    "created_at" => "2024-03-18T08:10:32.000000Z"
    "updated_at" => "2024-03-18T05:10:32.000000Z"
  ],
  [
    "id" => 2
    "url" => "http://www.cole.org/qui-fugiat-accusamus-molestiae-aspernatur-sequi-eum-non-quae.html"
    "ipn_id" => "de07604f-c06b-4ccf-9cb5-dd75aaaff99f"
    "type" => 0
    "status" => 1
    "created_at" => "2024-03-18T08:10:33.000000Z"
    "updated_at" => "2024-03-18T05:10:33.000000Z"
  ]
]

4. 提交订单请求端点

要提交订单请求,您可以使用Pesapal类中的createOrder方法。您需要构建如以下所示的PesapalOrderDataPesapalAddressData的DTO。该方法在成功响应时返回一个数组,在失败响应时返回一个Saloon Response实例。

info 您必须提供一个已注册的PesapalIpn

use NjoguAmos\Pesapal\Enums\ISOCurrencyCode;
use NjoguAmos\Pesapal\Enums\ISOCountryCode;
use NjoguAmos\Pesapal\Enums\RedirectMode;
use NjoguAmos\Pesapal\Pesapal;
use NjoguAmos\Pesapal\DTOs\PesapalOrderData;
use NjoguAmos\Pesapal\DTOs\PesapalAddressData;

$ipnId = PesapalIpn::latest()->first()->ipn_id;

 $orderData = new PesapalOrderData(
    id: fake()->uuid(),
    currency: ISOCurrencyCode::KES,
    amount: fake()->randomFloat(nbMaxDecimals: 2, min: 50, max: 500),
    description: 'Test order',
    callbackUrl: fake()->url(),
    notificationId: $ipnId,
    cancellationUrl: fake()->url(),
    redirectMode: RedirectMode::PARENT_WINDOW,
);

// All fields are optional except either phoneNumber or emailAddress
$billingAddress = new PesapalAddressData(
    phoneNumber: '0700325008',
    emailAddress: 'test@xample.com',
    countryCode: ISOCountryCode::KE
    firstName: 'Amos',
    middleName: 'Njogu'
//    lastName: ''
    line2: "Gil House, Nairobi, Tom Mboya Street",
//    city: "",
//    state: "",
//    postalCode: "",
//    zipCode: "",
);

$order = Pesapal::createOrder(
    orderData: $orderData,
    billingAddress: $billingAddress,
);

示例成功响应

[
    "order_tracking_id" => "b945e4af-80a5-4ec1-8706-e03f8332fb04",
    "merchant_reference" => "TEST1515111119",
    "redirect_url" => "https://cybqa.pesapal.com/pesapaliframe/PesapalIframe3/Index/?OrderTrackingId=b945e4af-80a5-4ec1-8706-e03f8332fb04",
    "error" => null,
    "status" => "200"
]

现在,您可以重定向用户到redirect_url以完成支付。

5. 获取交易状态端点

您可以使用创建订单时发放的OrderTrackingId来检查交易状态。您可以通过使用Pesapal类中的getTransactionStatus方法来实现。该方法在成功响应时返回一个数组,在失败响应时返回一个Saloon Response实例。

use NjoguAmos\Pesapal\Pesapal;

 $transaction = Pesapal::getTransactionStatus(
    orderTrackingId: 'b945e4af-80a5-4ec1-8706-e03f8332fb04',
);

// $transaction either an array or an instance of Saloon Response

示例成功响应

[
  "payment_method" => "MpesaKE"
  "amount" => 6.0
  "created_date" => "2024-03-19T20:08:46.39"
  "confirmation_code" => "SCJ8JQ26SW"
  "order_tracking_id" => "af2234da-03ee-4b60-b2dd-dd746bcda1bd"
  "payment_status_description" => "Completed"
  "description" => null
  "message" => "Request processed successfully"
  "payment_account" => "2547xxx56689"
  "call_back_url" => "http://127.0.0.1:8000/pesapal-callback?OrderTrackingId=af2234da-03ee-4b60-b2dd-dd746bcda1bd&OrderMerchantReference=1"
  "status_code" => 1
  "merchant_reference" => "1"
  "payment_status_code" => ""
  "currency" => "KES"
  "error" => [
    "error_type" => null
    "code" => null
    "message" => null
  ]
  "status" => "200"
]

6. 定期/订阅支付

  • TODO: 添加定期支付文档

7. 退款请求

  • TODO: 添加退款请求文档

关于响应的说明

为了灵活性和简单性,Pesapal静态方法在成功响应时返回一个array,在失败响应时返回一个Saloon Response实例。

例如,使用getTransactionStatus获取交易状态时,将返回交易详情的数组或如果请求失败,返回一个Saloon Response实例。

use NjoguAmos\Pesapal\Pesapal;

 $transaction = Pesapal::getTransactionStatus(
    orderTrackingId: 'b945e4af-80a5-4ec1-8706-e03f8332fb04',
);

if (is_array($transaction)) {
    // The API call was successful and response is an array
    //    [
    //      "payment_method" => "MpesaKE"
    //      "amount" => 6.0
    //      "created_date" => "2024-03-19T20:08:46.39"
    //      "confirmation_code" => "SCJ8JQ26SW"
    //      "....more field"
    //    ]
} else {
    // The API call was not successful. The response is an instance of Saloon Response
    // $transaction->status() ---> response status code.
    // $transaction->headers() ---> Returns all response headers
    // $transaction->getPendingRequest() ---> PendingRequest class that was built up for the request.
}

您可以了解更多关于[Saloon Response(https://docs.saloon.dev/the-basics/responses)的信息。您可以使用响应来诊断请求的问题。

测试

Info 在可能的情况下,测试使用真实的沙盒凭据,因此请求不是模拟的。结果响应被保存在tests/Fixtures中,并在未来的测试中使用。如果无法使用真实凭据,请求将被模拟。您可以通过删除tests/Fixtures并运行测试来重新创建固定装置。

composer test

变更日志

请参阅CHANGELOG以获取有关最近更改的更多信息。

贡献

请参阅CONTRIBUTING以获取详细信息。

安全漏洞

请审查我们的安全策略以了解如何报告安全漏洞。

致谢

许可

MIT许可(MIT)。请参阅许可文件以获取更多信息。