Skip to content
10 changes: 9 additions & 1 deletion Resource/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
# sample_payment.xxx: 1

# コンテナ定義
#services:
# 具象クラス (ハンドラ・ゲートウェイ) は本体の Plugin\ glob (app/config/eccube/services.php) が
# autowire+autoconfigure で自動登録するため、ここでは型の解決に必要なエイリアスのみ宣言する。
# 決済ハンドラの agent_commerce.payment_handler タグは本体 Kernel::build() の
# registerForAutoconfiguration が付与する (services.yaml の _instanceof はファイルスコープのため
# services.php で登録されるプラグインの具象クラスには届かない)。
services:
# エージェント決済ゲートウェイ実装の束ね先 (Stripe 実装へ差し替える場合はここを変更)。
Plugin\SamplePayment44\Service\AgentCommerce\Gateway\AgentPaymentGatewayInterface:
alias: Plugin\SamplePayment44\Service\AgentCommerce\Gateway\MockAgentPaymentGateway

# スロットリングの定義
eccube:
Expand Down
135 changes: 135 additions & 0 deletions Service/AgentCommerce/AbstractAgentCardHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* https://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\SamplePayment44\Service\AgentCommerce;

use Eccube\Entity\Order;
use Eccube\Service\AgentCommerce\MinorUnitConverter;
use Eccube\Service\AgentCommerce\Payment\PaymentOutcome;
use Plugin\SamplePayment44\Service\AgentCommerce\Gateway\AgentPaymentGatewayInterface;
use Plugin\SamplePayment44\Service\AgentCommerce\Gateway\GatewayResult;
use Plugin\SamplePayment44\Service\AgentCommerce\Gateway\GatewayStatus;
use Plugin\SamplePayment44\Service\Method\CreditCard;

/**
* ACP/UCP 共通のカード決済ハンドラ基底.
*
* コアの決済ハンドラ契約 (authorize/capture/supports) の共通ロジックを提供し、PSP 連携は
* {@link AgentPaymentGatewayInterface} (サンプルは {@link Gateway\MockAgentPaymentGateway}) に委譲する。
* プロトコル固有の差分 (handler_id・トークン償還/交換・対象プロトコル) は派生クラスが実装する。
*
* 本基底は **コアの決済ハンドラインターフェイスを直接 implements しない**。派生クラス側で
* {@link \Eccube\Service\AgentCommerce\Payment\AcpPaymentHandlerInterface} 等を実装することで、
* コアの `_instanceof` 自動タグ付与 (`agent_commerce.payment_handler`) が**具象のみ**に効き、
* 抽象クラスがタグ付き iterator に混入するのを避ける。
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated
*/
abstract class AbstractAgentCardHandler
{
public function __construct(
private readonly MinorUnitConverter $minorUnitConverter,
private readonly AgentPaymentGatewayInterface $gateway,
) {
}

/**
* このハンドラが扱うエージェントプロトコル ({@link \Eccube\Entity\Master\AgentProtocol} の定数).
*/
abstract protected function protocolId(): int;

/**
* complete リクエストの中立支払データを、ゲートウェイへ渡す instrument へ整形する.
*
* ACP は Shared Payment Token の償還、UCP は交換済みトークンの受け渡しを行う。
*
* @param array<string, mixed> $paymentData
*
* @return array<string, mixed>
*/
abstract protected function toGatewayInstrument(array $paymentData): array;

/**
* 本サンプルは通常購入の {@link CreditCard} (トークン決済) を流用するため、
* その method_class が割り当たり、かつ注文が自プロトコルのエージェント注文のときに扱う。
*/
public function supports(Order $order): bool
{
$payment = $order->getPayment();
if ($payment === null || $payment->getMethodClass() !== CreditCard::class) {
return false;
}

return $order->getAgentProtocol()?->getId() === $this->protocolId();
}

/**
* @param array<string, mixed> $paymentData
*/
public function authorize(Order $order, array $paymentData): PaymentOutcome
{
$result = $this->gateway->authorize(
$order->getCurrencyCode(),
$this->amount($order),
$this->toGatewayInstrument($paymentData),
$this->context($order),
);

return $this->toOutcome($result);
}
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated

/**
* @param array<string, mixed> $paymentData
*/
public function capture(Order $order, array $paymentData): PaymentOutcome
{
$result = $this->gateway->capture(
$order->getCurrencyCode(),
$this->amount($order),
$this->toGatewayInstrument($paymentData),
$this->context($order),
);

return $this->toOutcome($result);
}
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated

/**
* 注文の支払総額を minor unit 整数へ変換する.
*/
private function amount(Order $order): int
{
return $this->minorUnitConverter->toMinorUnits($order->getPaymentTotal(), $order->getCurrencyCode());
}

/**
* @return array<string, mixed>
*/
private function context(Order $order): array
{
return [
'order_id' => $order->getId(),
'order_no' => $order->getOrderNo(),
];
}

/**
* ゲートウェイ結果をコアの {@link PaymentOutcome} へ写像する.
*/
private function toOutcome(GatewayResult $result): PaymentOutcome
{
return match ($result->status) {
GatewayStatus::SUCCEEDED, GatewayStatus::REQUIRES_CAPTURE => PaymentOutcome::completed($result->transactionId, $result->metadata),
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated
GatewayStatus::REQUIRES_ACTION => PaymentOutcome::requiresAction($result->actionData, $result->metadata),
GatewayStatus::PROCESSING => PaymentOutcome::pending($result->metadata),
GatewayStatus::FAILED => PaymentOutcome::failed($result->errorCode ?? 'payment_failed', $result->errorMessage ?? '', $result->retryable),
};
}
}
80 changes: 80 additions & 0 deletions Service/AgentCommerce/Acp/AcpSampleCardHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* https://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\SamplePayment44\Service\AgentCommerce\Acp;

