academe/omnipay-adyen

Omnipay支付处理库的Adyen驱动程序

3.0.0 2018-11-22 14:43 UTC

This package is auto-updated.

Last update: 2024-09-15 00:11:33 UTC


README

Omnipay PHP支付处理库的Adyen驱动程序(HPP、CSE、在线支付和API集成)

Build Status Latest Stable Version Total Downloads

Omnipay 3.x 是一个与框架无关、多网关的PHP 5.6+支付处理库。

目录

安装

Omnipay通过 Composer 安装。

composer require academe/omnipay-adyen

常规

这个adyen的omnipay集成提供了多个API。

Adyen提供最新的API实现为“在线支付”

“在线支付”支持

  • 通过链接支付
  • 嵌入式支付
  • 组件
  • 仅API
  • iOS
  • Android

已被adyen弃用但仍实现

  • 托管支付页面(HPP)
  • 客户端端加密(CSE)

托管支付页面(HPP)

!!! 已弃用 !!!

此方法在网关上托管支付页面,并将用户发送到网关上的这些页面进行支付。

支持多种支付方式,具体取决于商家网站的位置(在此称为本地支付方式)。根据支付金额、使用的货币以及商家网站和国家/地区,该列表也可能发生变化。

选择支付方式的选择包括

  • 商家网站获取可用列表并展示给最终用户进行选择。如果需要,可能会进行过滤。
  • 商家网站服务器选择一种支付方式,并将最终用户直接带到该选择。
  • 前端获取选项并向用户提供选择。

服务器获取支付方式

$gateway = Omnipay\Omnipay::create('Adyen\Hpp');

$gateway->initialize([
    'secret' => $hmac,
    'skinCode' => $skinCode,
    'merchantAccount' => $merchantAccount,
    'testMode' => true,
    'currency' => 'EUR',
    // Optional; default set in account:
    'countryCode' => 'GB',
]);

$request = $gateway->fetchPaymentMethods([
    'transactionId' => $transactionId,
    'amount' => 9.99,
]);

$response = $request->send();

这会返回一个包含 PaymentMethod 对象的数组

$response->getPaymentMethods();

/*
array(7) {
  [0]=>
  object(Omnipay\Common\PaymentMethod)#48 (2) {
    ["id":protected]=>
    string(6) "diners"
    ["name":protected]=>
    string(11) "Diners Club"
  }
  [1]=>
  object(Omnipay\Common\PaymentMethod)#39 (2) {
    ["id":protected]=>
    string(8) "discover"
    ["name":protected]=>
    string(8) "Discover"
  }
  ...
}
*/

还可以获取原始支付方式,作为关联索引数组。这些包括各种尺寸的徽标。

$response->getPaymentMethodsAssoc();

/*
array(7) {
  ["diners"]=>
  array(3) {
    ["brandCode"]=>
    string(6) "diners"
    ["logos"]=>
    array(3) {
      ["normal"]=>
      string(44) "https://test.adyen.com/hpp/img/pm/diners.png"
      ["small"]=>
      string(50) "https://test.adyen.com/hpp/img/pm/diners_small.png"
      ["tiny"]=>
      string(49) "https://test.adyen.com/hpp/img/pm/diners_tiny.png"
    }
    ["name"]=>
    string(11) "Diners Club"
  }
  ...
}
*/

一些支付方式还将有发行商列表,也可以用来进一步细化提供给最终用户的选项。目前,此驱动程序不会进一步将此数据解析为对象。

客户端获取支付方式

使用与上一节(服务器获取支付方式)相同的方法,但不要发送请求。相反,获取其数据和使用端点供客户端使用

$data = $request->getData();
$endpoint = $request->getEndpoint();

$data通过POST方式发送到$endpoint以获取JSON响应。请注意,这些数据是经过签名的,因此在客户端无法更改参数。发送AJAX POST请求到$endpoint时,可能需要一些客户端JavaScript,并将$data作为表单数据。

HPP授权支付

准备重定向

首先实例化网关对象,与之前一样。

$gateway = Omnipay\Omnipay::create('Adyen\Hpp');

$gateway->initialize([
    'secret' => $hmac,
    'skinCode' => $skinCode,
    'merchantAccount' => $merchantAccount,
    'testMode' => true,
    'currency' => 'EUR',
    'countryCode' => 'DE',
]);

使用CreditCard类来提供任何账单详情。目前不支持发货详情,但将来可能会支持。

