-
Notifications
You must be signed in to change notification settings - Fork 0
API-GIFT-01: add paid report gifting contract #3448
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 all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
1e82a01
feat(commerce): add paid report gifting contract
fermatmind 54f01e5
chore(train): record API-GIFT-01 PR
fermatmind c5beeaa
fix(commerce): harden report gift lifecycle
fermatmind 2486a9c
chore(train): record API-GIFT-01 review repair
fermatmind db0ad43
fix(commerce): preserve durable gift entitlement sources
fermatmind d5332a0
chore(train): record durable gift source repair
fermatmind 65f2d2b
fix(commerce): harden paid gift recovery
fermatmind 7df0945
docs(codex): record third gift review repair
fermatmind ea12623
fix: harden paid gift recovery boundaries
fermatmind 8a20419
docs: record fourth gift recovery repair
fermatmind 21cda78
fix: close paid gift lifecycle race gaps
fermatmind 50b1b8e
docs: refresh API-GIFT-01 validation evidence
fermatmind aeaace8
fix: serialize gift payment action creation
fermatmind 56d66cb
docs: refresh API-GIFT-01 validation evidence
fermatmind f0ead6e
fix: close gift payment races
fermatmind dcf6abf
docs: record gift race validation
fermatmind 56c2726
fix: reject duplicate gift settlements
fermatmind 291d733
docs: record API-GIFT-01 review repair
fermatmind 5493466
fix: preserve intentional gift revocation
fermatmind 3615c4e
docs: record gift revocation repair
fermatmind 7d99498
test: narrow gift repair freeze exemption
fermatmind 721b76a
docs: record runtime freeze repair
fermatmind 8231998
fix: preserve revoked gifts on callback replay
fermatmind 625f63d
docs: record gift replay repair
fermatmind 472b256
fix: keep gift payer delivery private
fermatmind 74f7fca
docs: record gift privacy repair
fermatmind 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
170 changes: 170 additions & 0 deletions
170
backend/app/Http/Controllers/API/V0_3/ReportGiftController.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,170 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Http\Controllers\API\V0_3; | ||
|
|
||
| use App\Http\Controllers\Controller; | ||
| use App\Services\Commerce\ReportGiftService; | ||
| use App\Support\OrgContext; | ||
| use Illuminate\Http\JsonResponse; | ||
| use Illuminate\Http\Request; | ||
|
|
||
| final class ReportGiftController extends Controller | ||
| { | ||
| public function __construct( | ||
| private readonly OrgContext $orgContext, | ||
| private readonly ReportGiftService $gifts, | ||
| ) {} | ||
|
|
||
| public function store(Request $request, string $id): JsonResponse | ||
| { | ||
| $result = $this->gifts->createRequest( | ||
| $this->orgContext->orgId(), | ||
| $this->userId(), | ||
| $this->anonId($request), | ||
| $id | ||
| ); | ||
|
|
||
| return $this->respond($result, 201); | ||
| } | ||
|
|
||
| public function showOwner(Request $request, string $id, string $gift_request_id): JsonResponse | ||
| { | ||
| return $this->respond($this->gifts->showOwner( | ||
| $this->orgContext->orgId(), | ||
| $this->userId(), | ||
| $this->anonId($request), | ||
| $id, | ||
| $gift_request_id | ||
| )); | ||
| } | ||
|
|
||
| public function cancel(Request $request, string $id, string $gift_request_id): JsonResponse | ||
| { | ||
| return $this->respond($this->gifts->cancel( | ||
| $this->orgContext->orgId(), | ||
| $this->userId(), | ||
| $this->anonId($request), | ||
| $id, | ||
| $gift_request_id | ||
| )); | ||
| } | ||
|
|
||
| public function showPublic(string $token): JsonResponse | ||
| { | ||
| return $this->respond($this->gifts->showPublic($token, $this->orgContext->orgId())); | ||
| } | ||
|
|
||
| public function purchaseWechatMiniVirtual(Request $request, string $token): JsonResponse | ||
| { | ||
| $payload = $request->validate([ | ||
| 'idempotency_key' => ['nullable', 'string', 'max:128'], | ||
| 'wx_login_code' => ['required', 'string', 'max:128'], | ||
| 'target_attempt_id' => ['prohibited'], | ||
| 'sku' => ['prohibited'], | ||
| 'amount_cents' => ['prohibited'], | ||
| 'currency' => ['prohibited'], | ||
| 'goodsPrice' => ['prohibited'], | ||
| 'org_id' => ['prohibited'], | ||
| 'user_id' => ['prohibited'], | ||
| 'anon_id' => ['prohibited'], | ||
| ]); | ||
| $idempotencyKey = trim((string) ($request->header('Idempotency-Key') | ||
| ?: ($payload['idempotency_key'] ?? ''))); | ||
|
|
||
| $reserved = $this->gifts->reserveWechatMiniVirtualOrder( | ||
| $token, | ||
| $this->orgContext->orgId(), | ||
| $this->userId(), | ||
| $this->anonId($request), | ||
| $idempotencyKey, | ||
| $this->requestId($request) | ||
| ); | ||
| if (($reserved['ok'] ?? false) !== true || ! is_object($reserved['order'] ?? null)) { | ||
| return $this->respond($reserved); | ||
| } | ||
|
|
||
| $payment = $this->gifts->createWechatMiniVirtualPaymentAction( | ||
| $reserved['order'], | ||
| (string) $payload['wx_login_code'] | ||
| ); | ||
| if (($payment['ok'] ?? false) !== true) { | ||
| $this->gifts->releaseUnpayableReservationForOrder($reserved['order']); | ||
|
|
||
| return $this->respond($payment); | ||
| } | ||
|
|
||
| return response()->json([ | ||
| 'ok' => true, | ||
| 'order_no' => (string) $reserved['order_no'], | ||
| 'pay' => $payment['pay'] ?? null, | ||
| 'idempotent' => (bool) ($reserved['idempotent'] ?? false), | ||
| ]); | ||
| } | ||
|
|
||
| public function getOrder(Request $request, string $order_no): JsonResponse | ||
| { | ||
| $response = app(CommerceController::class)->getOrder($request, $order_no); | ||
| if ($response->getStatusCode() !== 200) { | ||
| return $response; | ||
| } | ||
|
|
||
| $payload = $response->getData(true); | ||
| if (! is_array($payload)) { | ||
| return $response; | ||
| } | ||
|
|
||
| return response()->json( | ||
| $this->gifts->presentOwnedOrderPayload( | ||
| $this->orgContext->orgId(), | ||
| $order_no, | ||
| $payload | ||
| ), | ||
| $response->getStatusCode() | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string,mixed> $result | ||
| */ | ||
| private function respond(array $result, int $successStatus = 200): JsonResponse | ||
| { | ||
| return response()->json( | ||
| $result, | ||
| ($result['ok'] ?? false) === true ? $successStatus : (int) ($result['status'] ?? 400) | ||
| ); | ||
| } | ||
|
|
||
| private function userId(): ?string | ||
| { | ||
| $userId = $this->orgContext->userId(); | ||
|
|
||
| return $userId !== null ? (string) $userId : null; | ||
| } | ||
|
|
||
| private function anonId(Request $request): ?string | ||
| { | ||
| foreach ([ | ||
| $this->orgContext->anonId(), | ||
| $request->attributes->get('anon_id'), | ||
| $request->attributes->get('fm_anon_id'), | ||
| $request->attributes->get('client_anon_id'), | ||
| ] as $candidate) { | ||
| $value = trim((string) $candidate); | ||
| if ($value !== '') { | ||
| return $value; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private function requestId(Request $request): ?string | ||
| { | ||
| $requestId = trim((string) ($request->header('X-Request-Id') | ||
| ?: $request->header('X-Request-ID', ''))); | ||
|
|
||
| return $requestId !== '' ? substr($requestId, 0, 128) : null; | ||
| } | ||
| } |
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
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.