-
Notifications
You must be signed in to change notification settings - Fork 14
feat(agent-commerce): ACP/UCP エージェント決済ハンドラと Gateway 抽象を追加 #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
5bd94d1
feat(agent-commerce): ACP/UCP エージェント決済ハンドラと Gateway 抽象を追加
nanasess 1a0ab2b
docs(agent-commerce): services.yaml のタグ付け説明を実装に合わせて修正
nanasess 8a90123
test(agent-commerce): PHPUnit を追加し決済ハンドラの指摘事項を修正する
nanasess 7f719d2
ci: PHPUnit ワークフローの DB 作成を --if-not-exists にする
nanasess 59ebb33
fix(agent-commerce): 再開時に本体から渡る PSP 参照で中断前の取引を続行する
nanasess e22cb7f
fix(agent-commerce): capture の戻り値と再試行可否を本体の契約に合わせる
nanasess File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 に混入するのを避ける。 | ||
| */ | ||
| 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); | ||
| } | ||
|
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); | ||
| } | ||
|
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), | ||
|
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), | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ''; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
ttokoro20240902 marked this conversation as resolved.
Outdated
|
||
| } | ||
47 changes: 47 additions & 0 deletions
47
Service/AgentCommerce/Gateway/AgentPaymentGatewayInterface.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
|
ttokoro20240902 marked this conversation as resolved.
Outdated
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
|
ttokoro20240902 marked this conversation as resolved.
Outdated
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.