$card = new Omnipay\Common\CreditCard([
    'firstName' => 'Joe',
    'lastName' => 'Bloggs',

    'billingAddress1' => '88B',
    'billingAddress2' => 'Address 2B',
    'billingState' => 'StateB',
    'billingCity' => 'CityB',
    'billingPostcode' => '412B',
    'billingCountry' => 'GB',
    'billingPhone' => '01234 567 890',

    'email' =>  'jason@example.co.uk',
]);

请求设置重定向。

$request = $gateway->authorize([
    'transactionId' => $transactionId,
    'amount' => 9.99,
    // The returnUrl can be defined in the account, and overridden here.
    'returnUrl' => 'https://example.co.uk/your/return/endpoint',
    'card' => $card,
]);

现在还有一些额外的参数需要解释。

可以使用paymentMethod来将用户重定向到特定的支付方式,而不必让用户从可用的支付方式中选择。

$request->setPaymentMethod('visa');
$request->setIssuer('optional issuer ID for the brandCode');

指定paymentMethod将跳过任何询问用户如何支付的页面,并将用户直接带到该支付方式。

限制提供给用户的支付方式的另一种方法是使用allowedMethodsblockedMethods列表。

$request->setAllowedMethod(['visa', 'diner']); // Only Visa and Diner cards.
$request->setBlockesMethod(['visa']); // All types except Visa.

addressHidden设置为true将隐藏提交给支付网关的地址。用户将看不到自己的地址,但你提交的信息将被存储在网关中。默认情况下,地址会显示给用户。

$request->setAddressHidden(true);

设置addressLocked将防止用户在网关上更改地址详情。尽管所有这些详情都将在重定向时发送到网关,但由于它们是经过签名的,因此任何用户更改它们的尝试都会导致拒绝。

$request->setAddressLocked(true);

将来将支持更多参数。

现在“发送”请求以获取重定向响应。

$response = $request->send();

重定向将是一个POST。要包含的详细信息在$response->getRedirectData()$response->getRedirectUrl()中,因此您可以构建一个表单进行提交或自动提交,无论是提交到顶部页面还是到iframe。或者,您也可以简单地使用echo $response->redirect()进行简单的重定向。

用户将被重定向到Adyen托管页面,将在该页面上输入他们的授权详情,然后将被返回到returnUrl。这是交易完成的地方(参见下一节)。

在返回时完成交易

用户将以查询参数的形式返回授权结果。这些参数的读取和解析方式如下

$response = $gateway->completeAuthorize()->send();

$response中,您可以获取结果,如果结果成功,则获取transactionReference和原始数据。

var_dump($response->getdata());
var_dump($response->getAuthResult());
var_dump($response->isSuccessful());
var_dump($response->isPending());
var_dump($response->isCancelled());
var_dump($response->getTransactionReference());
var_dump($response->getTransactionId());

数据是经过签名的,如果签名无效,则在send()操作期间将抛出异常。

要获取授权的更多详细信息,需要使用API从网关获取交易。这个结果只是给出了整体结果,并返回了您的transactionId,这样您可以确认这是您期望的交易结果(检查transactionId至关重要),因此以前的授权的URL不会被最终用户注入。

捕获授权

HPP请求只会授权支付。它还需要进行捕获。自动捕获可以在账户控制面板中打开,但默认情况下是关闭的。它也可以延迟打开,以便在设置的天数后自动捕获。

以编程方式请求捕获支付的方法如下

$gateway = Omnipay\Omnipay::create('Adyen\Hpp');

$gateway->initialize([
    'merchantAccount' => $merchantAccount,
    'testMode' => true,
    'currency' => 'EUR',
    'username' => $username,
    'password' => $password,
]);

$request = $gateway->capture(
    // The original transaction reference of the authorisation.
    'transactionReference' => $transactionReference,

    // The original amount in full or partial amount.
    'amount' => 9.99,

    // Optionally you can give the request an ID of your own.
    'transactionId' => $captureTransactionId,
]);

返回的响应将返回isSuccessful() === true,如果捕获请求被接受。但是,请注意,这只是一个对网关的请求。捕获的结果将通过notificaton通道返回,因此在这一点上您不知道捕获是否会成功。

一旦捕获成功,就可以使用$gateway->refund([...])消息进行全额或部分退款,使用与capture相同的参数。

