Repository navigation
1.9.79: ClickBank's INS works for every account, decrypted with each account's own key - #276
Conversation
Account › API integrations handed out tracking202/static/paykickstart.php as PayKickstart's Affiliate IPN URL under "No setup needed", and GET /system/integrations and `p202 system integrations` listed it. The file never existed, so every IPN sent there was a 404. The Affiliate IPN could not have worked anyway: it is unsigned JSON with PayKickstart's own tracking id and no custom variables, so nothing in it names a click. The URL now takes PayKickstart's product (vendor) IPN. A vendor puts subid=[[subid]] on the checkout link that is the campaign's offer URL, pastes the URL into each product's IPN Integration tab and stores each PayKickstart campaign's IPN secret key (202_users_pref, one per line, at most 20, never served back). Each IPN is verified against every account's stored keys with PayKickstart's published HMAC-SHA1 algorithm before anything in it is read, and is recorded for the account whose key verifies it, on a click of that account (another account's click reads as no click). sales records a conversion (source paykickstart, pixel type 2) under its transaction_id at its amount; refund reverses it; test mode, subscription-payment and every other event record nothing. A retry under a transaction id on file is a duplicate only when it states the same amount, and is refused (409) when it states another. Every outcome is a status PayKickstart's log shows, with the reason, and one log line without a key or buyer data. The receiving side is provider-neutral (Prosper202\PaymentNotification: a ProviderAdapter that verifies and parses, one Processor that records or reverses through p202RecordLegacyConversion(), one Response mapping), so JVZoo and ClickBank can follow as adapters. HandedOutUrlsNameServedFilesTest holds every script a served literal names, and every integration GET /system/integrations lists, to a file in the tree; jvzoo.php, zpn.php and slack.php are listed as known dead until their own changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
202-config/changelogs.json kept both: #258's updated lines, then the PayKickstart line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
#258 made the API answer a 5xx while the files are deployed and the upgrade not run with a 503 naming the upgrade. The IPN endpoint is not in the API's tree, and between those two moments its keys' column is not there yet: it answered 500. The Receiver now asks SchemaVersion::pendingUpgrade() on a failure and answers 503 with its sentence (OutcomeKind::UPGRADE_PENDING); PayKickstart's retry lands once the upgrade has run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…ey is "the key" admin-005 asks an agent to store a campaign's PayKickstart IPN secret key and give the URL to paste: the key is stored with user prefs update, the URL is the one system integrations answers, and neither the preferences read nor the reply repeats the key. The reference agent answers it; an agent that stores nothing and repeats the key fails all four checks. Search puts system integrations first for the ask (two tasks in one ask), so it is listed in searchKnownMisses rather than taught by a phrase lifted from the ask. A refusal of a single pasted key says "The key is 5 characters long" rather than "Key 1"; a list still names the key by its place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…what the reversal on file took back A refund with no amount (or 0) takes back the whole sale. Its comparison with the reversal on file was skipped when the amount was null, so a full refund after a partial one was answered 200 duplicate: PayKickstart stopped retrying while the rest of the sale stayed credited (CLAUDE.md #15). The Processor now reads the reversal on file with the sale it reverses and compares what the refund takes back, a full refund read as the sale's amount. Any other refund of a sale already reversed is a 409 naming both: "already reversed by 10.5, and 36.5 of its 47 is still credited; this refund states its full amount, 47". A sale is still reversed once. The other duplicate decision, a sale or rebill under a transaction id on file, compares two amounts that are never null (the sale's is required, the stored one NOT NULL). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…er a deleted reversal reverses again A sale stated again under a transaction id whose sale a refund took back was answered 200 duplicate while the sale stayed reversed. JVZoo sends an undone refund as RFND and then SALE again; the sale is now a 409 naming the reversal to delete to count it (ported from the JVZoo branch). Deleting that reversal did not make a later refund work: a deleted row keeps its dedupe key, every refund without an id of its own shared rev:<sale>:1, so the next refund of the sale found the deleted row and was answered 200 duplicate with nothing reversed. Such a refund is now keyed by its body, so only the same notification sent again unchanged finds a reversal already on file, live or deleted; any other is either compared with the reversal that stands (the writer refuses a second one under its lock, and the Processor answers duplicate when it takes back the same) or, when none stands, reverses the sale again. A refund with its own id is still told apart by that id. The 403 names the provider's signature field (ProviderAdapter:: signatureField(); PayKickstart's is hash). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
cb202.php read the ClickBank secret key and the Slack webhook of account 1 and of no other account, so a second account could not use ClickBank's Instant Notification Service at all. It also decrypted with AES-128-CBC, while ClickBank's INS article says notifications are encrypted with CBC-AES-256 and its PHP and Python samples decrypt them so: AES-128 reads 16 of the derived key's 32 bytes, and a notification encrypted as the article says did not decrypt for any account (executed: the openssl command line's AES-256 ciphertext, which Python's cryptography decrypts keyed as ClickBank's Python sample, is refused by the old code, which accepts only the AES-128 its own tests encrypted with). cb202.php now runs the shared payment-notification Receiver with a ClickBank adapter (#268's shape): - ClickBankCipher decrypts as ClickBank's samples do: AES-256-CBC keyed with the first 32 hex digits of SHA-1(secret), trailing control bytes trimmed, the order a JSON object naming its transactionType. - ClickBankAdapter tries every account's stored cb_key; the account is the one whose key decrypts the notification. A key two accounts store is refused (a new outcome, ambiguous_account, 409) and the log names the accounts. A ciphertext that decrypts to something other than an order answers as one no key decrypts: the old code answered the two with different 400s, which is what a padding-oracle attack needs. - The envelope is read before any key: a legacy cverify form (4.0 and earlier) is a 415 naming the version to choose, anything else that is not {"notification","iv"} a 400. - A SALE is recorded under its receipt at totalAccountAmount, stated as USD (ClickBank: "Total you received for the transaction in USD"), so an account counting in another currency is refused by the Processor. The amount is read exactly whether 8.0 sends it as a string or 7.0 as a number. The click is the first tracking code (tid=[[subid]]), or, with none, the affiliate's extclid that 8.0 sends; no click is 200 no_click rather than the 400 ClickBank resent five times. - TEST marks that account's cb_verified through a new optional interface, ConfirmsTestNotifications, which the Processor calls for a test one account verified; a failure answers 500 so ClickBank's Test URL shows it. cb202.php then posts to that account's own Slack webhook, after the answer (ClickBank waits 3 seconds). - Every other type is acknowledged as before (851K handles them). Shared code: InboundRequest carries the raw body; the Processor's test and ignored outcomes name the account when one verified; the Receiver's refusal no longer says "hash" (ClickBank's is a cipher). The page's ClickBank panel says to put tid=[[subid]] on the HopLink and asks for OpenSSL alone (mcrypt was never read). Tests: ClickBankAdapterTest (a known-answer vector from the openssl command line, the old cipher refused, the padding-oracle case, the envelope, every field's reading) and ClickBankInsIntegrationTest (two accounts and a third sharing a key, a real server and the real writer). PostbackHandlerTest is removed: it re-implemented the AES-128 decryption in the test and exercised nothing in the tree. tests/live/clickbank-ins.sh is the two-account live pass, encrypting with the openssl command line; legacy-pixels.sh encrypts as ClickBank does now. Backlog: 7PJV Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
… words The Processor's no_click answer names PayKickstart's checkout link and its subid, which is the wrong advice for a ClickBank HopLink. The adapter now answers no_click itself, as JVZoo's does, naming the type, the receipt and tid=[[subid]]. Backlog: 7PJV Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
changelogs.json: 1.9.79's lines, then the PayKickstart line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…ckBank branch The Receiver's 403 now names the provider's signature field (ProviderAdapter::signatureField()); ClickBank's is its encryption. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr # Conflicts: # 202-config/changelogs.json
…ClickBank branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr # Conflicts: # 202-config/changelogs.json
…ppened p202RecordLegacyConversion() dated every row time(), and the Processor dropped the time a notification states, so an IPN PayKickstart delivered days late (it retries a failed delivery up to 13 times) was filed on the day it arrived in every view by conversion time: the conversion list's time filter, attribution, the click-to-conversion time. The recorder now takes a conv_time, and the Processor passes the stated time, held between the earliest it can be (the click a sale credits; for a refund, the sale it takes back, so the sale keeps its own day) and its arrival (a clock ahead of this server's). The answer and the log line say when a time was held. With no time stated, the row is dated when it arrives, as before. transaction_time is signed (every non-empty field is); one that is not a Unix time is now a 400 naming it, where it was read as no time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…start IPN branch
GET /users/{id}/preferences now serves every integration secret as its
stored state (#270), and the PayKickstart IPN keys are one more:
paykickstart_ipn_secret_keys is on ServedPreferences::SECRET, answered
{"stored": true}, and the count this branch served in its place
(paykickstart_ipn_keys_stored, PayKickstartKeys::withoutKeys()) is gone.
GET /system/integrations keys_stored still says how many. The
PreferenceSecretsAreNotServedIntegrationTest round trip stores the keys
with the other secrets.
Conflicts: changelogs (1.9.79's lines, then this branch's), the
preferences docs and the OpenAPI schemas (1.9.79's, with the keys added
as a StoredSecret), and the controller (1.9.79's).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…ending The page's Remove form named a key by its place and the four characters it shows (…AB12). Another tab could remove that key and add a different one ending in the same four characters at its place; the first tab's form then matched the new key and removed it (measured on the pushed class: removed(['campaign-new-AB12', …], '1', '…AB12') dropped campaign-new-AB12). The form now carries an HMAC-SHA256 of the whole key under a secret this session holds server-side (PayKickstartKeys::removalToken()): the page shows nothing a reader could match a guess against, and a form made for one key cannot match another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…79's #270, #280, #281, #282) into the ClickBank branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr # Conflicts: # 202-config/changelogs.json # changelogs.txt
#268's Processor now dates a conversion by the time its notification states (held between the click and the arrival). ClickBankAdapter reads transactionTime with the offset it carries: RFC 3339 as 6.0 and 8.0 send it (2023-10-05T13:47:51-06:00) and ISO 8601's basic form as 7.0's notes describe (20200819T144359-0700). A time that is neither, or names no real moment (a 13th month, hour 24, beyond a conv_time), is a 400 naming the field rather than the time it arrived; none at all is dated on arrival, as before. Backlog: 7PJV Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, 202-config/functions-upgrade.php, changelogs.txt, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in paykickstart-ipn (this branch's stacked base), and through it 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
PayKickstartAdapter::storedSecrets() read every 202_users_pref row, and a
deleted account keeps its row and the keys in it. Its key kept verifying
IPNs, so a sale on a deleted account's click was recorded for it, and a key
the deleted account shared with a live one verified both. ClickBank's and
JVZoo's adapters, on the branches above, read their keys the same way, and
ClickBank refuses a key two accounts store with a 409: a deleted account
holding a live account's ClickBank key refused every notification of the
live account, with nothing in the UI to clear the deleted copy.
AccountSecrets::stored() is now the one read of a provider's secret column
that decides the accounts a notification can belong to: it joins
202_users and leaves out user_deleted = 1. A user switched off in Account ›
Users (user_active = 0) still owns its keys: a sale PayKickstart reports
for that account is not that person acting. ProviderAdapter documents it.
StoredSecretsReadThroughAccountSecretsTest holds every ProviderAdapter's
storedSecrets() to a call of AccountSecrets::stored() with no statement of
its own naming 202_users_pref, and every other statement in
202-config/PaymentNotification and the payment endpoints that names
202_users_pref to a listed reason (the Processor's currency read, for the
account already decided).
Tests: PayKickstartIpnIntegrationTest's new
testADeletedAccountsStoredKeyVerifiesNothing seeds a deleted account that
stores a live account's key and one of its own: the live account's sale is
recorded for it, the deleted account's click is a 404 and its own key a
403, and a switched-off account's sale is recorded. Planted: the
user_deleted filter removed from AccountSecrets fails it ("Recorded
transaction PK-TX-D2 on click 99040131"), and so does a user_active = 1
filter added (the switched-off account's IPN unverified); storedSecrets()
reading 202_users_pref itself fails both structural tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in #268's AccountSecrets: a deleted account's stored keys verify nothing. The conflict was in changelogs.txt, where both sides changed lines of 1.9.79: #268's PayKickstart line is kept as it is and this branch's ClickBank line follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
ClickBankAdapter::storedSecrets() read cb_key from every 202_users_pref row, and a deleted account keeps its row and its key. A notification that the key decrypts names every account that stores it, and a key two accounts store is refused (409, ambiguous_account): so a deleted account that still held the key a live account stores now made every one of the live account's notifications a 409, and the deleted account's copy cannot be cleared from the UI. A key only a deleted account stored still recorded sales on its clicks. storedSecrets() reads the keys through #268's AccountSecrets::stored(), which leaves out deleted accounts; a switched-off account (user_active = 0) still owns its key. StoredSecretsReadThroughAccountSecretsTest now holds ClickBankAdapter too, and lists the two statements it makes on 202_users_pref for the account a TEST already decided (confirmTest(), slackWebhook()). Tests: ClickBankInsIntegrationTest's new testADeletedAccountsStoredKeyIsNoSecondAccount: with a deleted account storing the first account's key, the first account's sale is recorded and its TEST marks only it verified; a key only the deleted account stores is a 403; a switched-off account's sale is recorded. Planted: storedSecrets() reading 202_users_pref itself, and AccountSecrets without its user_deleted filter, each fail it with the 409 ("The notification decrypts with a ClickBank secret key that more than one Prosper202 account stores"); a user_active = 1 filter fails it with a 403. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
|
The adversarial review marks fcd6019 BLOCKING (5 findings), so the check is red. Its report is encrypted for maintainers, so I can't see which finding blocks. The previous head, 0869d72, came back NON-BLOCKING with 4 findings. The only change since then is the deleted-account key fix (merged from #268, plus fcd6019):
I read that delta and found no defect. The column name is checked against That still doesn't rule out a real finding elsewhere in this PR; #288's BLOCKING turned out to be one. A self-review of this PR's ClickBank code is under way, covering the cipher, the account decision, the TEST path and the logs. Any real defect will be fixed here with a test that fails without the fix, and merged up into #288 and #319. I'll report the result on this thread. If a maintainer can share the blocking finding's title, I can go straight to it. Generated by Claude Code |
|
The self-review of fcd6019 is done: no blocking defect found, so nothing is pushed. Everything below was measured on a live instance, not read.
Two real but non-blocking points, both already present on 0869d72 (NON-BLOCKING):
Two reviews of code that contains this PR in full, #288 (5e7e990) and #319 (d537116), came back NON-BLOCKING. Together with the above, the BLOCKING mark on this head looks like reviewer variance. If a maintainer can share the blocking finding, I'll address it directly. Generated by Claude Code |
Stacked on #268 (PayKickstart IPN, branch
1.9.79-paykickstart-ipn): review and merge this after #268. Until #268 merges, this diff includes #268's commits. #268 is merged in at 8532b44, and with it 1.9.79 (#262, #270, #280, #281, #282), each by a merge commit. This PR's own commits are 5ec4eda, e1f2d2b and b2c115f, plus the merge resolutions. See them alone withgit diff origin/1.9.79-paykickstart-ipn...1.9.79-clickbank-per-account. #288 (851K: rebills, refunds, chargebacks) is stacked on this PR.Backlog: 7PJV ("ClickBank notifications only work for the first account")
What was wrong
tracking202/static/cb202.phpreadcb_keyanduser_slack_incoming_webhookfrom account 1 (lines 7 and 10). No other account could use ClickBank INS.openssl_decrypt(…, 'AES-256-CBC', substr(sha1($secretKey), 0, 32), OPENSSL_RAW_DATA, …), and its Python sample's 32-byte key is AES-256 too. AES-128 reads only 16 of the derived key's 32 bytes.cryptography, keyed as ClickBank's Python sample, decrypts that ciphertext. The old cb202.php answers it400 Unable to decrypt notification. It accepted only the AES-128 that its own tests (PostbackHandlerTest,legacy-pixels.sh) encrypted with: a mirror of the code, not of ClickBank (CLAUDE.md Update release notes in README #9).400 Unable to decrypt notification. One that decrypted to something other than an order answered400 Malformed order payload. That difference is the condition a padding-oracle attack needs. It was not demonstrated end to end.What changed
cb202.php is now #268's
Receiver, with a ClickBank adapter.Decryption:
ClickBankCipher. It decrypts as ClickBank's samples do:transactionType.Invalid UTF-8 is substituted rather than refused, because 7.0 is the first version to say it sends UTF-8.
Finding the account:
ClickBankAdapter.cb_keyis tried, with surrounding whitespace trimmed. The account is the one whose key decrypts the notification (CLAUDE.md [Maintenance] Remove deprecated PHP features #16).ambiguous_account(409). The log names the accounts; the answer does not.403). There is no padding oracle.cverifyform (version 4.0 and earlier) is a415that says to choose 8.0.{"notification","iv"}with base64 of whole AES blocks and a 16-byte IV is a400.Reading the order.
receiptattotalAccountAmount, through 1.9.79: PayKickstart's product IPN, verified with each campaign's key #268'sProcessor(ledger sourceclickbank, pixel type 2). The amount is read exactly, whether 8.0 sends it as a string or 7.0 as a number.422.tid=[[subid]]on the HopLink). When there is none, it is 8.0'saffiliateTrackingParameters.extclid.400naming it.200 no_click, worded for a HopLink.transactionTimeis read with the offset it carries and setsNotification::$occurredAt, which 1.9.79: PayKickstart's product IPN, verified with each campaign's key #268's Processor dates the row by (held at the click when earlier, at arrival when later).2023-10-05T13:47:51-06:00), and ISO 8601's basic form as 7.0's notes describe (20200819T144359-0700).conv_time), is a400namingtransactionTime.cb_verified, through a new optional interface,ConfirmsTestNotifications. A failure answers500, so ClickBank's Test URL shows it. cb202.php then postscb_key_verifiedto that account's own Slack webhook, after the answer.#268's adapter contract, as 1YEB listed it:
signatureField()answersencryption. INS 6.0 and later are encrypted, not signed: no field carries a signature, and the ciphertext arrives innotification. The method names what failed to verify in the 403, soencryptionmakes the answer read "The notification's encryption does not verify with any ClickBank secret key stored in Prosper202".notificationwould read "The notification's notification". The legacy versions, which did sign withcverify, are refused before any key is tried.$occurredAtis set as above.ServedPreferences::SECRETneeds no change. ClickBank's per-account secrets arecb_keyand the Slack webhook (user_slack_incoming_webhook), and 1.9.79: GET /users/{id}/preferences serves no integration secret #270 already lists both.cb_verifiedis a flag and is onSERVED.Shared code, kept out of the other adapters' way:
InboundRequestcarries the raw body.testandignoredoutcomes name the account when one verified.OutcomeKind::AMBIGUOUS_ACCOUNTis new (409).The page. The ClickBank panel says to put
tid=[[subid]]on the HopLink, and asks for OpenSSL alone.Docs and changelogs. There is a ClickBank section in
documentation/tutorials-and-guides/09-3rd-party-integrations.md, with setup steps, an outcome table and dating.changelogs.txtandchangelogs.jsonhave entries after 1.9.79's own.ClickBank's spec: what was verified and what was not
Read from support.clickbank.com, "Instant Notification Service (INS)" (article 10535147) and the related articles.
cverifyform posts.transactionType,receipt(8 to 21 characters),totalAccountAmount(USD; a number in the 7.0 example, a string in the 8.0 one),trackingCodes,affiliateTrackingParameters.extclid, andtransactionTime(RFC 3339 in the 6.0 table and the 8.0 example; 7.0's notes say ISO 8601 basic).********.openssl_decryptcall.tidis what fillstrackingCodes.Decisions a reviewer should check
422.affiliateTrackingParametersandcommonTrackingParameters).extclidfallback is new behaviour. A tracking code, when there is one, always decides.signatureField()isencryption(above).Verification
Scope report of
.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79 --changedon b2c115f (PHP 8.3.6, MariaDB 10.11, full vendor/). The schema tier had a scratch database, and the live instance's202-config.phpwas moved aside. Unit tier: 6167 tests, 4 skipped (the documented User DB-singleton skips). Verbatim:Integration suites, each alone on a database created for it:
ClickBankInsIntegrationTestPayKickstartIpnIntegrationTest(the shared Processor)LegacyPostbackCampaignIntegrationTestConversionBridgeIntegrationTestConversionLedgerIntegrationTestPlanted defects: 15 of 15 caught for the adapter (listed in the first version of this description), and the time-reading plants in #288's run (a time read without its offset, a 13th month read as January, a sale not dated, an unreadable time read as none), each caught by a named test.
Live pass,
tests/live/clickbank-ins.sh, on the merged tree: 41 passed, 0 failed. Notifications are encrypted by the openssl command line and posted with curl to an instance with two accounts:404;GET /system/integrationsreadverified: true, and account 1'sfalse;409;403;200 no_click;42.9is400;415; a GET is405;409, and records once it is one account's again;-06:00is stored with thatconv_time; a 13th month is a400;Before and after. Against the old cb202.php, 9 of the first 37 checks passed.
tests/live/legacy-pixels.sh: 61 passed (before the latest merge).Could not run
202-config/install.phpdoes after its licence check:INSTALL::install_databases(),InstallAccount::create(), a REST key in202_api_keys, and the partitions. Thenphp -Swas started with the CI router, asinstall-instance.shdoes. Skipped: the licence check itself, and the AutoCron and daily-email registrations. The API,dl.phpandcb202.phpwere driven directly.Found outside this item
🤖 Generated with Claude Code
https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr