@@ -1220,30 +1220,60 @@ def place_order(self, params: dict[str, Any]) -> dict[str, Any]:
12201220 )
12211221
12221222 preflight = self ._run_prepare_preflight (normalized )
1223+
1224+ if not self ._final_submit_enabled :
1225+ self ._append_place_order_journal (
1226+ mutation_id = mutation_id ,
1227+ normalized = normalized ,
1228+ submit_state = "submit_blocked" ,
1229+ verification_state = "pending" ,
1230+ broker_ack = {
1231+ "status" : "prepared" ,
1232+ "code" : "PREPARED" ,
1233+ "message" : preflight ["message" ],
1234+ "guard_reason" : self ._final_submit_guard_reason ,
1235+ "market" : normalized ["preview_receipt" ]["inputs" ]["market" ],
1236+ "symbol" : normalized ["preview_receipt" ]["inputs" ]["symbol" ],
1237+ "side" : normalized ["preview_receipt" ]["inputs" ]["side" ],
1238+ "quantity" : normalized ["preview_receipt" ]["inputs" ]["quantity" ],
1239+ "order_type" : normalized ["preview_receipt" ]["inputs" ]["order_type" ],
1240+ },
1241+ )
1242+ raise self ._mutation_error (
1243+ "place_order" ,
1244+ "order_submit_ready" ,
1245+ "capability_not_ready" ,
1246+ f"final submit is disabled ({ self ._final_submit_guard_reason } ); prepare preflight succeeded" ,
1247+ preflight ["context" ],
1248+ extra_diagnostics = {"mutation_id" : mutation_id },
1249+ )
1250+
1251+ broker_ack , broker_context = self ._run_broker_create (normalized , preflight )
1252+ submit_state = "submitted" if broker_ack .get ("status" ) == "submitted" else "broker_rejected"
12231253 self ._append_place_order_journal (
12241254 mutation_id = mutation_id ,
12251255 normalized = normalized ,
1226- submit_state = "submit_blocked" ,
1256+ submit_state = submit_state ,
12271257 verification_state = "pending" ,
1228- broker_ack = {
1229- "status" : "prepared" ,
1230- "code" : "PREPARED" ,
1231- "message" : preflight ["message" ],
1232- "market" : normalized ["preview_receipt" ]["inputs" ]["market" ],
1233- "symbol" : normalized ["preview_receipt" ]["inputs" ]["symbol" ],
1234- "side" : normalized ["preview_receipt" ]["inputs" ]["side" ],
1235- "quantity" : normalized ["preview_receipt" ]["inputs" ]["quantity" ],
1236- "order_type" : normalized ["preview_receipt" ]["inputs" ]["order_type" ],
1237- },
1238- )
1239- raise self ._mutation_error (
1240- "place_order" ,
1241- "order_submit_ready" ,
1242- "capability_not_ready" ,
1243- preflight ["message" ],
1244- preflight ["context" ],
1245- extra_diagnostics = {"mutation_id" : mutation_id },
1258+ broker_ack = broker_ack ,
12461259 )
1260+ return {
1261+ "ok" : True ,
1262+ "kind" : "place_order" ,
1263+ "source" : SOURCE ,
1264+ "checked_at" : broker_context .checked_at ,
1265+ "capability" : "order_submit_ready" ,
1266+ "data" : {
1267+ "mutation_id" : mutation_id ,
1268+ "submit_state" : submit_state ,
1269+ "verification_state" : "pending" ,
1270+ "broker_ack" : broker_ack ,
1271+ },
1272+ "diagnostics" : {
1273+ "endpoint_matrix" : broker_context .endpoint_matrix ,
1274+ "last_errors" : broker_context .last_errors ,
1275+ },
1276+ }
12471277 finally :
12481278 self ._mutation_inflight = False
12491279
@@ -2019,10 +2049,106 @@ def _run_prepare_preflight(self, normalized: dict[str, Any]) -> dict[str, Any]:
20192049 )
20202050
20212051 return {
2022- "message" : "prepare preflight succeeded; final create remains blocked until post-submit verify path is implemented " ,
2052+ "message" : "prepare preflight succeeded" ,
20232053 "context" : context ,
2054+ "account_no" : account_no ,
2055+ "submit_market" : submit_market ,
2056+ "currency_mode" : currency_mode ,
2057+ "allow_auto_exchange" : allow_auto_exchange ,
2058+ "prepare_payload" : prepare_payload ,
2059+ "prepare_body" : prepare_body ,
2060+ "prepared_order_info" : prepared_order_info ,
2061+ }
2062+
2063+ def _run_broker_create (
2064+ self ,
2065+ normalized : dict [str , Any ],
2066+ preflight : dict [str , Any ],
2067+ ) -> tuple [dict [str , Any ], QueryContext ]:
2068+ """Call POST /api/v2/wts/trading/order/create after prepare preflight succeeded.
2069+
2070+ Endpoint URL/body schema captured in Phase 0 P0-02 (HAR diff against
2071+ simulation baseline). Body = prepare_payload minus the ``withOrderKey``
2072+ key; ``orderKey`` is tracked server-side via session cookie.
2073+ """
2074+ prepare_payload = preflight ["prepare_payload" ]
2075+ create_payload = {key : value for key , value in prepare_payload .items () if key != "withOrderKey" }
2076+ account_no = preflight ["account_no" ]
2077+ inputs = normalized ["preview_receipt" ]["inputs" ]
2078+ market_label = str (inputs .get ("market" ) or "" )
2079+ symbol_label = str (inputs .get ("symbol" ) or "" )
2080+ side_label = str (inputs .get ("side" ) or "" )
2081+ quantity_label = inputs .get ("quantity" )
2082+ order_type_label = str (inputs .get ("order_type" ) or "" )
2083+
2084+ create_request = {
2085+ "name" : "order_create" ,
2086+ "method" : "POST" ,
2087+ "url" : "https://wts-cert-api.tossinvest.com/api/v2/wts/trading/order/create" ,
2088+ "path" : "/api/v2/wts/trading/order/create" ,
2089+ "headers" : {"X-Tossinvest-Account" : account_no },
2090+ "include_app_version" : True ,
2091+ "body" : create_payload ,
2092+ }
2093+ results = self ._fetch_many ([create_request ])
2094+ context = self ._make_context (results )
2095+ result = results [0 ]
2096+
2097+ ordered_at = now_kst ()
2098+ base_ack = {
2099+ "market" : market_label ,
2100+ "symbol" : symbol_label ,
2101+ "side" : side_label ,
2102+ "quantity" : quantity_label ,
2103+ "order_type" : order_type_label ,
20242104 }
20252105
2106+ if not result .get ("ok" ):
2107+ error = result .get ("error" ) or {}
2108+ return (
2109+ {
2110+ ** base_ack ,
2111+ "status" : "broker_rejected" ,
2112+ "code" : "BROKER_REJECTED_UNKNOWN" ,
2113+ "message" : str (error .get ("message" ) or "broker create request failed" ),
2114+ "ordered_at" : ordered_at ,
2115+ "http_status" : result .get ("status" ),
2116+ },
2117+ context ,
2118+ )
2119+
2120+ body = result .get ("json" ) or {}
2121+ broker_result = body .get ("result" ) or {}
2122+ order_id = str (broker_result .get ("orderId" ) or "" ).strip ()
2123+ if not order_id :
2124+ return (
2125+ {
2126+ ** base_ack ,
2127+ "status" : "broker_rejected" ,
2128+ "code" : "BROKER_REJECTED_UNKNOWN" ,
2129+ "message" : str (broker_result .get ("message" ) or body .get ("message" ) or "broker create response missing orderId" ),
2130+ "ordered_at" : ordered_at ,
2131+ "http_status" : result .get ("status" ),
2132+ },
2133+ context ,
2134+ )
2135+
2136+ return (
2137+ {
2138+ ** base_ack ,
2139+ "status" : "submitted" ,
2140+ "code" : "OK" ,
2141+ "message" : str (broker_result .get ("message" ) or "" ),
2142+ "broker_order_id" : order_id ,
2143+ "order_no" : broker_result .get ("orderNo" ),
2144+ "order_date" : broker_result .get ("orderDate" ),
2145+ "is_reserved" : bool (broker_result .get ("isReserved" ) or False ),
2146+ "ordered_at" : ordered_at ,
2147+ "http_status" : result .get ("status" ),
2148+ },
2149+ context ,
2150+ )
2151+
20262152 @staticmethod
20272153 def _resolve_prepare_market (receipt : dict [str , Any ]) -> str :
20282154 market_code = str (((receipt .get ("derived" ) or {}).get ("market_code" )) or "" ).upper ()
0 commit comments