在授权被捕获之前,可以使用 $gateway->void([...]) 完全取消授权。由于 void 消息旨在取消整个授权,因此不需要 amount

capture 一样,voidrefund 都只是待处理结果,最终结果由 notification 提供。

客户端端加密(CSE)

!!! 已弃用 !!!

Adyen 网关允许在您的应用程序页面直接使用信用卡表单。信用卡详情不会直接提交到您的商家网站,而是在客户端(浏览器)加密,然后加密字符串随任何附加详情一起提交到您的网站。

加密详情随后在向 API 发出授权请求时替代信用卡详情,实现服务器到服务器的通信。

构建加密表单

客户端功能可以完全手动构建,但以下最小示例展示了这个库如何帮助构建。示例中使用了 laravel blade 视图语法。

$gateway = Omnipay\Omnipay::create('Adyen\Cse');

$gateway->initialize([
    'testMode' => true,
    'publicKeyToken' => $cseLibraryPublicKeyToken,
]);

$request = $gateway->encryptionClient([
    'returnUrl' => 'https://merchant-site.example.com/payment-handler',
]);
<html>

<head>
    <script type="text/javascript" src="{{ $request->getLibraryUrl() }}"></script>
</head>

<body>
    /* Do NOT use the name attribute for any of these form credit card detail items. */
    /* The default card details shown are useful when testing, but not for production. */

    <form method="POST" action="{{ $request->getReturnUrl() }}" id="adyen-encrypted-form">
        <input type="text" size="20" data-encrypted-name="number" value="4444333322221111" />
        <input type="text" size="20" data-encrypted-name="holderName" value="User Name" />
        <input type="text" size="2" data-encrypted-name="expiryMonth" value="10" />
        <input type="text" size="4" data-encrypted-name="expiryYear" value="2020" />
        <input type="text" size="4" data-encrypted-name="cvc" value="737" />
        <input type="hidden" value="{{ $request->getGenerationtime() }}" data-encrypted-name="generationtime" />
        <input type="submit" value="Pay" />
    </form>

    <script>
    // The form element to encrypt.
    var form = document.getElementById('adyen-encrypted-form');

    // See https://github.com/Adyen/CSE-JS/blob/master/Options.md for details on the options to use.
    // The options were not listed in official documentation at time of writing.
    var options = {};

    // Bind encryption options to the form.
    adyen.createEncryptedForm(form, options);
    </script>
</body>

</html>

根据需要,可以在表单中添加其他应用程序特定字段。您还可以使用 AJAX 操作此表单以防止整个页面刷新。Adyen CSE 文档提供了更多详细信息。

只有当信用卡字段填写完整并有效时,Pay 按钮才会启用。

包含在标题中的 JavaScript 库会加密卡详情,并将结果默认添加到隐藏的 POST 字段 adyen-encrypted-data 中。您可以通过选项指定不同的字段名。该字段必须被定义为 returnUrlhttps://example.com/payment-handler 页面接受,以进行下一步操作。

加密卡授权支付

这是服务器端处理。一旦 CSE 表单已提交到您的客户端网站,加密的卡详情将可用。可以使用它提交如下的授权

$gateway = Omnipay\Omnipay::create('Adyen\Cse');

$gateway->initialize([
    'merchantAccount' => $merchantAccount,
    'testMode' => true,
    'currency' => 'EUR',
    'countryCode' => 'DE',
    'username' => $username,
    'password' => $password,
]);

$request = $gateway->authorize([
    'amount' => 11.99,
    'transactionId' => $transactionId,

    // The credit card object provides additional billing and
    // shipping details only.
    'card' => $creditCard,

    // You can pass in the encrypted card as the cardToken,
    // or leave the authorize request to extract it from current
    // POST data.
    'cardToken' => $_POST['encryptedData'],

    // If you want to use 3D Secure, then set the 3D Secure flag
    // and the URL to return the user to.
    '3DSecure' => true,
    'returnUrl' => 'https://example.com/complete-3d-secure-handler',
]);

// The response will provide the success status, transaction
// reference, fraud details, limited card details, etc.
$response = $request->send();

if ($response->isSuccessful()) {
    echo $response->getTransactionReference();
} elseif ($response->isRedirect()) {
    // Lazy way to do the redirect.
    // Or use $response->redirectUrl(), redirectMethod(), redirectData()
    $response->redirect();
}

如果 isSuccessful() 为真,则交易完成并结束。

