Add Credit2000 payment gateway (disabled by default) - #49
Conversation
Implements authorize → book → capture/abort via Credit2000 SOAP ASMX, disabled by default until staging credentials are configured. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Adds a disabled-by-default Credit2000 SOAP payment gateway supporting authorization, capture, and abort flows.
Changes:
- Implements Credit2000 SOAP requests, response parsing, currency mapping, and gateway lifecycle.
- Adds configuration and operational documentation.
- Adds unit coverage and gateway registration assertions.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
CREDIT2000.md |
Documents setup and lifecycle. |
config/checkout.php |
Registers and configures the gateway. |
src/Integrations/Credit2000/Connectors/Credit2000Connector.php |
Defines the Saloon connector. |
src/Integrations/Credit2000/Enums/Credit2000CurrencyEnum.php |
Maps supported currencies. |
src/Integrations/Credit2000/Requests/Credit2000SoapRequest.php |
Builds SOAP HTTP requests. |
src/Integrations/Credit2000/Resources/Credit2000PaymentResource.php |
Implements provider operations. |
src/Integrations/Credit2000/Support/Credit2000Xml.php |
Provides XML and identifier helpers. |
src/Payments/Gateways/Credit2000/Credit2000Gateway.php |
Implements payment lifecycle behavior. |
tests/Unit/Payments/Credit2000GatewayTest.php |
Tests gateway operations. |
tests/Unit/Payments/Gateways/PaymentGatewaysTest.php |
Verifies gateway registration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| $expectedTotal = (string) ($persistentData['total_pyment'] ?? ''); | ||
| $callbackTotalRaw = (string) ($callback['TotalPayment'] ?? $callback['TotalP'] ?? ''); | ||
|
|
||
| if ($expectedTotal !== '' && $callbackTotalRaw !== '' && ! $this->amountsMatch($expectedTotal, $callbackTotalRaw)) { |
There was a problem hiding this comment.
Addressed in \�93e2a8: \�uthorize()\ no longer relies on optional callback amount fields. After the redirect returns a UID, we call Credit2000 server-to-server via \getTokenAndApprovePro(uid)\ and require the provider-returned \uID, \product_Id, \ otal_Pyment, \currency, and \�ction_Type\ to match the persisted \prepare_data\ before authorization succeeds.
This is implemented with mocked unit coverage, but we are not treating Copilot Finding #1 as fully resolved until staging validates live \getTokenAndApprovePro\ responses against the real terminal.
| $capture = Credit2000Connector::make()->payment()->creditXml([ | ||
| 'cardNumber' => $token, | ||
| 'validationMonth' => $month ?? '00', | ||
| 'validationYear' => $year ?? '00', | ||
| 'actionType' => self::ACTION_CHARGE, |
There was a problem hiding this comment.
Addressed in \1527c40: \prepare_action_type=2\ (SendParams Test) is now rejected in \prepare(), and \capture()\ refuses to call CreditXML \�ctionType=4\ if a test prepare was stored. Documented test mode is therefore blocked from reaching a live charge path.
| // Approval-only and never captured: nothing to refund at the provider. | ||
| if ($token === '' || (! $alreadyCaptured && ! $chargedOnPage)) { | ||
| $resultData['cancel'] = [ | ||
| 'returnCode' => self::RETURN_OK, | ||
| 'mode' => 'release_uncaptured_approval', | ||
| ]; | ||
|
|
||
| return new AbortResult(isSuccessful: true, persistentData: $resultData); |
There was a problem hiding this comment.
Credit2000 confirmed there is no provider API to release/cancel an uncaptured ActionType 5 approval. Uncaptured approvals normally expire automatically within approximately 3 business days (depending on the merchant–acquirer agreement). Endpoint confirmed: pci_emv_ver4.
Addressed in 6feada5:
- Uncaptured abort no longer fabricates
returnCode=000or claims an immediate provider-side release (release_uncaptured_approval). - Checkout records
cancel.mode=uncaptured_approval_left_to_expire, returnsAbortResult(isSuccessful: true)because no capture occurred, and still blocks latercapture(). - Charged flows still refund via CreditXML
actionType=7.
Tests: 18 Credit2000 tests passed; full Pest suite 174 passed, 1 skipped.
Block prepare_action_type=2 and refuse CreditXML charge=4 if test prepare was stored, so documented Test mode cannot transition to a real charge. Co-authored-by: Cursor <cursoragent@cursor.com>
Bind callback UIDs to persisted prepare data using provider-returned product_Id, total_Pyment, currency, action_Type, and uID. Finding milzer-tech#1 remains unverified until staging validates live Pro responses. Co-authored-by: Cursor <cursoragent@cursor.com>
Credit2000 confirmed there is no release API; leave approvals to expire instead of fabricating a provider return code, and point defaults at pci_emv_ver4. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
SendParamToCredit2000→getTokenAndApprove→CreditXML) aligned with authorize → book → capture/abort.config/checkout.phpwithCHECKOUT_CREDIT2000_ACTIVE=falseby default.Configuration (staging, after merge)
CHECKOUT_CREDIT2000_VENDOR_NAMECHECKOUT_CREDIT2000_COMPANY_KEYCHECKOUT_CREDIT2000_ACTIVE=trueonly after credentials + E2ENotes for reviewers
Existing repository-wide tooling findings (not introduced by this PR)
pint --testfails largely due to pre-existingline_ending(and related style) differences across many existing files. This PR only ran/fixed Pint on the Credit2000-touched paths.phpstan analysereports a pre-existing error insrc/Livewire/TripDetailsPage.php(URL/temporarySignedRoute()unknown class). Credit2000 paths analyse cleanly.rector --dry-runproposes changes in unrelated existing files; those were not applied.Unverified merchant capability
action_Type=5(required for authorize → book → capture) is not yet verified against the live merchant terminal. Confirm with Credit2000 / staging E2E before enabling in staging.Test plan