use Eccube\Entity\Master\AgentProtocol;
use Eccube\Service\AgentCommerce\Payment\AcpPaymentHandlerInterface;
use Plugin\SamplePayment44\Service\AgentCommerce\AbstractAgentCardHandler;

/**
* ACP (Shared Payment Token) 向けのサンプルカード決済ハンドラ.
*
* 通常購入のトークン決済 ({@link \Plugin\SamplePayment44\Service\Method\CreditCard}) を流用し、
* ACP の complete で渡される SPT を {@link Gateway\MockAgentPaymentGateway} で課金する。
* 実 PSP 連携は {@link \Plugin\SamplePayment44\Service\AgentCommerce\Gateway\AgentPaymentGatewayInterface}
* の実装差し替えで対応する (stripe-payment-plugin への移植点)。
*/
class AcpSampleCardHandler extends AbstractAgentCardHandler implements AcpPaymentHandlerInterface
{
/** ACP の payment_data.handler_id と突合する識別子. */
public const HANDLER_ID = 'card_tokenized';

public function getHandlerId(): string
{
return self::HANDLER_ID;
}

public function redeemSharedPaymentToken(array $paymentData): array
{
// 実 PSP では SPT を課金可能な参照へ償還する。サンプルでは中立 instrument へ整形し、
// モックゲートウェイがトークン規約でシナリオ (成功/3DS/拒否) を判定できるようトークンを保持する。
return [
'token' => $this->extractToken($paymentData),
'authentication_result' => $paymentData['authentication_result'] ?? null,
'redeemed' => true,
];
}

protected function protocolId(): int
{
return AgentProtocol::ACP;
}

protected function toGatewayInstrument(array $paymentData): array
{
return $this->redeemSharedPaymentToken($paymentData);
}

/**
* payment_data から支払トークンを取り出す (`token` 直下、または `instrument.credential` 配下).
*
* @param array<string, mixed> $paymentData
*/
private function extractToken(array $paymentData): string
{
if (is_string($paymentData['token'] ?? null)) {
return $paymentData['token'];
}

$credential = $paymentData['instrument']['credential'] ?? null;
if (is_array($credential) && is_string($credential['token'] ?? null)) {
return $credential['token'];
}
if (is_string($credential)) {
return $credential;
}

return '';
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated
}
47 changes: 47 additions & 0 deletions Service/AgentCommerce/Gateway/AgentPaymentGatewayInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* https://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\SamplePayment44\Service\AgentCommerce\Gateway;

/**
* エージェント決済のゲートウェイ抽象 (PSP 隔離レイヤ).
*
* プロトコル層 (ACP/UCP ハンドラ) と PSP 実装を分離するための境界。サンプル実装は
* {@link MockAgentPaymentGateway} で、これを {@link StripeAgentPaymentGateway} 等へ差し替えれば
* stripe-payment-plugin などへ移植できる (ハンドラ・プロトコル層は無改変)。
*
* 金額は **minor unit 整数** (コアの {@link \Eccube\Service\AgentCommerce\MinorUnitConverter} で変換済)
* で受け取り、通貨はゼロデシマル判定のため別途渡す。
*/
interface AgentPaymentGatewayInterface
{
/**
* 与信 (オーソリ) を行う.
*
* @param string $currencyCode ISO 4217 (例: "JPY"/"USD")
* @param int $amount minor unit 整数 (JPY 等ゼロデシマルはそのままの数値)
* @param array<string, mixed> $instrument 中立な支払データ (token/credential・3DS の authentication_result 等)
* @param array<string, mixed> $context 注文番号等の付帯情報 (冪等キー生成・追跡用)
*/
public function authorize(string $currencyCode, int $amount, array $instrument, array $context = []): GatewayResult;

/**
* 売上確定 (キャプチャ) を行う. {@link authorize()} が成功した取引に対してのみ呼ぶ.
*
* @param string $currencyCode ISO 4217
* @param int $amount minor unit 整数
* @param array<string, mixed> $instrument authorize と同一の中立な支払データ (取引識別の導出に用いる)
* @param array<string, mixed> $context 付帯情報
*/
public function capture(string $currencyCode, int $amount, array $instrument, array $context = []): GatewayResult;
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated
}
76 changes: 76 additions & 0 deletions Service/AgentCommerce/Gateway/GatewayResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* https://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\SamplePayment44\Service\AgentCommerce\Gateway;

/**
* 決済ゲートウェイ (authorize/capture) の処理結果 DTO.
*
* PSP 固有のレスポンスを {@link GatewayStatus} へ正規化して保持し、ハンドラが
* コアの {@link \Eccube\Service\AgentCommerce\Payment\PaymentOutcome} へ写像する境界となる。
*/
final readonly class GatewayResult
{
/**
* @param array<string, mixed> $metadata payment_data へ保持する PSP 参照等 (機微情報はマスキング済)
* @param array<string, mixed> $actionData REQUIRES_ACTION 時の追加認証データ (3DS authentication_metadata 等)
*/
public function __construct(
public GatewayStatus $status,
public ?string $transactionId = null,
public array $metadata = [],
public array $actionData = [],
public ?string $errorCode = null,
public ?string $errorMessage = null,
public bool $retryable = true,
) {
}

/**
* @param array<string, mixed> $metadata
*/
public static function succeeded(string $transactionId, array $metadata = []): self
{
return new self(GatewayStatus::SUCCEEDED, $transactionId, $metadata);
}

/**
* @param array<string, mixed> $metadata
*/
public static function requiresCapture(string $transactionId, array $metadata = []): self
{
return new self(GatewayStatus::REQUIRES_CAPTURE, $transactionId, $metadata);
}

/**
* @param array<string, mixed> $actionData
* @param array<string, mixed> $metadata
*/
public static function requiresAction(array $actionData, ?string $transactionId = null, array $metadata = []): self
{
return new self(GatewayStatus::REQUIRES_ACTION, $transactionId, $metadata, $actionData);
}

/**
* @param array<string, mixed> $metadata
*/
public static function processing(?string $transactionId = null, array $metadata = []): self
{
return new self(GatewayStatus::PROCESSING, $transactionId, $metadata);
}

public static function failed(string $errorCode, string $errorMessage = '', bool $retryable = true): self
{
return new self(GatewayStatus::FAILED, null, [], [], $errorCode, $errorMessage, $retryable);
}
Comment thread
ttokoro20240902 marked this conversation as resolved.
Outdated
}
40 changes: 40 additions & 0 deletions Service/AgentCommerce/Gateway/GatewayStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* https://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\SamplePayment44\Service\AgentCommerce\Gateway;

/**
* 決済ゲートウェイの処理結果ステータス.
*
* PSP 非依存だが、実装の移植容易性のため **Stripe PaymentIntent.status の語彙に整合**させている
* (stripe-payment-plugin への移植時、本 enum をそのまま PaymentIntent.status の写像先にできる)。
* ハンドラ ({@link \Plugin\SamplePayment44\Service\AgentCommerce\AbstractAgentCardHandler}) が
* これをコアの {@link \Eccube\Service\AgentCommerce\Payment\PaymentOutcome} へ変換する。
*/
enum GatewayStatus: string
{
/** 売上確定済 (capture 成功). PaymentIntent.status=succeeded 相当. */
case SUCCEEDED = 'succeeded';

/** 与信済・キャプチャ待ち (authorize 成功). PaymentIntent.status=requires_capture 相当. */
case REQUIRES_CAPTURE = 'requires_capture';

/** 追加認証が必要 (EMV-3DS challenge 等). PaymentIntent.status=requires_action 相当. */
case REQUIRES_ACTION = 'requires_action';

/** 非同期処理中 (PSP の確定通知待ち). PaymentIntent.status=processing 相当. */
case PROCESSING = 'processing';

/** 拒否・エラー. PaymentIntent.status=requires_payment_method / canceled 相当. */
case FAILED = 'failed';
}
Loading
Loading