如果 isSuccessful() 不为真,并且 isRedirect() 为真,则用户必须被重定向到银行进行 3D Secure 授权。您收到的 redirectData() 通常包括 PaReqMDTermUrl

3D安全响应

从 3D Secure 授权返回后,您需要从网关获取结果,因为它们将保存在那里。通过将当前请求的 POST 数据中找到的数据发送,completeAuthorize() 方法执行此操作。

$request = $gateway->completeAuthorize([
    // shopperIp is not mandatory, but is strongly recommended.
    'shopperIp' => '123.45.67.89',
]);

$response = $request->send();

$response 将是最终响应,就像未启用或不可用时的额外 3D Secure 步骤一样,您将得到它。

在线支付(结账)

建议使用的 adyen 方法是 Checkout API。它支持 Drop-in,根据交易详情动态提供支付方式。您还可以使用 "Pay by Link"、单个 "Components" 或 "API only"。

嵌入式支付

最强大且推荐的方法是使用 Drop-in。它动态显示所有可用的支付方式,并在 onSubmit() 时返回一个包含支付方式详情的 JavaScript 状态数组,用于后续支付。Drop-in 还提供了 3D Secure 2 所需的信息。请参阅 Adyen 文档以获取更多信息。

构建嵌入式支付

<html>
    <head>
        <script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/3.14.1/adyen.js"
                integrity="sha384-6CKCjdBJ5e8ODBkoPb8aS4NUVZUT84+EwcOq/EdHIQJsHXZyRy4Hzmlo3Cm/3VX3"
                crossorigin="anonymous"></script>
        <link rel="stylesheet"
              href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/3.14.1/adyen.css"
              integrity="sha384-dNVP3hcwQ/DJnbhWtKjt/xpYSDyS1Ch8m8tnxNehvZoGzcrjeGuKk9/dGstY+vxu"
              crossorigin="anonymous">
    </head>
    <body>
        <div id="dropin-container"></div>
        <script>
            const configuration = {
                paymentMethodsResponse: "PAYMENT_RESPONSE", // The `/paymentMethods` response from the server.
                clientKey: "YOUR_CLIENT_KEY", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
                locale: "de-DE",
                environment: "test",
                paymentMethodsConfiguration: {
                    card: { // Example optional configuration for Cards
                        hasHolderName: true,
                        holderNameRequired: true,
                        enableStoreDetails: true,
                        hideCVC: false, // Change this to true to hide the CVC field for stored cards
                        name: 'Credit or debit card'
                    }
                },
                onSubmit: (state, dropin) => {
                    if(state.isValid) {
                        // Do any action if the payment information are correct.
                        // E.g. ajax call, to tokenize the informatio if it's a credit card and use the information returned.
                    }
                },
            };
            const checkout = new AdyenCheckout(configuration);
            const dropin = checkout.create('dropin').mount('#dropin-container');
        </script>
    </body>
</html>

支付方式请求

要获取传递给 Drop-in 所需的所有必要数据,您可以使用 Checkout 网关的 paymentMethods()-函数。

$request = $gateway->paymentMethods([
    'amount' => 11.99,
    'currency' => 'EUR',
    'countryCode' = 'DE',
    'channel' = 'Web',
    'shopperLocale' = 'de-DE',
    'shopperReference' = 'MyUniqueShopperReference'
]);

$response = $request->send();
$paymentMethods = $response->getPaymentMethodsResponse();

如果您想向客户显示存储的支付详情,则需要 shopperReference

标记卡

您可以为卡凭证进行标记以便以后使用。默认情况下,如果使用插入式组件或组件,将使用卡加密。截至目前,此实现中仅支持创建卡。

创建卡

$request = $gateway->paymentMethods([
    'paymentMethod' => $paymentMethod,
    'currency' => 'EUR',
    'transactionId' => 'TOKENIZATION_TRANSACTION_ID',
    'shopperReference' => 'MyUniqueShopperReference'
]);

$response = $request->send();

授权支付

授权是Omnipay的默认行为。请查看文档以获取3DS所需的字段。

$request = $gateway->authorize([
    'paymentMethod' => $paymentMethod, // Drop-in provided, you can also use the card parameters with unencrypted data
    'amount' => 11.99,
    'currency' => 'EUR',
    'transactionId' => 'YOUR_TRANSACTION_ID',
    'returnUrl' => 'https://merchant-site.example.com/payment-handler', // This is used for payment options which needs a redirect like giropay, 3DS
    '3DSecure' => false // If true, you need to specifiy much more. Please refer to Adyen documentation.
]);

$response = $request->send();

准备授权重定向

在某些情况下,您需要将用户重定向到另一个网站。这些网站将重定向用户回您的returnUrl。在这种情况下,授权请求将不会成功。但isRedirect()将是true。

$response = $authorizeRequest->send();
if(!$response->isSuccessful()) {
    if($response->isRedirect()) {
        $response->redirect(); // This will redirect immediatly. In most cases, you want to use getRedirectResponse or getRedirectUrl
    }
}

请小心使用自制的重定向。一些提供商使用带有附加数据的POST请求进行重定向。最好使用getRedirectResponse()。

在返回时完成

由于我们不知道解析支付类型响应所需的所有信息,我们必须提供一些东西。

$request = $gateway->completeAuthorize([
    'requestParameter' => $_REQUEST, // Include $_POST and $_GET parameters!
    'details' => $authorizeResponse->getData()['details'], // Details a list of fields, which are returned by the payent type
    'paymentData' => $authorizeResponse->getData()['paymentData'], // Some payment information.
]);

$response = $request->send();

通知

Adyen API在本质上是异步的。几乎每个事件都可以生成一个通知发送到您的应用程序。默认情况下,不会发送通知,但可以在管理页面中设置。

通知服务器请求(来自网关)

出于安全考虑,通知可以使用基本身份验证访问您的页面,并且基于一些关键字段的签名可以确认这些字段在传输过程中未被更改。

通知可以发送为SOAP、JSON或表单POST消息。此驱动程序支持JSON和表单POST。

通知请求被捕获在通知端点,如下所示

$gateway = Omnipay\Omnipay::create('Adyen\Api');

$gateway->initialize([
    'testMode' => true,
    // For validating signatures (optional).
    'secret' => $notificationsHmac,
    // For validating Basic Auth (optional).
    'username' => $username,
    'password' => $password,
]);

$request = $gateway->acceptNotification();

请注意,通知HMAC密钥与HPP HMAC密钥不同。HMAC检查是可选的,但如果您在此处提供密钥,则如果API未提供密钥,驱动程序将抛出异常。

您可能希望通过您的框架实现基本身份验证,例如在路由中间件中,而不是在此包中。这完全取决于您希望失败的基本身份验证请求在您的应用程序管道中走多远。这将决定可以记录的详细信息。

$request将提供有关通知的大量信息,以下是一些示例

// The eventCodel; the type of event.
$request->getEventCode()

// Merchant site transaction ID.
$request->getTransactionId()

// Gateway transaction ID.
$request->getTransactionReference()

// The Auth Code.
$request->getAuthCode()

// The amount requested as a Noney object.
$request->getAmountMoney()

// The captured billing address (returns an array if present).
$request->getBillingAddress()

// Indicates whether this is a live account (not testing).
$request->getLive()

// Test whether the HMAC validation is successful.
$request->isValidHmac()

// Throw an InvalidRequestException exception of the notification
// has an invalid signature.
$request->send()

通知响应(到网关)

网关将遵循一个算法,以递增的间隔重试发送通知,直到它收到接受响应。预期的响应将根据原始通知使用的内容类型而异。HTTP响应代码必须是200,除非签名检查或基本身份验证检查失败。《Content-Type》头是假定被适当设置的。

表单通知的响应体负载必须是文本

[accepted]

这在文档中显示如下,这是错误的(&5B%5D?这是什么内容类型?),但值得注意,以防此文档需要调整

&5Baccepted%5D

JSON通知的响应体负载必须是JSON数据

{"notificationResponse":"[accepted]"}

此驱动程序目前不处理响应,因此需要在商家网站上编写代码。商家网站必须接受通知,因此建议消息被排队以进行离线处理,以便通知端点可以尽快响应。

  • 待办事项:以编程方式实现对网关的响应。
  • 待办事项:测试响应是否必须与请求内容类型匹配,或者它是否只需要与响应《Content-Type》头一致。

支持

如果您在使用Omnipay时遇到一般问题,我们建议在Stack Overflow上发布。请确保添加omnipay标签,以便它容易被找到。

如果您认为发现了错误,请使用GitHub问题跟踪器报告,或者更好的做法是克隆库并提交拉取请求。