Expresní SMS platby#
Expresní SMS zaplatí jednu službu jedinou zprávou KLÍČOVÉ_SLOVO MODUL SLUŽBA ID, bez
platebního formuláře. Platba v okamžiku příchodu SMS neexistuje — vznikne až z ní
a pak běží stejným zpracováním jako platba z formuláře. Pro provozovatele je totéž
v Platba SMS.
Části#
| Část | Kde | Úkol |
|---|---|---|
ExpressSmsParser |
app/Core/Payments/Express/ |
parse() text → ExpressSmsCommand (expresní / formulářová / neplatná), compose() týž tvar pro front, serviceCodeFromKeyword() |
ExpressServiceProviderInterface |
tamtéž | jeden modul: getModuleCode(), resolve(), createPaymentItems(), describe() |
ExpressServiceRegistry |
tamtéž | slovo modulu → provider; provideři se hledají líně podle typu v DI kontejneru; describe(gateId) doplní ke službám číslo brány za jejich cenu |
AbstractQuickPriceExpressServiceProvider |
tamtéž | společná logika modulů s rychlými cenami (Bazaar, Comcat, Discussion) |
ExpressTarget, ExpressResolution, ExpressServiceDescription |
tamtéž | DTO mezi modulem a System vrstvou |
ExpressModuleCode |
tamtéž | slova modulů (INZ, FIRMA, DISK, UCET) — sdílí je Api i Front |
PaymentExpressExecuteTrait |
app/UI/Api/System/Presenters/Traits/Actions/ |
příjem SMS api/system/payment/express-execute |
PaymentExpressOffersTrait |
tamtéž | interní akce expressOffers — text a číslo pro front |
ExpressPaymentTrait |
app/Core/Traits/Front/Presenters/ |
sekce „Expresní platba“ u objektu na frontu |
Provideři dnes:
| Třída | Slovo | Služby | Callback |
|---|---|---|---|
Api\Bazaar\Payments\BazaarExpressServiceProvider |
INZ |
rychlé ceny tarifů inzerátu | BAZAAR_PAYMENT |
Api\Comcat\Payments\ComcatExpressServiceProvider |
FIRMA |
rychlé ceny tarifů firmy | COMCAT_PAYMENT |
Api\Discussion\Payments\DiscussionExpressServiceProvider |
DISK |
rychlé ceny tarifů příspěvku | DISCUSSION_PAYMENT |
Api\System\Payments\MembershipExpressServiceProvider |
UCET |
CLEN (tarify s duration > 0), KREDIT (tarify s credits > 0), ceny tarifů pro bránu |
SYSTEM_USER_PAYMENT |
Tabulka „expresních služeb“ v databázi záměrně není: službami jsou rychlé ceny
(cms_mod_*_tariff_quick_prices), resp. ceník tarifů členství. Druhý seznam by se od nich
tiše rozjel.
Průběh příjmu SMS#
internalActionExpressExecute() v PaymentExpressExecuteTrait:
- Brána z parametru
pg(prázdné =mobilniplatby); musí implementovatExpressSmsGatewayInterface. - Původ:
verifyNotificationOrigin()— sdílené tajemstvídelivery_secret, fail-closed. Chybějícíidneboshortcodeje odmítnutí. Odmítnutí nic nezakládá ani nemění, zapíše jen logexpressExecutebezpayment_id. - Idempotence: platba se stejným
gateway_payment_idu téže brány → vrátí se uložená původní odpověď a nic se nezpracuje. - Parse. Tvar
KLÍČOVÉ_SLOVO <kód>je formulářová SMS:loadPayment()→checkPayment()→processPayment(). - Expresní SMS:
registry->get(modul)→provider->resolve(služba, id, gateId). - Cena výhradně z
PaymentGatePhonepodleshortcode; vybere se cíl, jehož cena a měna se rovnají číslu (matchExpressPrice()). Cílů je víc, když službu nabízí víc regionů nebo — u členství — víc cen téhož tarifu. - Založí se
Payment(cena a měna čísla,user_id+ e-mail vlastníka, callback,gateway_payment_id, region), účetní snapshotcms_system_payment_itemsa položky modulu přescreatePaymentItems()— neaktivní. processPayment()se syntetickýmGatewayCheckResult: transakce, stav, callback modulu (aktivace),PAYMENT_DONE, doklad cronem. MO číslo (hladina 0) =COMPLETED, MT =PENDINGa dokončí ho doručenka.- Odpověď:
text/plain; charset=utf-8, HTTP 200, explicitníContent-Length, ASCII text; u MT;<hladina>.
Rozhraní provideru#
interface ExpressServiceProviderInterface
{
public function getModuleCode(): string;
public function resolve(string $serviceCode, int $objectId, int $paymentGateId): ExpressResolution;
public function createPaymentItems(ExpressTarget $target, int $paymentId): bool;
/** @return list<ExpressServiceDescription> */
public function describe(int $paymentGateId): array;
}
resolve()dostává neověřený vstup od kohokoli: službu z textu a id. Musí ověřit, že služba je teď nabízená a objekt existuje a je aktivní. VracíExpressResolution::found([...ExpressTarget]), nebofailed()s důvodemunknownService/objectNotFound/objectInactive.ExpressTargetnese tarif, cenu a měnu služby, region,callbackKey, vlastníka (ownerUserId,ownerEmail),allowDuplicateActivation, typ produktu a druh plnění pro doklad. Cenu z textu SMS v něm nikdy nemá.$paymentGateIdvyužije modul, jehož ceny se liší podle brány (tarify členství mají cenu na řádku region × brána × měna). Moduly s rychlými cenami ho ignorují.createPaymentItems()založí položky neaktivní přes manager modulu (s jeho save hooky); aktivuje je callback, až je platbaCOMPLETED.describe()vydá nabízené služby pro admin přehled a pro front. Číslo k nim doplní registr.
Postup: přidat expresní službu modulu s rychlými cenami#
Modul má tarify s rychlými cenami (tarif → rychlá cena na region, položka platby = objekt + tarif + platba), jako Bazaar, Comcat a Discussion.
- Do
ExpressModuleCodepřidejte slovo modulu (velká písmena, bez diakritiky). - V Api vrstvě modulu založte
app/UI/Api/<Modul>/Payments/<Modul>ExpressServiceProvider.phpjakofinal class … extends AbstractQuickPriceExpressServiceProvider. Konstruktor bere typované managery modulu (objekt, tarif, položka platby). - Implementujte
getModuleCode(),getCallbackKey(),buildObjectLabel()(ASCII, do SMS),createPaymentItemEntity(),findObject(),findActiveTariffs()(stranslationsaquickPrices) asavePaymentItem(). - Když objekt není inzerát, přepište háčky:
isObjectActive()— výchozí jeisActive()entity; příspěvek diskuze bere!banned && !deactivated,bindPaymentItemObject()— výchozí jesetAdvertId(); diskuze volásetMessageId(). Smazaný objekt (isDeleted()) odmítá základ sám, vlastníka čte zgetOwnerUserId().- Callback klíč musí být v
PaymentCallbackRegistry::HANDLERSa mít řádek vcms_system_payment_callbacks— bez něj skončí každá SMS jakocallbackMissing. - Registrace v DI není potřeba: třída leží v
app/UI, kterou procházísearch, a registr ji najde podle rozhraní. Po přidání třídy smažtetemp/cache(DI kontejner). - Sekci na frontu přidáte presenteru detailu objektu traitem
ExpressPaymentTrait:findExpressPaymentTariffs(),getExpressPaymentReference()(id objektu),getExpressPaymentModuleCode(). Vzor jeFront\Bazaar\…\AdvertDetailTrait. - Rozšiřte harness
tests/Manual/System/payment_gateway_auth_harness.php(sekce 13) o platnou SMS, neaktivní objekt a špatnou cenu.
Postup: přidat službu nad jiným ceníkem#
Když modul rychlé ceny nemá (tarify členství), implementujte rozhraní přímo — vzor
MembershipExpressServiceProvider:
- Rozhodněte, co je slovo služby. Svažte ho jen s tarify, které callback opravdu umí
aktivovat.
UserPaymentRepositoryprodlužujemember_untiltarifům sduration> 0 a připisuje kredity tarifům scredits> 0; tarif s oběma nulami by se zaplatil a nic neudělal, proto se nenabízí. findOffers($paymentGateId)vrací aktivní tarify × aktivní nenulové ceny pro bránu, která SMS přijala. Cena musí být konečná (brána máfinal_prices).resolve()ověří objekt čerstvým čtením (UserManager::findFresh()— zablokování musí platit hned, ne až po vypršení cache).createPaymentItems()založí položku neaktivní;afterSavemanageru pak nic neprodlužuje ani nepřipisuje a vše udělá callback jednou.
Idempotence#
Agregátor opakuje tutéž SMS až dvanáctkrát (stejné id, rostoucí att).
idagregátora se ukládá docms_system_payments.gateway_payment_idbez prefixu — doručenka páruje parametrrequestpřímo na tento sloupec.- Unikátní index
(cms_system_payment_gates_payment_gate_id, gateway_payment_id)brání dvěma platbám ze dvou souběžných pokusů. Prohraný pokus dostane zsaveExpressPayment()nulla odpoví neutrálně; agregátor se zeptá znovu a dostane uloženou odpověď. - Odpověď se ukládá do
cms_system_payment_error_logs(status_text = expressReply, text vlong_message) a opakování ji vrátí beze změny — u MT čísla i se stejnou hladinou. - Callback běží jednou za transakci (
claimExecution), stejně jako u formulářové platby — viz opakovaná platba.
Neúspěšná SMS jako reklamace#
Každá chybová větev po ověření původu (nesmyslný text, cizí klíčové slovo, neznámý modul
nebo služba, neexistující či neaktivní objekt, neznámé číslo, špatná cena, chybějící
callback, selhání položky) založí platbu FAILED s gateway_payment_id:
- dohledatelnost — u MO čísla operátor peníze strhl při odeslání; číslo platby je kód
reklamace v odpovědi a popis platby nese důvod (
Expresní SMS neaktivována: priceMismatch), - idempotence — opakovaný pokus najde platbu a vrátí tutéž odpověď,
- bez callbacku a bez položek — nic se neaktivuje.
U MT čísla nese odpověď ;FREE<hladina>, takže se zákazníkovi nic neúčtuje.
MobilniplatbyGateway::checkDelivery() odmítne doručenku k platbě, která není PENDING —
jinak by DELIVERED k FAILED platbě („FREE“) platbu dodatečně zaplatila.
Odmítnutí kvůli původu (chybějící nebo špatný secret) platbu nezakládá: kdo nezná tajemství, nesmí do tabulky plateb zapsat ani řádek.
Testy#
tests/Unit/Core/Payments/ExpressSmsParserTest.php— parser acompose().tests/Manual/System/payment_gateway_auth_harness.php, sekce 13 — celá cesta přesApiBridge: platná SMS, opakovanáatt, špatná cena, neexistující / neaktivní objekt, klíčové slovo, secret, formulářová SMS, doručenka u FAILED; 13.D diskuze; 13.U členství (kredity jednou, MT hladina + doručenka, zablokovaný uživatel). Fixtures po sobě maže. Spouštění:NETTE_DEBUG=1 php -d memory_limit=1G tests/Manual/System/payment_gateway_auth_harness.php.