'ram:ApplicableHeaderTradeSettlement', 'value' => array_filter( array( $this->get_payment_reference(), $this->get_payment_means(), $this->get_trade_tax(), $this->get_payment_terms(), $this->get_monetary_summation(), ) ), ); $data[] = apply_filters( 'wpo_ips_edi_cii_applicable_header_trade_settlement', $applicable_header_trade_settlement, $data, $options, $this ); return $data; } /** * Get the payment reference for the order. * * @return array|null */ public function get_payment_reference(): ?array { $order = $this->document->order; $reference = $order->get_order_number(); // Default to WooCommerce order number $reference = apply_filters( 'wpo_ips_edi_cii_payment_reference', $reference, $order, $this ); if ( empty( $reference ) ) { wpo_ips_edi_log( sprintf( 'CII ApplicableHeaderTradeSettlementHandler: Payment reference is empty in order %d.', $order->get_id() ), 'error' ); return null; } $payment_reference = array( 'name' => 'ram:PaymentReference', 'value' => wpo_ips_edi_sanitize_string( $reference ), ); return apply_filters( 'wpo_ips_edi_cii_payment_reference', $payment_reference, $this ); } /** * Get the payment means data for the order. * * @return array|null */ public function get_payment_means(): ?array { $payment = $this->get_payment_means_data(); // If no usable payment data, exit early if ( empty( $payment['type_code'] ) ) { wpo_ips_edi_log( sprintf( 'CII ApplicableHeaderTradeSettlementHandler: No usable payment means data found in order %d.', $this->document->order->get_id() ), 'error' ); return null; } $payment_means = array( 'name' => 'ram:SpecifiedTradeSettlementPaymentMeans', 'value' => array( array( 'name' => 'ram:TypeCode', 'value' => $payment['type_code'], ), ), ); // Add account if ( ! empty( $payment['iban'] ) || ! empty( $payment['account_number'] ) ) { $name = ! empty( $payment['iban'] ) ? 'ram:IBANID' : 'ram:ProprietaryID'; $value = ! empty( $payment['iban'] ) ? strtoupper( preg_replace( '/\s+/', '', $payment['iban'] ) ) : $payment['account_number']; $payment_means['value'][] = array( 'name' => 'ram:PayeePartyCreditorFinancialAccount', 'value' => array( array( 'name' => $name, 'value' => $value, ), ), ); } // Add transaction ID if applicable (e.g., PayPal, Stripe) if ( ! empty( $payment['transaction_id'] ) ) { $payment_means['value'][] = array( 'name' => 'ram:Information', 'value' => wpo_ips_edi_sanitize_string( $payment['transaction_id'] ), ); } // Add BIC if available (only relevant when IBAN is used) if ( ! empty( $payment['bic'] ) ) { $payment_means['value'][] = array( 'name' => 'ram:PayeeSpecifiedCreditorFinancialInstitution', 'value' => array( array( 'name' => 'ram:BICID', 'value' => strtoupper( $payment['bic'] ), ), ), ); } return apply_filters( 'wpo_ips_edi_cii_payment_means', $payment_means, $this ); } /** * Get the trade tax data for the order. * * @return array|null */ public function get_trade_tax(): ?array { $tax_reasons = EN16931::get_vatex(); $grouped_tax_data = $this->get_grouped_order_tax_data(); $trade_tax = array(); foreach ( $grouped_tax_data as $item ) { $percent = (float) ( $item['percentage'] ?? 0 ); $category = strtoupper( (string) ( $item['category'] ?? '' ) ); $reason = strtoupper( (string) ( $item['reason'] ?? 'NONE' ) ); $scheme = strtoupper( (string) ( $item['scheme'] ?? 'VAT' ) ); $basis_raw = (float) ( $item['total_ex'] ?? 0 ); $basis = wc_round_tax_total( $basis_raw ); $tax = wc_round_tax_total( $basis_raw * $percent / 100 ); // Skip empty non-Z groups. $is_z = ( 'Z' === $category ); if ( 0.0 === $basis && 0.0 === $tax && ! $is_z ) { continue; } $node = array( 'name' => 'ram:ApplicableTradeTax', 'value' => array( array( 'name' => 'ram:CalculatedAmount', 'value' => $this->format_decimal( $tax ), ), array( 'name' => 'ram:TypeCode', 'value' => $scheme ?: 'VAT', ), array( 'name' => 'ram:BasisAmount', 'value' => $this->format_decimal( $basis ), ), array( 'name' => 'ram:CategoryCode', 'value' => $category ?: 'Z', ), array( 'name' => 'ram:RateApplicablePercent', 'value' => $this->format_decimal( $percent, 1 ), ), ), ); // Only emit exemption data for 0% non-Z with an explicit reason. if ( 0.0 === $percent && 'Z' !== $category && 'NONE' !== $reason ) { $reason_mapped = ! empty( $tax_reasons[ $reason ] ) ? $tax_reasons[ $reason ] : $reason; $node['value'][] = array( 'name' => 'ram:ExemptionReasonCode', 'value' => $reason_mapped, ); $node['value'][] = array( 'name' => 'ram:ExemptionReason', 'value' => $reason_mapped, ); } $trade_tax[] = $node; } if ( empty( $trade_tax ) ) { return null; } return apply_filters( 'wpo_ips_edi_cii_trade_tax', $trade_tax, $this ); } /** * Get the payment terms for the order. * * @return array|null */ public function get_payment_terms(): ?array { $due_date_timestamp = is_callable( array( $this->document->order_document, 'get_due_date' ) ) ? $this->document->order_document->get_due_date() : 0; if ( empty( $due_date_timestamp ) ) { return null; } $date_format_code = $this->get_date_format_code(); $php_date_format = $this->get_php_date_format_from_code( $date_format_code ); $due_date = $this->normalize_date( $due_date_timestamp, $php_date_format ); if ( ! $this->validate_date_format( $due_date, $date_format_code ) ) { wpo_ips_edi_log( 'CII ApplicableHeaderTradeSettlementHandler: Invalid due date format.', 'error' ); return null; } $payment_terms = array( 'name' => 'ram:SpecifiedTradePaymentTerms', 'value' => array( array( 'name' => 'ram:DueDateDateTime', 'value' => array( array( 'name' => 'udt:DateTimeString', 'value' => $due_date, 'attributes' => array( 'format' => $date_format_code, ), ), ), ), ), ); return apply_filters( 'wpo_ips_edi_cii_payment_terms', $payment_terms, $this ); } /** * Get the monetary summation for the order. * * @return array|null */ public function get_monetary_summation(): ?array { $totals = $this->get_order_payment_totals( $this->document->order ); $currency = $this->document->order->get_currency(); $line_total = isset( $totals['lines_net'] ) ? $totals['lines_net'] : $totals['total_exc_tax']; $monetary_summation = array( 'name' => 'ram:SpecifiedTradeSettlementHeaderMonetarySummation', 'value' => array( array( 'name' => 'ram:LineTotalAmount', 'value' => $this->format_decimal( $line_total ), ), array( 'name' => 'ram:TaxBasisTotalAmount', 'value' => $this->format_decimal( $totals['total_exc_tax'] ), ), array( 'name' => 'ram:TaxTotalAmount', 'value' => $this->format_decimal( $totals['total_tax'] ), 'attributes' => array( 'currencyID' => $currency, ), ), array( 'name' => 'ram:GrandTotalAmount', 'value' => $this->format_decimal( $totals['total_inc_tax'] ), ), ), ); // Only output TotalPrepaidAmount when there is an actual prepayment. if ( $totals['prepaid_amount'] > 0 ) { $monetary_summation['value'][] = array( 'name' => 'ram:TotalPrepaidAmount', 'value' => $this->format_decimal( $totals['prepaid_amount'] ), ); } // Only include RoundingAmount when materially non-zero. if ( abs( $totals['rounding_diff'] ) >= 0.01 ) { $monetary_summation['value'][] = array( 'name' => 'ram:RoundingAmount', 'value' => $this->format_decimal( $totals['rounding_diff'] ), ); } $monetary_summation['value'][] = array( 'name' => 'ram:DuePayableAmount', 'value' => $this->format_decimal( $totals['payable_amount'] ), ); return apply_filters( 'wpo_ips_edi_cii_monetary_summation', $monetary_summation, $this ); } }