defmodule DaProductApp.MerchantRegistration.AlipayProvider do @moduledoc """ Alipay-specific merchant registration and inquiry logic. """ require Logger @doc """ Alipay-specific registration logic """ def register(params) do base_url = "https://open-sea-global.alipayplus.com/aps/api/v1/merchants/registration" client_id = "SANDBOX_5YEV5L30082Z03013" private_key = load_private_key() # Transform generic format to Alipay format alipay_params = transform_to_alipay_format(params) timestamp = DateTime.utc_now() |> DateTime.to_iso8601() path = "/aps/api/v1/merchants/registration" body = Jason.encode!(alipay_params) payload = construct_alipay_payload("POST", path, client_id, timestamp, alipay_params) signature = generate_alipay_signature(payload, private_key) headers = [ {"Content-Type", "application/json; charset=UTF-8"}, {"signature", "algorithm=RSA256, keyVersion=0, signature=#{signature}"}, {"client-id", client_id}, {"request-time", timestamp} ] Logger.debug("Sending request to Alipay with headers: #{inspect(headers)} and body: #{body}") case HTTPoison.post(base_url, body, headers) do {:ok, %HTTPoison.Response{status_code: 200, body: response_body}} -> Logger.info("Raw response from Alipay: #{response_body}") {:ok, Jason.decode!(response_body)} {:ok, %HTTPoison.Response{status_code: status_code, body: response_body}} -> Logger.warning("Non-200 response from Alipay: #{response_body}", []) {:error, %{status_code: status_code, response: response_body}} {:error, %HTTPoison.Error{reason: reason}} -> Logger.error("HTTPoison error: #{inspect(reason)}") {:error, "Network error: #{inspect(reason)}"} end end @doc """ Alipay-specific inquiry logic """ def inquire(params) do base_url = "https://open-sea-global.alipayplus.com/aps/api/v1/merchants/inquiryRegistrationStatus" client_id = "SANDBOX_5YEV5L30082Z03013" private_key = load_private_key() # Transform generic merchantId to Alipay's referenceMerchantId inquiry_params = %{"referenceMerchantId" => params["merchantId"]} timestamp = DateTime.utc_now() |> DateTime.to_iso8601() path = "/aps/api/v1/merchants/inquiryRegistrationStatus" body = Jason.encode!(inquiry_params) payload = construct_alipay_payload("POST", path, client_id, timestamp, inquiry_params) signature = generate_alipay_signature(payload, private_key) headers = [ {"Content-Type", "application/json; charset=UTF-8"}, {"signature", "algorithm=RSA256, keyVersion=0, signature=#{signature}"}, {"client-id", client_id}, {"request-time", timestamp} ] Logger.debug("Sending inquiry to Alipay with headers: #{inspect(headers)} and body: #{body}") case HTTPoison.post(base_url, body, headers) do {:ok, %HTTPoison.Response{status_code: 200, body: response_body}} -> Logger.info("Inquiry response from Alipay: #{response_body}") {:ok, Jason.decode!(response_body)} {:ok, %HTTPoison.Response{status_code: status_code, body: response_body}} -> Logger.warning("Non-200 inquiry response from Alipay: #{response_body}", []) {:error, %{status_code: status_code, response: response_body}} {:error, %HTTPoison.Error{reason: reason}} -> Logger.error("HTTPoison error during inquiry: #{inspect(reason)}") {:error, "Network error: #{inspect(reason)}"} end end # Transform generic format to Alipay-specific format defp transform_to_alipay_format(params) do merchant = params["merchant"] || %{} store = params["store"] || %{} region = params["region"] || "AE" # Use region from params or default to AE # Transform merchant info merchant_info = %{ "referenceMerchantId" => merchant["id"], "merchantDisplayName" => merchant["displayName"], "merchantMCC" => merchant["mcc"], # "websites" => transform_websites_for_alipay(merchant["websites"]), "merchantAddress" => transform_address_for_alipay(merchant["address"], region), "registrationDetail" => transform_legal_info_for_alipay(merchant["legalInfo"], region), "shareholderName" => get_in(merchant, ["ownerInfo", "name"]), "shareholderId" => get_in(merchant, ["ownerInfo", "id"]) } # Transform store info store_info = %{ "referenceStoreId" => store["id"], "storeName" => store["name"], "storeMCC" => store["mcc"], "storeAddress" => transform_address_for_alipay(store["address"], region) } %{ "registrationRequestId" => params["requestId"], "merchantInfo" => merchant_info, "storeInfo" => store_info, "productCodes" => transform_services_for_alipay(params["services"]) } end # Transform websites array from generic to Alipay format defp transform_websites_for_alipay(nil), do: nil defp transform_websites_for_alipay(websites) when is_list(websites) do Enum.map(websites, fn website -> %{ "url" => website["url"], "websiteType" => website["type"] || "WEB" } end) end defp transform_websites_for_alipay(_), do: nil # Transform address from generic to Alipay format defp transform_address_for_alipay(nil, region), do: nil defp transform_address_for_alipay(address, region) do %{ "region" => region, "address1" => address["street"] } end # Transform legal info from generic to Alipay registration detail format defp transform_legal_info_for_alipay(nil, _region), do: nil defp transform_legal_info_for_alipay(legal_info, region) do %{ "legalName" => legal_info["legalName"], "registrationType" => map_registration_type_for_alipay(legal_info["registrationType"]), "registrationNo" => legal_info["registrationNumber"], "registrationAddress" => transform_address_for_alipay(legal_info["registrationAddress"], region), "businessType" => map_business_type_for_alipay(legal_info["businessType"]) } end # Map generic registration types to Alipay's expected enum values defp map_registration_type_for_alipay(nil), do: "ENTERPRISE_REGISTRATION_NO" defp map_registration_type_for_alipay(type) do case String.upcase(type) do "LLC" -> "ENTERPRISE_REGISTRATION_NO" "CORP" -> "ENTERPRISE_REGISTRATION_NO" "CORPORATION" -> "ENTERPRISE_REGISTRATION_NO" "PARTNERSHIP" -> "ENTERPRISE_REGISTRATION_NO" "SOLE_PROPRIETORSHIP" -> "INDIVIDUAL_REGISTRATION_NO" "INDIVIDUAL" -> "INDIVIDUAL_REGISTRATION_NO" _ -> "ENTERPRISE_REGISTRATION_NO" # Default fallback end end # Map generic business types to Alipay's expected enum values defp map_business_type_for_alipay(nil), do: "ENTERPRISE" defp map_business_type_for_alipay(type) do case String.upcase(type) do "RETAIL" -> "ENTERPRISE" "SERVICE" -> "ENTERPRISE" "RESTAURANT" -> "ENTERPRISE" "ECOMMERCE" -> "ENTERPRISE" "INDIVIDUAL" -> "INDIVIDUAL" "PERSONAL" -> "INDIVIDUAL" _ -> "ENTERPRISE" # Default fallback end end # Transform generic services to Alipay product codes defp transform_services_for_alipay(nil), do: ["IN_STORE_PAYMENT"] defp transform_services_for_alipay(services) when is_list(services) do # Always return IN_STORE_PAYMENT as the only product code ["IN_STORE_PAYMENT"] end defp transform_services_for_alipay(_), do: ["IN_STORE_PAYMENT"] # Helper functions for Alipay API calls defp construct_alipay_payload(_method, path, client_id, timestamp, request_body) do body = Jason.encode!(request_body) "POST #{path}\n#{client_id}.#{timestamp}.#{body}" end defp generate_alipay_signature(payload, private_key) do Logger.debug("Payload to sign: #{payload}") case :public_key.pem_decode(private_key) do [entry] -> rsa_private_key = :public_key.pem_entry_decode(entry) signature = :public_key.sign(payload, :sha256, rsa_private_key) Base.url_encode64(signature, padding: false) [] -> Logger.error("Failed to decode private key") raise ArgumentError, "Invalid PEM format for private key" end end defp load_private_key do key_path = Application.get_env(:da_product_app, :alipay_private_key_path) File.read!(key_path) end end